SavK1 Claude Sonnet 4.6 commited on
Commit
2f02c01
·
1 Parent(s): 4b0fc8a

fix(v4): reduce MAX_COMP_LEN 384→192, fix eval hanging with for_inference

Browse files

MAX_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>

Files changed (1) hide show
  1. 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": {"display_name": "Python 3", "language": "python", "name": "python3"},
6
- "language_info": {"name": "python", "version": "3.12.0"}
 
 
 
 
 
 
 
7
  },
8
  "cells": [
9
  {
@@ -33,7 +40,9 @@
33
  "cell_type": "markdown",
34
  "id": "v4-s0",
35
  "metadata": {},
36
- "source": ["## 0. Install"]
 
 
37
  },
38
  {
39
  "cell_type": "code",
@@ -64,7 +73,9 @@
64
  "cell_type": "markdown",
65
  "id": "v4-s1",
66
  "metadata": {},
67
- "source": ["## 1. GPU Config + Patch"]
 
 
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": ["## 2. Clone PM-Ops Repo"]
 
 
106
  },
107
  {
108
  "cell_type": "code",
@@ -135,7 +124,9 @@
135
  "cell_type": "markdown",
136
  "id": "v4-s3",
137
  "metadata": {},
138
- "source": ["## 3. HuggingFace Login"]
 
 
139
  },
140
  {
141
  "cell_type": "code",
@@ -152,7 +143,9 @@
152
  "cell_type": "markdown",
153
  "id": "v4-s4",
154
  "metadata": {},
155
- "source": ["## 4. Start PM-Ops Server"]
 
 
156
  },
157
  {
158
  "cell_type": "code",
@@ -190,7 +183,9 @@
190
  "cell_type": "markdown",
191
  "id": "v4-s5",
192
  "metadata": {},
193
- "source": ["## 5. Verify Env"]
 
 
194
  },
195
  {
196
  "cell_type": "code",
@@ -220,7 +215,9 @@
220
  "cell_type": "markdown",
221
  "id": "v4-s6",
222
  "metadata": {},
223
- "source": ["## 6. Load Model — Unsloth 4-bit + LoRA"]
 
 
224
  },
225
  {
226
  "cell_type": "code",
@@ -272,7 +269,9 @@
272
  "cell_type": "markdown",
273
  "id": "v4-s7",
274
  "metadata": {},
275
- "source": ["## 7. Generate SFT Dataset"]
 
 
276
  },
277
  {
278
  "cell_type": "code",
@@ -338,7 +337,9 @@
338
  "cell_type": "markdown",
339
  "id": "v4-s8",
340
  "metadata": {},
341
- "source": ["## 8. SFT Training"]
 
 
342
  },
343
  {
344
  "cell_type": "code",
@@ -382,7 +383,9 @@
382
  "cell_type": "markdown",
383
  "id": "v4-s9",
384
  "metadata": {},
385
- "source": ["## 9. Verify SFT — Model Must Output Valid JSON"]
 
 
386
  },
387
  {
388
  "cell_type": "code",
@@ -446,7 +449,9 @@
446
  "cell_type": "markdown",
447
  "id": "v4-s10",
448
  "metadata": {},
449
- "source": ["## 10. GRPO Training Dataset"]
 
 
450
  },
451
  {
452
  "cell_type": "code",
@@ -468,7 +473,9 @@
468
  "cell_type": "markdown",
469
  "id": "v4-s11",
470
  "metadata": {},
471
- "source": ["## 11. GRPO Rollout + Reward"]
 
 
472
  },
473
  {
474
  "cell_type": "code",
@@ -663,7 +670,9 @@
663
  "cell_type": "markdown",
664
  "id": "v4-s12",
665
  "metadata": {},
666
- "source": ["## 12. GRPO Config + Trainer"]
 
 
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": ["## 13. Preflight Probe"]
 
 
719
  },
720
  {
721
  "cell_type": "code",
@@ -767,7 +740,9 @@
767
  "cell_type": "markdown",
768
  "id": "v4-s15",
769
  "metadata": {},
770
- "source": ["## 15. Save + Push to HF"]
 
 
771
  },
772
  {
773
  "cell_type": "code",
@@ -796,7 +771,9 @@
796
  "cell_type": "markdown",
797
  "id": "v4-s16",
798
  "metadata": {},
799
- "source": ["## 16. Evaluate"]
 
 
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": ["## 17. Teardown"]
 
 
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
+ }