Skip to content

[Examples]: MiniMax-M3 DSpark#1965

Merged
h-guo18 merged 2 commits into
mainfrom
haoguo/m3-dspark-recipe
Jul 26, 2026
Merged

[Examples]: MiniMax-M3 DSpark#1965
h-guo18 merged 2 commits into
mainfrom
haoguo/m3-dspark-recipe

Conversation

@h-guo18

@h-guo18 h-guo18 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: new example + bug fixes

Adds a MiniMax-M3 DSpark streaming training recipe under tools/launcher/examples/MiniMaxAI/MiniMax-M3/, plus the four fixes it needs to actually run. Each fix addresses a failure mode that is silent or misleading without it:

  1. Gemma-style final norm for the fake base (modeling_final_norm.py, modeling_fakebase.py): M3 uses a gemma-style final RMSNorm ((1 + weight) scale, fp32 multiply-then-cast). Selecting the norm by model_type alone picks plain rmsnorm — MiniMax's VL remote code coerces its model_type-less text_config to mixtral — which silently drops the +1 and corrupts the distillation target. New _FinalGemmaRMSNorm + selection by the explicit use_gemma_norm config flag (only MiniMax sets it).
  2. Loud failure for un-maskable answer_only_loss (hf_streaming_dataset.py): with a fast tokenizer whose chat template has no {% generation %} tags, apply_chat_template(return_assistant_tokens_mask=True) only warns and returns an all-zero mask — training runs at zero loss on every sample with no other symptom. Now raises at tokenization with an actionable message.
  3. SERVE_BLOCK_SIZE knob (train_eagle_streaming.sh): nemo_run exports env values unquoted, so a multi-token SERVE_EXTRA_ARGS="--trust-remote-code --block-size 128" loses everything after the first token. M3's MSA sparse attention requires KV block 128 (ValueError: No common block size for 16 at engine init otherwise), so --block-size gets a dedicated single-token knob.
  4. Relax the speculative_decoding transformers pin to <5.13 (match pyproject.toml): the old <5.4 pin downgrades recent vLLM containers (e.g. transformers 5.12.1, which also provides in-tree minimax_m3_vl) and breaks vllm serve (ALLOWED_LAYER_TYPES needs >=5.5.3).

The example itself encodes the validated M3 specifics: generation-tagged chat template copy (required for answer_only_loss — see fix 2), draft dims + base rope_theta=5e6 set explicitly (not inherited), mask token 200063 (reserved slot; added tokens end at 200060), EAGLE_CAPTURE_IDS = draft default target_layer_ids+1 + final layer, trust_remote_code at serve/export, and AWS-EFA NIXL notes (UCX segfaults at agent init on EFA nodes; LIBFABRIC required there).

Usage

cd tools/launcher
export SLURM_HOST=localhost SLURM_ACCOUNT=<account> SLURM_PARTITION=<partition> \
       SLURM_HF_LOCAL=<hf_models_dir> SLURM_JOB_DIR=<experiments_dir> NEMORUN_HOME=$PWD
uv run launch.py --yaml examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml \
       identity=$HOME/.ssh/id_ecdsa detach=True --yes

Testing

  • tests/unit/torch/speculative/plugins/: 129 passed inside a current vLLM x86_64 nightly container (transformers 5.12.1), including 3 new tests for the assistant-mask guard.
  • Generation-tagged template verified against real corpus samples: input_ids identical to the original template, mask covers exactly the assistant turns (think prefix + content + eos), contiguous, no user-prompt leak.
  • The recipe is exercised end-to-end by a live M3 DSpark training run (this yaml modulo cluster paths): streaming serve + NIXL transport + resume all healthy; drafter MT-Bench AL exceeds our Kimi-K2.6 DSpark reference by ~8k steps.
  • ruff check / ruff format (0.15.20) clean.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅ (norm selection only changes models with use_gemma_norm=True, previously mis-normed; the guard turns a silent zero-loss run into an error; SERVE_BLOCK_SIZE is opt-in; the pin relax widens the allowed range)
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: ❌ (can add if desired)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Gemma-style RMS normalization support for speculative decoding.
    • Added MiniMax-M3 DSpark multi-node training example, including a complete chat template for tool calls, multimodal content, and thinking modes.
    • Added optional SERVE_BLOCK_SIZE support for vLLM serve launches.
  • Bug Fixes
    • Improved answer_only_loss masking validation with clear, fail-fast errors when required {% generation %} markers are missing or when using a non-fast tokenizer.
  • Tests
    • Added unit test coverage for answer_only_loss behavior.
  • Compatibility
    • Expanded the supported Transformers version range for the speculative decoding examples.

