SavK1 commited on
Commit
962ea9c
·
1 Parent(s): 0ddcea5

fix(grpo): harden rollout reward injection on cloud runtime

Browse files

Add direct rollout probe fallback in PMOpsGRPOTrainer when cache is empty, and update train_v3 GRPO setup for rollout compatibility (disable fast RL patch/vLLM path, add trainer assertions and preflight probe).

Files changed (2) hide show
  1. training/pm_ops_trainer.py +23 -0
  2. training/train_v3.ipynb +103 -37
training/pm_ops_trainer.py CHANGED
@@ -105,6 +105,15 @@ class PMOpsGRPOTrainer(GRPOTrainer):
105
  completion_ids_list,
106
  ):
107
  """Inject cached rewards when available; fall back to reward_funcs otherwise."""
 
 
 
 
 
 
 
 
 
108
  cache = self._rollout_reward_cache
109
  n = len(completions)
110
 
@@ -120,6 +129,20 @@ class PMOpsGRPOTrainer(GRPOTrainer):
120
  if input_rewards:
121
  cache = input_rewards
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  if cache:
124
  # Handle num_generations > 1: TRL may call with n > len(cache)
125
  if len(cache) == n:
 
105
  completion_ids_list,
106
  ):
107
  """Inject cached rewards when available; fall back to reward_funcs otherwise."""
108
+ def _coerce_rewards(raw):
109
+ if raw is None:
110
+ return []
111
+ if isinstance(raw, torch.Tensor):
112
+ return [float(r) for r in raw.detach().cpu().flatten().tolist()]
113
+ if isinstance(raw, (int, float)):
114
+ return [float(raw)]
115
+ return [float(r) for r in list(raw)]
116
+
117
  cache = self._rollout_reward_cache
118
  n = len(completions)
119
 
 
129
  if input_rewards:
130
  cache = input_rewards
131
 
132
+ # Cloud fallback: some patched runtimes skip the normal rollout capture path
133
+ # before calling _calculate_rewards. Actively invoke rollout_func once here
134
+ # to populate reward cache from the exact prompt batch.
135
+ if not cache and self.rollout_func is not None and prompts:
136
+ try:
137
+ print("[PMOpsGRPOTrainer] cache empty — probing rollout_func for rewards")
138
+ out = self.rollout_func(prompts, trainer=self)
139
+ cache = self._rollout_reward_cache
140
+ if not cache and isinstance(out, dict):
141
+ cache = _coerce_rewards(out.get("reward", out.get("rewards", [])))
142
+ self._rollout_reward_cache = cache
143
+ except Exception as exc:
144
+ print(f"[PMOpsGRPOTrainer] rollout probe failed: {exc!r}")
145
+
146
  if cache:
147
  # Handle num_generations > 1: TRL may call with n > len(cache)
148
  if len(cache) == n:
