fix(dspark): re-apply verifier final RMSNorm in L1 distillation target - #76
Open
minziyu wants to merge 1 commit into
Open
fix(dspark): re-apply verifier final RMSNorm in L1 distillation target#76minziyu wants to merge 1 commit into
minziyu wants to merge 1 commit into
Conversation
ExampleHiddenStatesConnector exports pre-norm final hidden states, but the verifier's real sampling distribution is softmax(lm_head(norm(h))). The old F.linear(target_hidden, lm_head) therefore distilled towards a wrong distribution: self-consistent loss still converges but online acceptance is capped (A/B: 0.912 vs 0.619 on Qwen3-30B-A3B overfit, k=7). Add a frozen TargetFinalNorm loaded from the target checkpoint (key candidates model.norm.weight / model.language_model.norm.weight / norm.weight, gemma-style +1 folding, eps from config.json) and apply it to the L1 target whenever dspark_l1_loss_alpha > 0. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
DSpark drafter training distills the drafter towards the verifier's token distribution with an L1 loss. The target probabilities were computed as:
But the collected
target_hiddenstates come from vLLM'sExampleHiddenStatesConnector, which exports the verifier's final hidden state before its final RMSNorm. The verifier's real sampling distribution issoftmax(lm_head(rmsnorm(h, w))), so the L1 target was a systematically biased distribution (TV ≈ 0.145 measured on Qwen3-30B-A3B).The failure mode is insidious: the loss is self-consistent, so training still converges and outputs stay correct — only the online acceptance rate is capped well below what the same budget achieves with a correct target.
Fix
TargetFinalNorm(verl_speco/models/target/target_head.py): the verifier's frozen final RMSNorm loaded from the target checkpoint.model.norm.weight/model.language_model.norm.weight/norm.weightepsfromconfig.json(rms_norm_eps, incl. nestedtext_config)x*(1+w)parameterization folded tow+1at load time forgemma*/qwen3_5*model typesrequires_grad=FalseDSparkTrainerBackend._build_target_final_normbuilds it wheneverdspark_l1_loss_alpha > 0; the training step passesweight/epsthrough to the L1 chunk, which now re-applies the norm before the lm_head:h * rsqrt(mean(h²)+eps) * w → F.linear(·, lm_head).No config flag — the norm is unconditional whenever the L1 loss is on. The legacy no-norm path is only reachable with
dspark_l1_loss_alpha=0(L1 disabled entirely).Results (A/B validation)
Single-variable A/B on Qwen3-30B-A3B (single-sample overfit, 125 steps × batch 8 = 1000 learnings, greedy with pinned server- and request-side seeds, drafter deployed with vLLM speculative decoding, k=7):
Tests
tests/integration/test_target_final_norm.py—from_pretrained: key candidates, nestedtext_configeps, folding (qwen3 / qwen3_5 / gemma3 parametrized), frozen weight, missing-weightKeyError.tests/integration/test_dspark_trainer_backend.py— L1 target numerics match a hand-computed reference with the norm applied; the norm provably changes the target (anti-vacuous assertion); kwargs plumbing is guarded so the norm cannot be silently dropped betweenforward()and the L1 computation.test_dspark_checkpoint_preserves_source_config...fails identically on pristinemainbranch (pre-existing, non-blocking for this change).