@h-guo18
h-guo18 requested review from a team as code owners July 12, 2026 00:51
@h-guo18
h-guo18 requested a review from kevalmorabia97 July 12, 2026 00:51
@copy-pr-bot

copy-pr-bot Bot commented Jul 12, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add Gemma final-norm support, validate answer-only loss masking prerequisites, add a MiniMax-M3 chat template, and provide a multi-node DSpark streaming training configuration with configurable vLLM block size.

Changes

Speculative streaming support

Layer / File(s) Summary
Gemma final-norm support
modelopt/torch/speculative/plugins/modeling_final_norm.py, modelopt/torch/speculative/plugins/modeling_fakebase.py
Adds Gemma RMSNorm behavior and selects it from model type or use_gemma_norm configuration.
Chat-template loss-mask validation
modelopt/torch/speculative/plugins/hf_streaming_dataset.py, tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py
Rejects unsupported answer-only loss masking and tests tagged templates, full-loss behavior, and slow-tokenizer failures.
MiniMax-M3 chat rendering
tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja
Adds MiniMax-M3 tokens, role rendering, thinking modes, multimodal content, tool calls, and generation prompts.
MiniMax-M3 streaming launch
tools/launcher/common/eagle3/train_eagle_streaming.sh, tools/launcher/examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml, examples/speculative_decoding/requirements.txt
Adds SERVE_BLOCK_SIZE, widens the Transformers version range, and defines dataset preparation plus multi-node DSpark training and serving settings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: kevalmorabia97

Sequence Diagram(s)

sequenceDiagram
  participant DSparkPipeline
  participant MakeDataset
  participant TrainEagleStreaming
  participant VLLMServe
  DSparkPipeline->>MakeDataset: create train.jsonl
  DSparkPipeline->>TrainEagleStreaming: launch multi-node training
  TrainEagleStreaming->>VLLMServe: start vLLM with block size 128
  VLLMServe-->>TrainEagleStreaming: serve speculative decoding
Loading

Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Anti-Patterns ❌ Error New MiniMax-M3 recipe hardcodes model.trust_remote_code=true plus export/serve flags in the example YAML, which SECURITY.md forbids without an approved exception. Expose trust_remote_code as an opt-in config defaulting false (or remove the hardcoded example setting) and add explicit codeowner-approved justification if it must stay.
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the main change by identifying the new MiniMax-M3 DSpark example, though it omits the supporting fixes.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch haoguo/m3-dspark-recipe

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

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-26 01:35 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modelopt/torch/speculative/plugins/hf_streaming_dataset.py`:
- Around line 116-124: Update the error message in the assistant-mask validation
guard to reference the actual template location,
tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja,
while leaving the surrounding guidance unchanged.
- Around line 106-124: The guard around answer_only_loss must also raise when
the tokenizer is non-fast and no loss-mask recovery is available, regardless of
whether the chat template contains {% generation %} tags. Update the condition
using recovery, tokenizer.is_fast, and the existing template check, and adjust
the RuntimeError message to identify the unsupported slow-tokenizer path while
preserving the current guidance.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml`:
- Around line 99-100: Add the required MLM_MODEL_CFG environment variable
alongside HF_MODEL_CKPT in the MiniMax-M3 configuration, setting it to the
model’s Hugging Face repository ID and preserving the existing HF_MODEL_CKPT
reference.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja`:
- Line 1: Add the required provenance and licensing notices at the top of the
template before the special token variables: include the MiniMax source
reference, the upstream copyright and license notice, and the NVIDIA Apache 2.0
header. Also update the repository LICENSE to include the MiniMax Community
License.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 18176f48-ae3d-49dc-b512-26580fa2bc17

📥 Commits

Reviewing files that changed from the base of the PR and between 7ed19b2 and 468013d.

📒 Files selected for processing (8)
  • examples/speculative_decoding/requirements.txt
  • modelopt/torch/speculative/plugins/hf_streaming_dataset.py
  • modelopt/torch/speculative/plugins/modeling_fakebase.py
  • modelopt/torch/speculative/plugins/modeling_final_norm.py
  • tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py
  • tools/launcher/common/eagle3/train_eagle_streaming.sh
  • tools/launcher/examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml
  • tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja

Comment thread modelopt/torch/speculative/plugins/hf_streaming_dataset.py
Comment thread modelopt/torch/speculative/plugins/hf_streaming_dataset.py
@@ -0,0 +1,250 @@
{# ---------- special token variables ---------- #}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and related example templates for provenance/licensing patterns.
git ls-files 'tools/launcher/examples/MiniMaxAI/MiniMax-M3/*' | sed 's#^`#FILE`: #'
echo
wc -l tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja
echo
cat -n tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja | sed -n '1,80p'
echo
rg -n --hidden --no-messages 'SPDX-License-Identifier|copyright|license|provenance|source' tools/launcher/examples/MiniMaxAI/MiniMax-M3 tools/launcher/examples/MiniMaxAI -g '!**/*.pyc'

