Aditya Guntur commited on
Commit
29d6757
·
1 Parent(s): 14d4979

feat(training): PMOpsGRPOTrainer — override _calculate_rewards to inject rollout rewards directly

Browse files

Bypasses TRL 1.2.0 kwargs-drop bug: instead of reward_funcs receiving
pre-computed rewards via broken **kwargs, _calculate_rewards reads the
'reward' key from the input batch and returns it as a [batch_size, 1]
tensor. Falls back to standard GRPOTrainer if key is absent.

- training/pm_ops_trainer.py: new PMOpsGRPOTrainer subclass
- training/train_v2.ipynb: swap GRPOTrainer -> PMOpsGRPOTrainer in cell 24

Files changed (2) hide show
  1. training/pm_ops_trainer.py +106 -0
  2. training/train_v2.ipynb +53 -20
training/pm_ops_trainer.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PMOpsGRPOTrainer — GRPOTrainer subclass with direct reward injection.
2
+
3
+ Problem being solved
4
+ --------------------
5
+ TRL 1.2.0's GRPOTrainer._calculate_rewards() calls reward_funcs with
6
+ **reward_kwargs built from per-example keys in the input batch. This works
7
+ correctly when reward_funcs produce rewards from text completions alone.
8
+
9
+ It breaks for multi-turn env rollouts: our rollout_func computes rewards
10
+ inside the episode loop (where the env state is available), stores them as
11
+ a 'reward' key in the rollout output dict, and expects reward_func to read
12
+ them back via kwargs. TRL silently drops these keys when building
13
+ reward_kwargs from the batch, so reward_func always receives an empty dict
14
+ and returns 0.0 for every episode.
15
+
16
+ Fix
17
+ ---
18
+ Override _calculate_rewards to check for a pre-computed 'reward' key in the
19
+ input batch. If present, return it directly as a [batch_size, 1] tensor —
20
+ no reward_funcs called, no kwargs needed. If absent, fall back to the
21
+ standard TRL behaviour so the class works as a drop-in replacement.
22
+
23
+ Usage
24
+ -----
25
+ from training.pm_ops_trainer import PMOpsGRPOTrainer
26
+
27
+ trainer = PMOpsGRPOTrainer(
28
+ model=model,
29
+ processing_class=tokenizer,
30
+ reward_funcs=reward_func, # kept as-is; only used as fallback
31
+ train_dataset=dataset,
32
+ args=grpo_config,
33
+ rollout_func=rollout_func, # must put 'reward' key in output dict
34
+ )
35
+
36
+ rollout_func contract
37
+ ---------------------
38
+ rollout_func(prompts, trainer=None) must return a dict containing at minimum:
39
+ {
40
+ "prompt_ids": list[list[int]], # one per episode
41
+ "completion_ids": list[list[int]],
42
+ "logprobs": list[list[float]],
43
+ "reward": list[float], # one combined float per episode
44
+ }
45
+
46
+ The 'reward' value is the weighted combination of all sub-signals and is
47
+ injected directly as the GRPO advantage signal.
48
+ """
49
+
50
+ import torch
51
+ from trl import GRPOTrainer
52
+
53
+
54
+ class PMOpsGRPOTrainer(GRPOTrainer):
55
+ """Drop-in GRPOTrainer replacement with direct reward injection.
56
+
57
+ Overrides _calculate_rewards to read pre-computed rewards from the
58
+ rollout batch instead of calling reward_funcs with broken **kwargs.
59
+ Falls back to standard GRPOTrainer behaviour if 'reward' key is absent.
60
+ """
61
+
62
+ # Key written by rollout_func and read by _calculate_rewards
63
+ ROLLOUT_REWARD_KEY = "reward"
64
+
65
+ def _calculate_rewards(
66
+ self,
67
+ inputs,
68
+ prompts,
69
+ completions,
70
+ completion_ids_list,
71
+ ):
72
+ """Override: inject pre-computed rewards when available.
73
+
74
+ Args:
75
+ inputs: list[dict] — one dict per example in the batch.
76
+ rollout_func output keys land here per example.
77
+ prompts: list[str] — prompt texts (unused in this path)
78
+ completions: list[str] — completion texts (unused)
79
+ completion_ids_list: list[list[int]] — token IDs (unused)
80
+
81
+ Returns:
82
+ Tensor of shape [batch_size, 1] when pre-computed rewards are found.
83
+ Falls back to super()._calculate_rewards(...) otherwise.
84
+ """
85
+ # Fast-path: pre-computed reward key present in every example
86
+ if inputs and all(
87
+ self.ROLLOUT_REWARD_KEY in ex for ex in inputs
88
+ ):
89
+ device = self.accelerator.device
90
+ rewards = torch.tensor(
91
+ [float(ex[self.ROLLOUT_REWARD_KEY]) for ex in inputs],
92
+ dtype=torch.float32,
93
+ device=device,
94
+ ).unsqueeze(1) # [batch_size, 1]
95
+
96
+ # Log the mean reward so it shows up in training curves
97
+ mean_r = rewards.mean().item()
98
+ self.log({"reward/injected_mean": mean_r})
99
+
100
+ return rewards
101
+
102
+ # Fallback: standard TRL reward_funcs path
103
+ # Triggered when reward key is absent (e.g. non-rollout evaluation)
104
+ return super()._calculate_rewards(
105
+ inputs, prompts, completions, completion_ids_list
106
+ )
training/train_v2.ipynb CHANGED
@@ -21,7 +21,9 @@
21
  {
22
  "cell_type": "markdown",
23
  "metadata": {},
24
- "source": ["## 0. Install Dependencies"]
 
 
25
  },
26
  {
27
  "cell_type": "code",
@@ -47,7 +49,9 @@
47
  {
48
  "cell_type": "markdown",
49
  "metadata": {},
50
- "source": ["## 1. Version Check + GPU Detect"]
 
 
51
  },
52
  {
53
  "cell_type": "code",
@@ -81,7 +85,9 @@
81
  {
82
  "cell_type": "markdown",
83
  "metadata": {},
84
- "source": ["## 2. Clone PM-Ops Repo"]
 
 
85
  },
86
  {
87
  "cell_type": "code",
@@ -111,7 +117,9 @@
111
  {
112
  "cell_type": "markdown",
113
  "metadata": {},
114
- "source": ["## 3. HuggingFace Login"]
 
 
115
  },
116
  {
117
  "cell_type": "code",
@@ -161,7 +169,9 @@
161
  {
162
  "cell_type": "markdown",
163
  "metadata": {},
164
- "source": ["## 5. Verify Environment"]
 
 
165
  },
166
  {
167
  "cell_type": "code",
@@ -239,7 +249,9 @@
239
  {
240
  "cell_type": "markdown",
241
  "metadata": {},
242
- "source": ["## 7. Generate Training Dataset"]
 
 
243
  },
244
  {
245
  "cell_type": "code",
@@ -375,7 +387,9 @@
375
  {
376
  "cell_type": "markdown",
377
  "metadata": {},
378
- "source": ["## 10. Configure GRPO Training"]
 
 
379
  },
380
  {
381
  "cell_type": "code",
@@ -423,7 +437,9 @@
423
  {
424
  "cell_type": "markdown",
425
  "metadata": {},
426
- "source": ["## 11. Create Trainer"]
 
 
427
  },
428
  {
429
  "cell_type": "code",
@@ -431,17 +447,17 @@
431
  "metadata": {},
432
  "outputs": [],
433
  "source": [
434
- "from trl import GRPOTrainer\n",
435
  "\n",
436
- "trainer = GRPOTrainer(\n",
437
  " model=model,\n",
438
  " processing_class=tokenizer,\n",
439
- " reward_funcs=reward_func,\n",
440
  " train_dataset=dataset,\n",
441
  " args=grpo_config,\n",
442
  " rollout_func=rollout_func,\n",
443
  ")\n",
444
- "print('GRPOTrainer ready')"
445
  ]
446
  },
447
  {
@@ -485,7 +501,9 @@
485
  {
486
  "cell_type": "markdown",
487
  "metadata": {},
488
- "source": ["## 13. Save + Push"]
 
 
489
  },
490
  {
491
  "cell_type": "code",
@@ -509,7 +527,9 @@
509
  {
510
  "cell_type": "markdown",
511
  "metadata": {},
512
- "source": ["## 14. Merge LoRA → bf16 (Optional — for full-weight inference)"]
 
 
513
  },
514
  {
515
  "cell_type": "code",
@@ -533,7 +553,9 @@
533
  {
534
  "cell_type": "markdown",
535
  "metadata": {},
536
- "source": ["## 15. Evaluate: Baseline vs Trained"]
 
 
537
  },
538
  {
539
  "cell_type": "code",
@@ -621,7 +643,9 @@
621
  {
622
  "cell_type": "markdown",
623
  "metadata": {},
624
- "source": ["## 16. Plot"]
 
 
625
  },
626
  {
627
  "cell_type": "code",
@@ -660,7 +684,9 @@
660
  {
661
  "cell_type": "markdown",
662
  "metadata": {},
663
- "source": ["## 17. Teardown"]
 
 
664
  },
665
  {
666
  "cell_type": "code",
@@ -674,9 +700,16 @@
674
  }
675
  ],
676
  "metadata": {
677
- "kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"},
678
- "language_info": {"name": "python", "version": "3.12.0"}
 
 
 
 
 
 
 
679
  },
680
  "nbformat": 4,
681
  "nbformat_minor": 4
682
- }
 
21
  {
22
  "cell_type": "markdown",
23
  "metadata": {},
24
+ "source": [
25
+ "## 0. Install Dependencies"
26
+ ]
27
  },
28
  {
29
  "cell_type": "code",
 
49
  {
50
  "cell_type": "markdown",
51
  "metadata": {},
52
+ "source": [
53
+ "## 1. Version Check + GPU Detect"
54
+ ]
55
  },
56
  {
57
  "cell_type": "code",
 
85
  {
86
  "cell_type": "markdown",
87
  "metadata": {},
88
+ "source": [
89
+ "## 2. Clone PM-Ops Repo"
90
+ ]
91
  },
92
  {
93
  "cell_type": "code",
 
117
  {
118
  "cell_type": "markdown",
119
  "metadata": {},
120
+ "source": [
121
+ "## 3. HuggingFace Login"
122
+ ]
123
  },
124
  {
125
  "cell_type": "code",
 
169
  {
170
  "cell_type": "markdown",
171
  "metadata": {},
172
+ "source": [
173
+ "## 5. Verify Environment"
174
+ ]
175
  },
176
  {
177
  "cell_type": "code",
 
249
  {
250
  "cell_type": "markdown",
251
  "metadata": {},
252
+ "source": [
253
+ "## 7. Generate Training Dataset"
254
+ ]
255
  },
256
  {
257
  "cell_type": "code",
 
387
  {
388
  "cell_type": "markdown",
389
  "metadata": {},
390
+ "source": [
391
+ "## 10. Configure GRPO Training"
392
+ ]
393
  },
394
  {
395
  "cell_type": "code",
 
437
  {
438
  "cell_type": "markdown",
439
  "metadata": {},
440
+ "source": [
441
+ "## 11. Create Trainer"
442
+ ]
443
  },
444
  {
445
  "cell_type": "code",
 
447
  "metadata": {},
448
  "outputs": [],
449
  "source": [
450
+ "from training.pm_ops_trainer import PMOpsGRPOTrainer\n",
451
  "\n",
452
+ "trainer = PMOpsGRPOTrainer(\n",
453
  " model=model,\n",
454
  " processing_class=tokenizer,\n",
455
+ " reward_funcs=reward_func, # fallback only — injected rewards take priority\n",
456
  " train_dataset=dataset,\n",
457
  " args=grpo_config,\n",
458
  " rollout_func=rollout_func,\n",
459
  ")\n",
460
+ "print(\"PMOpsGRPOTrainer ready — direct reward injection active\")"
461
  ]
462
  },
463
  {
 
501
  {
502
  "cell_type": "markdown",
503
  "metadata": {},
504
+ "source": [
505
+ "## 13. Save + Push"
506
+ ]
507
  },
508
  {
509
  "cell_type": "code",
 
527
  {
528
  "cell_type": "markdown",
529
  "metadata": {},
530
+ "source": [
531
+ "## 14. Merge LoRA → bf16 (Optional — for full-weight inference)"
532
+ ]
533
  },
534
  {
535
  "cell_type": "code",
 
553
  {
554
  "cell_type": "markdown",
555
  "metadata": {},
556
+ "source": [
557
+ "## 15. Evaluate: Baseline vs Trained"
558
+ ]
559
  },
560
  {
561
  "cell_type": "code",
 
643
  {
644
  "cell_type": "markdown",
645
  "metadata": {},
646
+ "source": [
647
+ "## 16. Plot"
648
+ ]
649
  },
650
  {
651
  "cell_type": "code",
 
684
  {
685
  "cell_type": "markdown",
686
  "metadata": {},
687
+ "source": [
688
+ "## 17. Teardown"
689
+ ]
690
  },
691
  {
692
  "cell_type": "code",
 
700
  }
701
  ],
702
  "metadata": {
703
+ "kernelspec": {
704
+ "display_name": "Python 3",
705
+ "language": "python",
706
+ "name": "python3"
707
+ },
708
+ "language_info": {
709
+ "name": "python",
710
+ "version": "3.12.0"
711
+ }
712
  },
713
  "nbformat": 4,
714
  "nbformat_minor": 4
715
+ }