镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(speculative): keep padding out of DSpark and Domino loss masks - #2761

Open
h-guo18 wants to merge 1 commit into
mainfrom
haoguo/dflash-variant-loss-mask
Open

h-guo18 wants to merge 1 commit into
mainfrom
haoguo/dflash-variant-loss-mask

Conversation

@h-guo18

@h-guo18 h-guo18 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: Bug fix

DSpark and Domino online training could train on padding.

HFDFlashModel.forward builds its loss mask as (labels != -100) * attention_mask * loss_mask. The attention_mask term was added in #1975 because labels do not, in general, mark padding with -100. The online collators (LanguageDataCollator, VisionLanguageDataCollator) copy labels from the padded input_ids and write -100 only where answer_only_loss masks non-assistant tokens.

HFDSparkModel.forward and HFDominoModel.forward kept an older copy of the rule that consults attention_mask only when labels is absent. With labels present they ignored it, so on a padded batch they sampled anchors in the padded tail and put pad tokens into the loss.

The rule lived in three copies and #1975 updated only one. This PR moves DFlash's rule into a single helper, HFDFlashModel._build_loss_mask, and calls it from the DFlash, DSpark and Domino training forwards. The next change to which positions are supervised then reaches every variant. DFlash's mask is bit-identical to before (same ops in the same order). DFlash2 and LiLiCorr inherit the DFlash forward and need no change.

Affected: DSpark and Domino trained with data.mode=online and training.answer_only_loss=false on batches that contain padding, i.e. any sample shorter than training.training_seq_len.

Not affected:

  • The shipped DSpark and Domino recipes and launcher examples. They all set answer_only_loss: true, which gives padding -100 labels. This is why the bug went unnoticed.
  • Streaming and offline training. Their batches carry a loss_mask that is zero on padding.
  • DFlash, DFlash2 and LiLiCorr.

Usage

N/A. Bug fix with no API change.

Testing

  • New test_hf_dflash.py::test_padding_is_never_supervised[dflash|dspark|domino]. It runs each variant's real training forward on a batch whose only non -100 labels sit on padded positions, and expects zero loss.
    • Without the fix, the dspark and domino cases fail with loss 0.456 and 3.486.
    • With the fix, all three pass in under 0.4 s total on 4 CPUs (taskset -c 0-3).
  • tests/unit/torch/speculative on CPU with torch 2.10: 315 passed on transformers 5.5.4, 5.12.1 and 5.18.0. 5.5.4 and 5.18.0 fall within CI's tf_min (~=5.5.0) and tf_latest (~=5.18.0) pins. Main before this change: 312 passed on each.
  • Scratch checks, not checked in:
    • The helper reproduces DFlash's old inline mask bit for bit over 147 combinations of labels, attention_mask and loss_mask (absent, int, bool, float, bf16, fp64, fractional).
    • End to end in fp32 with a fixed seed, DFlash's loss and draft gradients are bit-identical to main on unpadded, padded and loss_mask batches. DSpark and Domino are bit-identical too, except on a padded batch whose padding keeps its labels, which is the fixed case.
  • pre-commit run --files <changed files>: clean.

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.).

  • Is this change backward compatible?: ✅ Configs and checkpoints are unchanged. Training changes only in the affected case above, where it stops supervising padding.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: N/A. No shipped recipe or example is affected.
  • Did you get Claude approval on this PR?: ❌ Not run yet.

Additional Information

Follow-up to #1975, which fixed the rule in the DFlash forward only. DSpark and Domino still duplicate the rest of the DFlash training-forward prologue (base forward, anchor sampling, draft inputs). This PR consolidates only the loss-mask rule.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Training loss now consistently excludes positions masked by padding or an optional loss mask across DFlash, DSpark, and Domino models.
    • Fixed a case where labels on padded positions could contribute to the training loss.

HFDFlashModel.forward builds the training loss mask as
(labels != -100) * attention_mask * loss_mask. The attention_mask term
was added in #1975 because labels do not, in general, mark padding with
-100: the online collators copy labels from the padded input_ids and
write -100 only where answer_only_loss masks non-assistant tokens.

HFDSparkModel.forward and HFDominoModel.forward kept an older copy of
the rule that consults attention_mask only when labels are absent. With
labels present they ignored it, so online training with
answer_only_loss=false on a padded batch sampled anchors in the padded
tail and trained on pad tokens. The shipped recipes set
answer_only_loss=true, which gives padding -100 labels, and streaming
and offline batches carry a loss_mask that is zero on padding, so no
shipped configuration hit this.

