"""SpikeWhale training control panel (CLI: python -m daisychain.spikewhale_panel). A web page with sliders for the SpikeWhale config. Pick a size your hardware can handle, hit Start, and it launches the real DaisyChain training (SpikeWhale + FineWeb-Edu) and streams the live loss. The exact env is shown so you can run the same command on other machines to train distributed. """ import json import os import subprocess import sys import threading from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer PORT = int(os.environ.get("SW_PANEL_PORT", "8899")) _proc = None _log = [] # rolling training log _lock = threading.Lock() def _pump(proc): for line in iter(proc.stdout.readline, ""): with _lock: _log.append(line.rstrip("\n")) if len(_log) > 400: del _log[:200] proc.stdout.close() def start_training(cfg): global _proc if _proc and _proc.poll() is None: return False, "already running" env = dict(os.environ) env.update({ "MASTER_ADDR": "127.0.0.1", "MASTER_PORT": "29610", "WORLD_SIZE": "1", "RANK": "0", "USE_LIBUV": "0", "PYTHONUNBUFFERED": "1", "DAISY_TASK": "daisychain.spikewhale_task:SpikeWhaleTask", "DAISY_SW_HIDDEN": str(cfg["hidden"]), "DAISY_SW_LAYERS": str(cfg["layers"]), "DAISY_SW_HEADS": str(cfg["heads"]), "DAISY_SW_EXPERTS": str(cfg["experts"]), "DAISY_SW_SEQLEN": str(cfg["seqlen"]), "DAISY_STEPS": str(cfg["steps"]), "DAISY_LR": str(cfg["lr"]), "DAISY_OPTIMIZER": "adam", "DAISY_BASE_BATCH": str(cfg["batch"]), }) if cfg.get("dataset"): env["DAISY_SW_DATASET"] = cfg["dataset"] # blank subset means the dataset's default config; unset any inherited one if cfg.get("subset"): env["DAISY_SW_SUBSET"] = cfg["subset"] else: env.pop("DAISY_SW_SUBSET", None) with _lock: _log.clear() _log.append(f"launching training: hidden={cfg['hidden']} layers={cfg['layers']} " f"experts={cfg['experts']} seqlen={cfg['seqlen']} steps={cfg['steps']} " f"dataset={env.get('DAISY_SW_DATASET', 'HuggingFaceFW/fineweb-edu')}" + (f":{env['DAISY_SW_SUBSET']}" if env.get("DAISY_SW_SUBSET") else "")) _proc = subprocess.Popen([sys.executable, "-u", "-m", "daisychain.train"], env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) threading.Thread(target=_pump, args=(_proc,), daemon=True).start() return True, "started" PAGE = """ SpikeWhale · DaisyChain trainer

🐋 SpikeWhale · DaisyChain

Pick a size your hardware can train, then start. Trains the real SpikeWhale on streamed FineWeb-Edu, distributed by DaisyChain. Smaller = faster on old hardware.

Model size
Training
Data

Any streamable text dataset with a text column works. For gated/private datasets, log in first with huggingface-cli login on this machine — the trainer inherits your token.

idle

Live loss
Log
""" class H(BaseHTTPRequestHandler): def _send(self, body, ctype="text/html; charset=utf-8", code=200): b = body.encode() if isinstance(body, str) else body self.send_response(code); self.send_header("Content-Type", ctype) self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b) def do_GET(self): if self.path.startswith("/log"): with _lock: running = _proc is not None and _proc.poll() is None self._send(json.dumps({"log": list(_log), "running": running}), "application/json") else: self._send(PAGE) def do_POST(self): if self.path.startswith("/start"): n = int(self.headers.get("Content-Length", 0)) cfg = json.loads(self.rfile.read(n) or "{}") ok, msg = start_training(cfg) self._send(json.dumps({"ok": ok, "msg": msg}), "application/json") elif self.path.startswith("/stop"): global _proc if _proc and _proc.poll() is None: _proc.terminate() with _lock: _log.append("training stopped from the panel") self._send(json.dumps({"ok": True}), "application/json") def log_message(self, *a): pass def main(): print(f"[spikewhale-panel] http://localhost:{PORT}", flush=True) ThreadingHTTPServer(("0.0.0.0", PORT), H).serve_forever() if __name__ == "__main__": main()