Speculative tokens (num_speculative_tokens)

Result as of 2026-10-24. Every speculative profile on the 2x DGX Spark (GB10) pair runs at depth 4. Depth 6 was slower on real prompts, and 7, 8 and 16 were slower at 8 concurrent streams. Deeper drafts only won with a single stream. DeepSeek-V4-Flash runs at depth 0 because its drafter does not load yet, not because 0 was tuned (see Drafters).

What the setting does

Expected tokens per step

If each drafted token is accepted with the same probability a, independently of the others:

E[tokens per step] = 1 + a + a² + … + a^k = (1 − a^(k+1)) / (1 − a)

The assumption is that acceptance is the same at every draft position and that positions are independent. Real drafters fail together on hard stretches of text and are rarely exactly geometric. The closest check here is DFlash2 at depth 16 (2026-10-02). There, the share of drafts that survived to positions 0–4 was 78.0, 60.1, 44.3, 33.7 and 25.7 %. Each position keeps 0.73–0.78 of the one before it, which is near-geometric.

Which a is which. vLLM’s spec_decode metrics report accepted ÷ drafted, not the per-position a above. Tokens per step is then 1 + rate × k (DFlash2 depth 4: 1 + 0.577 × 4 = 3.31, which matches the engine’s own tokens-per-pass). Read 0.45 as a per-position a and the formula undercounts. Under the formula’s assumption, a reported 0.435 at depth 4 corresponds to a per-position a of about 0.69.

Computed expected tokens per step vs depth 0 to 8: a = 0.45 rises from 1.00 to 1.78 at depth 4 and flattens at 1.82; a = 0.80 rises to 3.36 at depth 4 and 4.33 at depth 8. Measured tokens per pass: MTP real prompts 2.74 at depth 4 and 3.00 at depth 6; MTP synthetic 4.20 at depth 4; DFlash2 3.31, 3.75 and 4.04 at depths 4, 7 and 8 Tokens per target pass vs depth k (lines computed, marks measured) a = 0.80, computed a = 0.45, computed MTP, real prompts MTP, synthetic DFlash2, 8 streams 4 3 2 1 0 a = 0.80 · 4.33 a = 0.45 · 1.82 2.74 3.00 4.20 3.31 3.75 4.04 0 1 2 3 4 5 6 7 8 depth k (num_speculative_tokens)
Lines: (1 − a^(k+1)) / (1 − a), computed. Marks: 1 + reported rate × k from vLLM's spec-decode metrics. MTP marks: Qwen3.8 Flash-Next MoE at 8 seats, 2026-09-30. DFlash2 marks: Qwen3.8 27B dense at 8 streams, 2026-10-03. Different models and prompts; the lines are not fitted to the marks.

With a = 0.45, depth 4 already gets 1.78 of the 1.82 tokens per step that infinite depth would give. With a = 0.80, depth 4 gets 3.36 and depth 8 gets 4.33. Each extra drafted token is worth a^k, so the gain from depth falls off geometrically while the cost of each extra position stays roughly the same.

Why deeper costs

Each step drafts k tokens and verifies k + 1 positions per seat. With one stream, the GPU has spare compute and the extra positions are almost free. With many seats the batch already fills it, and a rejected draft takes throughput from another request. Speculative positions also hold KV.

The reported rate falls as depth rises even when the drafter is no worse per position: depth 4 → 6 took the MTP rate from 0.435 to 0.333, but both work out to a per-position a of about 0.69 (computed under the formula’s assumption). Deeper positions are reached less often, so they pull the average down.

Measured A/B

MTP, depth 4 vs 6. Qwen3.8 Flash-Next MoE (NVFP4), vLLM, TP=2, 8 seats, real prompts, 3 runs each (2026-09-30). Adoption rule fixed before the runs: +10 % on the primary metric or keep the default.

DepthDecode, agg tok/sReported rateMixed workload, wall sPeak live KV, mixed runsPrefix hits, warm rerun
4147.00.435281.726 %73 %
6139.7 (−4.9 %)0.333332.1 (+17.9 %)33 %37 %