The rule lived in three copies and #1975 updated only one. Move it into
one helper, HFDFlashModel._build_loss_mask, and call it from the
DFlash, DSpark and Domino training forwards, so the next change to which
positions are supervised reaches every variant. DFlash's mask is
bit-identical to before (same ops in the same order). DFlash2 and
LiLiCorr inherit the DFlash forward and need no change.

Add a regression test that runs each variant's real training forward on
a batch whose only labels sit on padded positions and expects nothing to
be supervised. Without this fix it fails for DSpark and Domino.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
@h-guo18
h-guo18 requested review from a team as code owners October 12, 2026 11:47
@h-guo18
h-guo18 requested a review from yeyu-nvidia October 12, 2026 11:47
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA/Model-Optimizer/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: bf2b9206-af4d-4f64-9ec1-580ca8b5015b

📥 Commits

Reviewing files that changed from the base of the PR and between f27b476 and 2d38b37.


📒 Files selected for processing (4)
  • modelopt/torch/speculative/plugins/hf_dflash.py
  • modelopt/torch/speculative/plugins/hf_domino.py
  • modelopt/torch/speculative/plugins/hf_dspark.py
  • tests/unit/torch/speculative/plugins/test_hf_dflash.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.



📝 Walkthrough

Walkthrough

DFlash adds shared loss-mask construction, and DFlash, Domino, and DSpark use it during training. A parameterized test checks that padding-only supervision produces zero loss.

Changes

Speculative loss masking

Layer / File(s) Summary
Build the shared loss mask
modelopt/torch/speculative/plugins/hf_dflash.py
_build_loss_mask combines valid-label positions, attention_mask, and an optional loss_mask.
Use and test shared masking
modelopt/torch/speculative/plugins/hf_dflash.py, modelopt/torch/speculative/plugins/hf_domino.py, modelopt/torch/speculative/plugins/hf_dspark.py, tests/unit/torch/speculative/plugins/test_hf_dflash.py
The three training paths call _build_loss_mask. A parameterized test checks that attention-masked padding positions with labels produce exactly zero loss.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: yeyu-nvidia


Merge Risk: ⚪ Minimal · up to 2d38b

This change stops DSpark and Domino from training on padded tokens, and DFlash masking is reported unchanged. No actionable merge risk remains from the supplied evidence.

Pre-merge checks | Passed 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: preventing padding from contributing to DSpark and Domino loss masks.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 4 files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns Passed PASS. The pull request changes only the DFlash, DSpark, and Domino mask logic plus a unit test. The added code contains no torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), hard…

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-2761/

Built to branch gh-pages at 2026-10-12 11:53 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.27%. Comparing base (f27b476) to head (2d38b37).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2761      +/-   ##
==========================================
+ Coverage   69.47%   78.27%   +8.79%     
==========================================
  Files         646      646              
  Lines       71672    71662      -10     
==========================================
+ Hits        49796    56090    +6294     
+ Misses      21876    15572    -6304     
Flag Coverage Δ
examples-diffusers 20.29% <15.38%> (+<0.01%) ⬆️
examples-gpt-oss 13.54% <15.38%> (+<0.01%) ⬆️
examples-hf_ptq 23.39% <15.38%> (-0.03%) ⬇️
examples-llm_distill 13.62% <15.38%> (-0.01%) ⬇️
examples-llm_eval 17.33% <15.38%> (+<0.01%) ⬆️
examples-llm_sparsity 15.87% <15.38%> (+<0.01%) ⬆️
examples-megatron_bridge 26.71% <15.38%> (-0.14%) ⬇️
examples-specdec_bench 13.32% <15.38%> (+<0.01%) ⬆️
examples-speculative_decoding 17.62% <15.38%> (-0.06%) ⬇️
examples-torch_onnx 21.67% <15.38%> (+<0.01%) ⬆️
examples-torch_trt 15.31% <15.38%> (+<0.01%) ⬆️
examples-vllm_serve 13.99% <15.38%> (+<0.01%) ⬆️
gpu 59.16% <76.92%> (+37.44%) ⬆️
regression 15.17% <84.61%> (+<0.01%) ⬆️
unit 59.90% <92.30%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant