Claude Cursor Skill

grpo-rlvr-training

Train reasoning and verifiable-task behavior with GRPO and reinforcement learning from verifiable rewards (RLVR). Use when task success is algorithmically checkable (math, code, tool calls, structured output), when designing GRPO reward functions, or when a GRPO run diverges or r

LLM Mart · 0 points · 17 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wshobson-agents-plugins_llm-finetuning_skills_grpo-rlvr-training-554237f.zip · 10 KB
Part of wshobson/agents — 170 skills

Install

skills CLI npx skills add https://github.com/wshobson/agents/tree/main/plugins/llm-finetuning/skills/grpo-rlvr-training
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wshobson-agents@llmmart
Git git clone https://github.com/wshobson/agents.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wshobson/agents collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

GRPO & RLVR Training

This skill assumes finetuning-method-selection already routed here because the target behavior has a verifiable pass/fail signal — not demonstrations (lora-qlora-recipes) or preference pairs (preference-optimization). What follows is when RL is the right tool, the reference recipe, the mandatory reward-inspection gate, and how to pick a GRPO variant when the base recipe misbehaves.

Input: a routing decision (RLVR via GRPO) plus a verifier (code executor, test suite, schema checker, or grader) for the target task. Output format: a validated GRPO config — the kwarg values in references/grpo-memory.md and the reward functions in references/reward-functions.md, not free-form advice — that llm-finetuning-training-engineer consumes directly.

When RL Applies

GRPO+RLVR only pays off when task success is algorithmically checkable — a unit test passes, a parser accepts the output, a tool call matches an expected schema, a math answer matches a ground truth. If grading the output requires human judgment or a subjective rubric, that's an eval-harness and judge-calibration problem first — see eval-harness-first — not a reason to skip straight to RL.

Before opening a GRPO run, confirm the model can sometimes succeed on the target task already. RL sharpens an existing capability by reweighting toward the samples that already work; it does not install a capability from zero.

  • The model never succeeds, even at low temperature across many samples: the gap is format or task understanding, not policy refinement. Route back to SFT first (lora-qlora-recipes) and only return to this skill once the base success rate is nonzero.
  • The model succeeds sometimes, inconsistently: this is the GRPO sweet spot — proceed to The Recipe below.

The standing rule for the whole plugin: DPO for taste, GRPO for reasoning. If the signal is a preference between two acceptable outputs, that's preference-optimization, not this skill.

The Recipe

The reference recipe is TRL's GRPOTrainer with vLLM-backed generation:

from trl import GRPOConfig, GRPOTrainer

grpo_args = GRPOConfig(
    output_dir="./outputs-grpo",
    use_vllm=True,
    vllm_mode="colocate",       # single GPU; "server" for multi-GPU
    num_generations=8,          # floor — fewer starves the group-relative baseline
    learning_rate=5e-7,         # settled range for GRPO
    beta=0.01,                  # KL coefficient vs the reference policy
    per_device_train_batch_size=8,
    gradient_accumulation_steps=4,
    bf16=True,
    logging_steps=10,
    seed=3407,
)

trainer = GRPOTrainer(
    model=SFT_CHECKPOINT,
    args=grpo_args,
    reward_funcs=[format_reward, correctness_reward],   # references/reward-functions.md
    train_dataset=prompts,       # prompt-only — GRPO generates its own completions
    processing_class=tokenizer,
)

trainer.train()
  • vllm_mode="colocate" runs generation and training on the same GPU — the default for a single-GPU box.
  • vllm_mode="server" points at a separate vLLM server process and is the multi-GPU path — generation and training don't compete for the same device.
  • num_generations ≥ 8 is a floor, not a suggestion: GRPO's advantage estimate is relative to the group mean, and fewer than 8 samples per prompt produces a noisy baseline.
  • Reward is composite — a format reward (did the output parse / match the required structure) plus a correctness reward (did the answer verify). A well-formed-but-wrong answer and a malformed one should not score identically; correctness alone loses that signal.
  • learning_rate=5e-7 and beta=0.01 are the settled starting point; deviate only after the base run is stable and reward-inspected (below).

Memory sizing for this recipe by target size class: references/grpo-memory.md.

The Inspection Rule

Run the reward function against 50–100 sampled outputs and manually read the results before starting the actual training run. This is a gate, not a one-time sanity check.