Repository: NVIDIA/Model-Optimizer

Length of output: 5262


🌐 Web query:

MiniMaxAI MiniMax-M3 chat_template.jinja license source commit minimax-community

💡 Result:

The MiniMax-M3 model is released under the MiniMax Community License [1][2][3], which is not an unrestricted permissive open-source license [1]. The chat_template.jinja file for MiniMax-M3 is hosted in the official MiniMaxAI/MiniMax-M3 repository on Hugging Face [4][5]. As of July 12, 2026, the model repository indicates that the license governing its use is the "minimax-community" license [2][3]. This license typically includes specific terms such as attribution requirements and restrictions on certain commercial uses [1]. You can view the specific content of the chat template and track its history directly via the Hugging Face repository [4][6]. The repository for MiniMax-M3 is maintained under the MiniMaxAI organization on Hugging Face [4][3], and related code can also be found in the MiniMax-AI GitHub organization [7]. Always refer to the official LICENSE file [8] located in the repository for the most accurate and legally binding terms regarding the model's usage.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the repo already has license notices or hook exclusions for this file/path.
rg -n --no-messages 'MiniMax|minimax-community|third-party|Third-Party Software Notices|insert-license|m3_chat_template_generation\.jinja' LICENSE .pre-commit-config.yaml tools/launcher/examples/MiniMaxAI -g '!**/*.pyc'

echo
echo "LICENSE lines:"
wc -l LICENSE
echo
sed -n '1,260p' LICENSE | cat -n | sed -n '1,260p'

echo
echo ".pre-commit-config.yaml lines:"
wc -l .pre-commit-config.yaml
echo
sed -n '1,260p' .pre-commit-config.yaml | cat -n | sed -n '1,260p'

Repository: NVIDIA/Model-Optimizer

Length of output: 26892


Add the MiniMax provenance and license notices here.
This template needs a source reference, the upstream copyright/license notice, and the NVIDIA Apache 2.0 header, plus an update to LICENSE for the MiniMax Community License.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja`
at line 1, Add the required provenance and licensing notices at the top of the
template before the special token variables: include the MiniMax source
reference, the upstream copyright and license notice, and the NVIDIA Apache 2.0
header. Also update the repository LICENSE to include the MiniMax Community
License.

Source: Coding guidelines

@h-guo18 h-guo18 changed the title [Examples]: MiniMax-M3 DSpark streaming recipe + the fixes it needs (gemma final norm, assistant-mask guard, SERVE_BLOCK_SIZE, transformers pin) [Examples]: MiniMax-M3 DSpark Jul 12, 2026
@h-guo18
h-guo18 force-pushed the haoguo/m3-dspark-recipe branch from 468013d to b31352a Compare July 12, 2026 02:06
@h-guo18

h-guo18 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

/claude review

"deepseek_v3": "rmsnorm",
"kimi_k2": "rmsnorm", # Kimi-K2 / K2-Thinking (DeepSeek-V3 arch) report model_type "kimi_k2"
"kimi_k25": "rmsnorm", # Kimi-K2.5 / K2.6 / K2.7 all report model_type "kimi_k25"
"minimax_m3_vl_text": "rmsnorm", # overridden to gemma_rmsnorm via use_gemma_norm below

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT ModeState] This new minimax_m3_vl_text → "rmsnorm" table entry is a silent-corruption fallback that undermines the fail-safe this file is built around.

The selection order in _select_final_norm_type is: use_gemma_norm first, then this table. So the entry is only ever consulted when use_gemma_norm is absent/falsy but model_type == "minimax_m3_vl_text". In that case it returns plain "rmsnorm", which drops the (1 + weight) scale — exactly the silent bias into the reconstructed logits / distillation target that the rest of this module (and the gpt_oss-disabled comment above) deliberately avoids.

The file's stated contract is "unlisted → no norm → hard error" precisely because a wrong norm is worse than no norm. Mapping M3 to rmsnorm as a fallback trades a loud RuntimeError (from _maybe_apply_base_final_norm) for a silent +1 drop.

Since every M3 config that reaches here is gemma-normed, either:

  • Remove this entry so a use_gemma_norm-less M3 falls through to None → hard error, or
  • Map it to "gemma_rmsnorm" so the correct flavor is applied even if the flag is missing.

Both are safer than the current "rmsnorm".

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Review Summary

Findings — CRITICAL: 0, IMPORTANT: 1, SUGGESTION: 0

Reviewed all 8 changed files (5 source/example, 1 test, 1 shell, 1 requirements). The four fixes are well-motivated and the per-fix rationale in the PR body / inline comments is unusually thorough. The Gemma-norm math (fp32 normalize -> *(1+weight) -> type_as) is correct and matches MiniMaxM3VLRMSNorm; the constructor signature matches _FinalRMSNorm so the registry dispatch is consistent; the transformers pin (<5.13) is consistent with pyproject.toml; and the SERVE_BLOCK_SIZE knob is a clean, opt-in addition.

Most impactful finding

[IMPORTANT ModeState] modeling_final_norm.py:93 — the new minimax_m3_vl_text -> "rmsnorm" table entry is a silent-corruption fallback. Because use_gemma_norm is checked first, this entry only fires when the gemma flag is absent — and then it applies plain RMSNorm, dropping the (1 + weight) scale and silently biasing the reconstructed distillation target. That is the exact failure mode this file's "unlisted -> hard error" design (and the disabled gpt_oss entry) exists to prevent. Map it to gemma_rmsnorm or remove the entry entirely so a flag-less M3 hard-errors rather than mis-norms.

Assessment

Low-to-moderate risk. The change is backward compatible (norm selection only affects use_gemma_norm=True models, the mask guard converts a silent zero-loss run into a loud error, and the block-size/pin changes are additive). The one IMPORTANT item is a latent fail-safe regression rather than a live bug, since M3 configs set use_gemma_norm in practice — but it is worth closing since defeating the safety net is precisely what the surrounding code warns against.

Note: CodeRabbit's pre-merge gate flags the hardcoded trust_remote_code=true in the YAML (SECURITY.md exception needed) and a slow-tokenizer edge in the mask guard — deferring those to CodeRabbit rather than duplicating.

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.09091% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.84%. Comparing base (d143276) to head (a9303ff).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...t/torch/speculative/plugins/modeling_final_norm.py 47.05% 9 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1965      +/-   ##
==========================================
+ Coverage   75.79%   77.84%   +2.05%     
==========================================
  Files         518      518              
  Lines       58658    58678      +20     
==========================================
+ Hits        44459    45680    +1221     
+ Misses      14199    12998    -1201     
Flag Coverage Δ
examples 43.27% <36.36%> (-0.21%) ⬇️
gpu 58.98% <31.81%> (+8.23%) ⬆️
regression 15.11% <31.81%> (+0.07%) ⬆️
unit 54.95% <59.09%> (-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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja (2)

209-212: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate tool messages against the immediately preceding tool-call turn.

last_tool_call.name remains set after a tool response and is not cleared by a subsequent user message, allowing a later standalone tool message to pass this guard incorrectly. Validate the adjacent message/contiguous tool block or clear the pending state whenever a non-tool turn intervenes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja`
around lines 209 - 212, Update the tool-message branch in the chat template to
validate that the tool message belongs to the immediately preceding assistant
tool-call turn or contiguous tool block, rather than relying on stale
last_tool_call.name state. Clear or invalidate the pending tool-call state
whenever a non-tool message intervenes, while preserving valid consecutive tool
responses.

159-166: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Treat an empty reasoning_content as absent.

