Skip to content

Dflash: Block Diffusion Speculative Decoding - #995

Open
vjanfaza wants to merge 16 commits into
quic:mainfrom
vjanfaza:DFlash
Open

Dflash: Block Diffusion Speculative Decoding#995
vjanfaza wants to merge 16 commits into
quic:mainfrom
vjanfaza:DFlash

Conversation

@vjanfaza

Copy link
Copy Markdown
Contributor

In this PR, we are adding the support for DFlash (Block Diffusion for Flash Speculative Decoding) approach that significantly accelerate LLM inference on Qualcomm platforms.
DFlash replaces autoregressive draft models with a diffusion-based block drafter that predicts an entire block of tokens in a single parallel pass, resulting in constant drafting latency independent of block size.

Key benefits:

5–6× lossless speedup over baseline decoding
~2.5× faster than EAGLE-3
Higher acceptance length enabled by KV injection of target hidden states into every draft layer
Improved accelerator utilization and reduced end-to-end latency

Verification is performed by the frozen target model, guaranteeing output identical to standard decoding.

Use-case(s)

High-throughput and low-latency LLM serving
Long chain-of-thought reasoning workloads (e.g., math and planning)
Code generation and agentic inference loops
Platform differentiation via advanced speculative decoding on AI100

#Requirements
DFlash speculative decoding with block diffusion drafting and target verification
Extraction and fusion of hidden states from multiple target layers during prefill
KV cache injection of fused target context into all draft layers
Integration with QEfficient, vLLM, and other applications
Support current architecture:
Target: single QPC for prefill + decode (expandable to DA serving)
Draft: lightweight QPC

Equally Contributors

@quic-rishinr

Copy link
Copy Markdown
Contributor

Design discussion pending.

@vjanfaza
vjanfaza force-pushed the DFlash branch 3 times, most recently from 630d915 to 4ec6a39 Compare May 29, 2026 04:59
@vjanfaza
vjanfaza force-pushed the DFlash branch 6 times, most recently from ce1f9b7 to db57abd Compare June 25, 2026 20:54
Add DFlash speculative decoding: a block-diffusion draft language model
(DLM) that proposes block_size tokens per step from the target model's
hidden states, verified by the target language model (TLM).

QEfficient side:
- Qwen3 DFlash draft model (two-stream attention over target_hidden + noise).
- DFlashTransform swaps in the DLM modules; DFlashDLMTransform injects
  lm_head/embed_tokens from the paired TLM checkpoint; DFlashTLMTransform
  attaches fc/hidden_norm (weights from the DLM checkpoint, fc scaled for
  fp16 range) and activates target hidden-state collection. Callers select
  the paths via qaic_config (dflash_dlm / target_layer_ids) plus the
  cross-checkpoint repo keys (dflash_tlm_repo / dflash_dlm_repo); no model
  surgery on the caller side.
- modeling_auto: DFlash export inputs/outputs, block_size specialization,
  and batched DLM decode compile.

examples/performance/dflash: single-prompt and benchmark runners plus a
make_models helper, driven through QEFFAutoModelForCausalLM.from_pretrained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
vjanfaza and others added 3 commits August 13, 2026 17:19
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>

# Conflicts:
#	QEfficient/transformers/models/modeling_auto.py
DFlashDLMTransform/DFlashTLMTransform.apply() use torch.no_grad()/torch.load, but the bare import torch was dropped when merging upstream main (which no longer needed it elsewhere in the file), leaving only from torch import nn.

Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Extends DFlash speculative decoding to vision-language models. gemma4 and
qwen3-vl TLMs collect target-layer hidden states, attach fc/hidden_norm,
and emit them as an extra output for the DFlash draft (DLM) to verify
against. DFlashTLMTransform generalizes to VLM configs (nested
text_config/language_model) alongside the existing dense-model path.

utils.py adds VLM-aware DLM weight injection (compile_gemma_vlm_dlm_qpc)
since a VLM base keeps lm_head/embed_tokens on a nested submodule that
the flat-key DFlashDLMTransform loader can't reach; extracting and
injecting them explicitly fixes drafts never being accepted (AR stuck at
1.00) and restores the expected acceptance rate.

