Multi-Teacher On-Policy Distillation (MOPD)#

Multi-Teacher On-Policy Distillation (MOPD) distills one or more teacher models into the policy by replacing GRPO’s reward-based advantage with a token-level distillation advantage (MiMo-V2-Flash Technical Report). MOPD runs on async GRPO and collects rollouts through NeMo Gym, so the agent loop drives multi-turn / multi-step interaction. Each token of the resulting student rollout is scored by a teacher, and the policy is updated to close the gap with the teacher.

Unlike the teacher-logit knowledge distillation in On-policy Distillation (run_distillation.py), MOPD runs on top of the GRPO trainer: it is selected with adv_estimator: opd and serves teachers from dedicated, non-colocated worker groups during async collection.

Advantage#

For each token t, the distillation advantage is the stop-gradient teacher-minus-student log-probability gap:

Â_t = sg[ log π_teacher(t) − log π_student(t) ]

log π_student is the policy’s prev_logprobs and log π_teacher is computed by the teacher worker group at collection time. Maximizing this advantage is reverse-KL minimization — it pushes the student toward the teacher’s token distribution — but, unlike forward-KL logit distillation, it needs only the teacher’s log-probability for the sampled token rather than the full vocabulary distribution.

The advantage is applied only to trained (assistant) tokens via the loss mask; tool / environment tokens contribute zero. Because the advantage subtracts a real prev_logprobs, MOPD requires the student log-probabilities to actually be computed — see Configuration.

Configuration#

Enable MOPD in two places: select the advantage estimator and add the on_policy_distillation block.

grpo:
  # MOPD runs on async GRPO with NeMo Gym rollouts.
  async_grpo:
    enabled: true
  adv_estimator:
    name: opd
  # OPD subtracts a real prev_logprobs, so it must not be skipped.
  seq_logprob_error_threshold: 2.0

loss_fn:
  # REINFORCE form (drop the PPO probability-ratio clipping); on-policy
  # correction is handled by the ICE-POP gate below instead.
  disable_ppo_ratio: true
  # ICE-POP hard gate: zero tokens whose train/inference importance-sampling
  # weight falls outside bounds, correcting async off-policy drift.
  use_importance_sampling_correction: true
  truncated_importance_sampling_type: icepop
  # Teacher distillation is the entire learning signal — no reference-policy KL.
  reference_policy_kl_penalty: 0.0

on_policy_distillation:
  enabled: true
  # Map each NeMo Gym agent name to a teacher checkpoint.
  teacher_model_by_agent_name:
    default_teacher: Qwen/Qwen3-1.7B
  # Agents not present in the map fall back to this alias (must be a mapped key).
  default_teacher_alias: default_teacher
  # If true, an unmapped agent raises instead of falling back.
  strict_agent_name_match: false
  # Aliases that share one checkpoint reuse a single teacher worker group.
  deduplicate_shared_teacher_checkpoints: true
  non_colocated_teachers:
    enabled: true
    # Resourcing for each teacher worker group.
    default_teacher_cfg:
      tensor_model_parallel_size: 2
      pipeline_model_parallel_size: 1
      context_parallel_size: 1
      num_nodes: 1
      gpus_per_node: 8
      precision: bf16
      micro_batch_size: 1
    # Optional per-alias overrides on top of default_teacher_cfg.
    teacher_overrides: {}

Note

Teachers run the Megatron backend in inference-only mode. A DTensor-configured policy is rejected for the teacher; PEFT / draft modules are stripped so adapters are never attached to the frozen teacher; and teachers run unquantized (a policy quant_cfg is ignored, with a warning).

Note

adv_estimator: opd fails fast at setup if the config would zero prev_logprobs (loss_fn.force_on_policy_ratio: true with no grpo.seq_logprob_error_threshold), because the advantage would silently degrade to teacher_logprobs 0.

Teacher routing#

Each rollout sample carries its NeMo Gym agent_ref. At collection time the agent name is resolved to a teacher alias (teacher_model_by_agent_name, falling back to default_teacher_alias), samples are grouped by teacher, and each group is scored by exactly one teacher — there is no ensemble averaging across teachers. When several aliases map to the same checkpoint, deduplicate_shared_teacher_checkpoints collapses them onto a single worker group so they share GPUs.

Resourcing#

Non-colocated teachers each get their own Ray cluster on dedicated GPUs (they are queried every rollout group, so time-sharing with the policy/generation would serialize and destroy the async overlap). Their nodes are reserved from the policy’s budget: with total_nodes total, the teacher groups take sum(num_nodes) and the policy uses the remainder (setup fails if nothing is left for the policy). Deduplicated teachers share one group’s nodes.

For example, the reference 3-node recipe lays out: 1 node policy (student, trainable) + 1 node vLLM generation (frozen) + 1 node teacher (frozen). Ten distinct teachers at 1 node each would instead add 10 nodes on top of the policy and generation nodes.

Running MOPD#

MOPD collects rollouts through NeMo Gym, so use the NeMo Gym GRPO entrypoint with an MOPD recipe. The checked-in recipe uses placeholder dataset paths; override them for your local data:

uv run examples/nemo_gym/run_grpo_nemo_gym.py \
  --config examples/configs/recipes/llm/mopd-qwen3-1.7b-3n8g-megatron-pack.yaml \
  data.train.data_path=/path/to/train.jsonl \
  data.validation.data_path=/path/to/val.jsonl

The reference recipe self-distills Qwen/Qwen3-1.7B (student == teacher) across 3 nodes (1 policy + 1 vLLM + 1 teacher) with sequence packing enabled. Because student and teacher are identical, the OPD loss stays near zero — it is a correctness smoke test, not a demonstration of distillation gains.

References#

  • LLM-Core Xiaomi, MiMo-V2-Flash Technical Report, which introduces the multi-teacher on-policy distillation paradigm: arxiv.org/abs/2601.02780