When reasoning_content is "", is string succeeds and the fallback parser is skipped. If message.content still contains <mm:think>...</mm:think>, the template emits an extra closing tag and leaves reasoning in visible content, corrupting answer-only training masks. Fall back to parsing when the field is empty.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja`
around lines 159 - 166, Update the reasoning_content condition in the message
template so an empty string is treated as absent and enters the existing
think-token parsing fallback. Preserve direct assignment for non-empty string
reasoning_content and ensure content containing think_end_token is split into
reasoning and visible content as before.
modelopt/torch/speculative/plugins/hf_streaming_dataset.py (1)

109-135: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Normalize dict-valued chat templates before regex validation. tokenizer.chat_template can be a dict for multi-template models, and re.search(...) will crash on it before apply_chat_template can select the default template. Normalize the template first and add a regression test for {"default": ...}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/speculative/plugins/hf_streaming_dataset.py` around lines 109
- 135, The template validation in the answer_only_loss guard must handle
dict-valued tokenizer.chat_template values before calling re.search. Normalize
the chat template using the default entry selected by apply_chat_template, then
validate that string for {% generation %} tags while preserving existing
behavior for string templates; add a regression test covering a {"default": ...}
template.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@modelopt/torch/speculative/plugins/hf_streaming_dataset.py`:
- Around line 109-135: The template validation in the answer_only_loss guard
must handle dict-valued tokenizer.chat_template values before calling re.search.
Normalize the chat template using the default entry selected by
apply_chat_template, then validate that string for {% generation %} tags while
preserving existing behavior for string templates; add a regression test
covering a {"default": ...} template.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja`:
- Around line 209-212: Update the tool-message branch in the chat template to
validate that the tool message belongs to the immediately preceding assistant
tool-call turn or contiguous tool block, rather than relying on stale
last_tool_call.name state. Clear or invalidate the pending tool-call state
whenever a non-tool message intervenes, while preserving valid consecutive tool
responses.
- Around line 159-166: Update the reasoning_content condition in the message
template so an empty string is treated as absent and enters the existing
think-token parsing fallback. Preserve direct assignment for non-empty string
reasoning_content and ensure content containing think_end_token is split into
reasoning and visible content as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ed5cf9b3-3c40-4de5-9cc1-3931fd61a122

📥 Commits

Reviewing files that changed from the base of the PR and between b31352a and b14501a.

📒 Files selected for processing (4)
  • modelopt/torch/speculative/plugins/hf_streaming_dataset.py
  • modelopt/torch/speculative/plugins/modeling_final_norm.py
  • tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py
  • tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja
🚧 Files skipped from review as they are similar to previous changes (1)
  • modelopt/torch/speculative/plugins/modeling_final_norm.py

@h-guo18
h-guo18 enabled auto-merge (squash) July 22, 2026 18:14
h-guo18 added 2 commits July 26, 2026 00:33
Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
@h-guo18
h-guo18 force-pushed the haoguo/m3-dspark-recipe branch from b14501a to a9303ff Compare July 26, 2026 00:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modelopt/torch/speculative/plugins/hf_streaming_dataset.py`:
- Around line 114-115: Update the template handling before the regex check in
the dataset formatting logic to support dict-valued tokenizer.chat_template by
selecting the effective named template, preferably the "default" entry, before
calling re.search. Preserve the existing empty-string fallback and intended
RuntimeError behavior when no generation marker exists, and add a regression
test covering a dictionary-valued chat template.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml`:
- Around line 99-100: Update the environment configuration in the MiniMax-M3
launcher recipe to include the required QUANT_CFG variable, setting it to the
appropriate quantization configuration for this job while preserving the
existing HF_MODEL_CKPT entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2fbb5455-915d-4f69-8a64-1e6c215dcbdc

📥 Commits

Reviewing files that changed from the base of the PR and between b14501a and a9303ff.

📒 Files selected for processing (8)
  • examples/speculative_decoding/requirements.txt
  • modelopt/torch/speculative/plugins/hf_streaming_dataset.py
  • modelopt/torch/speculative/plugins/modeling_fakebase.py
  • modelopt/torch/speculative/plugins/modeling_final_norm.py
  • tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py
  • tools/launcher/common/eagle3/train_eagle_streaming.sh
  • tools/launcher/examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml
  • tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/speculative_decoding/requirements.txt
  • modelopt/torch/speculative/plugins/modeling_fakebase.py
  • modelopt/torch/speculative/plugins/modeling_final_norm.py
  • tools/launcher/examples/MiniMaxAI/MiniMax-M3/m3_chat_template_generation.jinja

