Unboxing LLMs > loading...

August 19, 2025

GSPO vs GRPO in Practice: Stable RL for MoE with Unsloth + TRL

Key takeaways

  • The Unit of Optimization: GRPO wages war at the token level. GSPO wisely elevates the conflict to the sequence level.
  • Routing Replay is a Hack: GSPO’s holistic view of a sequence renders token-level “routing replay” obsolete.
  • Coherent Clipping: PPO’s clipping mechanism, when applied to a single sequence importance ratio, becomes a powerful stabilizer. Applying it to thousands of noisy token-level ratios is, by comparison, an exercise in futility for MoE.
  • The One-Line Fix: In TRL ≥ 0.20, this entire paradigm shift is unlocked by setting importance_sampling_level="sequence" in your RL config. The elegance of the fix belies its impact.
  • MoE’s Silver Bullet: Don’t expect miracles on dense models. The profound stability gains from GSPO are most visible where the pain is most acute: on Mixture-of-Experts.
  • Democratized RL: With Unsloth’s memory witchcraft and 4/8-bit quantization, this more stable form of RL is no longer the exclusive domain of hyperscalers. It runs at home.

Why token-level RL struggles on MoE

MoE models live and die by their routing gates. During RL, the stochastic nature of sampling means tiny fluctuations in token-wise log-probabilities can trigger violent routing oscillations. One token zigs to expert 4, the next zags to expert 7. With token-level objectives like GRPO, you’re trying to build a stable cathedral on seismic fault lines. The result is predictable:

  • Updates with punishingly high variance as different experts fire unpredictably across steps.
  • Extreme sensitivity to trivialities like sequence length and sampling temperature.
  • The proliferation of “routing replay” hacks – kludges designed to force consistency on which experts were used when computing advantages, adding complexity and overhead.

Sequence-level training reframes the problem entirely. Instead of micromanaging the likelihood of every single token, you judge the entire output as a single, coherent artifact. That is the observation GSPO is built on.


GSPO in one picture

 diagram


The math: token-level (GRPO) vs sequence-level (GSPO)

Token-level ratio (GRPO-style):

r_{t} \;=\; \frac{\pi_{\theta}(y_{t} \mid x,y_{<t})}{\pi_{\textrm{ref}}(y_{t} \mid x,y_{<t})}

with PPO clipping applied at each token – a noisy, often counterproductive affair:

\mathit{L}_{\textrm{token}} \;=\; - \mathbf{E}\Big[\min\big(r_{t} A_{t},\ \mathrm{clip}(r_{t},1-\varepsilon,1+\varepsilon) A_{t}\big)\Big]

Sequence-level ratio (GSPO): First, define the sequence likelihood ratio as the product of the token ratios.

r_{\textrm{seq}}(y\mid x) \;=\; \frac{\pi_{\theta}(y\mid x)}{\pi_{\textrm{ref}}(y\mid x)} \;=\; \prod_{t=1}^{T} \frac{\pi_{\theta}(y_{t}\mid x,y_{<t})}{\pi_{\textrm{ref}}(y_{t}\mid x,y_{<t})} \;=\; \exp\left(\sum_{t=1}^T \log r_{t}\right).

To prevent long sequences from dominating, use a normalized log-likelihood, like the per-token average:

\tilde{r}_{\textrm{seq}} \;=\; \exp\left(\frac{1}{T}\sum_{t=1}^T \log r_{t}\right).

Then, apply the PPO clip once, at the sequence level, where it can do some actual good:

\mathit{L}_{\textrm{seq}} \;=\; - \mathbf{E}\Big[\min\big(\tilde{r}_{\textrm{seq}} A(y),\ \mathrm{clip}(\tilde{r}_{\textrm{seq}},1-\varepsilon,1+\varepsilon) A(y)\big)\Big].

The Sanity Check

  • Token routing volatility is averaged out. One stable ratio per sequence.
  • The clipping mechanism is now bounded at a meaningful level, yielding smoother, safer updates grounded in the holistic quality of the output.
  • You no longer need to track and replicate token-wise expert paths. The hack is gone.

What changes in code?

The absurdity is that fixing this fundamental flaw requires almost no effort. If you’re already running GRPO with TRL and Unsloth, the switch to GSPO is a single configuration change. Here is a compact skeleton demonstrating just how simple it is.

Assumptions

  • TRL ≥ 0.20
  • Unsloth ≥ 2025.x (with FastLanguageModel)
  • Mixed precision + 4-bit or 8-bit weights on a single high-VRAM GPU
  • A reward function that returns a scalar reward per generated sequence
# GSPO training with Unsloth + TRL (sequence-level importance sampling)
# A practical, self-contained example.
# pip install -U unsloth trl accelerate datasets bitsandbytes transformers

from dataclasses import dataclass
from typing import Dict, Any, List
import torch
from datasets import load_dataset
from transformers import AutoTokenizer

from unsloth import FastLanguageModel
from trl import GRPOTrainer, GRPOConfig  # GSPO via importance_sampling_level="sequence"

# ---------------------------
# 1) Model & tokenizer (Unsloth)
# ---------------------------
base_model = "Qwen/Qwen2.5-7B-Instruct"  # MoE models benefit most from GSPO
load_8bit = True

model, tokenizer = FastLanguageModel.from_pretrained(
    base_model,
    load_in_4bit=not load_8bit,
    load_in_8bit=load_8bit,
    dtype=torch.bfloat16,
    use_flash_attention_2=True,
)

tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "left"  # For efficient generation on batched prompts

# ---------------------------
# 2) Data: n responses per prompt (GRPO/GSPO group sampling)
# ---------------------------
# Your dataset needs a "prompt" field. k responses will be sampled per prompt.
ds = load_dataset("json", data_files={"train": "train.json"})["train"]

# ---------------------------
# 3) Reward function (sequence-level)
# ---------------------------
def reward_fn(prompts: List[str], responses: List[str]) -> List[float]:
    # Replace this with your task metric, RM, or other heuristics.
    scores = []
    for p, r in zip(prompts, responses):
        good_finish = float(r.strip().endswith((".", "!", "?")))
        brevity_penalty = -0.001 * max(0, len(r) - 2048)
        scores.append(0.5 * good_finish + brevity_penalty)
    return scores

# ---------------------------
# 4) RL config - THE CRUCIAL GSPO SWITCH
# ---------------------------
config = GRPOConfig(
    learning_rate=1e-5,
    warmup_ratio=0.05,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=16,
    total_episodes=50_000,           # steps ~ episodes/k when group_size = k
    kl_coeff=0.02,                   # KL to ref (now sequence-level)
    clip_range=0.2,                  # PPO-style clipping on the sequence ratio
    generate_kwargs={"max_new_tokens": 256, "temperature": 0.7, "top_p": 0.95},
    group_size=4,                    # k responses per prompt
    importance_sampling_level="sequence",  # <-- THIS IS GSPO
    log_with="tensorboard",
)

# ---------------------------
# 5) Trainer
# ---------------------------
trainer = GRPOTrainer(
    model=model,
    ref_model=None,                  # Defaults to a frozen copy of the model
    tokenizer=tokenizer,
    args=config,
    train_dataset=ds,
    reward_funcs=[reward_fn],        # List of one or more reward heads
    prompt_column="prompt",
)

trainer.train()
trainer.save_model("gspo-checkpoint")
tokenizer.save_pretrained("gspo-checkpoint")

What changed from a standard GRPO script? A single line: importance_sampling_level="sequence". That’s it. Everything else – group sampling, reward functions, KL divergence from a reference policy – remains. Under the hood, TRL now computes normalized sequence likelihoods and applies PPO clipping to the sequence ratio, not the swarm of noisy token ratios.


Practical guidance (from the trenches)

  • Start with MoE. If you want to feel the difference, this is where it is. Dense models will show marginal, inconsistent gains at best. MoE models will feel like they’ve been let out of a cage.
  • Keep your prompts fresh. In long runs, periodically shuffle your query set. GSPO is more predictable, but training on stale prompts is still a recipe for flattened gains.
  • Longer sequences ≠ free lunch. Sequence-level objectives are less noisy, but as your max tokens increase, your KL and clipping parameters still need attention.
  • Stability First: In the early phases of training, err on the side of caution. A slightly higher KL coefficient (0.02–0.05) and a tighter clip_range (0.1–0.2) can prevent early divergence. You can relax KL once rewards are climbing steadily.
  • Forget Routing Replay: You don’t need it. This vastly simplifies distributed setups where experts might live on different devices.

GRPO vs GSPO: quick reference

AspectGRPO (token-level)GSPO (sequence-level)
Importance RatioPer token r_{t}Per sequence \tilde{r}_{\textrm{seq}} (length-normalized)
ClippingToken-wise, high varianceSequence-wise, stable
Variance on MoEHigh; pathological routing sensitivityLower; robust to routing by design
Routing ReplayA necessary, brittle hackObviated by the model
Sample EfficiencyGood on dense; fragile on MoEStrong on MoE; neutral on dense
Implementation Delta in TRLDefaultimportance_sampling_level="sequence"

Evaluating GSPO runs: the dashboard that matters

  • Reward Traces & KL Divergence: Your bread and butter. Track mean/p90 rewards and KL to the reference policy over training steps.
  • Sequence Clip Fraction: The canary in the coal mine. If it’s pegged high, you’re pushing too hard – throttle back your LR or tighten the clip range. If it’s zero, you’re not learning.
  • Response Length Distribution: Watch for drift. A rising reward might just mean the model is learning to generate longer or shorter text, not better text.
  • Expert Utilization: If you can inspect the gate, watch for routing collapse. When one expert becomes the star of the show and others go silent, your MoE is degenerating into a dense network with extra steps.
  • Win-Rate on a Fixed Eval Set: The ultimate sanity check. Does it actually perform better on your downstream task or against a baseline in pairwise preference tests?

Common pitfalls (and fixes)

  • The Reward Spike Trap: Rewards are too peaky. → Sequence-level training lowers variance, but extreme rewards will still detonate your updates. Try reward smoothing (e.g., a moving average baseline) or clipping.
  • KL Collapse: The model flies too close to the sun and forgets everything. → Increase kl_coeff, reduce the learning rate, or temporarily shorten max_new_tokens.
  • The Plateau of Indifference: Rewards are flat. → Ensure group_size is at least 2 and that your sampling parameters (temperature/top_p) encourage enough diversity for the model to learn a preference.
  • Dense Models Aren’t Improving: This is largely expected. GSPO is designed to fix MoE’s unique pathologies. For dense models, stick with GRPO or consider a hybrid curriculum.

Further reading and tooling


Closing thoughts

GSPO doesn’t reinvent reinforcement learning for language models; it re-scopes the objective to the unit of value that actually matters – the complete response. For MoE architectures, that change in scope is enough to tame the variance monster, discard a whole category of routing-specific hacks, and transform a “barely stable” process into something I can run with confidence on my own hardware.

If you have a GRPO script running, make the one-line switch. For MoE, you’ll find it’s the difference between navigating a minefield and walking on a paved road.

Posted in AI / ML, LLM Advanced