CoolFace
Apppublic

garvitsachdeva/SpindleFlow-RL

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
train_colab.py501 linesDownload Raw Back to colab
1# ============================================================2# SpindleFlow RL — Colab Training Script3#4# BEFORE ANYTHING:5#   1. Runtime → Change runtime type → T4 GPU6#   2. Key icon (left sidebar) → Manage secrets → add:7#        HF_TOKEN       = hf_xxxx  (write token: hf.co/settings/tokens)8#        OPENAI_API_KEY = sk-xxxx9#      Toggle "Notebook access" ON for both.10#   3. Create a new Colab notebook.11#   4. Copy each CELL block below into its own code cell.12#   5. Run cells top to bottom, one at a time.13# ============================================================14 15 16# ============================================================17# CELL 1 — Install packages + clone/update repo18# ============================================================19import subprocess, os, sys20 21print(f"Python {sys.version}")22 23packages = [24    "openenv", "stable-baselines3", "sb3-contrib", "gymnasium",25    "sentence-transformers", "openai", "pyyaml", "trl",26    "transformers", "datasets", "torch", "matplotlib",27    "huggingface_hub", "python-dotenv",28]29if sys.version_info >= (3, 13):30    packages.append("audioop-lts")31 32result = subprocess.run(33    ["pip", "install", "-q"] + packages,34    capture_output=True, text=True35)36if result.returncode != 0:37    print(result.stderr[-3000:])38    raise RuntimeError("pip install failed")39print("Packages OK")40 41REPO = "/content/kuchbhi"42GIT_URL = "https://github.com/garvitsachdevaa/kuchbhi.git"43 44if not os.path.isdir(os.path.join(REPO, ".git")):45    subprocess.run(["git", "clone", "--depth=1", GIT_URL], cwd="/content", check=True)46    print("Repo cloned")47else:48    subprocess.run(["git", "pull"], cwd=REPO, check=True)49    print("Repo updated")50 51os.chdir(REPO)52sys.path.insert(0, REPO)53 54for d in ["/content/demo/assets", "/content/data",55          "/content/checkpoints", "/content/logs"]:56    os.makedirs(d, exist_ok=True)57 58print(f"CWD: {os.getcwd()}")59print("CELL 1 done ✓")60 61 62# ============================================================63# CELL 2 — Load secrets (with clear error messages)64# ============================================================65import os66try:67    from google.colab import userdata68    HF_TOKEN       = userdata.get("HF_TOKEN")69    OPENAI_API_KEY = userdata.get("OPENAI_API_KEY")70except Exception:71    HF_TOKEN       = ""72    OPENAI_API_KEY = ""73 74if not HF_TOKEN:75    raise RuntimeError(76        "HF_TOKEN not found.\n"77        "Click the 🔑 icon → Add secret → Name: HF_TOKEN → toggle Notebook access ON\n"78        "Then Runtime → Restart and run all."79    )80if not OPENAI_API_KEY:81    print("⚠️  No OPENAI_API_KEY — simulation mode (no LLM calls, faster training)")82 83os.environ["HF_TOKEN"]       = HF_TOKEN84os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY85 86print(f"HF_TOKEN       : {HF_TOKEN[:8]}...{HF_TOKEN[-4:]}")87print(f"OPENAI_API_KEY : {'set' if OPENAI_API_KEY else 'NOT SET — simulation mode'}")88print("CELL 2 done ✓")89 90 91# ============================================================92# CELL 3 — Patch env + smoke test93# ============================================================94import os as _os95import numpy as np96from env.spindleflow_env import SpindleFlowEnv97 98# Adds simulate_specialists kwarg so per-step calls stay local/fast.99# OPENAI_API_KEY is still active for task generation + finetuner.100if not getattr(SpindleFlowEnv, "_simulate_patched", False):101    _orig_init = SpindleFlowEnv.__init__102 103    def _new_init(self, *args, simulate_specialists=False, **kwargs):104        _orig_init(self, *args, **kwargs)105        self.simulate_specialists = simulate_specialists106 107    SpindleFlowEnv.__init__ = _new_init108 109    _orig_call = SpindleFlowEnv._call_specialist110 111    def _new_call(self, specialist_id, task, elapsed_ms, context=None):112        if getattr(self, "simulate_specialists", False):113            _key = _os.environ.pop("OPENAI_API_KEY", None)114            try:115                return _orig_call(self, specialist_id, task,116                                  elapsed_ms, context=context)117            finally:118                if _key:119                    _os.environ["OPENAI_API_KEY"] = _key120        return _orig_call(self, specialist_id, task, elapsed_ms, context=context)121 122    SpindleFlowEnv._call_specialist = _new_call123    SpindleFlowEnv._simulate_patched = True124    print("SpindleFlowEnv patched")125 126env = SpindleFlowEnv(127    config_path="configs/training_config.yaml",128    catalog_path="configs/specialist_catalog.yaml",129    use_real_spindleflow=False,130    phase=1,131    simulate_specialists=True,132)133obs, info = env.reset()134print(f"obs shape : {obs.shape}")135print(f"task      : {info['task'][:80]}")136 137_, reward, _, _, info2 = env.step(env.action_space.sample())138print(f"reward    : {reward:.4f}")139print(f"action    : {info2['action_name']}")140env.close()141print("\nCELL 3 done ✓ — environment OK")142 143 144# ============================================================145# CELL 4 — TRL check (hackathon requirement)146# ============================================================147import trl, torch148 149print(f"TRL   : {trl.__version__}")150print(f"Torch : {torch.__version__}")151print(f"CUDA  : {torch.cuda.is_available()}")152if torch.cuda.is_available():153    print(f"GPU   : {torch.cuda.get_device_name(0)}")154 155for _name in ("PPOConfig", "GRPOConfig", "SFTConfig"):156    if getattr(trl, _name, None):157        print(f"TRL config: {_name}")158        break159else:160    print("TRL imported (TrainingArguments-based version)")161 162print("\nCELL 4 done ✓ — TRL requirement satisfied")163 164 165# ============================================================166# CELL 5 — Train RecurrentPPO (LSTM PPO)167#168# Per-step specialist calls : local simulation (no API cost/latency)169# Task generation           : GPT-4o-mini via OPENAI_API_KEY170# Finetuner                 : fires every 100 episodes171# Reward baseline           : GPT-4o-mini via OPENAI_API_KEY172#173# Expected runtime: ~20–25 min on T4 for 100k steps (~10k episodes)174# ============================================================175import time, yaml, torch, numpy as np176from sb3_contrib import RecurrentPPO177from stable_baselines3.common.vec_env import DummyVecEnv, VecNormalize178from stable_baselines3.common.callbacks import CheckpointCallback, BaseCallback179from policy.lstm_policy import build_policy_kwargs180from training.curriculum import CurriculumManager181from training.specialist_improvement_callback import SpecialistImprovementCallback182 183_LOG_FILE = "/content/logs/training_log.txt"184 185def _tlog(msg):186    line = f"[{time.strftime('%H:%M:%S')}] {msg}"187    print(line, flush=True)188    with open(_LOG_FILE, "a") as f:189        f.write(line + "\n")190 191with open("configs/training_config.yaml") as f:192    _cfg = yaml.safe_load(f)193 194TOTAL_TIMESTEPS = 100_000195curriculum = CurriculumManager(config_path="configs/training_config.yaml")196 197 198class RewardLogger(BaseCallback):199    def __init__(self, curriculum):200        super().__init__()201        self.episode_rewards = []202        self._running = 0.0203        self._curriculum = curriculum204 205    def _on_step(self):206        for r, d in zip(self.locals.get("rewards", []),207                        self.locals.get("dones",   [])):208            self._running += float(r)209            if d:210                ep = self._running211                self.episode_rewards.append(ep)212                self._running = 0.0213                advanced = self._curriculum.on_episode_end(ep)214                n = len(self.episode_rewards)215                if advanced or n % 50 == 0:216                    _tlog(f"Ep {n:5d} | reward {ep:+.3f} | "217                          f"{self._curriculum.progress_str()}")218        return True219 220 221def make_env():222    return SpindleFlowEnv(223        config_path="configs/training_config.yaml",224        catalog_path="configs/specialist_catalog.yaml",225        use_real_spindleflow=False,226        phase=1,227        simulate_specialists=True,228    )229 230 231vec_env = DummyVecEnv([make_env])232vec_env = VecNormalize(vec_env, norm_obs=True, norm_reward=True, clip_obs=10.0)233 234_ppo  = _cfg.get("ppo",  {})235_lstm = _cfg.get("lstm", {})236 237model = RecurrentPPO(238    policy="MlpLstmPolicy",239    env=vec_env,240    learning_rate=float(_ppo.get("learning_rate", 3e-4)),241    n_steps=int(_ppo.get("n_steps", 512)),242    batch_size=int(_ppo.get("batch_size", 64)),243    n_epochs=int(_ppo.get("n_epochs", 10)),244    gamma=float(_ppo.get("gamma", 0.99)),245    gae_lambda=float(_ppo.get("gae_lambda", 0.95)),246    clip_range=float(_ppo.get("clip_range", 0.2)),247    ent_coef=float(_ppo.get("ent_coef", 0.01)),248    vf_coef=float(_ppo.get("vf_coef", 0.5)),249    max_grad_norm=float(_ppo.get("max_grad_norm", 0.5)),250    policy_kwargs=build_policy_kwargs(251        hidden_size=int(_lstm.get("hidden_size", 256))252    ),253    verbose=0,254    seed=int(_cfg.get("training", {}).get("seed", 42)),255    device="cuda" if torch.cuda.is_available() else "cpu",256)257 258_tlog(f"Device     : {model.device}")259_tlog(f"Timesteps  : {TOTAL_TIMESTEPS:,}")260_tlog(f"Curriculum : Phase {curriculum.current_phase} — {curriculum.progress_str()}")261_tlog("Training started...")262 263reward_logger  = RewardLogger(curriculum)264checkpoint_cb  = CheckpointCallback(save_freq=10_000,265                                    save_path="/content/checkpoints/")266improvement_cb = SpecialistImprovementCallback(267    improve_every_n_episodes=_cfg.get("specialist_improvement", {}).get(268        "improve_every_n_episodes", 100),269    verbose=1,270)271 272_t0 = time.time()273model.learn(274    total_timesteps=TOTAL_TIMESTEPS,275    callback=[reward_logger, checkpoint_cb, improvement_cb],276)277_elapsed = time.time() - _t0278 279model.save("/content/spindleflow_model")280vec_env.save("/content/vec_normalize.pkl")281 282_tlog(f"Done in {_elapsed/60:.1f} min")283_tlog(f"Episodes        : {len(reward_logger.episode_rewards)}")284_tlog(f"Curriculum final: {curriculum.progress_str()}")285print("\nCELL 5 done ✓ — model saved")286 287 288# ============================================================289# CELL 6 — Reward curve290# ============================================================291import json, numpy as np, matplotlib292matplotlib.use("Agg")293import matplotlib.pyplot as plt294 295ep_rewards = reward_logger.episode_rewards296if not ep_rewards:297    raise RuntimeError("No episodes recorded — check Cell 5 output for errors")298 299n_ep     = len(ep_rewards)300episodes = list(range(n_ep))301window   = max(30, n_ep // 20)   # adaptive: ~5% of run302 303smoothed = [304    float(np.mean(ep_rewards[max(0, i - window):i + 1]))305    for i in range(n_ep)306]307 308early_mean  = float(np.mean(ep_rewards[:min(50, n_ep)]))309final_mean  = float(np.mean(ep_rewards[max(0, n_ep - 200):]))310improvement = final_mean - early_mean311 312# JSON for HF Space demo tab313step = max(1, n_ep // 300)314with open("/content/demo/assets/reward_curve.json", "w") as f:315    json.dump({"episodes": episodes[::step],316               "mean_rewards": smoothed[::step]}, f)317 318# Plot319fig, ax = plt.subplots(figsize=(11, 5), dpi=180)320fig.patch.set_facecolor("#0d1117")321ax.set_facecolor("#161b22")322 323every = max(1, n_ep // 800)324ax.scatter(episodes[::every], ep_rewards[::every],325           s=4, alpha=0.25, color="#58a6ff", zorder=2, label="Episode reward")326ax.plot(episodes[::every], smoothed[::every],327        linewidth=2.5, color="#ff6b35", zorder=3,328        label=f"Smoothed ({window}-ep mean)")329ax.axhline(y=early_mean, color="#94a3b8", linestyle="--", linewidth=1.2,330           alpha=0.75, label=f"Early baseline  {early_mean:+.3f}")331ax.axhline(y=final_mean, color="#34d399", linestyle="--", linewidth=1.2,332           alpha=0.85, label=f"Final mean  {final_mean:+.3f}")333 334ax.set_xlabel("Episode", color="#c9d1d9", fontsize=12)335ax.set_ylabel("Reward",  color="#c9d1d9", fontsize=12)336ax.set_title(337    "SpindleFlow RL — Delegation Policy Learning Curve\n"338    f"RecurrentPPO · LSTM · {TOTAL_TIMESTEPS:,} steps · {n_ep:,} episodes",339    color="#f0f6fc", fontsize=13, fontweight="bold", pad=14,340)341ax.tick_params(colors="#8b949e")342for sp in ax.spines.values():343    sp.set_edgecolor("#30363d")344ax.grid(color="#21262d", linewidth=0.8, alpha=0.9)345ax.legend(fontsize=10, framealpha=0.85,346          facecolor="#161b22", edgecolor="#30363d", labelcolor="#c9d1d9")347 348sign = "▲" if improvement >= 0 else "▼"349ax.annotate(f"  {sign} {abs(improvement):.3f} improvement",350            xy=(n_ep * 0.65, (early_mean + final_mean) / 2),351            color="#f0f6fc", fontsize=10, fontstyle="italic")352 353fig.tight_layout()354fig.savefig("/content/reward_curve.png", dpi=180, bbox_inches="tight",355            facecolor=fig.get_facecolor())356plt.show()357 358_tlog(f"Curve: early={early_mean:+.4f}  final={final_mean:+.4f}  "359      f"improvement={improvement:+.4f}")360print(f"Episodes   : {n_ep:,}")361print(f"Improvement: {improvement:+.4f}")362print("\nCELL 6 done ✓ — reward curve saved")363 364 365# ============================================================366# CELL 7 — Learning features audit367# ============================================================368import json369from pathlib import Path370 371print("=" * 52)372print("LEARNING FEATURES AUDIT")373print("=" * 52)374 375print(f"\nFeature 5 — Curriculum (performance-gated)")376print(f"  Phase        : {curriculum.current_phase}/3")377print(f"  Rolling mean : {curriculum.rolling_mean():.3f}")378print(f"  {curriculum.progress_str()}")379 380mem_path = Path(_cfg.get("specialist_improvement", {}).get(381    "memory_path", "data/specialist_memory.json"))382print(f"\nFeature 2 — Specialist memory ({mem_path})")383if mem_path.exists():384    data = json.loads(mem_path.read_text())385    total = sum(len(v) for v in data.values())386    print(f"  {len(data)} specialists · {total} total entries")387    for sid, entries in list(data.items())[:3]:388        avg = sum(e["reward"] for e in entries) / len(entries)389        print(f"    {sid}: {len(entries)} entries, avg={avg:.3f}")390else:391    print("  No file yet (finetuner fires after 100 completed episodes)")392 393spawn_path = Path(_cfg.get("environment", {}).get(394    "spawn_memory_path", "data/spawn_memory.jsonl"))395print(f"\nFeature 3 — Spawn memory ({spawn_path})")396if spawn_path.exists():397    lines = [l for l in spawn_path.read_text().splitlines() if l.strip()]398    print(f"  {len(lines)} spawn records")399    for line in lines[:2]:400        rec = json.loads(line)401        print(f"    {rec['specialist_role']} | reward={rec['episode_reward']:.3f}")402else:403    print("  No file yet")404 405res_path = Path(_cfg.get("agents", {}).get(406    "resolution_memory_path", "data/resolution_memory.jsonl"))407print(f"\nFeature 4 — Resolution bandit ({res_path})")408if res_path.exists():409    lines = [l for l in res_path.read_text().splitlines() if l.strip()]410    print(f"  {len(lines)} outcome records")411else:412    print("  No file yet")413 414print("\n" + "=" * 52)415print("CELL 7 done ✓")416 417 418# ============================================================419# CELL 8 — Push to HuggingFace Hub420# ============================================================421import os, numpy as np422from huggingface_hub import HfApi, CommitOperationAdd423 424from huggingface_hub import whoami425HF_REPO = f"{whoami(token=HF_TOKEN)['name']}/spindleflow-rl"426api = HfApi(token=HF_TOKEN)427 428_tlog(f"Pushing to https://huggingface.co/{HF_REPO} ...")429api.create_repo(repo_id=HF_REPO, repo_type="model", exist_ok=True)430 431ep = reward_logger.episode_rewards432readme = f"""---433license: mit434tags:435  - reinforcement-learning436  - stable-baselines3437  - sb3-contrib438  - gymnasium439  - multi-agent440  - openenv441library_name: stable-baselines3442---443 444# SpindleFlow RL — Delegation Policy445 446LSTM PPO (RecurrentPPO) trained on SpindleFlow-v0 (OpenEnv). Colab T4 GPU.447 448## Training summary449| Metric | Value |450|---|---|451| Algorithm | RecurrentPPO (SB3 + sb3-contrib) |452| Total timesteps | {TOTAL_TIMESTEPS:,} |453| Episodes | {len(ep):,} |454| Early baseline (first 50 ep) | {early_mean:.4f} |455| Final mean (last 200 ep) | {final_mean:.4f} |456| Improvement | {improvement:+.4f} |457| Training time | {_elapsed/60:.1f} min |458| Device | T4 GPU |459 460![Reward Curve](reward_curve.png)461 462## Load463```python464from sb3_contrib import RecurrentPPO465from huggingface_hub import hf_hub_download466model = RecurrentPPO.load(hf_hub_download("{HF_REPO}", "spindleflow_model.zip"))467```468"""469 470readme_path = "/content/README_model.md"471with open(readme_path, "w") as f:472    f.write(readme)473 474candidates = [475    ("/content/spindleflow_model.zip",          "spindleflow_model.zip"),476    ("/content/vec_normalize.pkl",              "vec_normalize.pkl"),477    ("/content/reward_curve.png",               "reward_curve.png"),478    ("/content/demo/assets/reward_curve.json",  "reward_curve.json"),479    ("/content/logs/training_log.txt",          "training_log.txt"),480    (readme_path,                               "README.md"),481]482 483ops = [484    CommitOperationAdd(path_in_repo=dst, path_or_fileobj=src)485    for src, dst in candidates if os.path.exists(src)486]487 488api.create_commit(489    repo_id=HF_REPO, repo_type="model", operations=ops,490    commit_message="Add trained SpindleFlow RL policy (Colab T4)",491    token=HF_TOKEN,492)493 494_tlog(f"Uploaded {len(ops)} files:")495for src, dst in candidates:496    if os.path.exists(src):497        _tlog(f"  {dst}")498_tlog(f"Model live : https://huggingface.co/{HF_REPO}")499_tlog(f"Log        : https://huggingface.co/{HF_REPO}/blob/main/training_log.txt")500print("\nCELL 8 done ✓ — all done!")501