Skip to content

docs(vllm): Qwen3.8-27B DFlash drafter recipe — 72.2 tok/s on Arc Pro B70 - #620

Open
rmacy wants to merge 3 commits into
intel:mainfrom
rmacy:qwen38-dflash-drafter-recipe
Open

docs(vllm): Qwen3.8-27B DFlash drafter recipe — 72.2 tok/s on Arc Pro B70#620
rmacy wants to merge 3 commits into
intel:mainfrom
rmacy:qwen38-dflash-drafter-recipe

Conversation

@rmacy

@rmacy rmacy commented Aug 16, 2026

Copy link
Copy Markdown

Summary

Adds the complete recipe for achieving 72.2 tok/s median (85.9 peak) on isolated C1 with Qwen3.8-27B FP8 + DSpark speculative decoding on 2× Intel Arc Pro B70 (TP=2).

Configuration Median tok/s
FP8 no spec 32.4
FP8 + MTP2 54.67
FP8 + DFlash drafter (this recipe) 72.2

Contents

  • vllm/patches/qwen38-dflash/: patched vLLM dflash files (kernel readout fix), Dockerfile, serve script, README
  • Kernel fix: SpecForge DSpark trains output j to predict token anchor+j+1 (LM-style); stock vLLM sampled offsets 1..k (BERT-style) — every draft off by one, capping acceptance at ~24%. Fix is 3 lines.
  • Dockerfile builds qwen38-fp8-dspark:v8 (also published at ghcr.io/rmacy/qwen38-fp8-dspark:v8)

Published artifacts

Quality

Greedy spec decode is lossless by construction. Verified 4/5 byte-identical outputs vs target-only baseline; cross-checked against independent bf16 reference endpoint. Zero drafter-induced quality degradation.

omp-agent and others added 3 commits August 16, 2026 23:16
Adds the complete recipe for achieving 72.2 tok/s median (85.9 peak) on
isolated C1 with Qwen3.8-27B FP8 + DSpark speculative decoding on 2x
Intel Arc Pro B70 (TP=2).

Includes:
- Patched vLLM dflash files (kernel readout fix for SpecForge drafters)
- Dockerfile for qwen38-fp8-dspark:v8 image
- Reference serving script
- Full README with quick start, kernel fix explanation, and quality proof

Published artifacts:
- Drafter: rwmacy/qwen3.8-27b-dflash-drafter-fp8-b70 (HF Hub)
- Image: ghcr.io/rmacy/qwen38-fp8-dspark:v8
- Kernel fix PR: inteliodev/vllm#1
- SpecForge XPU port PR: sgl-project/SpecForge#769
@xitation

Copy link
Copy Markdown

Seems your container image isn't accessible.

I' can't seem to get my own container build to load with similar config to what you suggested, it OOM's when I have speculative decode enabled. Loads fine without.

@xitation

Copy link
Copy Markdown

Ah never-mind found it here - docker pull ghcr.io/rmacy/qwen38-fp8-dspark:v12

@RealLdDrako RealLdDrako mentioned this pull request Aug 19, 2026
MikkoP88 added a commit to MikkoP88/llm-scaler that referenced this pull request Aug 22, 2026
SpecForge-trained DSpark drafters (e.g. drafter-fp8-v5 for
Qwen3.8-27B-FP8) declare architectures: ["DSparkDraftModel"]: the
DFlash backbone plus a low-rank Markov bigram-bias head and an
optional per-position acceptance-confidence head. Serving one failed
at startup:

  pydantic ValidationError for SpeculativeConfig:
  Model architectures ['DSparkDraftModel'] are not supported for now.

The drafter's auto_map remote code (dspark.py) imports specforge,
which is not installed in the image, so vLLM falls back to the
config architectures and the registry lookup fails. The missing
support cannot be taken from rmacy/vllm main: there DSpark landed as
upstream plumbing (Qwen3DSparkModel / qwen3_dspark.py, "dspark"
speculative method) on a much newer base than the v0.21.0 patch
tree. PR intel#620 (intel/llm-scaler) hot-patches the same support into a
running image but references qwen3_dflash.py / registry.py snapshots
it does not actually ship, so this ports the full support into the
multi-arc patch instead.

Changes (new patch hunks, base v0.21.0):

- qwen3_dflash.py: VanillaMarkov (markov_w1 embedding + markov_w2
  projection), AcceptRatePredictor (confidence head), and
  DSparkDraftModel(DFlashQwen3ForCausalLM) constructing the heads
  from the draft config (markov_rank, enable_confidence_head,
  confidence_head_with_markov), exposing compute_markov_bias(), and
  loading markov_head.*/confidence_head.* checkpoint keys alongside
  the DFlash backbone path.
