Commit Β·
2cb6617
1
Parent(s): 3b185f9
feat: upgrade notebook to Qwen3.5-4B with H100 hyperparams
Browse files- Model: unsloth/Qwen3.5-4B (from Qwen3-0.6B)
- num_generations: 8 (from 4) for smoother GRPO advantages
- learning_rate: 5e-5 (from 2e-4) for more stable training
- per_device_train_batch_size: 8 (from 4)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
training/notebooks/fusion_design_lab_training.ipynb
CHANGED
|
@@ -5,14 +5,14 @@
|
|
| 5 |
"id": "7fb27b941602401d91542211134fc71a",
|
| 6 |
"metadata": {},
|
| 7 |
"source": [
|
| 8 |
-
"# Fusion Design Lab
|
| 9 |
"\n",
|
| 10 |
"Train an LLM to optimize stellarator fusion reactor designs using **GRPO** (Group Relative Policy Optimization) with **Unsloth** and **TRL**.\n",
|
| 11 |
"\n",
|
| 12 |
"The agent interacts with a constrained optimization environment where it adjusts 4 geometric knobs of a stellarator boundary, aiming to **minimize max elongation** while satisfying 3 hard physics constraints:\n",
|
| 13 |
-
"- `aspect_ratio
|
| 14 |
-
"- `average_triangularity
|
| 15 |
-
"- `edge_iota_over_nfp
|
| 16 |
"\n",
|
| 17 |
"Each episode has **6 evaluations** budgeted. The agent produces a plan of actions and the environment scores it via the `constellaration` physics verifier.\n",
|
| 18 |
"\n",
|
|
@@ -59,29 +59,7 @@
|
|
| 59 |
"id": "72eea5119410473aa328ad9291626812",
|
| 60 |
"metadata": {},
|
| 61 |
"outputs": [],
|
| 62 |
-
"source": [
|
| 63 |
-
"from unsloth import FastLanguageModel\n",
|
| 64 |
-
"\n",
|
| 65 |
-
"MODEL_NAME = \"unsloth/Qwen3-0.6B\"\n",
|
| 66 |
-
"MAX_SEQ_LENGTH = 2048\n",
|
| 67 |
-
"\n",
|
| 68 |
-
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
| 69 |
-
" model_name=MODEL_NAME,\n",
|
| 70 |
-
" max_seq_length=MAX_SEQ_LENGTH,\n",
|
| 71 |
-
" load_in_4bit=True,\n",
|
| 72 |
-
" fast_inference=True,\n",
|
| 73 |
-
")\n",
|
| 74 |
-
"\n",
|
| 75 |
-
"model = FastLanguageModel.get_peft_model(\n",
|
| 76 |
-
" model,\n",
|
| 77 |
-
" r=32,\n",
|
| 78 |
-
" target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
|
| 79 |
-
" lora_alpha=32,\n",
|
| 80 |
-
" use_gradient_checkpointing=\"unsloth\",\n",
|
| 81 |
-
")\n",
|
| 82 |
-
"\n",
|
| 83 |
-
"print(f\"Model loaded: {MODEL_NAME}\")"
|
| 84 |
-
]
|
| 85 |
},
|
| 86 |
{
|
| 87 |
"cell_type": "markdown",
|
|
@@ -220,7 +198,7 @@
|
|
| 220 |
"source": [
|
| 221 |
"## 6. Reward Function\n",
|
| 222 |
"\n",
|
| 223 |
-
"The environment reward executes each generated action plan in the stellarator environment and returns the cumulative low-fidelity Reward V0 from the live environment. The environment's built-in reward decomposes feasibility (+3/-3 crossing bonuses, feasibility progress), objective (max elongation improvement), step costs, and failure penalties
|
| 224 |
"\n",
|
| 225 |
"For the current training workflow, the notebook ignores `submit` and does not auto-submit. GRPO therefore optimizes the low-fidelity `run` path only. The live observation telemetry still exposes `reward_breakdown` and `action_monitor` for debugging reward behavior.\n"
|
| 226 |
]
|
|
@@ -313,41 +291,7 @@
|
|
| 313 |
"id": "8a65eabff63a45729fe45fb5ade58bdc",
|
| 314 |
"metadata": {},
|
| 315 |
"outputs": [],
|
| 316 |
-
"source": [
|
| 317 |
-
"from trl import GRPOConfig, GRPOTrainer\n",
|
| 318 |
-
"\n",
|
| 319 |
-
"MAX_PROMPT_LENGTH = 768\n",
|
| 320 |
-
"MAX_COMPLETION_LENGTH = MAX_SEQ_LENGTH - MAX_PROMPT_LENGTH\n",
|
| 321 |
-
"\n",
|
| 322 |
-
"training_args = GRPOConfig(\n",
|
| 323 |
-
" output_dir=\"./grpo_fusion_output\",\n",
|
| 324 |
-
" learning_rate=2e-4,\n",
|
| 325 |
-
" num_generations=4,\n",
|
| 326 |
-
" max_completion_length=MAX_COMPLETION_LENGTH,\n",
|
| 327 |
-
" max_prompt_length=MAX_PROMPT_LENGTH,\n",
|
| 328 |
-
" per_device_train_batch_size=4,\n",
|
| 329 |
-
" gradient_accumulation_steps=1,\n",
|
| 330 |
-
" max_steps=60,\n",
|
| 331 |
-
" temperature=1.0,\n",
|
| 332 |
-
" logging_steps=1,\n",
|
| 333 |
-
" save_steps=20,\n",
|
| 334 |
-
" bf16=True,\n",
|
| 335 |
-
" report_to=\"none\",\n",
|
| 336 |
-
" seed=42,\n",
|
| 337 |
-
")\n",
|
| 338 |
-
"\n",
|
| 339 |
-
"trainer = GRPOTrainer(\n",
|
| 340 |
-
" model=model,\n",
|
| 341 |
-
" processing_class=tokenizer,\n",
|
| 342 |
-
" reward_funcs=[environment_reward_fn],\n",
|
| 343 |
-
" args=training_args,\n",
|
| 344 |
-
" train_dataset=dataset,\n",
|
| 345 |
-
")\n",
|
| 346 |
-
"\n",
|
| 347 |
-
"print(\"Starting GRPO training...\")\n",
|
| 348 |
-
"train_result = trainer.train()\n",
|
| 349 |
-
"print(f\"Training complete. Total steps: {train_result.global_step}\")"
|
| 350 |
-
]
|
| 351 |
},
|
| 352 |
{
|
| 353 |
"cell_type": "markdown",
|
|
@@ -401,7 +345,7 @@
|
|
| 401 |
"else:\n",
|
| 402 |
" axes[1].text(0.5, 0.5, \"Reward metrics not logged\", ha=\"center\", va=\"center\")\n",
|
| 403 |
"\n",
|
| 404 |
-
"plt.suptitle(\"Fusion Design Lab
|
| 405 |
"plt.tight_layout()\n",
|
| 406 |
"plt.savefig(\"training_curves.png\", dpi=150, bbox_inches=\"tight\")\n",
|
| 407 |
"plt.show()\n",
|
|
@@ -474,7 +418,7 @@
|
|
| 474 |
" episode = run_episode_with_actions(actions, seed_idx=seed_idx)\n",
|
| 475 |
" trace = [\n",
|
| 476 |
" (\n",
|
| 477 |
-
" f\"{step.action_label}
|
| 478 |
" f\"score={step.p1_score:.4f} feasible={step.constraints_satisfied} \"\n",
|
| 479 |
" f\"terms={reward_term_summary(step)}\"\n",
|
| 480 |
" )\n",
|
|
@@ -497,7 +441,7 @@
|
|
| 497 |
"for seed in range(len(RESET_SEEDS)):\n",
|
| 498 |
" reward, trace = run_episode_with_model(seed)\n",
|
| 499 |
" trained_rewards.append(reward)\n",
|
| 500 |
-
" print(f\"\\nSeed {seed}
|
| 501 |
" for line in trace:\n",
|
| 502 |
" print(f\" {line}\")\n",
|
| 503 |
"\n",
|
|
@@ -511,7 +455,7 @@
|
|
| 511 |
" seed_rewards = [run_random_episode(seed) for _ in range(10)]\n",
|
| 512 |
" random_rewards.extend(seed_rewards)\n",
|
| 513 |
" print(\n",
|
| 514 |
-
" f\"Seed {seed}
|
| 515 |
" )\n",
|
| 516 |
"\n",
|
| 517 |
"print(f\"\\nMean random reward: {sum(random_rewards) / len(random_rewards):.3f}\")\n",
|
|
@@ -550,7 +494,7 @@
|
|
| 550 |
"with FusionLabClient(base_url=HF_SPACE_URL) as env:\n",
|
| 551 |
" reset_result = env.reset(seed=42)\n",
|
| 552 |
" remote_obs = reset_result.observation\n",
|
| 553 |
-
" print(f\"\\nRemote reset
|
| 554 |
" print(f\" aspect_ratio: {remote_obs.aspect_ratio:.4f}\")\n",
|
| 555 |
" print(f\" constraints_satisfied: {remote_obs.constraints_satisfied}\")\n",
|
| 556 |
" print(f\" budget_remaining: {remote_obs.budget_remaining}\")\n",
|
|
@@ -576,7 +520,7 @@
|
|
| 576 |
" print(\n",
|
| 577 |
" f\" Step {i}: {action.intent} {action.parameter or ''} \"\n",
|
| 578 |
" f\"{action.direction or ''} {action.magnitude or ''} \"\n",
|
| 579 |
-
" f\"
|
| 580 |
" )\n",
|
| 581 |
" if result.done:\n",
|
| 582 |
" print(f\" Episode done. Final score: {step_obs.p1_score:.4f}\")\n",
|
|
|
|
| 5 |
"id": "7fb27b941602401d91542211134fc71a",
|
| 6 |
"metadata": {},
|
| 7 |
"source": [
|
| 8 |
+
"# Fusion Design Lab β GRPO Training\n",
|
| 9 |
"\n",
|
| 10 |
"Train an LLM to optimize stellarator fusion reactor designs using **GRPO** (Group Relative Policy Optimization) with **Unsloth** and **TRL**.\n",
|
| 11 |
"\n",
|
| 12 |
"The agent interacts with a constrained optimization environment where it adjusts 4 geometric knobs of a stellarator boundary, aiming to **minimize max elongation** while satisfying 3 hard physics constraints:\n",
|
| 13 |
+
"- `aspect_ratio β€ 4.0`\n",
|
| 14 |
+
"- `average_triangularity β€ -0.5`\n",
|
| 15 |
+
"- `edge_iota_over_nfp β₯ 0.3`\n",
|
| 16 |
"\n",
|
| 17 |
"Each episode has **6 evaluations** budgeted. The agent produces a plan of actions and the environment scores it via the `constellaration` physics verifier.\n",
|
| 18 |
"\n",
|
|
|
|
| 59 |
"id": "72eea5119410473aa328ad9291626812",
|
| 60 |
"metadata": {},
|
| 61 |
"outputs": [],
|
| 62 |
+
"source": "from unsloth import FastLanguageModel\n\nMODEL_NAME = \"unsloth/Qwen3.5-4B\"\nMAX_SEQ_LENGTH = 2048\n\nmodel, tokenizer = FastLanguageModel.from_pretrained(\n model_name=MODEL_NAME,\n max_seq_length=MAX_SEQ_LENGTH,\n load_in_4bit=True,\n fast_inference=True,\n)\n\nmodel = FastLanguageModel.get_peft_model(\n model,\n r=32,\n target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"],\n lora_alpha=32,\n use_gradient_checkpointing=\"unsloth\",\n)\n\nprint(f\"Model loaded: {MODEL_NAME}\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
},
|
| 64 |
{
|
| 65 |
"cell_type": "markdown",
|
|
|
|
| 198 |
"source": [
|
| 199 |
"## 6. Reward Function\n",
|
| 200 |
"\n",
|
| 201 |
+
"The environment reward executes each generated action plan in the stellarator environment and returns the cumulative low-fidelity Reward V0 from the live environment. The environment's built-in reward decomposes feasibility (+3/-3 crossing bonuses, feasibility progress), objective (max elongation improvement), step costs, and failure penalties β see `server/environment.py:_compute_reward_breakdown(...)`.\n",
|
| 202 |
"\n",
|
| 203 |
"For the current training workflow, the notebook ignores `submit` and does not auto-submit. GRPO therefore optimizes the low-fidelity `run` path only. The live observation telemetry still exposes `reward_breakdown` and `action_monitor` for debugging reward behavior.\n"
|
| 204 |
]
|
|
|
|
| 291 |
"id": "8a65eabff63a45729fe45fb5ade58bdc",
|
| 292 |
"metadata": {},
|
| 293 |
"outputs": [],
|
| 294 |
+
"source": "from trl import GRPOConfig, GRPOTrainer\n\nMAX_PROMPT_LENGTH = 768\nMAX_COMPLETION_LENGTH = MAX_SEQ_LENGTH - MAX_PROMPT_LENGTH\n\ntraining_args = GRPOConfig(\n output_dir=\"./grpo_fusion_output\",\n learning_rate=5e-5,\n num_generations=8,\n max_completion_length=MAX_COMPLETION_LENGTH,\n max_prompt_length=MAX_PROMPT_LENGTH,\n per_device_train_batch_size=8,\n gradient_accumulation_steps=1,\n max_steps=60,\n temperature=1.0,\n logging_steps=1,\n save_steps=20,\n bf16=True,\n report_to=\"none\",\n seed=42,\n)\n\ntrainer = GRPOTrainer(\n model=model,\n processing_class=tokenizer,\n reward_funcs=[environment_reward_fn],\n args=training_args,\n train_dataset=dataset,\n)\n\nprint(\"Starting GRPO training...\")\ntrain_result = trainer.train()\nprint(f\"Training complete. Total steps: {train_result.global_step}\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
},
|
| 296 |
{
|
| 297 |
"cell_type": "markdown",
|
|
|
|
| 345 |
"else:\n",
|
| 346 |
" axes[1].text(0.5, 0.5, \"Reward metrics not logged\", ha=\"center\", va=\"center\")\n",
|
| 347 |
"\n",
|
| 348 |
+
"plt.suptitle(\"Fusion Design Lab β GRPO Training Curves\", fontsize=14, fontweight=\"bold\")\n",
|
| 349 |
"plt.tight_layout()\n",
|
| 350 |
"plt.savefig(\"training_curves.png\", dpi=150, bbox_inches=\"tight\")\n",
|
| 351 |
"plt.show()\n",
|
|
|
|
| 418 |
" episode = run_episode_with_actions(actions, seed_idx=seed_idx)\n",
|
| 419 |
" trace = [\n",
|
| 420 |
" (\n",
|
| 421 |
+
" f\"{step.action_label} β reward={step.reward:.3f} \"\n",
|
| 422 |
" f\"score={step.p1_score:.4f} feasible={step.constraints_satisfied} \"\n",
|
| 423 |
" f\"terms={reward_term_summary(step)}\"\n",
|
| 424 |
" )\n",
|
|
|
|
| 441 |
"for seed in range(len(RESET_SEEDS)):\n",
|
| 442 |
" reward, trace = run_episode_with_model(seed)\n",
|
| 443 |
" trained_rewards.append(reward)\n",
|
| 444 |
+
" print(f\"\\nSeed {seed} β Total reward: {reward:.3f}\")\n",
|
| 445 |
" for line in trace:\n",
|
| 446 |
" print(f\" {line}\")\n",
|
| 447 |
"\n",
|
|
|
|
| 455 |
" seed_rewards = [run_random_episode(seed) for _ in range(10)]\n",
|
| 456 |
" random_rewards.extend(seed_rewards)\n",
|
| 457 |
" print(\n",
|
| 458 |
+
" f\"Seed {seed} β Mean: {sum(seed_rewards) / len(seed_rewards):.3f}, Best: {max(seed_rewards):.3f}\"\n",
|
| 459 |
" )\n",
|
| 460 |
"\n",
|
| 461 |
"print(f\"\\nMean random reward: {sum(random_rewards) / len(random_rewards):.3f}\")\n",
|
|
|
|
| 494 |
"with FusionLabClient(base_url=HF_SPACE_URL) as env:\n",
|
| 495 |
" reset_result = env.reset(seed=42)\n",
|
| 496 |
" remote_obs = reset_result.observation\n",
|
| 497 |
+
" print(f\"\\nRemote reset β max_elongation: {remote_obs.max_elongation:.4f}\")\n",
|
| 498 |
" print(f\" aspect_ratio: {remote_obs.aspect_ratio:.4f}\")\n",
|
| 499 |
" print(f\" constraints_satisfied: {remote_obs.constraints_satisfied}\")\n",
|
| 500 |
" print(f\" budget_remaining: {remote_obs.budget_remaining}\")\n",
|
|
|
|
| 520 |
" print(\n",
|
| 521 |
" f\" Step {i}: {action.intent} {action.parameter or ''} \"\n",
|
| 522 |
" f\"{action.direction or ''} {action.magnitude or ''} \"\n",
|
| 523 |
+
" f\"β reward={reward:.3f}, score={step_obs.p1_score:.4f}, terms={reward_term_summary(step_obs)}\"\n",
|
| 524 |
" )\n",
|
| 525 |
" if result.done:\n",
|
| 526 |
" print(f\" Episode done. Final score: {step_obs.p1_score:.4f}\")\n",
|