DFlash (Block Diffusion for Flash Speculative Decoding) predicts an entire block of tokens in a single forward pass using masked parallel prediction with KV injection from the target model's hidden states. Key features: - Feature fusion (multi-layer hidden states -> FC + RMSNorm) - KV injection (fused features as K/V in every draft layer with QK-norm) - Random anchor sampling with bidirectional intra-block attention - Logit distillation with exponential loss decay (gamma weighting) - Multi-node DDP training with checkpoint resume - Export to z-lab compatible HF format - Online validation (context-dependent ground truth) Training recipe: modelopt_recipes/general/speculative_decoding/dflash.yaml Results: examples/speculative_decoding/doc/dflash_results.md ### ModelOpt Eval (online validation, osl=512) | Dataset | z-lab | ModelOpt (306K) | Diff | |---------|-------|-----------------|------| | gsm8k | 4.10 | **5.19** | **+1.09** | | MT-Bench | 3.58 | **4.36** | **+0.78** | ### z-lab Official Eval (dflash.benchmark, osl=512) | Dataset | z-lab | ModelOpt (306K) | Diff | |---------|-------|-----------------|------| | gsm8k | **5.00** | 4.08 | -0.92 | | MT-Bench | **3.28** | 2.99 | -0.29 | > z-lab model trained with block_size=16. ModelOpt trained with block_size=8. ## Evaluation Method Impact (gsm8k) | Eval Method | z-lab checkpoint | ModelOpt (306K) | |-------------|-----------------|-----------------| | Fixed GT (ModelOpt eval) | 2.95 | 4.23 | | Online GT (ModelOpt eval) | 4.10 | **5.19** | | z-lab official eval | **5.00** | 4.08 | ### What does this PR do? Type of change: ? <!-- Use one of the following: Bug fix, new feature, new example, new tests, documentation. --> <!-- Details about the change. --> ### Usage ```python # Add a code snippet demonstrating how to use this ``` ### Testing <!-- Mention how have you tested your change if applicable. --> ### Before your PR is "*Ready for review*" Make sure you read and follow [Contributor guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md) and your commits are signed (`git commit -s -S`). Make sure you read and follow the [Security Best Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors) (e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(..., weights_only=False)`, `pickle`, etc.). - Is this change backward compatible?: ✅ / ❌ / N/A <!--- If ❌, explain why. --> - If you copied code from any other sources or added a new PIP dependency, did you follow guidance in `CONTRIBUTING.md`: ✅ / ❌ / N/A <!--- Mandatory --> - Did you write any new necessary tests?: ✅ / ❌ / N/A <!--- Mandatory for new features or examples. --> - Did you update [Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?: ✅ / ❌ / N/A <!--- Only for new features, API changes, critical bug fixes or backward incompatible changes. --> ### Additional Information <!-- E.g. related issue. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added DFlash speculative decoding mode with parallel block prediction support. * Included training launchers and MT-Bench evaluation scripts for DFlash models. * Added online acceptance rate validation for improved inference verification. * **Documentation** * DFlash quick start guide with configuration parameters and training examples. * Performance results and benchmarks for DFlash-trained models. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Chenhan Yu <chenhany@nvidia.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dataset Preparation
| Section | Description | Link |
|---|---|---|
| Building Chat Datasets | Scripts to build conversation datasets from Nemotron and other HuggingFace sources | [Link] |
| Tokenizing for Megatron Frameworks | Convert JSONL or HF datasets to Megatron binary format for distillation and pre-training | [Link] |
Building Chat Datasets
Utilities for building conversation datasets from NVIDIA Nemotron Post-Training
collections and other HuggingFace sources. These scripts produce datasets in
standard OpenAI chat format ({"messages": [{"role": ..., "content": ...}]})
and can be used for any downstream fine-tuning task — SFT, distillation,
speculative decoding draft-model training, etc.
Files
| File | Description |
|---|---|
make_nemotron_ptv3_dataset.py |
Build a dataset from the Nemotron PT v3 collection using a configurable YAML mix |
make_nemotron_ptv2_dataset.py |
Build a dataset from Nemotron-Post-Training-Dataset-v2 |
make_dataset.py |
General-purpose mixer for arbitrary HuggingFace datasets (mtbench, sharegpt, ultrachat, magpie, etc.) |
conversation_utils.py |
Shared utilities: augmentation, role normalization, assistant-turn stripping |
add_nemotron_chat.py |
Add Nemotron v2 chat conversations to an existing dataset |
augmentations.yaml |
Augmentation variants (language redirects, style hints) for make_nemotron_pt*.py |
nemotron_ptv3_datasets.yaml |
Dataset mix config for make_nemotron_ptv3_dataset.py |
example_data_config.yaml |
Example YAML config for make_dataset.py |
Quick Start
Install dependencies
pip install nvidia-modelopt[hf]
hf auth login --token <your token> # required for gated datasets
Build a Nemotron PT v3 dataset
# Synthetic data generation inputs (strips last assistant turn so a model can regenerate it)
python make_nemotron_ptv3_dataset.py --output-dir /tmp/ptv3_gen
# Full conversations for direct SFT training
python make_nemotron_ptv3_dataset.py --mode train --output-dir /tmp/ptv3_train
# Use a custom dataset mix
python make_nemotron_ptv3_dataset.py --config my_mix.yaml --output-dir /tmp/ptv3_custom
Build a Nemotron PT v2 dataset
python make_nemotron_ptv2_dataset.py --output-dir /tmp/ptv2_gen
python make_nemotron_ptv2_dataset.py --mode train --output-dir /tmp/ptv2_train
Build a general-purpose mixed dataset
python make_dataset.py --config example_data_config.yaml --output-dir /tmp/mixed
Dataset Modes
Both make_nemotron_pt*.py scripts support two modes:
| Mode | Description | Use case |
|---|---|---|
generate (default) |
Strips assistant turns, optionally augments prompts | Input data for synthetic generation (query a target model to produce training responses) |
train |
Keeps all turns, normalizes to clean OpenAI format | Direct SFT / distillation training |
Synthetic Generation Pipeline
The generate mode produces conversation skeletons that are fed to a target model
via tools/launcher/common/query.py (vLLM or TRT-LLM). The output becomes training
data for a draft model (e.g. EAGLE3 speculative decoding) or a distilled student:
make_nemotron_ptv3_dataset.py --mode generate → skeleton.jsonl
↓
query.py (target model generates responses turn-by-turn)
↓
training data for draft model / student
Augmentations
augmentations.yaml defines language-redirect and style-hint variants that are
applied cyclically across the dataset. Each enabled entry produces one augmented
copy of the source rows.
To customize augmentations:
- Disable a variant: add
enabled: false - Add a language redirect: append a
user_suffixentry - Add a system prompt: append a
system_promptentry
augmentations:
- type: user_suffix
text: " Please reply in French instead of English."
- type: system_prompt
content: "You are a helpful assistant."
enabled: false # disable without deleting
Dataset Mix Config (nemotron_ptv3_datasets.yaml)
Edit this file to add, remove, or re-weight datasets without touching the script:
datasets:
- repo_id: nvidia/Nemotron-Math-v2
splits: [high_part00, high_part01]
cap_per_split: 200000
augment: true
- repo_id: nvidia/OpenMathReasoning-mini
splits: [train]
augment: false # multilingual — skip language-redirect augmentation
Output Format
Every output row is a JSONL object with a single messages key:
{"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is 2+2?"},
{"role": "assistant", "content": "4"}
]}
In generate mode, assistant turns are stripped so the row ends with a user turn.
Tokenizing for Megatron Frameworks
The distillation and pre-training scripts in Megatron-Bridge or Megatron-LM expect data pre-tokenized in Megatron's binary indexed format (.bin / .idx).
Use the megatron_preprocess_data utility to tokenize any JSONL or Hugging Face dataset.
The tokenization scripts below prints the list of output prefixes (e.g. tokenized_qwen3/data1_text) that you can use for the data_paths argument (with relative weights on different files) in Megatron training scripts.
Important Notes:
- For Pretraining / raw-text data (
textkey) — use--append_eodso Megatron can tell where documents end when concatenating them into long sequences. - For Post-training chat data (
messageskey) — omit--append_eod; the chat template already appends EOS at the end of each conversation. - Set
--max_sequence_length 256_000to avoid rare OOM errors if some text is very long.
From JSONL files
python -m modelopt.torch.utils.plugins.megatron_preprocess_data \
--jsonl_paths /path/to/data1.jsonl /path/to/data2.jsonl ... \
--json_keys text \
--tokenizer Qwen/Qwen3-0.6B \
--output_dir tokenized_qwen3 \
--workers 32 \
--append_eod
python -m modelopt.torch.utils.plugins.megatron_preprocess_data \
--jsonl_paths /path/to/sft_data.jsonl \
--json_keys messages \
--tokenizer Qwen/Qwen3-0.6B \
--output_dir tokenized_qwen3 \
--workers 32
Instead of --jsonl_paths, pass --input_dir /path/to/dir to tokenize all JSONL files in a directory (.jsonl and .jsonl.gz are both supported).
From Hugging Face Hub
To tokenize a dataset directly from Hugging Face Hub:
python -m modelopt.torch.utils.plugins.megatron_preprocess_data \
--hf_dataset nvidia/Nemotron-Pretraining-SFT-v1 \
--hf_name Nemotron-SFT-Code \
--hf_split train \
--hf_max_samples_per_split 10_000_000 \
--json_keys text \
--tokenizer Qwen/Qwen3-0.6B \
--output_dir tokenized_qwen3 \
--workers 32 \
--append_eod
Omit --hf_name to process all subsets, --hf_split for all splits, or --hf_max_samples_per_split for all samples.
To quickly test, use nvidia/Nemotron-Pretraining-Dataset-sample.
For very large datasets (tens of millions of documents), add --hf_streaming --hf_max_samples_per_split <num_samples> to avoid downloading the full dataset — only the rows actually consumed are fetched.
Performance note: Non-streaming mode downloads all Parquet shards once and caches them as Arrow files on disk. Re-runs read from cache and are much faster. Streaming re-downloads on every run with no cache, so it is slower for full-dataset processing.
Nemotron Post-Training v3 (reasoning_content)
v3 datasets include a reasoning_content field in assistant messages (chain-of-thought separate from
the final answer). Use --reasoning_content to control how it is handled:
| Value | Behaviour |
|---|---|
strip (default) |
Field is discarded before apply_chat_template. Safe for any tokenizer. |
inline |
Wrapped as <think>…</think> and prepended to content. Preserves reasoning in a tokenizer-agnostic way. |
native |
Passed unchanged. Requires the tokenizer's chat template to handle the field (e.g. Qwen3). |
python -m modelopt.torch.utils.plugins.megatron_preprocess_data \
--hf_dataset nvidia/Nemotron-Post-Training-Dataset-v3 \
--json_keys messages \
--tokenizer Qwen/Qwen3-0.6B \
--output_dir tokenized_qwen3 \
--workers 32 \
--reasoning_content inline
Synthetic Test Dataset
synthetic_conversations_1k.jsonl is a 1,000-sample dataset in OpenAI messages format
(900 single-turn + 100 two-turn conversations) covering writing, reasoning, math, coding,
STEM, extraction, humanities, and roleplay categories.
This dataset was synthesized by Claude (Anthropic) and is licensed under Apache-2.0. It is intended for testing and CI regression — not for production training.
{"messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}