If the reward function's judgment disagrees with a human reading of that sample, fix the reward function first. Training against an uninspected reward, or tuning hyperparameters to compensate for one silently scoring the wrong thing, is how a run reward-hacks: the model optimizes cleanly toward the wrong target, and that doesn't surface as a training-loop bug.

This inspection is a Phase 1 gate input for /finetune — the same 50–100-sample read that catches a broken reward function here is what that command checks for before it lets a GRPO brief proceed.

Complete reward function implementations to inspect against — exact-match, schema-validation, unit-test-execution, a length-penalty wrapper, and a rubric-as-reward judge pattern: references/reward-functions.md.

Variant Selection

The base recipe above is the default. Reach for a variant only when a specific failure mode shows up, not preemptively:

Failure mode Variant Why
Entropy collapse / degenerate long chain-of-thought DAPO Decouples clip bounds and relaxes the KL penalty that over-regularizes exploration on long reasoning traces
Reward or output length trends up regardless of quality Dr.GRPO Removes GRPO's length-normalization bias so reward tracks correctness, not completion length
Training a mixture-of-experts model GSPO Moves the importance-sampling ratio to the sequence level instead of per-token — per-token ratios are unstable on MoE routing, so GSPO is required here, not optional

Start with plain GRPO. Watch for the specific symptom — collapsing entropy on long CoT, a length-reward correlation, or MoE instability — and only then swap in the matching variant above. Don't pre-select a variant before the base recipe has actually shown the failure mode.

VLM RL Is Reference-Only

Vision-language RL is not executed by this plugin in v1 — it's documented here for context, not as a runnable path. Tooling is fragmented across ms-swift and EasyR1-derived forks with no one-line TRL command yet, and naive text-only GRPO applied to a VLM tends to reward-hack by optimizing the text-reasoning trace while ignoring the image — the model learns to sound right without looking at the input. A VLM RL run is a research spike outside this skill's supported recipe, not a variant of The Recipe above.

References

  • references/reward-functions.md — complete Python reward functions (exact-match correctness, schema validation, unit-test execution, a length-penalty wrapper, and a rubric-as-reward judge pattern) to inspect under The Inspection Rule before any training run.
  • references/grpo-memory.md — memory sizing by target size class, vLLM sleep-mode and optimizer-state tactics, Unsloth's long-context RL chunking, and the DGX Spark bandwidth caveat for decode-heavy rollouts.

Related skills: finetuning-method-selection routes here once a verifiable pass/fail signal exists; preference-optimization is the sibling skill for preference pairs rather than verifiable rewards; eval-harness-first covers judge calibration for any reward that isn't purely code-checkable. On DGX Spark, defer to the dgx-spark-ops plugin's skills, when installed, for the memory/thermal remediation ladder this skill's memory table doesn't cover.

Files (agents)
  • references
    • grpo-memory.md 3.5 KB
      Last verified: 2026-07-13
      
      # GRPO Memory Sizing
      
      GRPO's memory footprint is heavier than SFT or
      DPO at the same parameter count: a training run
      plus a co-resident (or server-side) vLLM
      generation engine sampling `num_generations`
      completions per prompt, on top of the usual
      optimizer-state and activation costs. Base models
      are never named here — anchors are size classes
      only.
      
      ## Memory Anchors by Size Class
      
      | Size class | Feasible with |
      |---|---|
      | Small (≤~3B) | 24GB-class GPU — vLLM sleep mode + 8-bit AdamW + gradient checkpointing |
      | ~32B-class | H200-class GPU |
      | ~70B-class | B200-class GPU |
      
      - **24GB-class is feasible for small models**, but
        only with all three levers engaged together, not
        any one alone:
        - **vLLM sleep mode** releases the generation
          engine's KV-cache and weight memory between
          the rollout phase and the training-step phase
          instead of holding both resident simultaneously.
        - **8-bit AdamW** (`optim="adamw_8bit"`) cuts
          optimizer-state memory the same way it does
          for SFT/DPO — see `lora-qlora-recipes`.
        - **Gradient checkpointing** trades recompute for
          activation memory, same tradeoff as elsewhere
          in the plugin.
      - **H200-class is the anchor for ~32B-class
        models** — the policy model, reference model (for
        the KL term), and co-resident vLLM generation
        engine no longer fit together below that class.
      - **B200-class is the anchor for ~70B-class
        models**, for the same three-way residency
        reason at greater scale.
      
      These are starting anchors, not hard floors —
      `vllm_mode="server"` (a separate generation
      process, possibly on separate GPUs) changes the
      residency math versus `"colocate"`; re-derive
      before assuming a size class is out of reach on
      a given box.
      
      ## Unsloth Long-Context RL Chunking
      
      Unsloth's chunked-loss RL path extends usable RL
      context to roughly **7x longer** than an
      unchunked GRPO setup at the same memory budget —
      an order-of-magnitude figure from Unsloth's own
      published benchmarks, not re-measured here; verify
      against the current Unsloth release notes before
      sizing a context budget precisely on it. This
      matters specifically for RL because rollouts
      — especially long chain-of-thought completions —
      are the memory pressure point GRPO adds on top of
      the base training cost; chunking is the lever that
      buys headroom there without changing
      `num_generations` or batch size.
      
      ## DGX Spark: Bandwidth-Bound Rollouts
      
      GRPO's rollout phase is **decode-heavy** —
      `num_generations` ≥ 8 completions sampled per
      prompt, often with long chain-of-thought — and
      decode is memory-bandwidth-bound, not
      compute-bound. On DGX Spark, the shared memory
      bandwidth ceiling is the constraint that bites
      first for GRPO specifically, ahead of raw VRAM:
      measured sustained bandwidth runs well below the
      273 GB/s spec figure. This is gotcha G5 in the
      `dgx-spark-ops` plugin's `spark-training-gotchas`
      skill, when installed — that skill's bandwidth
      budget (180–192 GB/s sustained, not the spec
      ceiling) is the number to plan rollout throughput
      against, not the headline spec.
      
      **Practical consequence: prefer small models for
      GRPO on Spark.** A size class that would train
      comfortably via SFT or DPO on a single Spark can
      still bottleneck badly under GRPO once rollout
      decode saturates shared bandwidth — the Memory
      Anchors table above tells you whether it *fits*,
      this section tells you whether it *runs fast
      enough to be worth doing* on that hardware. When
      Spark rollout throughput is the binding
      constraint, dropping to a smaller size class is
      usually more effective than further GRPO
      hyperparameter tuning.
      
    • reward-functions.md 11.5 KB
      Last verified: 2026-07-13
      
      # GRPO Reward Function Library
      
      Complete, runnable reward functions for TRL's
      `GRPOTrainer`. Every function here follows the
      current TRL reward-function signature: it accepts
      `completions` plus any extra dataset columns as
      keyword arguments, and returns a `list[float]` the
      same length as `completions`. Base models are never
      named here — `SFT_CHECKPOINT`/`JUDGE_MODEL` are
      placeholders; see `finetuning-method-selection`'s
      `references/model-catalog.md` for actual
      checkpoints.
      
      **These examples assume the standard (string)
      completion format** — `completions: list[str]` — and
      call `.strip()`, `json.loads()`, `.split()`, etc.
      directly on each `completion`. TRL's conversational
      dataset format instead passes each completion as
      `[{"role": "assistant", "content": "..."}]`; on that
      format, extract `completion[0]["content"]` before
      applying any of the string operations below, in
      every function in this file.
      
      **Before wiring any of these into a training run,
      inspect them against 50–100 sampled outputs by
      hand** — this is `SKILL.md`'s Inspection Rule, not
      optional. A reward function that looks correct in
      isolation can still disagree with human judgment
      on real model outputs.
      
      ## Format Reward
      
      Checks structural compliance — did the completion
      follow the required response shape at all — as a
      prerequisite to grading correctness:
      
      ```python
      import re
      
      def format_reward(completions, **kwargs) -> list[float]:
          """1.0 if the completion has a <reasoning>...</reasoning>
          block followed by an <answer>...</answer> block, else 0.0.
          This is a gate, not the correctness signal — a
          well-formed wrong answer still scores 0 on
          correctness_reward below.
          """
          pattern = re.compile(
              r"^<reasoning>.*?</reasoning>\s*<answer>.*?</answer>$",
              re.DOTALL,
          )
          return [1.0 if pattern.match(c.strip()) else 0.0 for c in completions]
      ```
      
      ## Correctness Reward — Exact Match
      
      The baseline verifiable-answer reward, for tasks
      with a single ground-truth string (math final
      answers, closed-form lookups):
      
      ```python
      def correctness_reward(completions, answer, **kwargs) -> list[float]:
          """`answer` is the ground-truth column from the
          training dataset, aligned index-for-index with
          `completions`. Extracts the <answer> block from
          format_reward's expected shape and compares.
          """
          rewards = []
          for completion, gold in zip(completions, answer):
              match = re.search(r"<answer>(.*?)</answer>", completion, re.DOTALL)
              predicted = match.group(1).strip() if match else None
              rewards.append(2.0 if predicted == gold.strip() else 0.0)
          return rewards
      ```
      
      ## Correctness Reward — Schema Validation
      
      For structured-output and tool-call tasks, where
      "correct" means "conforms to the required JSON
      schema," not string equality:
      
      ```python
      import json
      from jsonschema import validate, ValidationError
      
      def schema_reward(completions, output_schema, **kwargs) -> list[float]:
          """`output_schema` is a JSON Schema dict, either a
          single constant schema for the whole batch or a
          per-example list the same length as `completions`.
          Rewards valid, schema-conformant JSON; 0.0 for
          anything that doesn't parse or doesn't validate.
          """
          if isinstance(output_schema, dict):
              # Constant case: one schema dict for every completion —
              # zip()-ing a bare dict would iterate its keys instead,
              # not the schema itself, so normalize first.
              schemas = [output_schema] * len(completions)
          else:
              schemas = list(output_schema)
              if len(schemas) != len(completions):
                  raise ValueError(
                      f"schema_reward: {len(schemas)} schemas for "
                      f"{len(completions)} completions"
                  )
          rewards = []
          for completion, schema in zip(completions, schemas):
              try:
                  parsed = json.loads(completion)
                  validate(instance=parsed, schema=schema)
                  rewards.append(1.0)
              except (json.JSONDecodeError, ValidationError):
                  rewards.append(0.0)
          return rewards
      ```
      
      ## Correctness Reward — Unit Test Execution
      
      For code-generation tasks, where "correct" means
      the generated function passes a held-out test
      suite. Execute in a subprocess with a hard
      timeout — never `exec()` untrusted completions
      in-process.
      
      **WARNING — this function executes model-generated
      code and REQUIRES an isolated environment:** a
      network-disabled container, gVisor/firejail, or a
      dedicated CI sandbox, with **no secrets or
      credentials in the environment** — no HF tokens,
      experiment-tracker keys, cloud credentials, or SSH
      keys. GRPO will, by design, push adversarial
      completions through this path as the policy
      explores. Never run it directly on a training host
      holding credentials. The timeout below protects
      training-loop liveness only — **it is NOT a
      security boundary**. Likewise, `TemporaryDirectory`
      confines where the harness writes its files, not
      what the executed code can read or reach. The
      function below enforces this: it takes the sandbox
      boundary as a required argument and refuses to run
      at all — returning reward 0.0 — when the caller
      doesn't supply one. It never falls back to host
      execution.
      
      ```python
      import logging
      import subprocess
      import tempfile
      from pathlib import Path
      
      logger = logging.getLogger(__name__)
      
      def test_execution_reward(
          completions, test_code, sandbox_cmd, timeout_s=10, **kwargs
      ) -> list[float]:
          """`test_code` is a per-example pytest snippet that
          imports the candidate under a fixed module name
          and asserts expected behavior. Runs each candidate
          in its own subprocess with a wall-clock timeout;
          an infinite loop or crash scores 0.0 instead of
          hanging the training loop.
      
          SECURITY: executes model-generated code. This
          function REQUIRES an isolation boundary — it does
          not run anything on the host by itself.
      
          `sandbox_cmd` (list[str], required) is a command
          prefix that wraps pytest in that boundary, e.g. a
          network-disabled, resource-capped Docker container:
      
              # sandbox_cmd = [
              #     "docker", "run", "--rm", "--network=none",
              #     "--memory=1g", "--cpus=1",
              #     "-v", f"{workdir}:/work:ro", "-w", "/work",
              #     "python:3.12-slim",
              # ]
      
          If `sandbox_cmd` is falsy, this function refuses to
          execute anything and returns 0.0 for every
          completion — it never falls back to running
          pytest on the host. The subprocess environment is
          scrubbed to a minimal PATH (no HF tokens,
          experiment-tracker keys, cloud credentials, or SSH
          keys). The timeout is a liveness guard for the
          training loop, NOT a security boundary — isolation
          comes entirely from `sandbox_cmd`; the temporary
          directory only confines harness writes, not what
          executed code can read or reach.
          """
          if not sandbox_cmd:
              logger.warning(
                  "test_execution_reward: no sandbox boundary provided "
                  "— refusing to execute model-generated code"
              )
              return [0.0 for _ in completions]
      
          scrubbed_env = {"PATH": "/usr/bin:/bin"}
          rewards = []
          for completion, tests in zip(completions, test_code):
              with tempfile.TemporaryDirectory() as tmp:
                  candidate_path = Path(tmp) / "candidate.py"
                  test_path = Path(tmp) / "test_candidate.py"
                  candidate_path.write_text(completion)
                  test_path.write_text(tests)
                  try:
                      result = subprocess.run(
                          [*sandbox_cmd, "python", "-m", "pytest",
                           str(test_path), "-q"],
                          cwd=tmp,
                          capture_output=True,
                          timeout=timeout_s,
                          env=scrubbed_env,
                      )
                      rewards.append(1.0 if result.returncode == 0 else 0.0)
                  except subprocess.TimeoutExpired:
                      rewards.append(0.0)
          return rewards
      ```
      
      ## Length-Penalty Wrapper
      
      Wraps any reward function above to discourage
      runaway completion length without replacing the
      underlying correctness signal — use when a
      correctness-only reward starts trending toward
      longer, padded outputs:
      
      ```python
      def with_length_penalty(reward_fn, target_len=512, penalty_per_token=0.001):
          """Returns a new reward function that subtracts a
          small per-token penalty for every token past
          `target_len`, applied on top of `reward_fn`'s
          output. Penalty is capped so it can't drive an
          otherwise-correct reward negative — it discourages
          padding without overriding correctness.
          """
          def wrapped(completions, **kwargs) -> list[float]:
              base_rewards = reward_fn(completions, **kwargs)
              adjusted = []
              for reward, completion in zip(base_rewards, completions):
                  overflow = max(0, len(completion.split()) - target_len)
                  penalty = min(reward, overflow * penalty_per_token)
                  adjusted.append(reward - penalty)
              return adjusted
          return wrapped
      ```
      
      Note that `len(completion.split())` counts words
      as a cheap proxy for tokens — use the model's own
      tokenizer for true token counts when tuning
      `target_len`.
      
      This is a targeted fix for observed length
      creep, not a substitute for Dr.GRPO — if length
      bias is systemic rather than an occasional
      overflow, route to the Dr.GRPO variant in
      `SKILL.md`'s Variant Selection instead of stacking
      penalty wrappers.
      
      ## Rubric-as-Reward Judge Pattern
      
      For tasks where correctness isn't code-checkable
      but the pass/fail line is still crisp enough for a
      judge to apply consistently — e.g., "did the
      response follow the requested format and stay
      on-topic" rather than "is this a good essay."
      Binary pass/fail, not a Likert score:
      
      TRL calls reward functions with `completions` plus
      whatever dataset columns the trainer was given, via
      `**kwargs` — it does not inject arbitrary objects
      like a judge client. Bind `judge_client` and the
      fixed `rubric` in a closure before handing the
      result to `GRPOTrainer(reward_funcs=[...])`, rather
      than declaring them as parameters TRL is expected to
      supply:
      
      ```python
      def make_rubric_judge_reward(judge_client, rubric):
          """`judge_client` calls JUDGE_MODEL — a model from a
          *different* model family than the model under
          training, never the model being trained or a
          same-family relative of it. `rubric` is a fixed
          pass/fail criterion string, not a free-form
          quality prompt, so both are bound here rather than
          read from TRL-supplied per-example kwargs. Returns a
          reward function matching TRL's actual signature.
          """
          def rubric_judge_reward(completions, prompts, **kwargs) -> list[float]:
              """Returns 1.0/0.0 per completion, never an
              intermediate score."""
              rewards = []
              for prompt, completion in zip(prompts, completions):
                  verdict = judge_client.judge(
                      rubric=rubric,
                      prompt=prompt,
                      response=completion,
                      output_format="pass_fail",   # binary only — no Likert scale
                  )
                  rewards.append(1.0 if verdict == "pass" else 0.0)
              return rewards
          return rubric_judge_reward
      ```
      
      **Calibration is a hard prerequisite, not a
      nice-to-have.** An uncalibrated judge is a
      noisier, more expensive version of the exact-match
      reward above — before wiring `rubric_judge_reward`
      into a GRPO run, the judge must be calibrated
      against human labels (train/dev/sealed-test
      splits, TPR/TNR reported, judge pinned to a fixed
      snapshot). That calibration workflow lives in
      `eval-harness-first`; do not skip it because the
      rubric "looks obviously right" — the same
      plugin-wide judge-calibration prerequisite applies
      here as everywhere else a judge grades a reward.
      
  • SKILL.md 7.6 KB
    ---
    name: grpo-rlvr-training
    description: Train reasoning and verifiable-task behavior with GRPO and reinforcement learning from verifiable rewards (RLVR). Use when task success is algorithmically checkable (math, code, tool calls, structured output), when designing GRPO reward functions, or when a GRPO run diverges or reward-hacks.
    ---
    
    # GRPO & RLVR Training
    
    This skill assumes `finetuning-method-selection`
    already routed here because the target behavior
    has a verifiable pass/fail signal — not
    demonstrations (`lora-qlora-recipes`) or
    preference pairs (`preference-optimization`).
    What follows is when RL is the right tool, the
    reference recipe, the mandatory reward-inspection
    gate, and how to pick a GRPO variant when the
    base recipe misbehaves.
    
    **Input:** a routing decision (RLVR via GRPO)
    plus a verifier (code executor, test suite,
    schema checker, or grader) for the target task.
    **Output format:** a validated GRPO config — the
    kwarg values in `references/grpo-memory.md` and
    the reward functions in
    `references/reward-functions.md`, not free-form
    advice — that `llm-finetuning-training-engineer`
    consumes directly.
    
    ## When RL Applies
    
    GRPO+RLVR only pays off when task success is
    **algorithmically checkable** — a unit test
    passes, a parser accepts the output, a tool call
    matches an expected schema, a math answer matches
    a ground truth. If grading the output requires
    human judgment or a subjective rubric, that's an
    eval-harness and judge-calibration problem first
    — see `eval-harness-first` — not a reason to skip
    straight to RL.
    
    Before opening a GRPO run, confirm the model can
    **sometimes** succeed on the target task already.
    RL sharpens an existing capability by reweighting
    toward the samples that already work; it does not
    install a capability from zero.
    
    - **The model never succeeds, even at low
      temperature across many samples:** the gap is
      format or task understanding, not policy
      refinement. Route back to SFT first
      (`lora-qlora-recipes`) and only return to this
      skill once the base success rate is nonzero.
    - **The model succeeds sometimes,
      inconsistently:** this is the GRPO sweet spot —
      proceed to The Recipe below.
    
    The standing rule for the whole plugin: **DPO for
    taste, GRPO for reasoning.** If the signal is a
    preference between two acceptable outputs, that's
    `preference-optimization`, not this skill.
    
    ## The Recipe
    
    The reference recipe is TRL's `GRPOTrainer` with
    vLLM-backed generation:
    
    ```python
    from trl import GRPOConfig, GRPOTrainer
    
    grpo_args = GRPOConfig(
        output_dir="./outputs-grpo",
        use_vllm=True,
        vllm_mode="colocate",       # single GPU; "server" for multi-GPU
        num_generations=8,          # floor — fewer starves the group-relative baseline
        learning_rate=5e-7,         # settled range for GRPO
        beta=0.01,                  # KL coefficient vs the reference policy
        per_device_train_batch_size=8,
        gradient_accumulation_steps=4,
        bf16=True,
        logging_steps=10,
        seed=3407,
    )
    
    trainer = GRPOTrainer(
        model=SFT_CHECKPOINT,
        args=grpo_args,
        reward_funcs=[format_reward, correctness_reward],   # references/reward-functions.md
        train_dataset=prompts,       # prompt-only — GRPO generates its own completions
        processing_class=tokenizer,
    )
    
    trainer.train()
    ```
    
    - **`vllm_mode="colocate"`** runs generation and
      training on the same GPU — the default for a
      single-GPU box.
    - **`vllm_mode="server"`** points at a separate
      vLLM server process and is the multi-GPU path —
      generation and training don't compete for the
      same device.
    - **`num_generations` ≥ 8** is a floor, not a
      suggestion: GRPO's advantage estimate is
      relative to the group mean, and fewer than 8
      samples per prompt produces a noisy baseline.
    - **Reward is composite** — a format reward (did
      the output parse / match the required
      structure) plus a correctness reward (did the
      answer verify). A well-formed-but-wrong answer
      and a malformed one should not score
      identically; correctness alone loses that
      signal.
    - **`learning_rate=5e-7`** and **`beta=0.01`** are
      the settled starting point; deviate only after
      the base run is stable and reward-inspected
      (below).
    
    Memory sizing for this recipe by target size
    class: `references/grpo-memory.md`.
    
    ## The Inspection Rule
    
    **Run the reward function against 50–100 sampled
    outputs and manually read the results before
    starting the actual training run.** This is a
    gate, not a one-time sanity check.
    
    If the reward function's judgment disagrees with
    a human reading of that sample, fix the reward
    function first. Training against an uninspected
    reward, or tuning hyperparameters to compensate
    for one silently scoring the wrong thing, is how
    a run reward-hacks: the model optimizes cleanly
    toward the wrong target, and that doesn't surface
    as a training-loop bug.
    
    This inspection is a Phase 1 gate input for
    `/finetune` — the same 50–100-sample read that
    catches a broken reward function here is what that
    command checks for before it lets a GRPO brief
    proceed.
    
    Complete reward function implementations to
    inspect against — exact-match, schema-validation,
    unit-test-execution, a length-penalty wrapper, and
    a rubric-as-reward judge pattern:
    `references/reward-functions.md`.
    
    ## Variant Selection
    
    The base recipe above is the default. Reach for a
    variant only when a specific failure mode shows
    up, not preemptively:
    
    | Failure mode | Variant | Why |
    |---|---|---|
    | Entropy collapse / degenerate long chain-of-thought | **DAPO** | Decouples clip bounds and relaxes the KL penalty that over-regularizes exploration on long reasoning traces |
    | Reward or output length trends up regardless of quality | **Dr.GRPO** | Removes GRPO's length-normalization bias so reward tracks correctness, not completion length |
    | Training a mixture-of-experts model | **GSPO** | Moves the importance-sampling ratio to the sequence level instead of per-token — per-token ratios are unstable on MoE routing, so GSPO is required here, not optional |
    
    Start with plain GRPO. Watch for the specific
    symptom — collapsing entropy on long CoT, a
    length-reward correlation, or MoE instability —
    and only then swap in the matching variant above.
    Don't pre-select a variant before the base recipe
    has actually shown the failure mode.
    
    ## VLM RL Is Reference-Only
    
    Vision-language RL is **not executed by this
    plugin in v1** — it's documented here for
    context, not as a runnable path. Tooling is
    fragmented across ms-swift and EasyR1-derived
    forks with no one-line TRL command yet, and naive
    text-only GRPO applied to a VLM tends to
    reward-hack by optimizing the text-reasoning trace
    while ignoring the image — the model learns to
    sound right without looking at the input. A VLM
    RL run is a research spike outside this skill's
    supported recipe, not a variant of The Recipe
    above.
    
    ## References
    
    - `references/reward-functions.md` — complete
      Python reward functions (exact-match
      correctness, schema validation, unit-test
      execution, a length-penalty wrapper, and a
      rubric-as-reward judge pattern) to inspect under
      The Inspection Rule before any training run.
    - `references/grpo-memory.md` — memory sizing by
      target size class, vLLM sleep-mode and
      optimizer-state tactics, Unsloth's long-context
      RL chunking, and the DGX Spark bandwidth caveat
      for decode-heavy rollouts.
    
    Related skills: `finetuning-method-selection`
    routes here once a verifiable pass/fail signal
    exists; `preference-optimization` is the sibling
    skill for preference pairs rather than verifiable
    rewards; `eval-harness-first` covers judge
    calibration for any reward that isn't purely
    code-checkable. On DGX Spark, defer to the
    `dgx-spark-ops` plugin's skills, when installed,
    for the memory/thermal remediation ladder this
    skill's memory table doesn't cover.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related