Repository navigation
Conversation
…step HFDFlashModel.forward built the batch, ran the draft and computed the loss inline, and the variants built on it got at the loss's pieces piecemeal: DFlash2 asked _compute_loss for its unreduced terms (return_terms, _DFlashLossTerms) to recompose the block objective, and DFlash2 and LiLiCorr overrode forward() only to carry out the metrics their _compute_loss had left on the model (_selector_metrics, _lilicorr_metrics). The forward now hands the loss one object per step: - DraftBatch: the batch, its blocks and the draft's hidden states. - LossContext: the labels (_block_targets), the draft's logits (DraftLogits: the distribution the draft serves, and the backbone's before any head) and the position weights, with the per-token cross-entropy and the teacher's logits computed lazily, once. - _compute_loss(ctx) returns a LossOutput: the loss, the accuracy train_acc reports and detached 0-dim metrics, which the forward returns as draft_metrics. What a variant may change becomes a hook: _online_base_outputs (the base model's outputs), _draft_logits (a head on the backbone's logits), _position_weighting and _compute_loss. The loss mask comes from _build_loss_mask. The DSpark/Domino padding fix, a separate PR against main, adds the same helper, with the same text, at the same place (right after get_exporter), so a merge of the two keeps one copy of it. DFlash2 and LiLiCorr take the context: DFlash2's tv/lambda blend reads the cross-entropy and weights from it, and both return their metrics instead of overriding forward(). DSpark and Domino keep their own forwards for now. Verified against the previous commit with one train step and one generation step per case, in fp32 and bf16: DFlash (KD and CE), DFlash2 (ce/tv/lambda with the selector) and LiLiCorr, offline and online, D-PACE and decay, including zero-loss steps, give bitwise-equal loss, gradients, train_acc and generated tokens (32/32). The selector and LiLiCorr metrics keep their names and values under draft_metrics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
…sses.py The block-supervision helpers (_block_positions, _block_gather, _block_tokens, _BlockTargets, _block_targets), the position weighting (_dpace_position_weights, _block_position_weights), _masked_top1, the loss context (DraftLogits, LossOutput, LossContext) and DSpark's per-token TVD (_tvd_chunk, _tvd_per_token) move unchanged from hf_dflash.py and hf_dspark.py into one module that names no variant. The next PR in this stack builds the shared loss terms on them, and hf_dspark.py would otherwise be imported by the generic loss code for its TVD. A pure move: every moved definition is byte-identical to its previous text, and only import lines change elsewhere. The new file carries the SpecForge and DeepSpec notices of the two files the code came from and is excluded from the license-insertion hook like them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
…_losses.py Update the call sites of the internals the previous two commits changed or moved, keeping each test's intent: - _compute_loss tests (D-PACE/decay/KD branches, trailing padding blocks, KD from the base hidden vs. full logits) go through _loss_context and _compute_loss on given draft logits. - "KD without base_outputs raises" becomes "KD without a teacher raises": the context always carries the base outputs, so the remaining failure is a batch with neither the base logits nor its final hidden. - The selector and LiLiCorr metrics are read from draft_metrics, LiLiCorr's as tensors. - _block_targets, _dpace_position_weights and DSpark's TVD helpers are imported from dflash_losses, where the move put them. One new test pins which objectives keep the per-token cross-entropy's gradient. The context computes the CE once and sets its grad mode from ce_grad, overriding the no_grad under which D-PACE reads it first. The test inspects the context that a real DFlash2 training forward under D-PACE hands _compute_loss: ce, tv and lambda keep the CE's gradient, and KD does not. Mutation check. If _loss_context always asks for the gradient, LossContext ignores ce_grad, or the CE always keeps its graph, then each KD step under D-PACE (the default configuration) holds a vocab-wide graph that it never backpropagates. The loss and gradients stay bitwise equal (DFlash, DFlash2 and LiLiCorr KD, fp32 and bf16: 6/6), and the rest of the suite passes (512 passed); only [kd] fails. Dropping the gradient instead already failed three existing tests, and now also fails [ce], [tv] and [lambda]. tests/unit/torch/speculative and test_hf_spec_rope_export.py: 513 passed on transformers 5.5.4, 5.12.1 and 5.18.0. On 4 CPUs, the changed tests take at most 3.4 s each and the new cases under 0.1 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
…ing changes DFlash2's selector_metrics and LiLiCorr's lilicorr_metrics arrive in 0.48.0, which this change follows. The first commit of this series replaces both keys with draft_metrics, keeps their inner keys, and returns tensors where LiLiCorr returned Python floats. Nothing in the repo reads these keys, but user code that reads them breaks. The entry goes under the 0.49.0 heading: release/0.48.0 is branched and ships both keys, so this change first reaches users in 0.49.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
This was referenced Oct 24, 2026
This branch has not been deployed
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.
What does this PR do?
Type of change: Refactor. Training behavior is unchanged. One backward-breaking API change: DFlash2's and LiLiCorr's training metrics move under a single key (see Metrics).
This PR is stacked on #2762, which is stacked on #2757. #2762's branch is its base. It is the first of two PRs: merge it first, then #2990, which builds the shared loss terms on this seam and moves DSpark and Domino onto the shared forward.
One loss context per step.
HFDFlashModel.forwardbuilt the batch, ran the draft and computed the loss inline. The variants built on it reached the loss's pieces piecemeal. DFlash2 asked_compute_lossfor its unreduced terms (return_terms,_DFlashLossTerms) to recompose the block objective. DFlash2 and LiLiCorr overrodeforward()only to carry out the metrics their_compute_losshad left on the model. The forward now hands the loss one object per step:DraftBatch: the batch, its blocks and the draft's hidden states.LossContext: the labels, the draft's logits and the position weights.DraftLogitsholds the distribution the draft serves and the backbone's logits before any head. The per-token cross-entropy and the teacher's logits are computed lazily, each at most once. The cross-entropy keeps its gradient only when the objective trains through it, not under KD, even though D-PACE reads it first underno_grad._compute_loss(ctx)returns aLossOutput: the loss, the accuracytrain_accreports, and detached 0-dim metrics, which the forward returns asdraft_metrics.Hooks. What a variant may change becomes a hook on
HFDFlashModel:_online_base_outputs: what the base model provides online._draft_logits: a head on the backbone's logits._position_weighting: D-PACE or decay._compute_loss: the loss on the context.DFlash2's
tv/lambdablend reads the cross-entropy and the weights from the context. DFlash2 and LiLiCorr return their metrics instead of overridingforward(). DSpark and Domino keep their own forwards in this PR. The forward's loss mask now comes from_build_loss_mask. The separate padding PR against main adds the same helper, with the same text, at the same place (right afterget_exporter), so a merge of the two keeps one copy of it.A byte-identical move. The block-supervision helpers, the position weighting,
_masked_top1, the loss context and DSpark's per-token TVD move fromhf_dflash.pyandhf_dspark.pyinto the newdflash_losses.py. The new module names no variant. Every moved definition is byte-identical to its previous text, and only import lines change elsewhere. The move is its own commit, separate from the reshaping.Metrics (backward breaking). DFlash2's
selector_metricsand LiLiCorr'slilicorr_metricsare replaced by one key,draft_metrics, as detached 0-dim tensors with no host sync..item()where it needs a float. DFlash2's were already tensors.draft_metrics. The CHANGELOG lists this under Backward Breaking Changes.dspark_metricsanddomino_metricsuntil [2/2] refactor(speculative): train the DFlash family through one forward with shared loss terms #2990, which also reports DFlash's objective terms. Until then DFlash's owndraft_metricsis empty.Removed:
return_terms,_DFlashLossTermsand its TODO. Also removed: DFlash2's and LiLiCorr'sforward()overrides, with the_selector_metrics/_lilicorr_metricsside channels.The zero losses stay as they were:
final.sum() * 0.0), so an FSDP2 unit around the draft still runs its backward._draft_zero_loss.Usage
Configs are unchanged. A variant changes its head and its loss through hooks:
Testing
tests/unit/torch/speculativeplustests/unit/torch/export/test_hf_spec_rope_export.py: 513 passed on transformers 5.5.4, 5.12.1 and 5.18.0 (509 on fix(speculative): DSpark and Domino AR validation and label alignment #2762's head; the other 4 are this PR's new cases). 5.5.4 and 5.18.0 fall within CI'stf_minandtf_latestpins. On 4 CPUs, each changed test takes at most 3.4 s and each new case under 0.1 s.tests/gpu/torch/speculative/pluginson one GPU: 20 passed, 1 skipped (it needs 2 GPUs).train_acc, the metrics with DFlash2's and LiLiCorr's keys mapped ontodraft_metrics, and the generated draft tokens.ceandlambda) and LiLiCorr KD, DFlash2 KD online, and six cases whose labels are -100 on some tokens.hf_dflash.pyandhf_dspark.pyreappear byte-identically indflash_losses.py. No other definition changes, and the other files change only in import lines.fully_shardunit, and one rank on the in-loss zero branch. Both ranks finish, under CE and under KD, with the same losses as on fix(speculative): DSpark and Domino AR validation and label alignment #2762's head. DFlash2 and LiLiCorr under the same wrapping stop with the same mixedTensor/DTensorerror on fix(speculative): DSpark and Domino AR validation and label alignment #2762's head and on this one; this PR does not change that._build_loss_mask. On that tree, the padding PR's regression test and the DSpark and Domino test files pass.ce,tvandlambdabut not for KD._loss_contextalways asks for the gradient,LossContextignoresce_grad, orLossContext.cealways keeps its graph, the rest of the suite still passes (512 tests) and only the KD case fails.ce,tvandlambdacases._compute_losstests go through_loss_contextand_compute_loss, and the selector and LiLiCorr metrics are read fromdraft_metrics. "KD without base_outputs raises" becomes "KD without a teacher raises": the context always carries the base outputs, so the remaining failure is a batch with neither the base logits nor its final hidden.Before your PR is "Ready for review"
Make sure you read and follow Contributor guidelines and your commits are signed (
git commit -s -S).Make sure you read and follow the Security Best Practices (e.g. avoiding hardcoded
trust_remote_code=True,torch.load(..., weights_only=False),pickle, etc.).draft_metricsreplaces the forward output'sselector_metricsandlilicorr_metrics, and LiLiCorr's values that were Python floats are now 0-dim tensors (see Metrics and the CHANGELOG entry).CONTRIBUTING.md: ✅dflash_losses.pycarries the SpecForge and DeepSpec notices of the two files its code moved from. Like them, it is excluded from the license-insertion hook._loss_context,_compute_loss,draft_metrics), and the A/B covers the rest.Additional Information
Against #2762's head, the source changes are +489 / −478 lines. Of the added lines, 132 are ten definitions moved byte-identically into
dflash_losses.py, and 37 are its license header.DraftLogits,LossOutputandLossContext(68 lines) are new in the first commit and move with them. Reshaping and moving stay in separate commits.Merge order: this PR, then #2990.
🤖 Generated with Claude Code