What on-policy distillation is
Standard distillation is off-policy: the student trains on text
generated by the teacher, then gets evaluated on text it generates.
The distribution seen during training therefore differs from the
distribution encountered at inference.
On-policy distillation closes that gap by having the student generate
its own trajectory and having the teacher evaluate that same trajectory:
┌────────────────────┐
│ Current Student │
│ Qwen2.5-0.5B │
└─────────┬──────────┘
│
sample
│
▼
┌────────────────────┐
│ Student-generated │
│ trajectory │
└─────────┬──────────┘
│
same tokens
│
▼
┌────────────────────┐
│ Teacher │
│ Qwen2.5-1.5B │
└─────────┬──────────┘
│
top-K logits
│
▼
┌────────────────────┐
│ Truncated │
│ reverse KL │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ Update Student │
└────────────────────┘
The training loop is:
- Sample a completion from the current student policy (no
gradient).
- Run one teacher forward pass over the student's sampled tokens (no
gradient).
- Compute token-level reverse KL between student and teacher
distributions.
- Backpropagate the loss through the student.
With γ=0, supervision is local to the current token rather than
propagating a future reward signal backward. There is no REINFORCE term
or explicit outcome reward. The teacher supplies a dense token-level
training signal while only performing forward passes.
This makes the setup substantially simpler than outcome-reward RL,
although it does not eliminate the optimization and distribution-shift
problems that can arise during on-policy training.
Results
The reported checkpoint was selected at step 20, before the training collapse
described below.
Evaluation used a single seed and 100 problems for greedy evaluation. The
any-of-4 experiment used 50 problems with four sampled completions per problem.
Note: These are empirical results from this single experimental run, not
benchmark claims.
Evaluation results
Table with columns: Metric, Base Student, OPD Student, Teacher| Metric | Base Student | OPD Student | Teacher |
|---|
| GSM8K pass@1 | 9.0% | 11.0% | 17.0% |
| SVAMP pass@1 (out-of-domain) | 40.0% | 35.0% | 57.0% |
| GSM8K any-of-4 success | 18.0% | 4.0% | — |
| Format compliance | |
Interpreting the results
The pass@1 differences are not interpretable at this evaluation
size.
With only 100 problems, the uncertainty around a 0.11 proportion is
large. The +2 point GSM8K change and −5 point SVAMP change are therefore
not evidence of a reliable improvement or degradation.
The any-of-4 result is the most interesting diagnostic signal.
The fraction of problems for which at least one of four sampled
completions was correct fell from 18% to 4%.
This is a much larger change than the pass@1 difference and moves in the
opposite direction. It is consistent with the hypothesis that the
current reverse-KL objective narrowed the student's useful sampling
distribution.
However, this is still a small, single-seed experiment. It should be
treated as a strong diagnostic signal rather than a statistically
established claim that OPD universally causes diversity collapse.
A larger evaluation, additional seeds, and explicit diversity metrics
such as unique completions per problem would be required to establish
that conclusion.
The key lesson from this experiment is therefore not that the distilled
model is better. It is that pass@1 alone can hide substantial changes
in sampled behavior.
Training instability
The training run entered a degenerate state:
- KL decreased toward zero
- completion length approached the generation cap
- accuracy fell toward zero
- outputs eventually degraded into repeated tokens or symbol-like
sequences
Two learning-rate experiments showed the same qualitative behavior:
Learning rate Approximate collapse onset
1e-5 ~step 25
1e-6 ~step 40
Lowering the learning rate delayed the degradation but did not prevent
it.
This suggests that, within this small-model configuration, the problem
was not simply one excessively large update. The reported checkpoint was
therefore selected at step 20, before the observed drift.
Grad norms near collapse were also relatively calm (around 11.4 in the
observed run and well below the clipping threshold), providing no
evidence of a single exploding-gradient event. The degradation appeared
gradual over multiple steps.
Failure modes hit and fixed
These are documented because they consumed a substantial part of the
project and several of them did not immediately look like training
failures.
FP16 overflow on Turing
The Colab T4 does not support BF16. Running the student in FP16 produced
non-finite logits, which eventually caused torch.multinomial to fail
with a device-side CUDA assertion.
Greedy decoding initially hid the problem because argmax can still
return an index even when the underlying logits are corrupted.
Fix: run the student in FP32 because it is the model being sampled
from and backpropagated through, while keeping the frozen teacher in
FP16 for inference.
Destroyed tied embeddings
Qwen2.5-0.5B ties its input embedding and output projection weights.
Gradients through the LM head therefore also update the token embedding
matrix.
An early experiment corrupted this tied parameter block and produced
severely degraded generations, including vocabulary-like output.
Fix: freeze the tied embedding. This also removes a large parameter
block from the optimizer while leaving the transformer body trainable.
Reentrant gradient checkpointing
The default reentrant checkpointing implementation caused problems with
the training setup.
Fix: use:
and enable input gradients where required.
Incorrect completion-length measurement
Using len(row) on batched generation output does not necessarily
represent the actual completion length of an individual example because
generated tensors can include padding.
Fix: count non-padding completion tokens when measuring generation
length.
Miscalibrated gradient guard
An initial gradient-norm skip threshold of 10.0 rejected essentially
every training step. The observed normal range for this setup was closer
to 20--32.
Fix: log the gradient distribution before selecting a safety
threshold.
Implementation notes
Truncated reverse KL
Full-vocabulary KL over Qwen's approximately 151K-token vocabulary is
memory-intensive on a 16GB T4.
The implementation therefore restricts the objective to the teacher's
top-64 tokens and renormalizes both student and teacher distributions
over that support.
This is a teacher-top-K renormalized reverse-KL approximation, not
the exact full-vocabulary reverse KL.
That distinction matters because truncating the support changes the
objective and can introduce optimization behavior that differs from
full-vocabulary KL.
Logit alignment
Causal LM logits at position i predict the token at position i+1.
The student and teacher logits therefore need to be shifted relative to
the generated-token mask.
This is an easy bug to introduce because a misaligned implementation can
still produce a smoothly decreasing loss while training the wrong
positions.
A self-KL sanity check is run before training. Feeding identical logits
into the objective should produce approximately zero KL.
Sampling temperature
Student rollouts are sampled at T=1.0.
Lowering the sampling temperature reduces exploration of the student's
current policy and can substantially alter the on-policy distribution.
Temperature is therefore treated as part of the training configuration
rather than merely an inference parameter.
Memory
The target hardware is a 16GB NVIDIA T4.
The setup uses:
- Student: FP32
- Teacher: FP16
- 8-bit Adam
- Gradient checkpointing
- Batch size: 2
- Frozen tied embedding
8-bit optimizer states substantially reduce optimizer memory, while
gradient checkpointing reduces activation memory.
The resulting configuration fits within the constraints of a free-tier
T4 without requiring multiple GPUs.
Demo: Load and Run the Model
You can load the model directly with 🤗 Transformers:
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL_ID = "YOUR_USERNAME/qwen2.5-0.5b-opd-gsm8k"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.float32,
device_map="auto"
)
print("Successfully loaded:", MODEL_ID)
question = """A store has 240 apples. It sells 35% of them in the morning.
In the afternoon, it sells 48 more apples. How many apples are left?"""
messages = [
{
"role": "system",
"content": "Solve the math problem. Reason step by step, then give the final answer as: #### <number>"
},
{
"role": "user",
"content": question
}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=256,
do_sample=False,
pad_token_id=tokenizer.eos_token_id
)
answer = tokenizer.decode(
outputs[0][inputs["input_ids"].shape[1]:],
skip_special_tokens=True
)
print(answer)
Expected usage
The model is intended for small-scale mathematical reasoning experiments.
The example above uses greedy decoding (do_sample=False) and the same system
prompt used during evaluation.
Replace YOUR_USERNAME with your Hugging Face username after uploading the
model.
Important: This checkpoint is an experimental OPD model. The reported
evaluation results are small-scale and should not be interpreted as evidence
that this model consistently outperforms the base Qwen2.5-0.5B-Instruct model.
Evaluation methodology
The evaluation intentionally measures more than a single accuracy
number.
1. In-domain pass@1
GSM8K is used as the primary in-domain mathematical reasoning benchmark.
2. Out-of-domain pass@1
SVAMP is used as a separate arithmetic reasoning dataset with different
questions and phrasing.
The purpose is to check whether behavior transfers beyond the training
distribution.
3. Any-of-4 success
Four sampled completions are generated for each problem.
A problem counts as successful if at least one of the four completions
produces the correct numerical answer.
This is reported as any-of-4 success rather than as a formal
diversity metric. A future version should additionally report unique
completion and unique-answer rates to directly measure output diversity.
The requested output format is:
Format compliance is tracked separately from mathematical correctness.
5. Completion length
Mean completion length is tracked because generation-length inflation or
generation truncation can mask changes in model behavior.
A future evaluation should additionally report the fraction of
generations that hit the maximum token limit.
Running it
pip install transformers datasets accelerate matplotlib bitsandbytes
python on_policy_distillation.py
python evals.py
The training run takes approximately 40 minutes on a T4, depending on
the environment.
Restart the Colab runtime between training and evaluation. The scripts
load their own models and the available 16GB VRAM is insufficient to
keep the full training setup and evaluation models resident
simultaneously.
Limitations
This is a small-scale implementation and not a research result
demonstrating that OPD improves mathematical reasoning.
- Single seed; no repeated-run error bars.
- Only 20 training steps are reported for the selected checkpoint.
- Batch size is 2, corresponding to approximately 40 sampled training
examples.
- No compute-matched comparison against SFT, GRPO, or other
distillation baselines.
- The teacher itself scores only 0.17 pass@1 on the evaluated GSM8K
subset, limiting the available capability ceiling in this
experiment.
- The evaluation uses a small subset rather than the full benchmark.
- Format compliance is close to zero across the models, so the
reported correctness numbers primarily measure whether the correct
number appeared somewhere in the generated text rather than strict
adherence to the
#### <number> protocol.
- The any-of-4 experiment is based on only 50 problems and four
samples per problem.
- No explicit unique-completion metric was included in this first
version.
- The current implementation uses a teacher-top-K approximation rather
than full-vocabulary reverse KL.
- Same-tokenizer student/teacher setup only. Cross-tokenizer OPD
introduces additional alignment problems.
The experiment should therefore be interpreted as an investigation of
one OPD configuration on a small model and constrained hardware, not as
evidence about the general effectiveness of OPD.
What this experiment taught me
The main result was not a benchmark improvement.
It was learning how easy it is for an on-policy training loop to look
healthy while the policy is moving in an undesirable direction.
A decreasing KL loss does not by itself imply improved reasoning.
A stable gradient norm does not imply stable behavior.
A small increase in pass@1 does not necessarily imply increased
capability.
And a model that can still solve individual problems can simultaneously
lose useful diversity across sampled trajectories.
Those were the failure modes this implementation was built to expose.
References