New vision example scripts (basic_inference_vision.py,
benchmark_vision.py, dflash_spd_vision_single_prompt.py,
dflash_spd_vision_benchmark.py, dflash_spd_vision_text_benchmark.py) run
the vision-encoder + DFlash-aware language QPC + draft QPC pipeline for
single-prompt and dataset-benchmark use, covering text-only, image+text,
and language-only-on-text-dataset modes.

Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
@vjanfaza
vjanfaza marked this pull request as ready for review August 24, 2026 18:11
Comment thread QEfficient/transformers/models/gemma4/modeling_gemma4.py
Comment thread QEfficient/transformers/models/modeling_auto.py Outdated
Comment thread QEfficient/transformers/models/modeling_auto.py
Comment thread QEfficient/transformers/models/pytorch_transforms.py Outdated
Comment thread examples/performance/dflash/basic_inference_text.py
@quic-rishinr

Copy link
Copy Markdown
Contributor

Please add unit test, full end to end test and update the documentation as well

vjanfaza and others added 2 commits August 27, 2026 14:16
… draft model classes, refactor example scripts

- Extract the DFlash TLM target-layer hidden-state computation (concat -> fc ->
  hidden_norm) shared by llama/qwen3/gemma4/qwen3_vl into
  QEfficient/transformers/spd/dflash.py, reused via compute_dflash_target_hidden_states.
- Fix a dict-overwrite bug in the DFlash draft model's ONNX export
  (QEfficient/transformers/models/modeling_auto.py) that silently dropped
  input_ids/position_ids from dynamic_axes, baking a static seq_len into the
  exported graph and causing a runtime buffer-size mismatch.
- Rename the DFlash draft model wrapper classes from QEffQwen3* to QEffDFlash*
  to avoid confusion with the real Qwen3 modeling classes.
- Move the DFlash generation/SPD loops out of examples/performance/dflash/utils.py
  and the deleted single-prompt scripts into QEfficient/generation/dflash_generation.py,
  exposing run_text_inference/run_vision_inference as direct in-process entry points.
- Rename basic_inference.py to basic_inference_text.py; basic_inference_text.py and
  basic_inference_vision.py now call dflash_generation directly instead of
  shelling out to dflash_spd_single_prompt.py/dflash_spd_vision_single_prompt.py
  (both removed).
- Remove the now-dead DFlash target_hidden_states rebinding block from
  kv_offload_generate in modeling_auto.py (never exercised by any dflash example).
- Update examples/performance/dflash/README.md to match the current script layout.

Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
vjanfaza and others added 2 commits August 27, 2026 15:24
…che sizing

- QEffDFlashRotaryEmbedding.forward sliced cos/sin to [:seq_len] before
  returning, but callers gather by absolute position and DFlash's noise
  positions run up to 2x kv_seq_len ahead of the context; return the full
  cached table instead of truncating it.
- Size the dummy KV cache to seq_len*2 for dflash_dlm exports so it covers
  both the context and noise-position halves of the window.

Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Adds a CPU-only unit test suite for DFlashTransform/DFlashDLMTransform/
DFlashTLMTransform and compute_dflash_target_hidden_states, and a full
end-to-end test on real QAIC hardware that drives run_spd_inference_single
through a tiny random-weight TLM/DLM pair. Also documents DFlash in the
supported features table.

Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py Outdated
Comment thread QEfficient/generation/dflash_generation.py
Comment thread examples/performance/dflash/.gitignore Outdated
@@ -0,0 +1,7 @@
# DFlash example run artifacts (not source)

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.

please remove this file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed that .gitignore matches upstream main and is no longer present in the current PR diff.

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.

it is part of the current PR diff

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Comment thread QEfficient/transformers/models/modeling_auto.py Outdated
Comment thread QEfficient/transformers/models/pytorch_transforms.py
Comment thread QEfficient/transformers/models/pytorch_transforms.py Outdated
Comment thread QEfficient/transformers/models/pytorch_transforms.py Outdated
vjanfaza and others added 3 commits September 3, 2026 17:12
Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
@vjanfaza
vjanfaza force-pushed the DFlash branch 2 times, most recently from 1e4ef14 to a8e49e1 Compare September 4, 2026 00:59
Comment thread QEfficient/transformers/models/dflash_draft/modeling_dflash_draft.py Outdated
Comment thread QEfficient/generation/dflash_generation.py
mm_token_type_ids: np.ndarray | None = None,
vision_embeds: np.ndarray | None = None,
) -> SpecDecodingMetrics:
batch_size = 1

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.

if we are supporing only batch_size=1 we should add a fallback warning if user is passing batch size > 1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We have the support of higher batch sizes and I tested it in vLLM with no issue. I fixed this part of the code

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.

Then in that case we should be remove hardcoding batch_size to 1