- registry.py: register "DSparkDraftModel" so ModelConfig creation
  and SpeculativeConfig.update_arch_ resolve it.
- eagle.py: the dflash arch rewrite now passes DSpark*-prefixed
  architectures through unchanged (otherwise the EAGLEConfig wrap
  mangles "DSparkDraftModel" into "DFlashDSparkDraftModel").
- dflash.py: DSpark proposer logic from PR intel#620 (verified compatible
  with our llm_base_proposer signatures): propose() captures the
  accepted bonus token as the Markov previous-token context;
  _greedy_sample applies the full-vocab bigram bias
  semi-autoregressively across block positions (position i
  conditioned on position i-1's biased prediction) and optionally
  evaluates the confidence head; _truncate_by_confidence enables
  adaptive draft block length via DSPARK_ADAPTIVE_BLOCK=1 (off by
  default).

Not ported from PR intel#620's snapshots: the gpu_model_runner.py MTP
long-prompt-skip tweak (orthogonal MTP feature) and a comment-only
utils.py change. The list-type draft_token_ids padding the adaptive
path needs is already present (1a03357).

Verified: git apply --check passes on pristine v0.21.0; patch grew
22859 -> 23249 lines, diff limited to the four files above. The
"Unable to load dspark.DSparkDraftModel ... No module named
'specforge'" startup warnings are expected and harmless: remote-code
loading fails, then the in-tree class resolves via the registry.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Mikko Pesonen <mikko.pesonen@kotiverkko.net>
@dominick253

Copy link
Copy Markdown

Independent replication evidence: dual Arc Pro B70

I reproduced this recipe on a bare-metal host with 2× Intel Arc Pro B70 GPUs.

Configuration

  • PR revision: e250e0d49bb68c350f0f2f580a31383b2c0d4970
  • Image: ghcr.io/rmacy/qwen38-fp8-dspark:v12
  • Target: Qwen/Qwen3.8-27B-FP8
  • Drafter: rwmacy/qwen3.8-27b-dflash-drafter-fp8-b70
  • TP: 2
  • Target dtype: BF16
  • Mamba cache: FP16
  • Context: 8192
  • Batched tokens: 4096
  • Sequences: 1
  • Block size: 64
  • Async scheduling enabled
  • DFlash fixed block: 4 speculative tokens
  • Local OpenAI endpoint: port 8001, alias Qwen38-27b-0

PR-style short benchmark

The probe used temperature 0, an approximately zero-prefill prompt (1 2, three tokenizer tokens), and 16 generated tokens. I ran ten measured requests after warmup.

Metric Result
Median 71.20 tok/s
Mean 70.98 tok/s
Peak 71.54 tok/s

This closely reproduces the PR's reported 72.2 tok/s median.

Sustained acceptance-friendly decode

I generated 512 tokens per request from the same deterministic sequence prompt.

Run Throughput
1 112.58 tok/s
2 111.49 tok/s
3 111.98 tok/s
Median 111.98 tok/s

The three runs produced 1,536 target tokens. vLLM reported 1,233 accepted draft tokens from 1,248 proposed draft tokens, or 98.8% draft-token acceptance.

Acceptance-dependent natural text

Three 512-token technical prompts measured 38.43, 40.98, and 53.00 tok/s. Therefore, the 71–112 tok/s results are valid for acceptance-friendly decoding. They are not representative of every natural-text request.

Correctness and stability

  • 17 × 23 returned 391.
  • The response ended with finish_reason=stop and nonempty final output.
  • DSpark draft and acceptance counters were nonzero.
  • The qualified service invocation had zero restarts, zero application error lines, and zero new Xe fault lines.

Host topology note

The published CCL_TOPO_P2P_ACCESS=1 plus CCL_ZE_IPC_EXCHANGE=drmfd settings hung during compilation after model load on this host. oneCCL identified the cards as connected through PCIe. Privileged whole-DRI access did not resolve the hang.

The working deployment required the no-P2P oneCCL path:

FI_PROVIDER=tcp
FI_TCP_IFACE=lo
CCL_TOPO_P2P_ACCESS=0
CCL_ZE_IPC_EXCHANGE=pidfd
CCL_ATL_TRANSPORT=ofi
CCL_SEND=direct
CCL_RECV=direct

All model, drafter, batching, dtype, and speculative-decoding settings remained aligned with the recipe.

BF16 short-prompt edge case

A raw completion containing only one prompt token entered the ESIMD decode kernel before drafting and failed with:

Expected query.scalar_type() == torch::kHalf to be true

Multi-token completion prompts and chat-template prompts passed. Setting the target dtype to FP16 avoided this edge case, but BF16 was retained for the reported recipe-aligned benchmark.

@dominick253 dominick253 mentioned this pull request Aug 25, 2026
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.

4 participants