Comment on lines +114 to +115
template = getattr(tokenizer, "chat_template", None) or ""
if not re.search(r"\{%-?\s*generation\b", template):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and surrounding logic.
sed -n '1,220p' modelopt/torch/speculative/plugins/hf_streaming_dataset.py

# Find any existing tests or usages related to chat_template handling.
rg -n "chat_template|generation\\b|hf_streaming_dataset" modelopt tests -S

Repository: NVIDIA/Model-Optimizer

Length of output: 40279


🌐 Web query:

Transformers 4.56 chat_template dict effective template apply_chat_template tokenizer.chat_template dictionary named templates

💡 Result:

In the Hugging Face Transformers library, the tokenizer.chat_template attribute typically stores a string containing a Jinja2 template used to format conversation history [1][2]. However, for models that support multiple use cases (such as standard chat, tool-use, or retrieval-augmented generation), tokenizer.chat_template can be defined as a dictionary [3][4]. When tokenizer.chat_template is a dictionary, each key represents the name of a specific template [3][5]. The apply_chat_template method handles these named templates with the following logic: 1. Default Behavior: In most scenarios, apply_chat_template automatically looks for a template named default [3][4]. If this key is missing, the method will raise an error [6][7]. 2. Tool Use: If the user passes a tools argument to apply_chat_template and a template named tool_use exists within the dictionary, the method will automatically prioritize and use that template [3][2]. 3. Explicit Selection: To use a template other than the default or the tool-use-specific template, you must explicitly provide the name of the desired template by passing it to the chat_template argument of the apply_chat_template method (e.g., apply_chat_template(messages, chat_template="rag")) [6][4]. Hugging Face generally recommends using a single, unified template with Jinja2 conditional logic (such as if tools is defined) to handle different use cases, rather than relying on a dictionary of multiple templates, to reduce complexity [3][1].

Citations:


🏁 Script executed:

python3 - <<'PY'
try:
    import transformers
    import inspect
    from transformers import tokenization_utils_base
    print("transformers", transformers.__version__)
    src = inspect.getsource(tokenization_utils_base.PreTrainedTokenizerBase.apply_chat_template)
    for needle in ["chat_template", "dict", "default", "Template"]:
        if needle in src:
            print(f"FOUND:{needle}")
    # Print the relevant chunk around chat_template handling if available
    lines = src.splitlines()
    for i, line in enumerate(lines):
        if "chat_template" in line or "dict" in line or "default" in line:
            start = max(0, i - 6)
            end = min(len(lines), i + 12)
            print("\n--- chunk ---")
            print("\n".join(f"{j+1}: {lines[j]}" for j in range(start, end)))
            break
except Exception as e:
    print(type(e).__name__, e)
PY

Repository: NVIDIA/Model-Optimizer

Length of output: 211


Handle dict-valued tokenizer.chat_template before regex matching. tokenizer.chat_template can be a dict of named templates, and passing it straight to re.search raises TypeError before the intended RuntimeError. Resolve the effective template string first (for example, the "default" template) and add a regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/speculative/plugins/hf_streaming_dataset.py` around lines 114
- 115, Update the template handling before the regex check in the dataset
formatting logic to support dict-valued tokenizer.chat_template by selecting the
effective named template, preferably the "default" entry, before calling
re.search. Preserve the existing empty-string fallback and intended RuntimeError
behavior when no generation marker exists, and add a regression test covering a
dictionary-valued chat template.

Comment on lines +99 to +100
environment:
- HF_MODEL_CKPT: <<global_vars.hf_model>>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Set the required QUANT_CFG environment variable.

New launcher model configurations must declare the intended quantization config; this recipe currently provides none. Set the appropriate value for this MiniMax-M3 job.

As per path instructions, “Set QUANT_CFG environment variable … when adding a new model config.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tools/launcher/examples/MiniMaxAI/MiniMax-M3/hf_streaming_dspark_multi_node.yaml`
around lines 99 - 100, Update the environment configuration in the MiniMax-M3
launcher recipe to include the required QUANT_CFG variable, setting it to the
appropriate quantization configuration for this job while preserving the
existing HF_MODEL_CKPT entry.

Source: Path instructions

@h-guo18
h-guo18 merged commit 6105fe8 into main Jul 26, 2026
57 checks passed
@h-guo18
h-guo18 deleted the haoguo/m3-dspark-recipe branch July 26, 2026 01:35
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.

2 participants