@vjanfaza vjanfaza Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed by adding fallback as you suggested. We have it in vLLM so in the fallback warning we are suggesting to use vLLM for higher batch sizes since the current SpD implementation in QEfficient for higher batch sizes is not very efficient and it's just repeating it based on batch size which is not very effective way to do it. However, in vLLM we have an efficient implementation of it which the results can be found in this link

Comment thread QEfficient/transformers/models/dflash_draft/modeling_dflash_draft.py Outdated
Comment thread QEfficient/transformers/models/dflash_draft/modeling_dflash_draft.py Outdated
cache_kwargs = {"batch_index": batch_index, "position_ids": position_ids_target}
if comp_ctx_lengths is not None:
attention_mask = attention_mask[:, :, :, : comp_ctx_lengths.shape[-1]]
cache_kwargs["CCL"] = attention_mask.shape[-1]

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.

Was the CCL tested in this case? since write_only() only reads position_ids and batch_index, it ignores CCL in Dynamic write_only() method, So it will fall back to full ctx length.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

CCL doesn't have any effect on write_only and it only helps during gathering which happens in update and read_only functions

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.

yes, but CCL is passed to write_only(), which ignores it and then cache_kwargs is updated in line 276 "cache_kwargs = {"batch_index": batch_index, "position_ids": position_ids_target}" before update(). This way update() never receives CCL and falls back to the full cache length.

Was this tested with CCL?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Very good point. We tested it with CCL before but later with some newer changes we missed this point. Thank you for pointing it out. Fixed it.

Comment thread QEfficient/utils/constants.py
Comment thread examples/performance/dflash/utils.py Outdated
Comment thread examples/performance/dflash/utils.py Outdated
Comment thread examples/performance/dflash/basic_inference_vision.py
fannanya and others added 2 commits September 9, 2026 19:38
Introduce the `dflash2_draft` package (the DFlash-2 draft model, which has no
upstream `transformers` implementation, so its classes are written export-ready
and used directly) together with the wiring that selects it: the new
`DFlash2DLMTransform` in `pytorch_transforms.py` and the
`qaic_config['dflash2_dlm']` path in `modeling_auto.py`. The `dflash_dlm` and
`dflash2_dlm` flags are mutually exclusive, and DFlash-2 shares DFlash's
doubled KV-cache sizing, `target_hidden` example input, and block-size
specialization.

For the existing DFlash draft, move the rotary embedding from per-attention to
the model level: `QEffDFlashModel.__qeff_init__` builds the table once and folds
`attention_scaling` into the `sin_cached`/`cos_cached` Parameters, which are then
threaded through the decoder layers into attention. This removes the per-layer
rotary instance and its `forward`. The whole table is passed down because
`qeff_apply_rope_two_streams` gathers it once per stream — `position_ids_target`
for the context keys and `position_ids` for the noise keys — so a pre-gather at
the model level would drop the target stream.

Only the modeling files are in scope here; the compilation and TLM paths are not
complete yet.

Co-authored-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: FNU Annanya <fannanya@qti.qualcomm.com>
@quic-rishinr

Copy link
Copy Markdown
Contributor

@vjanfaza Let's not increase the scope of this PR. Adding DFlash2 changes here will only delay the merge further.

mm_token_type_ids: np.ndarray | None = None,
vision_embeds: np.ndarray | None = None,
) -> SpecDecodingMetrics:
batch_size = 1

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.

Then in that case we should be remove hardcoding batch_size to 1

cache_kwargs["CCL"] = attention_mask.shape[-1]

# first write for target positon_id
past_key_value.write_only(k_ctx, v_ctx, self.layer_idx, cache_kwargs)

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.

was it benchmarked? Can you share the latency numbers?

cache_kwargs = {"batch_index": batch_index, "position_ids": position_ids_target}
if comp_ctx_lengths is not None:
attention_mask = attention_mask[:, :, :, : comp_ctx_lengths.shape[-1]]
cache_kwargs["CCL"] = attention_mask.shape[-1]

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.

yes, but CCL is passed to write_only(), which ignores it and then cache_kwargs is updated in line 276 "cache_kwargs = {"batch_index": batch_index, "position_ids": position_ids_target}" before update(). This way update() never receives CCL and falls back to the full cache length.

Was this tested with CCL?

Comment thread examples/performance/dflash/.gitignore Outdated
@@ -0,0 +1,7 @@
# DFlash example run artifacts (not source)

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.

it is part of the current PR diff

Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
vjanfaza and others added 2 commits September 11, 2026 15:40
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
Signed-off-by: Vahid Janfaza <vjanfaza@qti.qualcomm.com>
Co-authored-by: FNU Annanya <fannanya@qti.qualcomm.com>
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.

3 participants