Spaces:
Running
Running
Commit Β·
f096486
1
Parent(s): 46f0850
Clean final training notebook: no demo, no Drive, all steps including plots + eval + download
Browse files- plot_rewards.py +32 -9
- train_colab.ipynb +196 -385
plot_rewards.py
CHANGED
|
@@ -98,10 +98,16 @@ def load_log(log_path: str) -> pd.DataFrame:
|
|
| 98 |
|
| 99 |
df.sort_values("episode", inplace=True)
|
| 100 |
df.reset_index(drop=True, inplace=True)
|
| 101 |
-
|
|
|
|
| 102 |
return df
|
| 103 |
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
# ββ Plots βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 106 |
|
| 107 |
def plot_reward_curve(df: pd.DataFrame, out_dir: Path, window: int = 20):
|
|
@@ -235,16 +241,33 @@ def plot_all(log_path: str = "logs/training.jsonl", out_dir: str = "plots/",
|
|
| 235 |
out = Path(out_dir)
|
| 236 |
out.mkdir(parents=True, exist_ok=True)
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
| 241 |
plot_curriculum(df, out)
|
| 242 |
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
|
| 250 |
if __name__ == "__main__":
|
|
|
|
| 98 |
|
| 99 |
df.sort_values("episode", inplace=True)
|
| 100 |
df.reset_index(drop=True, inplace=True)
|
| 101 |
+
n = len(df)
|
| 102 |
+
print(f"[plot_rewards] Loaded {n} episodes from {log_path}")
|
| 103 |
return df
|
| 104 |
|
| 105 |
|
| 106 |
+
def _adaptive_window(df: pd.DataFrame, requested: int) -> int:
|
| 107 |
+
"""Use min(requested, len/3) so plots are never flat lines with few data points."""
|
| 108 |
+
return max(3, min(requested, len(df) // 3))
|
| 109 |
+
|
| 110 |
+
|
| 111 |
# ββ Plots βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 112 |
|
| 113 |
def plot_reward_curve(df: pd.DataFrame, out_dir: Path, window: int = 20):
|
|
|
|
| 241 |
out = Path(out_dir)
|
| 242 |
out.mkdir(parents=True, exist_ok=True)
|
| 243 |
|
| 244 |
+
# Adaptive window β avoid flat lines with small datasets
|
| 245 |
+
w = _adaptive_window(df, window)
|
| 246 |
+
|
| 247 |
+
plot_reward_curve(df, out, w)
|
| 248 |
+
plot_success_rate(df, out, w)
|
| 249 |
+
plot_accuracy_gain(df, out, w)
|
| 250 |
plot_curriculum(df, out)
|
| 251 |
|
| 252 |
+
# ββ Print summary stats βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 253 |
+
n = len(df)
|
| 254 |
+
avg_r = df["reward"].mean()
|
| 255 |
+
max_r = df["reward"].max()
|
| 256 |
+
min_r = df["reward"].min()
|
| 257 |
+
succ = df["success"].mean()
|
| 258 |
+
max_lvl = int(df["level"].max())
|
| 259 |
+
lvl_names = {0: "tutorial", 1: "easy", 2: "medium", 3: "hard"}
|
| 260 |
+
|
| 261 |
+
print(f"\n{'='*50}")
|
| 262 |
+
print(f" TRAINING SUMMARY ({n} episodes)")
|
| 263 |
+
print(f"{'='*50}")
|
| 264 |
+
print(f" Avg reward : {avg_r:+.3f}")
|
| 265 |
+
print(f" Min / Max : {min_r:+.3f} / {max_r:+.3f}")
|
| 266 |
+
print(f" Success rate : {succ:.1%}")
|
| 267 |
+
print(f" Max level : {max_lvl} ({lvl_names.get(max_lvl, '?')})")
|
| 268 |
+
print(f" Window used : {w} episodes")
|
| 269 |
+
print(f"{'='*50}")
|
| 270 |
+
print(f"\n Plots saved to: {out}/")
|
| 271 |
|
| 272 |
|
| 273 |
if __name__ == "__main__":
|
train_colab.ipynb
CHANGED
|
@@ -3,8 +3,7 @@
|
|
| 3 |
"nbformat_minor": 5,
|
| 4 |
"metadata": {
|
| 5 |
"colab": {
|
| 6 |
-
"provenance": []
|
| 7 |
-
"gpuType": "T4"
|
| 8 |
},
|
| 9 |
"kernelspec": {
|
| 10 |
"display_name": "Python 3",
|
|
@@ -18,306 +17,153 @@
|
|
| 18 |
"cells": [
|
| 19 |
{
|
| 20 |
"cell_type": "markdown",
|
| 21 |
-
"id": "
|
| 22 |
"metadata": {},
|
| 23 |
"source": [
|
| 24 |
-
"#
|
| 25 |
-
"**OpenEnv Hackathon 2026** \u00b7 [Space](https://huggingface.co/spaces/Aswinis-Kumar/data-centric-env) \u00b7 [GitHub](https://github.com/CelestialWorthyOfHeavenAndEarth/data-centric-env)\n\n",
|
| 26 |
-
"Trains **Qwen2.5-3B-Instruct** (4-bit, LoRA) to orchestrate data-cleaning specialist agents via RL.\n\n",
|
| 27 |
-
"| Phase | What happens | Time |\n",
|
| 28 |
-
"|-------|-------------|------|\n",
|
| 29 |
-
"| SFT warmup | Teaches valid command grammar on ~9k examples | ~20 min |\n",
|
| 30 |
-
"| GRPO training | Learns strategy via live environment rollouts | ~3\u20135 hrs |\n",
|
| 31 |
-
"| Eval + plots | Reward curves, baseline comparison | ~5 min |\n\n",
|
| 32 |
-
"**Runtime required:** T4 GPU \u2192 `Runtime \u2192 Change runtime type \u2192 T4 GPU`\n\n",
|
| 33 |
-
"> **Quick demo mode:** Set `DEMO_MODE = True` in cell 1 to run a tiny 10-step version in ~10 min."
|
| 34 |
-
]
|
| 35 |
-
},
|
| 36 |
-
{
|
| 37 |
-
"cell_type": "markdown",
|
| 38 |
-
"id": "a1",
|
| 39 |
-
"metadata": {},
|
| 40 |
-
"source": [
|
| 41 |
-
"## \u2699\ufe0f Step 1 \u2014 Configuration"
|
| 42 |
-
]
|
| 43 |
-
},
|
| 44 |
-
{
|
| 45 |
-
"cell_type": "code",
|
| 46 |
-
"execution_count": null,
|
| 47 |
-
"id": "b0_config",
|
| 48 |
-
"metadata": {},
|
| 49 |
-
"outputs": [],
|
| 50 |
-
"source": [
|
| 51 |
-
"# \u2500\u2500\u2500 Set DEMO_MODE=True for a quick 10-step judge verification run (~5 min)\n",
|
| 52 |
-
"DEMO_MODE = False # \u2190 Change to True for quick verification\n",
|
| 53 |
"\n",
|
| 54 |
-
"
|
| 55 |
-
"HF_SPACE = 'https://aswinis-kumar-data-centric-env.hf.space'\n",
|
| 56 |
-
"\n",
|
| 57 |
-
"# Model: 1.5B trains 3x faster than 3B on T4, still very capable\n",
|
| 58 |
-
"# Change to '3B-Instruct' if you want more capacity but slower runs\n",
|
| 59 |
-
"MODEL_NAME = 'Qwen/Qwen2.5-1.5B-Instruct'\n",
|
| 60 |
-
"\n",
|
| 61 |
-
"if DEMO_MODE:\n",
|
| 62 |
-
" print('\ud83d\ude80 DEMO MODE \u2014 quick 10-step run for judge verification')\n",
|
| 63 |
-
" SFT_EPOCHS = 1\n",
|
| 64 |
-
" GRPO_STEPS = 10\n",
|
| 65 |
-
" GRPO_BATCH = 2\n",
|
| 66 |
-
" GRPO_GENS = 2\n",
|
| 67 |
-
"else:\n",
|
| 68 |
-
" print('\ud83d\udd2c FULL TRAINING MODE')\n",
|
| 69 |
-
" SFT_EPOCHS = 1\n",
|
| 70 |
-
" GRPO_STEPS = 150 # ~45-60 min on T4 with 1.5B. Run 2-3x for best results!\n",
|
| 71 |
-
" GRPO_BATCH = 2\n",
|
| 72 |
-
" GRPO_GENS = 2\n",
|
| 73 |
"\n",
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"\n",
|
| 77 |
-
"
|
| 78 |
-
"
|
| 79 |
-
"
|
| 80 |
-
"
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
]
|
| 85 |
},
|
| 86 |
{
|
| 87 |
"cell_type": "markdown",
|
| 88 |
-
"id": "
|
| 89 |
"metadata": {},
|
| 90 |
"source": [
|
| 91 |
-
"##
|
| 92 |
]
|
| 93 |
},
|
| 94 |
{
|
| 95 |
"cell_type": "code",
|
| 96 |
"execution_count": null,
|
| 97 |
-
"id": "
|
| 98 |
"metadata": {},
|
| 99 |
"outputs": [],
|
| 100 |
"source": [
|
| 101 |
-
"#
|
| 102 |
-
"# (transformers
|
| 103 |
"!pip install \"transformers>=4.44.0,<4.52.0\" --quiet\n",
|
| 104 |
"\n",
|
| 105 |
-
"#
|
| 106 |
"!pip install \"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git\" --quiet\n",
|
| 107 |
"\n",
|
| 108 |
-
"#
|
| 109 |
"!pip install trl>=0.15.0 datasets>=2.0.0 accelerate>=0.30.0 --quiet\n",
|
| 110 |
"!pip install scikit-learn>=1.3.0 pandas>=2.0.0 numpy matplotlib --quiet\n",
|
| 111 |
"!pip install \"openenv-core[core]>=0.2.1\" --quiet\n",
|
| 112 |
-
"
|
| 113 |
-
"\n",
|
| 114 |
-
"
|
| 115 |
-
"
|
| 116 |
-
"
|
| 117 |
-
" print('\u2705 Unsloth loaded successfully')\n",
|
| 118 |
-
"except Exception as e:\n",
|
| 119 |
-
" print(f'\u274c Unsloth import failed: {e}')\n",
|
| 120 |
-
" print(' Try: Runtime \u2192 Factory reset runtime, then re-run')\n",
|
| 121 |
-
" raise"
|
| 122 |
-
]
|
| 123 |
-
},
|
| 124 |
-
{
|
| 125 |
-
"cell_type": "markdown",
|
| 126 |
-
"id": "a2",
|
| 127 |
-
"metadata": {},
|
| 128 |
-
"source": [
|
| 129 |
-
"## \ud83d\udcbe Step 3 \u2014 Mount Google Drive (optional, recommended)\n",
|
| 130 |
-
"Checkpoints save every 50 GRPO steps to Drive. If Colab disconnects, you can resume.\n\n",
|
| 131 |
-
"**Skip this cell if you don't want Drive integration.**"
|
| 132 |
-
]
|
| 133 |
-
},
|
| 134 |
-
{
|
| 135 |
-
"cell_type": "code",
|
| 136 |
-
"execution_count": null,
|
| 137 |
-
"id": "b2",
|
| 138 |
-
"metadata": {},
|
| 139 |
-
"outputs": [],
|
| 140 |
-
"source": [
|
| 141 |
-
"from google.colab import drive\n",
|
| 142 |
-
"drive.mount('/content/drive')\n",
|
| 143 |
-
"print('Drive mounted')"
|
| 144 |
]
|
| 145 |
},
|
| 146 |
{
|
| 147 |
"cell_type": "markdown",
|
| 148 |
-
"id": "
|
| 149 |
"metadata": {},
|
| 150 |
"source": [
|
| 151 |
-
"##
|
| 152 |
]
|
| 153 |
},
|
| 154 |
{
|
| 155 |
"cell_type": "code",
|
| 156 |
"execution_count": null,
|
| 157 |
-
"id": "
|
| 158 |
"metadata": {},
|
| 159 |
"outputs": [],
|
| 160 |
"source": [
|
| 161 |
-
"import os, sys, shutil\n",
|
| 162 |
"\n",
|
|
|
|
| 163 |
"WORK_DIR = '/content/data-centric-env'\n",
|
| 164 |
"\n",
|
| 165 |
-
"# Clone or
|
| 166 |
"if os.path.exists(f'{WORK_DIR}/pyproject.toml'):\n",
|
| 167 |
-
" print('Repo exists
|
| 168 |
-
"
|
| 169 |
"else:\n",
|
| 170 |
-
" if os.path.exists(WORK_DIR):\n",
|
| 171 |
-
"
|
| 172 |
-
" !git clone {REPO} {WORK_DIR}\n",
|
| 173 |
"\n",
|
| 174 |
"os.chdir(WORK_DIR)\n",
|
| 175 |
"sys.path.insert(0, WORK_DIR)\n",
|
|
|
|
|
|
|
| 176 |
"print('CWD:', os.getcwd())\n",
|
| 177 |
-
"
|
| 178 |
-
"\n",
|
| 179 |
-
"
|
| 180 |
-
"!pip install -e . --quiet 2>/dev/null || echo 'pip install -e . skipped'\n",
|
| 181 |
-
"\n",
|
| 182 |
-
"# Symlink output dirs to Drive if mounted\n",
|
| 183 |
-
"DRIVE_DIR = '/content/drive/MyDrive/data-centric-training'\n",
|
| 184 |
-
"if os.path.exists('/content/drive/MyDrive'):\n",
|
| 185 |
-
" os.makedirs(DRIVE_DIR, exist_ok=True)\n",
|
| 186 |
-
" for d in ['data-centric-checkpoints', 'data-centric-adapter', 'logs', 'plots']:\n",
|
| 187 |
-
" local = os.path.join(WORK_DIR, d)\n",
|
| 188 |
-
" remote = os.path.join(DRIVE_DIR, d)\n",
|
| 189 |
-
" os.makedirs(remote, exist_ok=True)\n",
|
| 190 |
-
" if not os.path.exists(local):\n",
|
| 191 |
-
" os.symlink(remote, local)\n",
|
| 192 |
-
" print(f' Drive link: {d}')\n",
|
| 193 |
-
" else:\n",
|
| 194 |
-
" print(f' Exists: {d}')\n",
|
| 195 |
-
" print('Checkpoints will save to Drive')\n",
|
| 196 |
-
"else:\n",
|
| 197 |
-
" for d in ['logs', 'plots']:\n",
|
| 198 |
-
" os.makedirs(os.path.join(WORK_DIR, d), exist_ok=True)\n",
|
| 199 |
-
" print('Drive not mounted \u2014 checkpoints save locally only')\n",
|
| 200 |
-
"\n",
|
| 201 |
-
"print('Setup complete')"
|
| 202 |
-
]
|
| 203 |
-
},
|
| 204 |
-
{
|
| 205 |
-
"cell_type": "markdown",
|
| 206 |
-
"id": "a35",
|
| 207 |
-
"metadata": {},
|
| 208 |
-
"source": [
|
| 209 |
-
"## \u2705 Step 5 \u2014 Verify All Features Work\n",
|
| 210 |
-
"Quick smoke test: confirms the environment, all specialist agents, and the rubric system are connected."
|
| 211 |
-
]
|
| 212 |
-
},
|
| 213 |
-
{
|
| 214 |
-
"cell_type": "code",
|
| 215 |
-
"execution_count": null,
|
| 216 |
-
"id": "b35",
|
| 217 |
-
"metadata": {},
|
| 218 |
-
"outputs": [],
|
| 219 |
-
"source": [
|
| 220 |
-
"import os, sys\n",
|
| 221 |
-
"os.chdir('/content/data-centric-env')\n",
|
| 222 |
-
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 223 |
-
"\n",
|
| 224 |
-
"from models import DataCentricAction\n",
|
| 225 |
-
"from server.data_centric_environment import DataCentricEnvironment\n",
|
| 226 |
-
"\n",
|
| 227 |
-
"env = DataCentricEnvironment()\n",
|
| 228 |
-
"obs = env.reset(task='task_1_easy', seed=42)\n",
|
| 229 |
-
"\n",
|
| 230 |
-
"checks = {}\n",
|
| 231 |
-
"\n",
|
| 232 |
-
"# 1. query_analyst\n",
|
| 233 |
-
"obs = env.step(DataCentricAction(message='query_analyst'))\n",
|
| 234 |
-
"checks['query_analyst'] = 'DIAGNOSIS' in obs.response and 'RECOMMENDED PLAN' in obs.response\n",
|
| 235 |
-
"\n",
|
| 236 |
-
"# 2. Smarter specialists\n",
|
| 237 |
-
"obs = env.step(DataCentricAction(message='query_cleaner'))\n",
|
| 238 |
-
"checks['smarter_specialists'] = any(k in obs.response for k in ['skew', 'Risk:', 'Reason:', '%'])\n",
|
| 239 |
-
"\n",
|
| 240 |
-
"# 3. Drift detection (apply then check)\n",
|
| 241 |
-
"obs = env.step(DataCentricAction(message='apply 1'))\n",
|
| 242 |
-
"checks['drift_detection'] = 'drift' in obs.response.lower() or obs.response != ''\n",
|
| 243 |
-
"\n",
|
| 244 |
-
"# 4+5. Dual classifier + feature importance\n",
|
| 245 |
-
"obs = env.step(DataCentricAction(message='validate'))\n",
|
| 246 |
-
"checks['dual_classifier'] = 'RF Accuracy' in obs.response or 'accuracy' in obs.response.lower()\n",
|
| 247 |
-
"checks['feature_importance'] = 'Feature importance' in obs.response or 'feature' in obs.response.lower()\n",
|
| 248 |
-
"\n",
|
| 249 |
-
"print('\\n=== Feature Smoke Test ===')\n",
|
| 250 |
-
"all_pass = True\n",
|
| 251 |
-
"for name, passed in checks.items():\n",
|
| 252 |
-
" status = '\u2705 PASS' if passed else '\u274c FAIL'\n",
|
| 253 |
-
" if not passed: all_pass = False\n",
|
| 254 |
-
" print(f' {status}: {name}')\n",
|
| 255 |
-
"\n",
|
| 256 |
-
"if all_pass:\n",
|
| 257 |
-
" print('\\n\u2705 All features verified \u2014 ready to train!')\n",
|
| 258 |
-
"else:\n",
|
| 259 |
-
" print('\\n\u26a0\ufe0f Some checks failed \u2014 check git pull ran correctly')"
|
| 260 |
-
]
|
| 261 |
-
},
|
| 262 |
-
{
|
| 263 |
-
"cell_type": "markdown",
|
| 264 |
-
"id": "a4",
|
| 265 |
-
"metadata": {},
|
| 266 |
-
"source": [
|
| 267 |
-
"## \ud83d\udda5\ufe0f Step 6 \u2014 Start Environment Server"
|
| 268 |
]
|
| 269 |
},
|
| 270 |
{
|
| 271 |
"cell_type": "code",
|
| 272 |
"execution_count": null,
|
| 273 |
-
"id": "
|
| 274 |
"metadata": {},
|
| 275 |
"outputs": [],
|
| 276 |
"source": [
|
| 277 |
-
"import subprocess, time, requests, os
|
| 278 |
-
"os.chdir('/content/data-centric-env')\n",
|
| 279 |
-
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 280 |
"\n",
|
| 281 |
-
"# Kill any
|
| 282 |
-
"
|
| 283 |
"time.sleep(2)\n",
|
| 284 |
"\n",
|
| 285 |
-
"
|
| 286 |
" ['python', '-m', 'uvicorn', 'server.app:app',\n",
|
| 287 |
" '--host', '0.0.0.0', '--port', '8000', '--log-level', 'warning'],\n",
|
| 288 |
" stdout=subprocess.PIPE, stderr=subprocess.PIPE\n",
|
| 289 |
")\n",
|
| 290 |
"\n",
|
| 291 |
-
"for i in range(
|
| 292 |
" try:\n",
|
| 293 |
-
"
|
| 294 |
-
"
|
|
|
|
| 295 |
" break\n",
|
| 296 |
-
" except:\n",
|
| 297 |
-
" time.sleep(1)\n",
|
| 298 |
"else:\n",
|
| 299 |
-
"
|
| 300 |
-
" out, err = server_proc.communicate()\n",
|
| 301 |
" print('STDOUT:', out.decode()[-2000:])\n",
|
| 302 |
" print('STDERR:', err.decode()[-2000:])\n",
|
| 303 |
-
" raise RuntimeError('Server failed to start
|
| 304 |
"\n",
|
| 305 |
-
"
|
| 306 |
-
"
|
| 307 |
]
|
| 308 |
},
|
| 309 |
{
|
| 310 |
"cell_type": "markdown",
|
| 311 |
-
"id": "
|
| 312 |
"metadata": {},
|
| 313 |
"source": [
|
| 314 |
-
"##
|
| 315 |
]
|
| 316 |
},
|
| 317 |
{
|
| 318 |
"cell_type": "code",
|
| 319 |
"execution_count": null,
|
| 320 |
-
"id": "
|
| 321 |
"metadata": {},
|
| 322 |
"outputs": [],
|
| 323 |
"source": [
|
|
@@ -328,26 +174,26 @@
|
|
| 328 |
"if os.path.exists(sft_path):\n",
|
| 329 |
" count = sum(1 for _ in open(sft_path))\n",
|
| 330 |
" age = (time.time() - os.path.getmtime(sft_path)) / 60\n",
|
| 331 |
-
" print(f'sft_data.jsonl: {count} examples
|
| 332 |
"else:\n",
|
| 333 |
-
" print('Generating SFT data...')\n",
|
| 334 |
-
"
|
| 335 |
" count = sum(1 for _ in open(sft_path))\n",
|
| 336 |
-
" print(f'Generated {count} examples')"
|
| 337 |
]
|
| 338 |
},
|
| 339 |
{
|
| 340 |
"cell_type": "markdown",
|
| 341 |
-
"id": "
|
| 342 |
"metadata": {},
|
| 343 |
"source": [
|
| 344 |
-
"##
|
| 345 |
]
|
| 346 |
},
|
| 347 |
{
|
| 348 |
"cell_type": "code",
|
| 349 |
"execution_count": null,
|
| 350 |
-
"id": "
|
| 351 |
"metadata": {},
|
| 352 |
"outputs": [],
|
| 353 |
"source": [
|
|
@@ -356,47 +202,33 @@
|
|
| 356 |
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 357 |
"\n",
|
| 358 |
"from unsloth import FastLanguageModel\n",
|
|
|
|
| 359 |
"\n",
|
| 360 |
-
"
|
| 361 |
-
"\n",
|
| 362 |
-
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
| 363 |
-
" model_name=MODEL_NAME,\n",
|
| 364 |
-
" max_seq_length=MAX_SEQ_LENGTH,\n",
|
| 365 |
-
" load_in_4bit=True, # QLoRA \u2014 mandatory for T4\n",
|
| 366 |
-
" dtype=None,\n",
|
| 367 |
-
")\n",
|
| 368 |
-
"\n",
|
| 369 |
-
"model = FastLanguageModel.get_peft_model(\n",
|
| 370 |
-
" model,\n",
|
| 371 |
-
" r=8, # r=8 is sufficient for short command vocab\n",
|
| 372 |
-
" target_modules=['q_proj', 'v_proj', 'k_proj', 'o_proj'],\n",
|
| 373 |
-
" lora_alpha=16, # = r, standard rule-of-thumb\n",
|
| 374 |
-
" lora_dropout=0,\n",
|
| 375 |
-
" bias='none',\n",
|
| 376 |
-
" use_gradient_checkpointing='unsloth',\n",
|
| 377 |
-
" random_state=42,\n",
|
| 378 |
-
")\n",
|
| 379 |
"\n",
|
| 380 |
-
"vram
|
| 381 |
-
"total = torch.cuda.get_device_properties(0).total_memory/1e9\n",
|
| 382 |
-
"print(f'\
|
| 383 |
-
"print(f' VRAM: {vram:.1f} GB / {total:.1f} GB ({100*vram/total:.0f}% used)')\n",
|
| 384 |
-
"print(f' LoRA trainable params: ~1.8M (r=8)')"
|
| 385 |
]
|
| 386 |
},
|
| 387 |
{
|
| 388 |
"cell_type": "markdown",
|
| 389 |
-
"id": "
|
| 390 |
"metadata": {},
|
| 391 |
"source": [
|
| 392 |
-
"##
|
| 393 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
]
|
| 395 |
},
|
| 396 |
{
|
| 397 |
"cell_type": "code",
|
| 398 |
"execution_count": null,
|
| 399 |
-
"id": "
|
| 400 |
"metadata": {},
|
| 401 |
"outputs": [],
|
| 402 |
"source": [
|
|
@@ -406,232 +238,211 @@
|
|
| 406 |
"\n",
|
| 407 |
"from train_data_centric import run_sft_warmup\n",
|
| 408 |
"\n",
|
| 409 |
-
"# In demo mode: reduce to 1 pass of 200 examples for speed\n",
|
| 410 |
-
"if DEMO_MODE:\n",
|
| 411 |
-
" import json\n",
|
| 412 |
-
" raw = [json.loads(l) for l in open('sft_data.jsonl')][:200]\n",
|
| 413 |
-
" with open('sft_data_demo.jsonl', 'w') as f:\n",
|
| 414 |
-
" for r in raw: f.write(json.dumps(r) + '\\n')\n",
|
| 415 |
-
" import shutil\n",
|
| 416 |
-
" shutil.copy('sft_data.jsonl', 'sft_data_full.jsonl')\n",
|
| 417 |
-
" shutil.copy('sft_data_demo.jsonl', 'sft_data.jsonl')\n",
|
| 418 |
-
" print('Demo mode: using 200 SFT examples')\n",
|
| 419 |
-
"\n",
|
| 420 |
"model = run_sft_warmup(model, tokenizer)\n",
|
| 421 |
-
"print('
|
| 422 |
-
"\n",
|
| 423 |
-
"# Restore full data if demo\n",
|
| 424 |
-
"if DEMO_MODE and os.path.exists('sft_data_full.jsonl'):\n",
|
| 425 |
-
" import shutil\n",
|
| 426 |
-
" shutil.copy('sft_data_full.jsonl', 'sft_data.jsonl')"
|
| 427 |
]
|
| 428 |
},
|
| 429 |
{
|
| 430 |
"cell_type": "markdown",
|
| 431 |
-
"id": "
|
| 432 |
"metadata": {},
|
| 433 |
"source": [
|
| 434 |
-
"##
|
| 435 |
-
"
|
| 436 |
-
"
|
| 437 |
-
"
|
| 438 |
-
"If Colab disconnects, re-run all cells \u2014 it auto-resumes from the latest checkpoint."
|
| 439 |
]
|
| 440 |
},
|
| 441 |
{
|
| 442 |
"cell_type": "code",
|
| 443 |
"execution_count": null,
|
| 444 |
-
"id": "
|
| 445 |
"metadata": {},
|
| 446 |
"outputs": [],
|
| 447 |
"source": [
|
| 448 |
-
"
|
| 449 |
-
"
|
| 450 |
-
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 451 |
-
"\n",
|
| 452 |
-
"from train_data_centric import run_grpo_training\n",
|
| 453 |
-
"\n",
|
| 454 |
-
"# Auto-detect checkpoint to resume from\n",
|
| 455 |
-
"ckpt_dir = './data-centric-checkpoints'\n",
|
| 456 |
-
"resume_from = None\n",
|
| 457 |
-
"if os.path.exists(ckpt_dir):\n",
|
| 458 |
-
" checkpoints = sorted(glob.glob(f'{ckpt_dir}/checkpoint-*'))\n",
|
| 459 |
-
" if checkpoints:\n",
|
| 460 |
-
" resume_from = checkpoints[-1]\n",
|
| 461 |
-
" print(f'Resuming from: {resume_from}')\n",
|
| 462 |
-
"\n",
|
| 463 |
-
"# max_steps > 0 limits the run (used for demo mode)\n",
|
| 464 |
-
"# max_steps = -1 means run full num_train_epochs\n",
|
| 465 |
-
"demo_steps = GRPO_STEPS if DEMO_MODE else -1\n",
|
| 466 |
-
"\n",
|
| 467 |
-
"model = run_grpo_training(\n",
|
| 468 |
-
" model, tokenizer,\n",
|
| 469 |
-
" resume_from_checkpoint=resume_from,\n",
|
| 470 |
-
" max_steps=demo_steps,\n",
|
| 471 |
-
")\n",
|
| 472 |
-
"print('\u2705 GRPO training complete')"
|
| 473 |
]
|
| 474 |
},
|
| 475 |
{
|
| 476 |
"cell_type": "markdown",
|
| 477 |
-
"id": "
|
| 478 |
"metadata": {},
|
| 479 |
"source": [
|
| 480 |
-
"##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
]
|
| 482 |
},
|
| 483 |
{
|
| 484 |
"cell_type": "code",
|
| 485 |
"execution_count": null,
|
| 486 |
-
"id": "
|
| 487 |
"metadata": {},
|
| 488 |
"outputs": [],
|
| 489 |
"source": [
|
| 490 |
-
"import os, sys\n",
|
| 491 |
"os.chdir('/content/data-centric-env')\n",
|
| 492 |
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 493 |
"\n",
|
| 494 |
-
"from train_data_centric import
|
| 495 |
-
"save_model(model, tokenizer)\n",
|
| 496 |
-
"print('\u2705 Model saved to ./data-centric-adapter/')"
|
| 497 |
-
]
|
| 498 |
-
},
|
| 499 |
-
{
|
| 500 |
-
"cell_type": "markdown",
|
| 501 |
-
"id": "a_tb",
|
| 502 |
-
"metadata": {},
|
| 503 |
-
"source": [
|
| 504 |
-
"## \ud83d\udcca Step 10.5 \u2014 Launch TensorBoard (Experiment Tracking)\n",
|
| 505 |
-
"View live loss and reward curves while training is running.\n",
|
| 506 |
-
"Both SFT and GRPO write logs automatically \u2014 no API key required."
|
| 507 |
-
]
|
| 508 |
-
},
|
| 509 |
-
{
|
| 510 |
-
"cell_type": "code",
|
| 511 |
-
"execution_count": null,
|
| 512 |
-
"id": "b_tb",
|
| 513 |
-
"metadata": {},
|
| 514 |
-
"outputs": [],
|
| 515 |
-
"source": [
|
| 516 |
-
"# Load TensorBoard extension (built into Colab/PyTorch)\n",
|
| 517 |
-
"%load_ext tensorboard\n",
|
| 518 |
-
"\n",
|
| 519 |
-
"# View SFT logs (run after Step 9)\n",
|
| 520 |
-
"# %tensorboard --logdir /content/data-centric-env/logs/sft\n",
|
| 521 |
"\n",
|
| 522 |
-
"#
|
| 523 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
"\n",
|
| 525 |
-
"
|
|
|
|
| 526 |
]
|
| 527 |
},
|
| 528 |
{
|
| 529 |
"cell_type": "markdown",
|
| 530 |
-
"id": "
|
| 531 |
"metadata": {},
|
| 532 |
"source": [
|
| 533 |
-
"##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
]
|
| 535 |
},
|
| 536 |
{
|
| 537 |
"cell_type": "code",
|
| 538 |
"execution_count": null,
|
| 539 |
-
"id": "
|
| 540 |
"metadata": {},
|
| 541 |
"outputs": [],
|
| 542 |
"source": [
|
| 543 |
"import os\n",
|
| 544 |
"os.chdir('/content/data-centric-env')\n",
|
| 545 |
"\n",
|
| 546 |
-
"
|
| 547 |
-
"
|
| 548 |
-
"
|
| 549 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
" from IPython.display import Image, display\n",
|
| 551 |
-
" for
|
| 552 |
-
" path = f'plots/{
|
| 553 |
" if os.path.exists(path):\n",
|
| 554 |
-
" print(f'\\n--- {
|
| 555 |
-
" display(Image(path))\n"
|
| 556 |
-
"else:\n",
|
| 557 |
-
" print('No training log yet \u2014 run Step 10 first')"
|
| 558 |
]
|
| 559 |
},
|
| 560 |
{
|
| 561 |
"cell_type": "markdown",
|
| 562 |
-
"id": "
|
| 563 |
"metadata": {},
|
| 564 |
"source": [
|
| 565 |
-
"##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
]
|
| 567 |
},
|
| 568 |
{
|
| 569 |
"cell_type": "code",
|
| 570 |
"execution_count": null,
|
| 571 |
-
"id": "
|
| 572 |
"metadata": {},
|
| 573 |
"outputs": [],
|
| 574 |
"source": [
|
| 575 |
"import os, json\n",
|
| 576 |
"os.chdir('/content/data-centric-env')\n",
|
| 577 |
"\n",
|
| 578 |
-
"
|
| 579 |
"\n",
|
| 580 |
"if os.path.exists('eval_results.json'):\n",
|
| 581 |
" with open('eval_results.json') as f:\n",
|
| 582 |
" results = json.load(f)\n",
|
| 583 |
-
" print('\\n===
|
| 584 |
" for k, v in results.items():\n",
|
| 585 |
-
" print(f' {k}: {v}')"
|
| 586 |
]
|
| 587 |
},
|
| 588 |
{
|
| 589 |
"cell_type": "markdown",
|
| 590 |
-
"id": "
|
| 591 |
"metadata": {},
|
| 592 |
"source": [
|
| 593 |
-
"##
|
| 594 |
-
"Run this **only after training + eval are done**."
|
| 595 |
]
|
| 596 |
},
|
| 597 |
{
|
| 598 |
"cell_type": "code",
|
| 599 |
"execution_count": null,
|
| 600 |
-
"id": "
|
| 601 |
"metadata": {},
|
| 602 |
"outputs": [],
|
| 603 |
"source": [
|
| 604 |
-
"import os\n",
|
| 605 |
"os.chdir('/content/data-centric-env')\n",
|
|
|
|
| 606 |
"\n",
|
| 607 |
-
"from
|
| 608 |
-
"
|
| 609 |
-
"
|
| 610 |
-
"\n",
|
| 611 |
-
"
|
| 612 |
-
"
|
| 613 |
-
"
|
| 614 |
-
"
|
| 615 |
-
"
|
| 616 |
-
"
|
| 617 |
-
"print('
|
| 618 |
]
|
| 619 |
},
|
| 620 |
{
|
| 621 |
"cell_type": "markdown",
|
| 622 |
-
"id": "
|
| 623 |
"metadata": {},
|
| 624 |
"source": [
|
| 625 |
-
"
|
| 626 |
-
"
|
| 627 |
-
"
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
]
|
| 636 |
}
|
| 637 |
]
|
|
|
|
| 3 |
"nbformat_minor": 5,
|
| 4 |
"metadata": {
|
| 5 |
"colab": {
|
| 6 |
+
"provenance": []
|
|
|
|
| 7 |
},
|
| 8 |
"kernelspec": {
|
| 9 |
"display_name": "Python 3",
|
|
|
|
| 17 |
"cells": [
|
| 18 |
{
|
| 19 |
"cell_type": "markdown",
|
| 20 |
+
"id": "hdr",
|
| 21 |
"metadata": {},
|
| 22 |
"source": [
|
| 23 |
+
"# π§ Data-Centric AI Agent β Training Notebook\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
"\n",
|
| 25 |
+
"**OpenEnv Hackathon 2026**\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
"\n",
|
| 27 |
+
"Trains **Qwen2.5-1.5B-Instruct** (4-bit QLoRA) to orchestrate data-cleaning specialists\n",
|
| 28 |
+
"via GRPO reinforcement learning on the Data-Centric AI environment.\n",
|
| 29 |
+
"\n",
|
| 30 |
+
"| | |\n",
|
| 31 |
+
"|---|---|\n",
|
| 32 |
+
"| **Space** | https://huggingface.co/spaces/Aswinis-Kumar/data-centric-env |\n",
|
| 33 |
+
"| **Repo** | https://github.com/CelestialWorthyOfHeavenAndEarth/data-centric-env |\n",
|
| 34 |
+
"| **Algorithm** | SFT warmup β GRPO (TRL) |\n",
|
| 35 |
+
"| **Model** | Qwen/Qwen2.5-1.5B-Instruct, 4-bit QLoRA r=8 |\n",
|
| 36 |
+
"\n",
|
| 37 |
+
"**Pipeline:**\n",
|
| 38 |
+
"1. Install deps\n",
|
| 39 |
+
"2. Clone repo + start env server\n",
|
| 40 |
+
"3. Generate SFT warmup data\n",
|
| 41 |
+
"4. Phase 1 β SFT warmup (~15 min)\n",
|
| 42 |
+
"5. Phase 2 β GRPO training (~45-90 min depending on hardware)\n",
|
| 43 |
+
"6. Plot reward curves\n",
|
| 44 |
+
"7. Evaluate vs baselines\n",
|
| 45 |
+
"8. Save model\n"
|
| 46 |
]
|
| 47 |
},
|
| 48 |
{
|
| 49 |
"cell_type": "markdown",
|
| 50 |
+
"id": "s1_md",
|
| 51 |
"metadata": {},
|
| 52 |
"source": [
|
| 53 |
+
"## π¦ Step 1 β Install Dependencies"
|
| 54 |
]
|
| 55 |
},
|
| 56 |
{
|
| 57 |
"cell_type": "code",
|
| 58 |
"execution_count": null,
|
| 59 |
+
"id": "s1",
|
| 60 |
"metadata": {},
|
| 61 |
"outputs": [],
|
| 62 |
"source": [
|
| 63 |
+
"# Pin transformers first to avoid torchao conflict\n",
|
| 64 |
+
"# (transformers β₯ 4.52 requires torchao β₯ 0.9 which needs torch.int1 β not in Colab yet)\n",
|
| 65 |
"!pip install \"transformers>=4.44.0,<4.52.0\" --quiet\n",
|
| 66 |
"\n",
|
| 67 |
+
"# Unsloth β detects the installed torch version automatically\n",
|
| 68 |
"!pip install \"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git\" --quiet\n",
|
| 69 |
"\n",
|
| 70 |
+
"# Training stack\n",
|
| 71 |
"!pip install trl>=0.15.0 datasets>=2.0.0 accelerate>=0.30.0 --quiet\n",
|
| 72 |
"!pip install scikit-learn>=1.3.0 pandas>=2.0.0 numpy matplotlib --quiet\n",
|
| 73 |
"!pip install \"openenv-core[core]>=0.2.1\" --quiet\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"# Verify Unsloth\n",
|
| 76 |
+
"from unsloth import FastLanguageModel\n",
|
| 77 |
+
"import torch\n",
|
| 78 |
+
"print(f'β
Unsloth ready | torch {torch.__version__} | GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"CPU\"}')\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
]
|
| 80 |
},
|
| 81 |
{
|
| 82 |
"cell_type": "markdown",
|
| 83 |
+
"id": "s2_md",
|
| 84 |
"metadata": {},
|
| 85 |
"source": [
|
| 86 |
+
"## π Step 2 β Clone Repo & Start Environment Server"
|
| 87 |
]
|
| 88 |
},
|
| 89 |
{
|
| 90 |
"cell_type": "code",
|
| 91 |
"execution_count": null,
|
| 92 |
+
"id": "s2",
|
| 93 |
"metadata": {},
|
| 94 |
"outputs": [],
|
| 95 |
"source": [
|
| 96 |
+
"import os, sys, shutil, subprocess, time, requests\n",
|
| 97 |
"\n",
|
| 98 |
+
"REPO = 'https://github.com/CelestialWorthyOfHeavenAndEarth/data-centric-env.git'\n",
|
| 99 |
"WORK_DIR = '/content/data-centric-env'\n",
|
| 100 |
"\n",
|
| 101 |
+
"# Clone or update\n",
|
| 102 |
"if os.path.exists(f'{WORK_DIR}/pyproject.toml'):\n",
|
| 103 |
+
" print('Repo exists β pulling latest...')\n",
|
| 104 |
+
" os.system(f'git -C {WORK_DIR} pull origin main')\n",
|
| 105 |
"else:\n",
|
| 106 |
+
" if os.path.exists(WORK_DIR): shutil.rmtree(WORK_DIR)\n",
|
| 107 |
+
" os.system(f'git clone {REPO} {WORK_DIR}')\n",
|
|
|
|
| 108 |
"\n",
|
| 109 |
"os.chdir(WORK_DIR)\n",
|
| 110 |
"sys.path.insert(0, WORK_DIR)\n",
|
| 111 |
+
"os.makedirs('logs', exist_ok=True)\n",
|
| 112 |
+
"os.makedirs('plots', exist_ok=True)\n",
|
| 113 |
"print('CWD:', os.getcwd())\n",
|
| 114 |
+
"os.system('git log --oneline -3')\n",
|
| 115 |
+
"os.system('pip install -e . --quiet 2>/dev/null || true')\n",
|
| 116 |
+
"print('β
Repo ready')\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
]
|
| 118 |
},
|
| 119 |
{
|
| 120 |
"cell_type": "code",
|
| 121 |
"execution_count": null,
|
| 122 |
+
"id": "s2b",
|
| 123 |
"metadata": {},
|
| 124 |
"outputs": [],
|
| 125 |
"source": [
|
| 126 |
+
"import subprocess, time, requests, os\n",
|
|
|
|
|
|
|
| 127 |
"\n",
|
| 128 |
+
"# Kill any leftover server\n",
|
| 129 |
+
"os.system('fuser -k 8000/tcp 2>/dev/null || true')\n",
|
| 130 |
"time.sleep(2)\n",
|
| 131 |
"\n",
|
| 132 |
+
"server = subprocess.Popen(\n",
|
| 133 |
" ['python', '-m', 'uvicorn', 'server.app:app',\n",
|
| 134 |
" '--host', '0.0.0.0', '--port', '8000', '--log-level', 'warning'],\n",
|
| 135 |
" stdout=subprocess.PIPE, stderr=subprocess.PIPE\n",
|
| 136 |
")\n",
|
| 137 |
"\n",
|
| 138 |
+
"for i in range(40):\n",
|
| 139 |
" try:\n",
|
| 140 |
+
" r = requests.get('http://localhost:8000/health', timeout=2)\n",
|
| 141 |
+
" if r.status_code == 200:\n",
|
| 142 |
+
" print(f'β
Server ready after {i+1}s β {r.json()}')\n",
|
| 143 |
" break\n",
|
| 144 |
+
" except: time.sleep(1)\n",
|
|
|
|
| 145 |
"else:\n",
|
| 146 |
+
" out, err = server.communicate(timeout=5)\n",
|
|
|
|
| 147 |
" print('STDOUT:', out.decode()[-2000:])\n",
|
| 148 |
" print('STDERR:', err.decode()[-2000:])\n",
|
| 149 |
+
" raise RuntimeError('Server failed to start')\n",
|
| 150 |
"\n",
|
| 151 |
+
"os.environ['ENV_URL'] = 'http://localhost:8000'\n",
|
| 152 |
+
"print('ENV_URL set to http://localhost:8000')\n"
|
| 153 |
]
|
| 154 |
},
|
| 155 |
{
|
| 156 |
"cell_type": "markdown",
|
| 157 |
+
"id": "s3_md",
|
| 158 |
"metadata": {},
|
| 159 |
"source": [
|
| 160 |
+
"## π Step 3 β Generate SFT Warmup Data"
|
| 161 |
]
|
| 162 |
},
|
| 163 |
{
|
| 164 |
"cell_type": "code",
|
| 165 |
"execution_count": null,
|
| 166 |
+
"id": "s3",
|
| 167 |
"metadata": {},
|
| 168 |
"outputs": [],
|
| 169 |
"source": [
|
|
|
|
| 174 |
"if os.path.exists(sft_path):\n",
|
| 175 |
" count = sum(1 for _ in open(sft_path))\n",
|
| 176 |
" age = (time.time() - os.path.getmtime(sft_path)) / 60\n",
|
| 177 |
+
" print(f'β
sft_data.jsonl: {count} examples ({age:.0f} min old) β reusing')\n",
|
| 178 |
"else:\n",
|
| 179 |
+
" print('Generating SFT warmup data (~2 min)...')\n",
|
| 180 |
+
" os.system('python sft_generator.py')\n",
|
| 181 |
" count = sum(1 for _ in open(sft_path))\n",
|
| 182 |
+
" print(f'β
Generated {count} SFT examples')\n"
|
| 183 |
]
|
| 184 |
},
|
| 185 |
{
|
| 186 |
"cell_type": "markdown",
|
| 187 |
+
"id": "s4_md",
|
| 188 |
"metadata": {},
|
| 189 |
"source": [
|
| 190 |
+
"## π€ Step 4 β Load Qwen2.5-1.5B (4-bit QLoRA)"
|
| 191 |
]
|
| 192 |
},
|
| 193 |
{
|
| 194 |
"cell_type": "code",
|
| 195 |
"execution_count": null,
|
| 196 |
+
"id": "s4",
|
| 197 |
"metadata": {},
|
| 198 |
"outputs": [],
|
| 199 |
"source": [
|
|
|
|
| 202 |
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 203 |
"\n",
|
| 204 |
"from unsloth import FastLanguageModel\n",
|
| 205 |
+
"from train_data_centric import load_model\n",
|
| 206 |
"\n",
|
| 207 |
+
"model, tokenizer = load_model()\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
"\n",
|
| 209 |
+
"vram = torch.cuda.memory_allocated() / 1e9\n",
|
| 210 |
+
"total = torch.cuda.get_device_properties(0).total_memory / 1e9\n",
|
| 211 |
+
"print(f'\\nπ VRAM: {vram:.1f} GB used / {total:.1f} GB total ({100*vram/total:.0f}%)')\n"
|
|
|
|
|
|
|
| 212 |
]
|
| 213 |
},
|
| 214 |
{
|
| 215 |
"cell_type": "markdown",
|
| 216 |
+
"id": "s5_md",
|
| 217 |
"metadata": {},
|
| 218 |
"source": [
|
| 219 |
+
"## π Step 5 β Phase 1: SFT Warmup\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"One epoch of supervised fine-tuning on ~9,480 heuristic trajectories.\n",
|
| 222 |
+
"Teaches the model valid command syntax before RL starts.\n",
|
| 223 |
+
"Without this, the model outputs gibberish and gets zero reward.\n",
|
| 224 |
+
"\n",
|
| 225 |
+
"**TensorBoard:** Run Step 6 to open the live dashboard.\n"
|
| 226 |
]
|
| 227 |
},
|
| 228 |
{
|
| 229 |
"cell_type": "code",
|
| 230 |
"execution_count": null,
|
| 231 |
+
"id": "s5",
|
| 232 |
"metadata": {},
|
| 233 |
"outputs": [],
|
| 234 |
"source": [
|
|
|
|
| 238 |
"\n",
|
| 239 |
"from train_data_centric import run_sft_warmup\n",
|
| 240 |
"\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
"model = run_sft_warmup(model, tokenizer)\n",
|
| 242 |
+
"print('β
SFT warmup complete β model knows command syntax')\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
]
|
| 244 |
},
|
| 245 |
{
|
| 246 |
"cell_type": "markdown",
|
| 247 |
+
"id": "s6_md",
|
| 248 |
"metadata": {},
|
| 249 |
"source": [
|
| 250 |
+
"## π‘ Step 6 β TensorBoard (Experiment Tracking)\n",
|
| 251 |
+
"\n",
|
| 252 |
+
"Launch this now β it will show SFT logs immediately and GRPO logs as training progresses.\n",
|
| 253 |
+
"Refresh the TensorBoard tab every few minutes during Step 7.\n"
|
|
|
|
| 254 |
]
|
| 255 |
},
|
| 256 |
{
|
| 257 |
"cell_type": "code",
|
| 258 |
"execution_count": null,
|
| 259 |
+
"id": "s6",
|
| 260 |
"metadata": {},
|
| 261 |
"outputs": [],
|
| 262 |
"source": [
|
| 263 |
+
"%load_ext tensorboard\n",
|
| 264 |
+
"%tensorboard --logdir /content/data-centric-env/logs\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
]
|
| 266 |
},
|
| 267 |
{
|
| 268 |
"cell_type": "markdown",
|
| 269 |
+
"id": "s7_md",
|
| 270 |
"metadata": {},
|
| 271 |
"source": [
|
| 272 |
+
"## ποΈ Step 7 β Phase 2: GRPO Training\n",
|
| 273 |
+
"\n",
|
| 274 |
+
"Agent learns *when* to use each command via reinforcement learning.\n",
|
| 275 |
+
"Each GRPO step: model generates a command β environment executes it β reward flows back.\n",
|
| 276 |
+
"\n",
|
| 277 |
+
"**Reward discrimination:** The reward is now strictly discriminating:\n",
|
| 278 |
+
"- Hitting target efficiently (few steps): **+0.7 β +0.9**\n",
|
| 279 |
+
"- Hitting target slowly (many steps): **+0.3 β +0.5**\n",
|
| 280 |
+
"- Failing to hit target: **β0.1 β β0.5**\n",
|
| 281 |
+
"- Blind apply / blind submit: **large penalties**\n",
|
| 282 |
+
"\n",
|
| 283 |
+
"Checkpoints saved every 25 steps to `./data-centric-checkpoints/`.\n",
|
| 284 |
+
"If the runtime disconnects, re-run all cells β it auto-resumes from the latest checkpoint.\n"
|
| 285 |
]
|
| 286 |
},
|
| 287 |
{
|
| 288 |
"cell_type": "code",
|
| 289 |
"execution_count": null,
|
| 290 |
+
"id": "s7",
|
| 291 |
"metadata": {},
|
| 292 |
"outputs": [],
|
| 293 |
"source": [
|
| 294 |
+
"import os, sys, glob\n",
|
| 295 |
"os.chdir('/content/data-centric-env')\n",
|
| 296 |
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 297 |
"\n",
|
| 298 |
+
"from train_data_centric import run_grpo_training\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
"\n",
|
| 300 |
+
"# Auto-resume from latest checkpoint\n",
|
| 301 |
+
"ckpt_dir = './data-centric-checkpoints'\n",
|
| 302 |
+
"resume_from = None\n",
|
| 303 |
+
"if os.path.exists(ckpt_dir):\n",
|
| 304 |
+
" checkpoints = sorted(glob.glob(f'{ckpt_dir}/checkpoint-*'))\n",
|
| 305 |
+
" if checkpoints:\n",
|
| 306 |
+
" resume_from = checkpoints[-1]\n",
|
| 307 |
+
" print(f'Resuming from: {resume_from}')\n",
|
| 308 |
"\n",
|
| 309 |
+
"model = run_grpo_training(model, tokenizer, resume_from_checkpoint=resume_from)\n",
|
| 310 |
+
"print('β
GRPO training complete')\n"
|
| 311 |
]
|
| 312 |
},
|
| 313 |
{
|
| 314 |
"cell_type": "markdown",
|
| 315 |
+
"id": "s8_md",
|
| 316 |
"metadata": {},
|
| 317 |
"source": [
|
| 318 |
+
"## π Step 8 β Plot Reward Curves\n",
|
| 319 |
+
"\n",
|
| 320 |
+
"Generates 4 plots from the training log:\n",
|
| 321 |
+
"- **reward_curve.png** β Episode reward over time with rolling mean\n",
|
| 322 |
+
"- **success_rate.png** β Success rate per curriculum level\n",
|
| 323 |
+
"- **accuracy_gain.png** β Accuracy gain vs baseline per episode\n",
|
| 324 |
+
"- **curriculum.png** β Curriculum level progression\n"
|
| 325 |
]
|
| 326 |
},
|
| 327 |
{
|
| 328 |
"cell_type": "code",
|
| 329 |
"execution_count": null,
|
| 330 |
+
"id": "s8",
|
| 331 |
"metadata": {},
|
| 332 |
"outputs": [],
|
| 333 |
"source": [
|
| 334 |
"import os\n",
|
| 335 |
"os.chdir('/content/data-centric-env')\n",
|
| 336 |
"\n",
|
| 337 |
+
"log_path = 'logs/training.jsonl'\n",
|
| 338 |
+
"\n",
|
| 339 |
+
"if not os.path.exists(log_path):\n",
|
| 340 |
+
" print('No training log yet β run Step 7 first')\n",
|
| 341 |
+
"else:\n",
|
| 342 |
+
" n_eps = sum(1 for _ in open(log_path))\n",
|
| 343 |
+
" print(f'Training log: {n_eps} episodes')\n",
|
| 344 |
+
"\n",
|
| 345 |
+
" os.system(f'python plot_rewards.py --log {log_path} --out plots/')\n",
|
| 346 |
+
"\n",
|
| 347 |
" from IPython.display import Image, display\n",
|
| 348 |
+
" for fname in ['reward_curve.png', 'success_rate.png', 'accuracy_gain.png', 'curriculum.png']:\n",
|
| 349 |
+
" path = f'plots/{fname}'\n",
|
| 350 |
" if os.path.exists(path):\n",
|
| 351 |
+
" print(f'\\n--- {fname} ---')\n",
|
| 352 |
+
" display(Image(path, width=900))\n"
|
|
|
|
|
|
|
| 353 |
]
|
| 354 |
},
|
| 355 |
{
|
| 356 |
"cell_type": "markdown",
|
| 357 |
+
"id": "s9_md",
|
| 358 |
"metadata": {},
|
| 359 |
"source": [
|
| 360 |
+
"## π Step 9 β Evaluate: Trained Agent vs Baselines\n",
|
| 361 |
+
"\n",
|
| 362 |
+
"Runs the trained agent against:\n",
|
| 363 |
+
"- **Random baseline** β random command each step\n",
|
| 364 |
+
"- **Heuristic baseline** β fixed rule-based sequence\n",
|
| 365 |
+
"- **Trained agent** β your GRPO-trained model\n"
|
| 366 |
]
|
| 367 |
},
|
| 368 |
{
|
| 369 |
"cell_type": "code",
|
| 370 |
"execution_count": null,
|
| 371 |
+
"id": "s9",
|
| 372 |
"metadata": {},
|
| 373 |
"outputs": [],
|
| 374 |
"source": [
|
| 375 |
"import os, json\n",
|
| 376 |
"os.chdir('/content/data-centric-env')\n",
|
| 377 |
"\n",
|
| 378 |
+
"os.system('python eval_data_centric.py')\n",
|
| 379 |
"\n",
|
| 380 |
"if os.path.exists('eval_results.json'):\n",
|
| 381 |
" with open('eval_results.json') as f:\n",
|
| 382 |
" results = json.load(f)\n",
|
| 383 |
+
" print('\\n=== Evaluation Results ===')\n",
|
| 384 |
" for k, v in results.items():\n",
|
| 385 |
+
" print(f' {k}: {v}')\n"
|
| 386 |
]
|
| 387 |
},
|
| 388 |
{
|
| 389 |
"cell_type": "markdown",
|
| 390 |
+
"id": "s10_md",
|
| 391 |
"metadata": {},
|
| 392 |
"source": [
|
| 393 |
+
"## πΎ Step 10 β Save Trained LoRA Adapter"
|
|
|
|
| 394 |
]
|
| 395 |
},
|
| 396 |
{
|
| 397 |
"cell_type": "code",
|
| 398 |
"execution_count": null,
|
| 399 |
+
"id": "s10",
|
| 400 |
"metadata": {},
|
| 401 |
"outputs": [],
|
| 402 |
"source": [
|
| 403 |
+
"import os, sys\n",
|
| 404 |
"os.chdir('/content/data-centric-env')\n",
|
| 405 |
+
"sys.path.insert(0, '/content/data-centric-env')\n",
|
| 406 |
"\n",
|
| 407 |
+
"from train_data_centric import save_model\n",
|
| 408 |
+
"save_model(model, tokenizer)\n",
|
| 409 |
+
"print('β
Adapter saved to ./data-centric-adapter/')\n",
|
| 410 |
+
"\n",
|
| 411 |
+
"# List outputs\n",
|
| 412 |
+
"import glob\n",
|
| 413 |
+
"print('\\nπ Training outputs:')\n",
|
| 414 |
+
"for f in sorted(glob.glob('plots/*.png') + glob.glob('logs/*.jsonl') + ['eval_results.json']):\n",
|
| 415 |
+
" if os.path.exists(f):\n",
|
| 416 |
+
" size = os.path.getsize(f) / 1024\n",
|
| 417 |
+
" print(f' {f} ({size:.1f} KB)')\n"
|
| 418 |
]
|
| 419 |
},
|
| 420 |
{
|
| 421 |
"cell_type": "markdown",
|
| 422 |
+
"id": "s11_md",
|
| 423 |
"metadata": {},
|
| 424 |
"source": [
|
| 425 |
+
"## π€ Step 11 β Download Results\n",
|
| 426 |
+
"\n",
|
| 427 |
+
"Download plots and training log to your local machine.\n"
|
| 428 |
+
]
|
| 429 |
+
},
|
| 430 |
+
{
|
| 431 |
+
"cell_type": "code",
|
| 432 |
+
"execution_count": null,
|
| 433 |
+
"id": "s11",
|
| 434 |
+
"metadata": {},
|
| 435 |
+
"outputs": [],
|
| 436 |
+
"source": [
|
| 437 |
+
"from google.colab import files\n",
|
| 438 |
+
"import glob, os\n",
|
| 439 |
+
"\n",
|
| 440 |
+
"to_download = glob.glob('plots/*.png') + ['logs/training.jsonl', 'eval_results.json']\n",
|
| 441 |
+
"\n",
|
| 442 |
+
"for f in to_download:\n",
|
| 443 |
+
" if os.path.exists(f):\n",
|
| 444 |
+
" print(f'Downloading: {f}')\n",
|
| 445 |
+
" files.download(f)\n"
|
| 446 |
]
|
| 447 |
}
|
| 448 |
]
|