At 16 seats: 183.6 (depth 4, 3 runs) vs 168.3 (depth 6, 1 run). Depth 6 drafts more tokens per pass (3.00 vs 2.74), yet the larger verify batches took more KV, pushed cached prefixes out and cost wall time.

DFlash2, depth 4 vs 7 vs 8. Qwen3.8 27B dense NVFP4, vLLM, TP=2, prefill budget 4,096 (2026-10-03). Depth 4 was run twice; its totals agreed within 1.4 %.

Measured decode throughput. MTP at 8 seats: depth 4 147.0, depth 6 139.7 tok/s. DFlash2 at 8 streams: depth 4 201.5, depth 7 187.6, depth 8 185.9. DFlash2 at 1 stream: depth 4 52.8, depth 8 70.45 Decode tok/s, measured (2 px per tok/s) MTP · Flash-Next MoE · 8 seats, aggregate depth 4 147.0 depth 6 139.7 −4.9 % DFlash2 · 27B dense · 8 streams, aggregate depth 4 201.5 depth 7 187.6 depth 8 185.9 DFlash2 · 27B dense · 1 stream depth 4 52.8 depth 8 70.45 +33 %
MTP: 3-run means, 2026-09-30. DFlash2: 8-stream figures are means of the depth-4 run and its repeat, 2026-10-03; 1-stream figures are one reasoning prompt. The two models and prompt sets differ, so compare bars only within a group.
DFlash2 depthReported rate, 8 streamsTokens / passPer stream, 8 streams4 streams, agg
40.5773.3129.9128.9
70.3933.7528.6134.4
80.3804.0427.5123.4
16 (2026-10-02)0.14–0.283.3–5.520.684.5

Depth 16 is from a separate run with a different harness. It came in at 116.8 tok/s aggregate at 8 streams, 0.89x of the same harness with speculation off (131.1): slower than no drafter at all.

The best depth depends on concurrency. Depth 8 wins with 1 stream, depth 7 at 4 streams, and depth 4 at 8. The panel’s default is set for the 8+ seats the pair actually serves. A pair serving one user at a time should be set deeper.

Synthetic prompts overstate acceptance

The same MTP profile reported 0.80 on synthetic prompts (lists of NATO-alphabet words) and 0.43–0.47 on prompts from 24 hours of real traffic (2026-09-30). Repetitive synthetic text is easy for a drafter to guess. At depth 4 that is 4.20 vs 2.74 tokens per pass. A depth sweep on synthetic text therefore rewards depth that real traffic does not repay. Every depth above was chosen on real prompts or real Claude Code sessions.

A separate effect of the same kind: DFlash2’s published depth-16 acceptance was 93.1 % on two GB10s, against 14–28 % measured here on a different NVFP4 export of the target (cause not isolated).

Current values

ProfileDrafterDepthBasis
Qwen3.8 Flash-Next MoE, vLLMMTP4real Claude Code sessions: 145.2 tok/s at depth 4 vs 119.0 at depth 2
Qwen3.8 Flash-Next MoE, SGLangMTP (NEXTN)4not swept on SGLang; the 2026-10-05 seat sweep ran without the drafter (it did not load then)
Qwen3.8 27B dense, vLLMDFlash24table above, 2026-10-03
Qwen3.8 27B dense, SGLangDFlash2498.4 tok/s over 13,210 requests at 32 seats, 2026-10-03/04
GLM-5.3-Flash, vLLMMTP4not measured on this pair: a published 2x DGX Spark run (76.2 tok/s at 8 concurrent; depth 3 best single-stream)
DeepSeek-V4-Flash, vLLMDSpark0drafter does not load yet; recipe value 5. Without it: 28.8 tok/s, 1 stream

Related settings: concurrency (decides where the depth crossover sits), prefix caching (what deeper drafts pushed out), KV type (FP4 KV and a drafter do not combine on this pair).

Questions? Let's Chat :)

vllm settings