PRM-guided GRPO (v0.71.30)
soup train with task: grpo can now use a trained Process Reward Model as the per-step reward signal, the o1-era process-supervision idea: grade every reasoning step, not just the final answer. Point it at a PRM you trained with soup train with task: prm and it scores each step of a generated answer, folding the per-step scores into one reward that GRPO optimizes.
We have not found another OSS fine-tuning CLI that uses a PRM as the per-step GRPO reward.
Config
task: grpo
backend: transformers # required (the PRM reward runs a transformers forward)
modality: text # required
data:
format: chatml
train: ./grpo_prompts.jsonl
training:
prm_reward: ./my-prm # a `soup train` with `task: prm` checkpoint dir, or an HF id
prm_aggregate: min # min (weakest-link, default) | prod | last
num_generations: 4
grpo_beta: 0.04prm_rewardis a local directory or a Hub id. A Hub id (org/name, optionallyorg/name@revision) is downloaded before training through Soup's Hub download wrapper: the repo-id, revision and namespace-pin checks run first, and only weights, tokenizer files and custom-code modules are fetched. A value that is not a Soup-trained PRM (no reward head), a failed download, or a string that is neither an existing directory nor a repo id stops the run with one message naming the value, before any training, and an existing local path with no reward head now fails early too. (Since closed in v0.76.0 (#1466): through v0.75.2 anorg/namevalue loaded the tokenizer and the model and then died at the first reward call with "path is not a directory".)prm_rewardreplacesreward_fn; TRL logs it asrewards/prm_reward, and the v0.71.26 reward-hack controller observes it like any other reward.prm_aggregatefolds the per-step scores into one number.minis the weakest-link default and the safe choice;prodassumes calibrated[0,1]step scores (it can blow up or flip sign on uncalibrated labels);lastuses the final step.- Step boundaries are a newline heuristic; each step is scored with one PRM forward pass.
Training the PRM (task: prm), changes in v0.76.0
The PRM you point prm_reward at comes from soup train with task: prm. Three things changed for that task in v0.76.0:
- It trains on fp32 master weights on every device (#1235). On CUDA it used to load the base and the reward head in bf16 with no fp32 copy for the optimizer. Since v0.74.0 that made the first step fail (outside DeepSpeed) with
mat1 and mat2 must have the same dtype, and before that the base trained in pure bf16, where most AdamW steps at PRM learning rates round away: an RMSNorm weight at 1.0 never moves atlr1e-5 or 2e-5. With the default optimizer, expect about 16 bytes per parameter before activations instead of about 8, a saved checkpoint in fp32 (without DeepSpeed), and about twice the host RAM of a bf16 load to read it. A PRM trained on CUDA before this fix did not train in fp32. - It reads
optimizer,scheduler,warmup_ratio,weight_decayandmax_grad_norm(#805). Through v0.75.2 the PRM trainer did not read them, so a run that set them trained with defaults it had not been given.batch_size: autois also resolved for this task now (it printsAuto batch size (PRM)); before that it fell back to 1. soup train --resumeresumes it (#1220). It used to restart at step 0 and overwrite the checkpoints.
The trainer fine-tunes every base parameter, so a non-default training.lora block is refused when the config loads (task='prm' does not apply training.lora: the PRM trainer fine-tunes every base parameter. Remove the lora block (or set lora.r: 0).). An explicit training.quantization: 4bit or 8bit prints a warning and is ignored, because the trainer never quantises the base; upstream says v0.77 will refuse it.
Re-run this
| If you trained | What was wrong | What to do |
|---|---|---|
task: prm on CUDA | The base and head were bf16 with no fp32 master weight: the run either stopped at step 1 (outside DeepSpeed) or trained in pure bf16 where most AdamW steps round away (#1235) | Re-run if the PRM mattered; a GRPO run that used it as its reward inherits it |
task: prm with optimizer, scheduler, warmup_ratio, weight_decay or max_grad_norm set | The trainer did not read them (#805) | Re-run if the defaults it used mattered |
soup train --resume on task: prm | It restarted at step 0 and overwrote the checkpoints (#1220) | Do not trust a PRM that was "resumed" on an earlier version |
Bundled rollout environments
v0.71.30 also ships three deterministic, pure-Python toy environments so the GRPO openenv rollout path works with no external setup:
training:
rollout_backend: openenv
rollout_func: soup_cli.envs.calculator:rollout # or retrieval_qa | guess_number
reward_fn: verifiable
verifiable_domain: mathReady-made recipes: grpo-env-calculator, grpo-env-retrieval-qa, grpo-env-guess-number (part of catalog 134 to 137). They are single-shot prompt/answer seeders (the live openenv contract passes seed prompts, not the model), not interactive multi-turn episodes.
Honesty
Proof-of-mechanism only, validated on SmolLM2-135M with a tiny synthetic PRM and synthetic reward on a single RTX 3050, not a production reward-model claim. Scale validation is tracked in issue #286.
See also
- GRPO Plus — the 7 GRPO variants, rollout backends, and stability controls this builds on.
- Online DPO — the judge-in-the-loop RL alternative when you have a judge instead of a PRM.
- RLVR — verifiable rewards for math / code / JSON with no reward model at all.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.