training/train_v3.ipynb CHANGED
@@ -1,17 +1,4 @@
1
  {
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.11.0"
13
- }
14
- },
15
  "cells": [
16
  {
17
  "cell_type": "markdown",
@@ -47,10 +34,10 @@
47
  },
48
  {
49
  "cell_type": "code",
 
50
  "id": "cell-1",
51
  "metadata": {},
52
  "outputs": [],
53
- "execution_count": null,
54
  "source": [
55
  "# Unsloth + vLLM first — let Unsloth resolve torch compat\n",
56
  "!pip install -q unsloth vllm\n",
@@ -73,16 +60,22 @@
73
  },
74
  {
75
  "cell_type": "code",
 
76
  "id": "cell-2",
77
  "metadata": {},
78
  "outputs": [],
79
- "execution_count": null,
80
  "source": [
81
  "import torch\n",
82
  "\n",
83
- "# Patch GRPO BEFORE importing GRPOTrainer\n",
 
84
  "from unsloth import FastLanguageModel, PatchFastRL\n",
85
- "PatchFastRL('GRPO', FastLanguageModel)\n",
 
 
 
 
 
86
  "\n",
87
  "import trl\n",
88
  "print(f'torch : {torch.__version__}')\n",
@@ -115,10 +108,10 @@
115
  },
116
  {
117
  "cell_type": "code",
 
118
  "id": "cell-3",
119
  "metadata": {},
120
  "outputs": [],
121
- "execution_count": null,
122
  "source": [
123
  "import os, sys\n",
124
  "\n",
@@ -149,10 +142,10 @@
149
  },
150
  {
151
  "cell_type": "code",
 
152
  "id": "cell-4",
153
  "metadata": {},
154
  "outputs": [],
155
- "execution_count": null,
156
  "source": [
157
  "from huggingface_hub import notebook_login\n",
158
  "notebook_login()"
@@ -168,10 +161,10 @@
168
  },
169
  {
170
  "cell_type": "code",
 
171
  "id": "cell-5",
172
  "metadata": {},
173
  "outputs": [],
174
- "execution_count": null,
175
  "source": [
176
  "import subprocess, time, requests\n",
177
  "\n",
@@ -204,10 +197,10 @@
204
  },
205
  {
206
  "cell_type": "code",
 
207
  "id": "cell-6",
208
  "metadata": {},
209
  "outputs": [],
210
- "execution_count": null,
211
  "source": [
212
  "import trl.experimental.openenv # must be importable\n",
213
  "from openenv.core import GenericEnvClient\n",
@@ -231,10 +224,10 @@
231
  },
232
  {
233
  "cell_type": "code",
 
234
  "id": "cell-7",
235
  "metadata": {},
236
  "outputs": [],
237
- "execution_count": null,
238
  "source": [
239
  "MODEL_NAME = 'Qwen/Qwen3-1.7B'\n",
240
  "LORA_RANK = 16\n",
@@ -243,7 +236,7 @@
243
  " model_name = MODEL_NAME,\n",
244
  " max_seq_length = 4096 + MAX_COMP_LEN,\n",
245
  " load_in_4bit = True,\n",
246
- " fast_inference = True, # enables vLLM path for GRPO rollouts\n",
247
  " max_lora_rank = LORA_RANK,\n",
248
  " gpu_memory_utilization = 0.50, # leave headroom for SFT activations\n",
249
  ")\n",
@@ -255,7 +248,7 @@
255
  " lora_alpha = LORA_RANK,\n",
256
  " use_gradient_checkpointing = 'unsloth',\n",
257
  " random_state = 42,\n",
258
- ")\n",
259
  "tokenizer.pad_token = tokenizer.eos_token\n",
260
  "tokenizer.padding_side = 'left'\n",
261
  "model.print_trainable_parameters()\n",
@@ -291,10 +284,10 @@
291
  },
292
  {
293
  "cell_type": "code",
 
294
  "id": "cell-9",
295
  "metadata": {},
296
  "outputs": [],
297
- "execution_count": null,
298
  "source": [
299
  "import json as _json\n",
300
  "from datasets import Dataset\n",
@@ -373,10 +366,10 @@
373
  },
374
  {
375
  "cell_type": "code",
 
376
  "id": "cell-10",
377
  "metadata": {},
378
  "outputs": [],
379
- "execution_count": null,
380
  "source": [
381
  "from trl import SFTTrainer, SFTConfig\n",
382
  "\n",
@@ -423,10 +416,10 @@
423
  },
424
  {
425
  "cell_type": "code",
 
426
  "id": "cell-11",
427
  "metadata": {},
428
  "outputs": [],
429
- "execution_count": null,
430
  "source": [
431
  "from training.rollout import extract_json_action, _obs_to_dict, _current_obs_text, build_messages\n",
432
  "\n",
@@ -492,10 +485,10 @@
492
  },
493
  {
494
  "cell_type": "code",
 
495
  "id": "cell-13",
496
  "metadata": {},
497
  "outputs": [],
498
- "execution_count": null,
499
  "source": [
500
  "from training.dataset import generate_triage_dataset\n",
501
  "\n",
@@ -515,10 +508,10 @@
515
  },
516
  {
517
  "cell_type": "code",
 
518
  "id": "cell-14",
519
  "metadata": {},
520
  "outputs": [],
521
- "execution_count": null,
522
  "source": [
523
  "from trl.experimental.openenv import generate_rollout_completions\n",
524
  "from training.rollout import (\n",
@@ -630,11 +623,71 @@
630
  },
631
  {
632
  "cell_type": "code",
 
633
  "id": "cell-15",
634
  "metadata": {},
635
  "outputs": [],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  "execution_count": null,
637
- "source": "from trl import GRPOConfig\nfrom training.pm_ops_trainer import PMOpsGRPOTrainer\n\nOUTPUT_DIR = 'pm-ops-grpo-Qwen3-1.7B-triage-v3'\nHF_REPO_ID = f'Saurav1/{OUTPUT_DIR}'\n\ngrpo_cfg = GRPOConfig(\n # Training\n num_train_epochs = 2,\n learning_rate = 1e-6, # lower LR: model has SFT init, don't overwrite it\n gradient_accumulation_steps = GRAD_ACCUM,\n per_device_train_batch_size = 1,\n warmup_steps = 5,\n num_generations = NUM_GEN,\n # Sequence lengths\n max_completion_length = MAX_COMP_LEN,\n max_prompt_length = 4096,\n # Unsloth vLLM for fast generation\n use_vllm = True,\n # Output\n output_dir = OUTPUT_DIR,\n report_to = 'trackio',\n trackio_space_id = OUTPUT_DIR,\n logging_steps = 1,\n save_steps = 20,\n gradient_checkpointing = False, # Unsloth handles this\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 x {NUM_GEN} gen x {grpo_cfg.num_train_epochs} epochs -> ~{total_steps} steps')\n\n# PMOpsGRPOTrainer overrides _calculate_rewards to read the pre-computed\n# 'reward' key directly from the rollout batch — bypasses the broken\n# PatchFastRL kwargs plumbing that strips custom rollout keys.\ntrainer = PMOpsGRPOTrainer(\n model = model,\n processing_class = tokenizer,\n reward_funcs = grpo_reward_func, # kept as fallback only\n train_dataset = grpo_dataset,\n args = grpo_cfg,\n rollout_func = grpo_rollout_func,\n)\nprint('PMOpsGRPOTrainer ready')"
 
 
 
 
 
 
 
 
 
638
  },
639
  {
640
  "cell_type": "markdown",
@@ -653,10 +706,10 @@
653
  },
654
  {
655
  "cell_type": "code",
 
656
  "id": "cell-16",
657
  "metadata": {},
658
  "outputs": [],
659
- "execution_count": null,
660
  "source": [
661
  "trainer_stats = trainer.train()\n",
662
  "\n",
@@ -678,10 +731,10 @@
678
  },
679
  {
680
  "cell_type": "code",
 
681
  "id": "cell-17",
682
  "metadata": {},
683
  "outputs": [],
684
- "execution_count": null,
685
  "source": [
686
  "grpo_env.close()\n",
687
  "\n",
@@ -702,10 +755,10 @@
702
  },
703
  {
704
  "cell_type": "code",
 
705
  "id": "cell-18",
706
  "metadata": {},
707
  "outputs": [],
708
- "execution_count": null,
709
  "source": [
710
  "from transformers import AutoModelForCausalLM\n",
711
  "\n",
@@ -797,10 +850,10 @@
797
  },
798
  {
799
  "cell_type": "code",
 
800
  "id": "cell-19",
801
  "metadata": {},
802
  "outputs": [],
803
- "execution_count": null,
804
  "source": [
805
  "import matplotlib.pyplot as plt\n",
806
  "import numpy as np\n",
@@ -841,14 +894,27 @@
841
  },
842
  {
843
  "cell_type": "code",
 
844
  "id": "cell-20",
845
  "metadata": {},
846
  "outputs": [],
847
- "execution_count": null,
848
  "source": [
849
  "server_proc.terminate()\n",
850
  "print('Local PM-Ops server stopped')"
851
  ]
852
  }
853
- ]
854
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "cells": [
3
  {
4
  "cell_type": "markdown",
 
34
  },
35
  {
36
  "cell_type": "code",
37
+ "execution_count": null,
38
  "id": "cell-1",
39
  "metadata": {},
40
  "outputs": [],
 
41
  "source": [
42
  "# Unsloth + vLLM first — let Unsloth resolve torch compat\n",
43
  "!pip install -q unsloth vllm\n",
 
60
  },
61
  {
62
  "cell_type": "code",
63
+ "execution_count": null,
64
  "id": "cell-2",
65
  "metadata": {},
66
  "outputs": [],
 
67
  "source": [
68
  "import torch\n",
69
  "\n",
70
+ "# PatchFastRL can bypass custom rollout_func on some cloud runtimes.\n",
71
+ "# Keep it disabled for PMOpsGRPOTrainer rollout compatibility.\n",
72
  "from unsloth import FastLanguageModel, PatchFastRL\n",
73
+ "ENABLE_FAST_RL_PATCH = False\n",
74
+ "if ENABLE_FAST_RL_PATCH:\n",
75
+ " PatchFastRL('GRPO', FastLanguageModel)\n",
76
+ " print('PatchFastRL enabled')\n",
77
+ "else:\n",
78
+ " print('PatchFastRL disabled for rollout_func compatibility')\n",
79
  "\n",
80
  "import trl\n",
81
  "print(f'torch : {torch.__version__}')\n",
 
108
  },
109
  {
110
  "cell_type": "code",
111
+ "execution_count": null,
112
  "id": "cell-3",
113
  "metadata": {},
114
  "outputs": [],
 
115
  "source": [
116
  "import os, sys\n",
117
  "\n",
 
142
  },
143
  {
144
  "cell_type": "code",
145
+ "execution_count": null,
146
  "id": "cell-4",
147
  "metadata": {},
148
  "outputs": [],
 
149
  "source": [
150
  "from huggingface_hub import notebook_login\n",
151
  "notebook_login()"
 
161
  },
162
  {
163
  "cell_type": "code",
164
+ "execution_count": null,
165
  "id": "cell-5",
166
  "metadata": {},
167
  "outputs": [],
 
168
  "source": [
169
  "import subprocess, time, requests\n",
170
  "\n",
 
197
  },
198
  {
199
  "cell_type": "code",
200
+ "execution_count": null,
201
  "id": "cell-6",
202
  "metadata": {},
203
  "outputs": [],
 
204
  "source": [
205
  "import trl.experimental.openenv # must be importable\n",
206
  "from openenv.core import GenericEnvClient\n",
 
224
  },
225
  {
226
  "cell_type": "code",
227
+ "execution_count": null,
228
  "id": "cell-7",
229
  "metadata": {},
230
  "outputs": [],
 
231
  "source": [
232
  "MODEL_NAME = 'Qwen/Qwen3-1.7B'\n",
233
  "LORA_RANK = 16\n",
 
236
  " model_name = MODEL_NAME,\n",
237
  " max_seq_length = 4096 + MAX_COMP_LEN,\n",
238
  " load_in_4bit = True,\n",
239
+ " fast_inference = False, # disable fast RL path to preserve rollout_func behaviour\n",
240
  " max_lora_rank = LORA_RANK,\n",
241
  " gpu_memory_utilization = 0.50, # leave headroom for SFT activations\n",
242
  ")\n",
 
248
  " lora_alpha = LORA_RANK,\n",
249
  " use_gradient_checkpointing = 'unsloth',\n",
250
  " random_state = 42,\n",
251
+ " )\n",
252
  "tokenizer.pad_token = tokenizer.eos_token\n",
253
  "tokenizer.padding_side = 'left'\n",
254
  "model.print_trainable_parameters()\n",
 
284
  },
285
  {
286
  "cell_type": "code",
287
+ "execution_count": null,
288
  "id": "cell-9",
289
  "metadata": {},
290
  "outputs": [],
 
291
  "source": [
292
  "import json as _json\n",
293
  "from datasets import Dataset\n",
 
366
  },
367
  {
368
  "cell_type": "code",
369
+ "execution_count": null,
370
  "id": "cell-10",
371
  "metadata": {},
372
  "outputs": [],
 
373
  "source": [
374
  "from trl import SFTTrainer, SFTConfig\n",
375
  "\n",
 
416
  },
417
  {
418
  "cell_type": "code",
419
+ "execution_count": null,
420
  "id": "cell-11",
421
  "metadata": {},
422
  "outputs": [],
 
423
  "source": [
424
  "from training.rollout import extract_json_action, _obs_to_dict, _current_obs_text, build_messages\n",
425
  "\n",
 
485
  },
486
  {
487
  "cell_type": "code",
488
+ "execution_count": null,
489
  "id": "cell-13",
490
  "metadata": {},
491
  "outputs": [],
 
492
  "source": [
493
  "from training.dataset import generate_triage_dataset\n",
494
  "\n",
 
508
  },
509
  {
510
  "cell_type": "code",
511
+ "execution_count": null,
512
  "id": "cell-14",
513
  "metadata": {},
514
  "outputs": [],
 
515
  "source": [
516
  "from trl.experimental.openenv import generate_rollout_completions\n",
517
  "from training.rollout import (\n",
 
623
  },
624
  {
625
  "cell_type": "code",
626
+ "execution_count": null,
627
  "id": "cell-15",
628
  "metadata": {},
629
  "outputs": [],
630
+ "source": [
631
+ "from trl import GRPOConfig\n",
632
+ "from training.pm_ops_trainer import PMOpsGRPOTrainer\n",
633
+ "\n",
634
+ "OUTPUT_DIR = 'pm-ops-grpo-Qwen3-1.7B-triage-v3'\n",
635
+ "HF_REPO_ID = f'Saurav1/{OUTPUT_DIR}'\n",
636
+ "\n",
637
+ "grpo_cfg = GRPOConfig(\n",
638
+ " # Training\n",
639
+ " num_train_epochs = 2,\n",
640
+ " learning_rate = 1e-6, # lower LR: model has SFT init, don't overwrite it\n",
641
+ " gradient_accumulation_steps = GRAD_ACCUM,\n",
642
+ " per_device_train_batch_size = 1,\n",
643
+ " warmup_steps = 5,\n",
644
+ " num_generations = NUM_GEN,\n",
645
+ " # Sequence lengths\n",
646
+ " max_completion_length = MAX_COMP_LEN,\n",
647
+ " max_prompt_length = 4096,\n",
648
+ " # Keep disabled for rollout_func compatibility on cloud runtimes\n",
649
+ " use_vllm = False,\n",
650
+ " # Output\n",
651
+ " output_dir = OUTPUT_DIR,\n",
652
+ " report_to = 'trackio',\n",
653
+ " trackio_space_id = OUTPUT_DIR,\n",
654
+ " logging_steps = 1,\n",
655
+ " save_steps = 20,\n",
656
+ " gradient_checkpointing = False, # Unsloth handles this\n",
657
+ ")\n",
658
+ "\n",
659
+ "eff_batch = grpo_cfg.per_device_train_batch_size * GRAD_ACCUM\n",
660
+ "total_steps = len(grpo_dataset) * NUM_GEN * grpo_cfg.num_train_epochs // eff_batch\n",
661
+ "print(f'GRPO: {len(grpo_dataset)} eps x {NUM_GEN} gen x {grpo_cfg.num_train_epochs} epochs -> ~{total_steps} steps')\n",
662
+ "\n",
663
+ "# PMOpsGRPOTrainer overrides _calculate_rewards to read the pre-computed\n",
664
+ "# 'reward' key directly from the rollout batch — bypasses broken kwargs plumbing.\n",
665
+ "trainer = PMOpsGRPOTrainer(\n",
666
+ " model = model,\n",
667
+ " processing_class = tokenizer,\n",
668
+ " reward_funcs = grpo_reward_func, # kept as fallback only\n",
669
+ " train_dataset = grpo_dataset,\n",
670
+ " args = grpo_cfg,\n",
671
+ " rollout_func = grpo_rollout_func,\n",
672
+ ")\n",
673
+ "print(f'PMOpsGRPOTrainer ready: {type(trainer).__name__}')\n",
674
+ "assert isinstance(trainer, PMOpsGRPOTrainer), 'trainer must be PMOpsGRPOTrainer'\n",
675
+ "assert grpo_cfg.use_vllm is False, 'use_vllm must be False for rollout compatibility'"
676
+ ]
677
+ },
678
+ {
679
+ "cell_type": "code",
680
  "execution_count": null,
681
+ "id": "9a7c99b4",
682
+ "metadata": {},
683
+ "outputs": [],
684
+ "source": [
685
+ "# Preflight: ensure rollout returns reward and trainer received rollout_func\n",
686
+ "probe = grpo_rollout_func([grpo_dataset[0]['prompt']], trainer=trainer)\n",
687
+ "print('probe keys:', list(probe.keys()))\n",
688
+ "print('probe reward sample:', probe['reward'][:1])\n",
689
+ "assert len(probe['reward']) == 1, 'rollout probe did not return reward values'"
690
+ ]
691
  },
692
  {
693
  "cell_type": "markdown",
 
706
  },
707
  {
708
  "cell_type": "code",
709
+ "execution_count": null,
710
  "id": "cell-16",
711
  "metadata": {},
712
  "outputs": [],
 
713
  "source": [
714
  "trainer_stats = trainer.train()\n",
715
  "\n",
 
731
  },
732
  {
733
  "cell_type": "code",
734
+ "execution_count": null,
735
  "id": "cell-17",
736
  "metadata": {},
737
  "outputs": [],
 
738
  "source": [
739
  "grpo_env.close()\n",
740
  "\n",
 
755
  },
756
  {
757
  "cell_type": "code",
758
+ "execution_count": null,
759
  "id": "cell-18",
760
  "metadata": {},
761
  "outputs": [],
 
762
  "source": [
763
  "from transformers import AutoModelForCausalLM\n",
764
  "\n",
 
850
  },
851
  {
852
  "cell_type": "code",
853
+ "execution_count": null,
854
  "id": "cell-19",
855
  "metadata": {},
856
  "outputs": [],
 
857
  "source": [
858
  "import matplotlib.pyplot as plt\n",
859
  "import numpy as np\n",
 
894
  },
895
  {
896
  "cell_type": "code",
897
+ "execution_count": null,
898
  "id": "cell-20",
899
  "metadata": {},
900
  "outputs": [],
 
901
  "source": [
902
  "server_proc.terminate()\n",
903
  "print('Local PM-Ops server stopped')"
904
  ]
905
  }
906
+ ],
907
+ "metadata": {
908
+ "kernelspec": {
909
+ "display_name": "Python 3",
910
+ "language": "python",
911
+ "name": "python3"
912
+ },
913
+ "language_info": {
914
+ "name": "python",
915
+ "version": "3.11.0"
916
+ }
917
+ },
918
+ "nbformat": 4,
919
+ "nbformat_minor": 5
920
+ }