Spaces:
Sleeping
Sleeping
fix(v4): reduce MAX_COMP_LEN 384→192, fix eval hanging with for_inference
Browse filesMAX_COMP_LEN=384 caused clipped_ratio=1.000 every step → all completion
gradients masked → Training Loss=0.000. JSON actions are 80-120 tokens;
192 gives headroom without filling TRL's buffer.
Eval cell hung because Unsloth's training hooks were still active after
trainer.train(). FastLanguageModel.for_inference(model) is required before
model.generate() post-training. Also reduced eval max_new_tokens 256→128.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- training/train_v4.ipynb +64 -167
training/train_v4.ipynb
CHANGED
|
@@ -2,8 +2,15 @@
|
|
| 2 |
"nbformat": 4,
|
| 3 |
"nbformat_minor": 5,
|
| 4 |
"metadata": {
|
| 5 |
-
"kernelspec": {
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
},
|
| 8 |
"cells": [
|
| 9 |
{
|
|
@@ -33,7 +40,9 @@
|
|
| 33 |
"cell_type": "markdown",
|
| 34 |
"id": "v4-s0",
|
| 35 |
"metadata": {},
|
| 36 |
-
"source": [
|
|
|
|
|
|
|
| 37 |
},
|
| 38 |
{
|
| 39 |
"cell_type": "code",
|
|
@@ -64,7 +73,9 @@
|
|
| 64 |
"cell_type": "markdown",
|
| 65 |
"id": "v4-s1",
|
| 66 |
"metadata": {},
|
| 67 |
-
"source": [
|
|
|
|
|
|
|
| 68 |
},
|
| 69 |
{
|
| 70 |
"cell_type": "code",
|
|
@@ -72,37 +83,15 @@
|
|
| 72 |
"metadata": {},
|
| 73 |
"execution_count": null,
|
| 74 |
"outputs": [],
|
| 75 |
-
"source":
|
| 76 |
-
"import torch\n",
|
| 77 |
-
"from unsloth import FastLanguageModel, PatchFastRL\n",
|
| 78 |
-
"\n",
|
| 79 |
-
"# Must patch BEFORE any trl imports\n",
|
| 80 |
-
"PatchFastRL('GRPO', FastLanguageModel)\n",
|
| 81 |
-
"\n",
|
| 82 |
-
"import trl\n",
|
| 83 |
-
"\n",
|
| 84 |
-
"gpu = torch.cuda.get_device_properties(0)\n",
|
| 85 |
-
"TOTAL_GB = round(gpu.total_memory / 1024**3, 1)\n",
|
| 86 |
-
"IS_A100 = TOTAL_GB >= 35\n",
|
| 87 |
-
"\n",
|
| 88 |
-
"NUM_GEN = 6 if IS_A100 else 2\n",
|
| 89 |
-
"GRAD_ACCUM = 32 if IS_A100 else 8\n",
|
| 90 |
-
"MAX_COMP_LEN = 384\n",
|
| 91 |
-
"N_SFT_EPISODES = 120 if IS_A100 else 20\n",
|
| 92 |
-
"N_GRPO_EPISODES = 150 if IS_A100 else 30\n",
|
| 93 |
-
"TRAIN_MAX_STEPS = 12\n",
|
| 94 |
-
"\n",
|
| 95 |
-
"print(f'torch={torch.__version__} trl={trl.__version__}')\n",
|
| 96 |
-
"print(f'GPU: {gpu.name} ({TOTAL_GB} GB) IS_A100={IS_A100}')\n",
|
| 97 |
-
"print(f'num_gen={NUM_GEN} grad_accum={GRAD_ACCUM} '\n",
|
| 98 |
-
" f'sft_eps={N_SFT_EPISODES} grpo_eps={N_GRPO_EPISODES} max_steps={TRAIN_MAX_STEPS}')"
|
| 99 |
-
]
|
| 100 |
},
|
| 101 |
{
|
| 102 |
"cell_type": "markdown",
|
| 103 |
"id": "v4-s2",
|
| 104 |
"metadata": {},
|
| 105 |
-
"source": [
|
|
|
|
|
|
|
| 106 |
},
|
| 107 |
{
|
| 108 |
"cell_type": "code",
|
|
@@ -135,7 +124,9 @@
|
|
| 135 |
"cell_type": "markdown",
|
| 136 |
"id": "v4-s3",
|
| 137 |
"metadata": {},
|
| 138 |
-
"source": [
|
|
|
|
|
|
|
| 139 |
},
|
| 140 |
{
|
| 141 |
"cell_type": "code",
|
|
@@ -152,7 +143,9 @@
|
|
| 152 |
"cell_type": "markdown",
|
| 153 |
"id": "v4-s4",
|
| 154 |
"metadata": {},
|
| 155 |
-
"source": [
|
|
|
|
|
|
|
| 156 |
},
|
| 157 |
{
|
| 158 |
"cell_type": "code",
|
|
@@ -190,7 +183,9 @@
|
|
| 190 |
"cell_type": "markdown",
|
| 191 |
"id": "v4-s5",
|
| 192 |
"metadata": {},
|
| 193 |
-
"source": [
|
|
|
|
|
|
|
| 194 |
},
|
| 195 |
{
|
| 196 |
"cell_type": "code",
|
|
@@ -220,7 +215,9 @@
|
|
| 220 |
"cell_type": "markdown",
|
| 221 |
"id": "v4-s6",
|
| 222 |
"metadata": {},
|
| 223 |
-
"source": [
|
|
|
|
|
|
|
| 224 |
},
|
| 225 |
{
|
| 226 |
"cell_type": "code",
|
|
@@ -272,7 +269,9 @@
|
|
| 272 |
"cell_type": "markdown",
|
| 273 |
"id": "v4-s7",
|
| 274 |
"metadata": {},
|
| 275 |
-
"source": [
|
|
|
|
|
|
|
| 276 |
},
|
| 277 |
{
|
| 278 |
"cell_type": "code",
|
|
@@ -338,7 +337,9 @@
|
|
| 338 |
"cell_type": "markdown",
|
| 339 |
"id": "v4-s8",
|
| 340 |
"metadata": {},
|
| 341 |
-
"source": [
|
|
|
|
|
|
|
| 342 |
},
|
| 343 |
{
|
| 344 |
"cell_type": "code",
|
|
@@ -382,7 +383,9 @@
|
|
| 382 |
"cell_type": "markdown",
|
| 383 |
"id": "v4-s9",
|
| 384 |
"metadata": {},
|
| 385 |
-
"source": [
|
|
|
|
|
|
|
| 386 |
},
|
| 387 |
{
|
| 388 |
"cell_type": "code",
|
|
@@ -446,7 +449,9 @@
|
|
| 446 |
"cell_type": "markdown",
|
| 447 |
"id": "v4-s10",
|
| 448 |
"metadata": {},
|
| 449 |
-
"source": [
|
|
|
|
|
|
|
| 450 |
},
|
| 451 |
{
|
| 452 |
"cell_type": "code",
|
|
@@ -468,7 +473,9 @@
|
|
| 468 |
"cell_type": "markdown",
|
| 469 |
"id": "v4-s11",
|
| 470 |
"metadata": {},
|
| 471 |
-
"source": [
|
|
|
|
|
|
|
| 472 |
},
|
| 473 |
{
|
| 474 |
"cell_type": "code",
|
|
@@ -663,7 +670,9 @@
|
|
| 663 |
"cell_type": "markdown",
|
| 664 |
"id": "v4-s12",
|
| 665 |
"metadata": {},
|
| 666 |
-
"source": [
|
|
|
|
|
|
|
| 667 |
},
|
| 668 |
{
|
| 669 |
"cell_type": "code",
|
|
@@ -671,51 +680,15 @@
|
|
| 671 |
"metadata": {},
|
| 672 |
"execution_count": null,
|
| 673 |
"outputs": [],
|
| 674 |
-
"source":
|
| 675 |
-
"from trl import GRPOConfig\n",
|
| 676 |
-
"from training.pm_ops_trainer import PMOpsGRPOTrainer\n",
|
| 677 |
-
"\n",
|
| 678 |
-
"OUTPUT_DIR = 'pm-ops-grpo-Qwen3-1.7B-triage-v4'\n",
|
| 679 |
-
"HF_REPO_ID = f'Saurav1/{OUTPUT_DIR}'\n",
|
| 680 |
-
"\n",
|
| 681 |
-
"grpo_cfg = GRPOConfig(\n",
|
| 682 |
-
" num_train_epochs = 2,\n",
|
| 683 |
-
" learning_rate = 1e-6,\n",
|
| 684 |
-
" gradient_accumulation_steps = GRAD_ACCUM,\n",
|
| 685 |
-
" per_device_train_batch_size = 1,\n",
|
| 686 |
-
" warmup_steps = 5,\n",
|
| 687 |
-
" num_generations = NUM_GEN,\n",
|
| 688 |
-
" max_completion_length = MAX_COMP_LEN,\n",
|
| 689 |
-
" max_prompt_length = 4096,\n",
|
| 690 |
-
" use_vllm = False,\n",
|
| 691 |
-
" output_dir = OUTPUT_DIR,\n",
|
| 692 |
-
" report_to = 'none',\n",
|
| 693 |
-
" logging_steps = 1,\n",
|
| 694 |
-
" save_steps = 20,\n",
|
| 695 |
-
" gradient_checkpointing = False,\n",
|
| 696 |
-
")\n",
|
| 697 |
-
"\n",
|
| 698 |
-
"eff_batch = grpo_cfg.per_device_train_batch_size * GRAD_ACCUM\n",
|
| 699 |
-
"total_steps = len(grpo_dataset) * NUM_GEN * grpo_cfg.num_train_epochs // eff_batch\n",
|
| 700 |
-
"print(f'GRPO: {len(grpo_dataset)} eps × {NUM_GEN} gen × {grpo_cfg.num_train_epochs} epochs → ~{total_steps} steps')\n",
|
| 701 |
-
"\n",
|
| 702 |
-
"trainer = PMOpsGRPOTrainer(\n",
|
| 703 |
-
" model = model,\n",
|
| 704 |
-
" processing_class = tokenizer,\n",
|
| 705 |
-
" reward_funcs = grpo_reward_func,\n",
|
| 706 |
-
" train_dataset = grpo_dataset,\n",
|
| 707 |
-
" args = grpo_cfg,\n",
|
| 708 |
-
" rollout_func = grpo_rollout_func,\n",
|
| 709 |
-
")\n",
|
| 710 |
-
"assert grpo_cfg.use_vllm is False\n",
|
| 711 |
-
"print(f'Trainer: {type(trainer).__name__} ready')"
|
| 712 |
-
]
|
| 713 |
},
|
| 714 |
{
|
| 715 |
"cell_type": "markdown",
|
| 716 |
"id": "v4-s13",
|
| 717 |
"metadata": {},
|
| 718 |
-
"source": [
|
|
|
|
|
|
|
| 719 |
},
|
| 720 |
{
|
| 721 |
"cell_type": "code",
|
|
@@ -767,7 +740,9 @@
|
|
| 767 |
"cell_type": "markdown",
|
| 768 |
"id": "v4-s15",
|
| 769 |
"metadata": {},
|
| 770 |
-
"source": [
|
|
|
|
|
|
|
| 771 |
},
|
| 772 |
{
|
| 773 |
"cell_type": "code",
|
|
@@ -796,7 +771,9 @@
|
|
| 796 |
"cell_type": "markdown",
|
| 797 |
"id": "v4-s16",
|
| 798 |
"metadata": {},
|
| 799 |
-
"source": [
|
|
|
|
|
|
|
| 800 |
},
|
| 801 |
{
|
| 802 |
"cell_type": "code",
|
|
@@ -804,95 +781,15 @@
|
|
| 804 |
"metadata": {},
|
| 805 |
"execution_count": null,
|
| 806 |
"outputs": [],
|
| 807 |
-
"source": [
|
| 808 |
-
"from training.rollout import extract_json_action, step_aware_fallback\n",
|
| 809 |
-
"from inference import baseline_agent\n",
|
| 810 |
-
"\n",
|
| 811 |
-
"N_EVAL = 15\n",
|
| 812 |
-
"EVAL_MAX_STEPS = 12\n",
|
| 813 |
-
"EVAL_SEED_BASE = 9000\n",
|
| 814 |
-
"\n",
|
| 815 |
-
"\n",
|
| 816 |
-
"def run_eval(n=N_EVAL):\n",
|
| 817 |
-
" scores = []\n",
|
| 818 |
-
" model.eval()\n",
|
| 819 |
-
" with GenericEnvClient(base_url=ENV_URL).sync() as env:\n",
|
| 820 |
-
" for i in range(n):\n",
|
| 821 |
-
" result = env.reset(seed=EVAL_SEED_BASE + i)\n",
|
| 822 |
-
" obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n",
|
| 823 |
-
" task_brief = obs_dict.get('task_brief', '')\n",
|
| 824 |
-
" history, step, score, done = [], 0, 0.0, False\n",
|
| 825 |
-
"\n",
|
| 826 |
-
" while not done and step < EVAL_MAX_STEPS:\n",
|
| 827 |
-
" obs_text = _current_obs_text(obs_dict, step, task_brief)\n",
|
| 828 |
-
" msgs = build_messages(history, obs_text)\n",
|
| 829 |
-
" prompt = tokenizer.apply_chat_template(\n",
|
| 830 |
-
" msgs, add_generation_prompt=True, tokenize=False, enable_thinking=False\n",
|
| 831 |
-
" )\n",
|
| 832 |
-
" inputs = tokenizer([prompt], return_tensors='pt', truncation=True, max_length=4096)\n",
|
| 833 |
-
" inputs = {k: v.to(model.device) for k, v in inputs.items()}\n",
|
| 834 |
-
" with torch.no_grad():\n",
|
| 835 |
-
" out_ids = model.generate(\n",
|
| 836 |
-
" **inputs, max_new_tokens=256, do_sample=False,\n",
|
| 837 |
-
" pad_token_id=tokenizer.eos_token_id,\n",
|
| 838 |
-
" )\n",
|
| 839 |
-
" completion = tokenizer.decode(\n",
|
| 840 |
-
" out_ids[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True\n",
|
| 841 |
-
" )\n",
|
| 842 |
-
" parsed = extract_json_action(completion) or step_aware_fallback(step, EVAL_MAX_STEPS)\n",
|
| 843 |
-
" result = env.step({'action_type': parsed['action_type'], 'args': parsed.get('args', {})})\n",
|
| 844 |
-
" obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n",
|
| 845 |
-
" done = bool(getattr(result, 'done', obs_dict.get('done', False)))\n",
|
| 846 |
-
" score = float(getattr(result, 'reward', obs_dict.get('reward', 0.0)))\n",
|
| 847 |
-
" history.append({'obs_text': obs_text, 'completion': completion, 'is_runbook': False})\n",
|
| 848 |
-
" step += 1\n",
|
| 849 |
-
"\n",
|
| 850 |
-
" scores.append(score)\n",
|
| 851 |
-
" model.train()\n",
|
| 852 |
-
" return scores\n",
|
| 853 |
-
"\n",
|
| 854 |
-
"\n",
|
| 855 |
-
"def run_baseline(n=N_EVAL):\n",
|
| 856 |
-
" scores = []\n",
|
| 857 |
-
" with GenericEnvClient(base_url=ENV_URL).sync() as env:\n",
|
| 858 |
-
" for i in range(n):\n",
|
| 859 |
-
" result = env.reset(seed=EVAL_SEED_BASE + i)\n",
|
| 860 |
-
" obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n",
|
| 861 |
-
" org_config, step, score, done = {}, 0, 0.0, False\n",
|
| 862 |
-
" while not done and step < EVAL_MAX_STEPS:\n",
|
| 863 |
-
" at, args = baseline_agent(obs_dict, org_config)\n",
|
| 864 |
-
" result = env.step({'action_type': at, 'args': args})\n",
|
| 865 |
-
" obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n",
|
| 866 |
-
" done = bool(getattr(result, 'done', obs_dict.get('done', False)))\n",
|
| 867 |
-
" score = float(getattr(result, 'reward', obs_dict.get('reward', 0.0)))\n",
|
| 868 |
-
" if at == 'meta.read_runbook':\n",
|
| 869 |
-
" last = obs_dict.get('last_action_result') or {}\n",
|
| 870 |
-
" if last.get('ok'):\n",
|
| 871 |
-
" data = last.get('data') or {}\n",
|
| 872 |
-
" if isinstance(data, dict) and 'org_config' in data:\n",
|
| 873 |
-
" org_config.update(data['org_config'])\n",
|
| 874 |
-
" step += 1\n",
|
| 875 |
-
" scores.append(score)\n",
|
| 876 |
-
" return scores\n",
|
| 877 |
-
"\n",
|
| 878 |
-
"\n",
|
| 879 |
-
"print('--- Baseline ---')\n",
|
| 880 |
-
"baseline_scores = run_baseline()\n",
|
| 881 |
-
"print('--- Trained ---')\n",
|
| 882 |
-
"trained_scores = run_eval()\n",
|
| 883 |
-
"\n",
|
| 884 |
-
"b_avg = sum(baseline_scores) / N_EVAL\n",
|
| 885 |
-
"t_avg = sum(trained_scores) / N_EVAL\n",
|
| 886 |
-
"print(f'\\nBaseline avg : {b_avg:.3f}')\n",
|
| 887 |
-
"print(f'Trained avg : {t_avg:.3f}')\n",
|
| 888 |
-
"print(f'Delta : {t_avg - b_avg:+.3f}')"
|
| 889 |
-
]
|
| 890 |
},
|
| 891 |
{
|
| 892 |
"cell_type": "markdown",
|
| 893 |
"id": "v4-s17",
|
| 894 |
"metadata": {},
|
| 895 |
-
"source": [
|
|
|
|
|
|
|
| 896 |
},
|
| 897 |
{
|
| 898 |
"cell_type": "code",
|
|
@@ -906,4 +803,4 @@
|
|
| 906 |
]
|
| 907 |
}
|
| 908 |
]
|
| 909 |
-
}
|
|
|
|
| 2 |
"nbformat": 4,
|
| 3 |
"nbformat_minor": 5,
|
| 4 |
"metadata": {
|
| 5 |
+
"kernelspec": {
|
| 6 |
+
"display_name": "Python 3",
|
| 7 |
+
"language": "python",
|
| 8 |
+
"name": "python3"
|
| 9 |
+
},
|
| 10 |
+
"language_info": {
|
| 11 |
+
"name": "python",
|
| 12 |
+
"version": "3.12.0"
|
| 13 |
+
}
|
| 14 |
},
|
| 15 |
"cells": [
|
| 16 |
{
|
|
|
|
| 40 |
"cell_type": "markdown",
|
| 41 |
"id": "v4-s0",
|
| 42 |
"metadata": {},
|
| 43 |
+
"source": [
|
| 44 |
+
"## 0. Install"
|
| 45 |
+
]
|
| 46 |
},
|
| 47 |
{
|
| 48 |
"cell_type": "code",
|
|
|
|
| 73 |
"cell_type": "markdown",
|
| 74 |
"id": "v4-s1",
|
| 75 |
"metadata": {},
|
| 76 |
+
"source": [
|
| 77 |
+
"## 1. GPU Config + Patch"
|
| 78 |
+
]
|
| 79 |
},
|
| 80 |
{
|
| 81 |
"cell_type": "code",
|
|
|
|
| 83 |
"metadata": {},
|
| 84 |
"execution_count": null,
|
| 85 |
"outputs": [],
|
| 86 |
+
"source": "import torch\nfrom unsloth import FastLanguageModel, PatchFastRL\n\n# Must patch BEFORE any trl imports\nPatchFastRL('GRPO', FastLanguageModel)\n\nimport trl\n\ngpu = torch.cuda.get_device_properties(0)\nTOTAL_GB = round(gpu.total_memory / 1024**3, 1)\nIS_A100 = TOTAL_GB >= 35\n\nNUM_GEN = 6 if IS_A100 else 2\nGRAD_ACCUM = 32 if IS_A100 else 8\n# Per-step max tokens: JSON actions are ~80-120 tokens; 192 gives headroom\n# without filling TRL's completion buffer and zeroing the policy loss.\nMAX_COMP_LEN = 192\nN_SFT_EPISODES = 120 if IS_A100 else 20\nN_GRPO_EPISODES = 150 if IS_A100 else 30\nTRAIN_MAX_STEPS = 12\n\nprint(f'torch={torch.__version__} trl={trl.__version__}')\nprint(f'GPU: {gpu.name} ({TOTAL_GB} GB) IS_A100={IS_A100}')\nprint(f'num_gen={NUM_GEN} grad_accum={GRAD_ACCUM} '\n f'sft_eps={N_SFT_EPISODES} grpo_eps={N_GRPO_EPISODES} '\n f'max_steps={TRAIN_MAX_STEPS} max_comp_len={MAX_COMP_LEN}')"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
},
|
| 88 |
{
|
| 89 |
"cell_type": "markdown",
|
| 90 |
"id": "v4-s2",
|
| 91 |
"metadata": {},
|
| 92 |
+
"source": [
|
| 93 |
+
"## 2. Clone PM-Ops Repo"
|
| 94 |
+
]
|
| 95 |
},
|
| 96 |
{
|
| 97 |
"cell_type": "code",
|
|
|
|
| 124 |
"cell_type": "markdown",
|
| 125 |
"id": "v4-s3",
|
| 126 |
"metadata": {},
|
| 127 |
+
"source": [
|
| 128 |
+
"## 3. HuggingFace Login"
|
| 129 |
+
]
|
| 130 |
},
|
| 131 |
{
|
| 132 |
"cell_type": "code",
|
|
|
|
| 143 |
"cell_type": "markdown",
|
| 144 |
"id": "v4-s4",
|
| 145 |
"metadata": {},
|
| 146 |
+
"source": [
|
| 147 |
+
"## 4. Start PM-Ops Server"
|
| 148 |
+
]
|
| 149 |
},
|
| 150 |
{
|
| 151 |
"cell_type": "code",
|
|
|
|
| 183 |
"cell_type": "markdown",
|
| 184 |
"id": "v4-s5",
|
| 185 |
"metadata": {},
|
| 186 |
+
"source": [
|
| 187 |
+
"## 5. Verify Env"
|
| 188 |
+
]
|
| 189 |
},
|
| 190 |
{
|
| 191 |
"cell_type": "code",
|
|
|
|
| 215 |
"cell_type": "markdown",
|
| 216 |
"id": "v4-s6",
|
| 217 |
"metadata": {},
|
| 218 |
+
"source": [
|
| 219 |
+
"## 6. Load Model — Unsloth 4-bit + LoRA"
|
| 220 |
+
]
|
| 221 |
},
|
| 222 |
{
|
| 223 |
"cell_type": "code",
|
|
|
|
| 269 |
"cell_type": "markdown",
|
| 270 |
"id": "v4-s7",
|
| 271 |
"metadata": {},
|
| 272 |
+
"source": [
|
| 273 |
+
"## 7. Generate SFT Dataset"
|
| 274 |
+
]
|
| 275 |
},
|
| 276 |
{
|
| 277 |
"cell_type": "code",
|
|
|
|
| 337 |
"cell_type": "markdown",
|
| 338 |
"id": "v4-s8",
|
| 339 |
"metadata": {},
|
| 340 |
+
"source": [
|
| 341 |
+
"## 8. SFT Training"
|
| 342 |
+
]
|
| 343 |
},
|
| 344 |
{
|
| 345 |
"cell_type": "code",
|
|
|
|
| 383 |
"cell_type": "markdown",
|
| 384 |
"id": "v4-s9",
|
| 385 |
"metadata": {},
|
| 386 |
+
"source": [
|
| 387 |
+
"## 9. Verify SFT — Model Must Output Valid JSON"
|
| 388 |
+
]
|
| 389 |
},
|
| 390 |
{
|
| 391 |
"cell_type": "code",
|
|
|
|
| 449 |
"cell_type": "markdown",
|
| 450 |
"id": "v4-s10",
|
| 451 |
"metadata": {},
|
| 452 |
+
"source": [
|
| 453 |
+
"## 10. GRPO Training Dataset"
|
| 454 |
+
]
|
| 455 |
},
|
| 456 |
{
|
| 457 |
"cell_type": "code",
|
|
|
|
| 473 |
"cell_type": "markdown",
|
| 474 |
"id": "v4-s11",
|
| 475 |
"metadata": {},
|
| 476 |
+
"source": [
|
| 477 |
+
"## 11. GRPO Rollout + Reward"
|
| 478 |
+
]
|
| 479 |
},
|
| 480 |
{
|
| 481 |
"cell_type": "code",
|
|
|
|
| 670 |
"cell_type": "markdown",
|
| 671 |
"id": "v4-s12",
|
| 672 |
"metadata": {},
|
| 673 |
+
"source": [
|
| 674 |
+
"## 12. GRPO Config + Trainer"
|
| 675 |
+
]
|
| 676 |
},
|
| 677 |
{
|
| 678 |
"cell_type": "code",
|
|
|
|
| 680 |
"metadata": {},
|
| 681 |
"execution_count": null,
|
| 682 |
"outputs": [],
|
| 683 |
+
"source": "from trl import GRPOConfig\nfrom training.pm_ops_trainer import PMOpsGRPOTrainer\n\nOUTPUT_DIR = 'pm-ops-grpo-Qwen3-1.7B-triage-v4'\nHF_REPO_ID = f'Saurav1/{OUTPUT_DIR}'\n\ngrpo_cfg = GRPOConfig(\n num_train_epochs = 2,\n learning_rate = 1e-6,\n gradient_accumulation_steps = GRAD_ACCUM,\n per_device_train_batch_size = 1,\n warmup_steps = 5,\n num_generations = NUM_GEN,\n # max_completion_length must match MAX_COMP_LEN so TRL's internal\n # single-turn generation doesn't overflow and zero the policy loss\n # (clipped_ratio=1.0 means all gradient is masked → loss=0.000).\n max_completion_length = MAX_COMP_LEN,\n max_prompt_length = 4096,\n use_vllm = False,\n output_dir = OUTPUT_DIR,\n report_to = 'none',\n logging_steps = 1,\n save_steps = 20,\n gradient_checkpointing = False,\n)\n\neff_batch = grpo_cfg.per_device_train_batch_size * GRAD_ACCUM\ntotal_steps = len(grpo_dataset) * NUM_GEN * grpo_cfg.num_train_epochs // eff_batch\nprint(f'GRPO: {len(grpo_dataset)} eps × {NUM_GEN} gen × {grpo_cfg.num_train_epochs} epochs → ~{total_steps} steps')\n\ntrainer = PMOpsGRPOTrainer(\n model = model,\n processing_class = tokenizer,\n reward_funcs = grpo_reward_func,\n train_dataset = grpo_dataset,\n args = grpo_cfg,\n rollout_func = grpo_rollout_func,\n)\nassert grpo_cfg.use_vllm is False\nprint(f'Trainer: {type(trainer).__name__} ready')"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
},
|
| 685 |
{
|
| 686 |
"cell_type": "markdown",
|
| 687 |
"id": "v4-s13",
|
| 688 |
"metadata": {},
|
| 689 |
+
"source": [
|
| 690 |
+
"## 13. Preflight Probe"
|
| 691 |
+
]
|
| 692 |
},
|
| 693 |
{
|
| 694 |
"cell_type": "code",
|
|
|
|
| 740 |
"cell_type": "markdown",
|
| 741 |
"id": "v4-s15",
|
| 742 |
"metadata": {},
|
| 743 |
+
"source": [
|
| 744 |
+
"## 15. Save + Push to HF"
|
| 745 |
+
]
|
| 746 |
},
|
| 747 |
{
|
| 748 |
"cell_type": "code",
|
|
|
|
| 771 |
"cell_type": "markdown",
|
| 772 |
"id": "v4-s16",
|
| 773 |
"metadata": {},
|
| 774 |
+
"source": [
|
| 775 |
+
"## 16. Evaluate"
|
| 776 |
+
]
|
| 777 |
},
|
| 778 |
{
|
| 779 |
"cell_type": "code",
|
|
|
|
| 781 |
"metadata": {},
|
| 782 |
"execution_count": null,
|
| 783 |
"outputs": [],
|
| 784 |
+
"source": "from training.rollout import extract_json_action, step_aware_fallback\nfrom inference import baseline_agent\n\nN_EVAL = 15\nEVAL_MAX_STEPS = 12\nEVAL_SEED_BASE = 9000\n\n# Switch model to Unsloth fast inference mode.\n# Required after training — without this, model.generate() is very slow\n# and may hang on T4 because Unsloth's training hooks are still active.\nFastLanguageModel.for_inference(model)\nmodel.eval()\nprint('Model switched to fast inference mode')\n\n\ndef run_eval(n=N_EVAL):\n scores = []\n with GenericEnvClient(base_url=ENV_URL).sync() as env:\n for i in range(n):\n result = env.reset(seed=EVAL_SEED_BASE + i)\n obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n task_brief = obs_dict.get('task_brief', '')\n history, step, score, done = [], 0, 0.0, False\n\n while not done and step < EVAL_MAX_STEPS:\n obs_text = _current_obs_text(obs_dict, step, task_brief)\n msgs = build_messages(history, obs_text)\n prompt = tokenizer.apply_chat_template(\n msgs, add_generation_prompt=True, tokenize=False, enable_thinking=False\n )\n inputs = tokenizer([prompt], return_tensors='pt', truncation=True, max_length=4096)\n inputs = {k: v.to(model.device) for k, v in inputs.items()}\n with torch.no_grad():\n out_ids = model.generate(\n **inputs,\n max_new_tokens = 128,\n do_sample = False,\n pad_token_id = tokenizer.eos_token_id,\n )\n completion = tokenizer.decode(\n out_ids[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True\n )\n parsed = extract_json_action(completion) or step_aware_fallback(step, EVAL_MAX_STEPS)\n result = env.step({'action_type': parsed['action_type'], 'args': parsed.get('args', {})})\n obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n done = bool(getattr(result, 'done', obs_dict.get('done', False)))\n score = float(getattr(result, 'reward', obs_dict.get('reward', 0.0)))\n history.append({'obs_text': obs_text, 'completion': completion, 'is_runbook': False})\n step += 1\n\n scores.append(score)\n print(f' Trained ep {i+1}/{n}: score={score:.3f}')\n return scores\n\n\ndef run_baseline(n=N_EVAL):\n scores = []\n with GenericEnvClient(base_url=ENV_URL).sync() as env:\n for i in range(n):\n result = env.reset(seed=EVAL_SEED_BASE + i)\n obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n org_config, step, score, done = {}, 0, 0.0, False\n while not done and step < EVAL_MAX_STEPS:\n at, args = baseline_agent(obs_dict, org_config)\n result = env.step({'action_type': at, 'args': args})\n obs_dict = _obs_to_dict(result.observation if hasattr(result, 'observation') else result)\n done = bool(getattr(result, 'done', obs_dict.get('done', False)))\n score = float(getattr(result, 'reward', obs_dict.get('reward', 0.0)))\n if at == 'meta.read_runbook':\n last = obs_dict.get('last_action_result') or {}\n if last.get('ok'):\n data = last.get('data') or {}\n if isinstance(data, dict) and 'org_config' in data:\n org_config.update(data['org_config'])\n step += 1\n scores.append(score)\n print(f' Baseline ep {i+1}/{n}: score={score:.3f}')\n return scores\n\n\nprint('--- Baseline ---')\nbaseline_scores = run_baseline()\nprint('\\n--- Trained ---')\ntrained_scores = run_eval()\n\nb_avg = sum(baseline_scores) / N_EVAL\nt_avg = sum(trained_scores) / N_EVAL\nprint(f'\\nBaseline avg : {b_avg:.3f}')\nprint(f'Trained avg : {t_avg:.3f}')\nprint(f'Delta : {t_avg - b_avg:+.3f}')"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
},
|
| 786 |
{
|
| 787 |
"cell_type": "markdown",
|
| 788 |
"id": "v4-s17",
|
| 789 |
"metadata": {},
|
| 790 |
+
"source": [
|
| 791 |
+
"## 17. Teardown"
|
| 792 |
+
]
|
| 793 |
},
|
| 794 |
{
|
| 795 |
"cell_type": "code",
|
|
|
|
| 803 |
]
|
| 804 |
}
|
| 805 |
]
|
| 806 |
+
}
|