跳到正文
北京时间
原文
LMSYS:Blog(Chatbot Arena 团队)· SGLang Team·· 2026-07-06精选AI 评分63

SGLang 集成 DSpark 推测解码:置信度驱动的可变长度验证

Blog DSpark in SGLang: Speculative Decoding with Confidence-Driven, Variable-Length Verification Speculative decoding trades extra compute for fewer decode steps, and the trade sours as load grows: at batch size B with K speculative tokens the target verifies B K tokens every step, and past a po... SGLang Team

AI 导读

SGLang 团队将 DSpark 推测解码算法集成到开源推理引擎中。该算法采用半自回归块起草器一次生成一组 token,并利用置信度头与顺序温度缩放(STS)为每个请求动态分配可变验证长度,从而在高负载下裁剪无效验证成本。SGLang 支持密集模型(如 Qwen3)和稀疏模型(如 DeepSeek-V4),通过全 CUDA 图处理不规则的每请求验证长度。提供三种验证模式:`static`(全长)、`compact`(生产路径)和 `cap-accept`(接受上限测量)。还引入了零开销调度、基于离线成本表的在线调度器、融合 Triton 核等优化。在 H200 上使用 DeepSeek-V4-Flash 的测试中,DSpark 在整个并发扫描范围内比 MTP 和非推测基线实现了更优的吞吐量-延迟权衡。

推荐理由

DSpark 这版推测解码的工程落地比论文本身更有看头——SGLang 用 ragged CUDA Graph 和动态调度把 trimm 掉的计算按字节级回收,383 tok/s 的单请求速度对部署方是即时可用的性能增益。

正文 · AI 翻译

投机解码用额外的计算换取更少的解码步数,而随着负载增长,这笔交易会变得不划算:在 batch size 为 B、投机 token 数为 K 时,目标模型每一步要验证 B * K 个 token,超过某个临界点后,其成本就高于所节省的开销。DSpark 从两端同时入手——一个半自回归块草稿器(每次草稿前向就生成一整个块,因此接受率保持很高),以及由草稿模型自身置信度驱动的按请求可变的验证长度,从而不再去验证那些工作负载不太可能接受的 token。该算法及其收益来自 DSpark 论文。

SGLang 现已在稠密和稀疏模型(例如 Qwen3 和 DeepSeek-V4)上支持 DSpark。本文介绍的是这次集成(sgl-project/sglang#30261)。我们在一个开放的服务引擎上复现了论文收益的形态——即单用户加速比,以及随负载上升而收缩的验证预算——并描述了把这一调度转化为实际墙钟时间的工程:在参差不齐的按请求验证之上使用完整的 CUDA graphs(这样被裁剪后的 batch 重放的是真正更小的图,而不是填充过的图);一条感知重叠的投机路径,把调度器隐藏在 forward 之后;一个成本表 profiler,让调度器能够在线为每个请求设定验证预算;以及针对接受率上限的可观测性——否则这一上限会被裁剪所掩盖。硬件、引擎和流量都与论文不同,因此我们复现的是其机制和曲线,而非逐位复现其数字,下文每一个“更快”都是与我们自己的对照组相比测得的——除投机配置外完全相同。

相对 MTP 和非投机解码的加速比

Aggregate throughput vs. per-user decode speed on H200 dp4, one curve per arm: non-spec floor, MTP, and DSpark. Right-and-up is better; each marker is a batch size averaged over three rounds.

图 1. 聚合吞吐量(y 轴)与单用户解码速度(x 轴);每条曲线将并发数从 batch 1 扫描到 256,每条曲线对应一个方案。越高越靠右越好。

在整个并发扫描范围内,DSpark 都实现了最佳的吞吐量/延迟权衡,在图 1 的示例中明显领先于 MTP 和非投机解码基线。三个方案均在 H200 上运行 DeepSeek-V4-Flash,采用跨四个 rank 的 DP-attention,除投机配置外完全相同——非投机解码基线、MTP(EAGLE 风格的基线,取 1-1-2 和 3-1-4 配置在各 batch size 下的最优值),以及 DSpark。

在 SGLang 中采用 DSpark

DSpark 算法取自该论文,由三个草稿侧组件构成:

  • 块草稿器——包含一条稠密线(如 Qwen3)和一条稀疏线(如 DeepSeek-V4);一次前向传播即可输出一个 gamma token 的块,并配有一个轻量级的顺序头(Markov 或 RNN),使每一步都以上一个 token 为条件,因此该块是半自回归的。
  • 置信度头——为每个草稿 token 通过验证的概率打分;整个块的乘积即为该块的存活概率。
  • 顺序温度缩放(STS)——对这些分数进行校准,使存活概率反映调度器所依据的真实接受率。

围绕这一点,SGLang 增加了服务支持层面:

  • 置信度调度器——每一步将每个块的存活概率转换为每个请求的验证预算。
  • 按请求的不规则验证——在一个批次内每个请求具有可变的验证长度(static / compact / cap-accept)。
  • 完整 CUDA graph——在不规则、可变长度的验证上捕获。
  • 可观测性——裁剪下的接受上限及其他指标。
  • 可加性 SPS 成本表——一个离线剖析的步时模型,由调度器在线读取。
  • 数据并行注意力——与其他并行维度一同支持。
  • 零开销调度——集成到 SGLang 的重叠调度器中,几乎没有 DSpark 特有的特殊处理。
  • 性能优化——融合的 Triton kernel 和分片的 block-drafter matmul。

验证模式

三种验证模式是本文其余部分所围绕的核心轴线。static 每一步都验证完整的草稿块(基线)。compact 只验证调度器所选取的每请求窗口——即生产路径。cap-accept 验证完整块,但只提交到该窗口为止:输出与 compact 相同,同时暴露出完整验证本会接受的内容——这正是我们衡量裁剪之下上限的方式。

完整 CUDA 图下的不规则验证

每请求窗口无法适配固定形状的 CUDA 图:一个批次中某个请求验证两个 token、另一个验证六个 token,就不存在单一的查询长度,而把所有人填充到完整块宽度,只会把裁剪又填回来。因此我们让批次保持不规则,并以 总 token 数作为图的键——将变长请求前向紧凑打包进一个缓冲区,并向上取整到最近的已捕获档位。当预算裁剪时,打包后的总数降到更小的档位,DSpark 便重放一个真正更廉价的图(更少的注意力行和 MLP 行,而非掩码的全宽前向);在 DP 注意力下,各 rank 共享同一档位(任一 rank 所需的最大档位)并一同降档。

打包后的缓冲区是一种 cu_seqlens 风格的 varlen 输入,因此紧凑验证复用了后端已有的注意力 kernel——在 DeepSeek-V4 上即模型自身的 sparse-MLA 路径(flash_mla),无需新 kernel;每个受支持的后端只需在图重放时根据打包布局重建其 varlen 元数据。

A fixed-shape decode graph pads every request to the full block width (N x W = 18 cells, 8 of them padding); the ragged compact graph front-packs the scheduled tokens into one buffer and rounds only the total up to the nearest captured tier (12 cells, 2 of them padding). Both run their padding through the forward, so ragged computes far fewer padded cells.

图 2. 将一批具有逐请求可变验证长度的请求拟合进捕获的 CUDA 图中。固定形状的图会将每个请求填充至完整的块宽度(N x W);而不规则路径则将已调度的 token 前向紧凑排列,仅将总数向上取整到最近的已捕获层级,从而在相同的已接受 token 数下计算出远少的填充单元。

可观测性

裁剪会遮蔽上限:紧凑模式仅验证一个块的前几个位置——即调度器的窗口——因此一个完整块验证在该步本可接受多少 token 从未被观测到——没有它,你无法区分一次好的裁剪与一次有损的裁剪。一次上限接受运行可以恢复它:它验证完整块但仅提交到窗口为止,因此它提交的内容与紧凑模式完全一致,同时暴露出上限。我们还呈现逐请求的置信度和校准指标(例如 ECE)以供事后分析。

在裁剪下估计上限

一种块接受估计器,专为生产运行或其他不需要额外伴随运行的场景而设计,可直接在紧凑运行内部恢复估计的被遮蔽上限。它利用目标 token 在未来步骤中的利用率及其 logprobs 来实现,并计算反事实尾部的估计区间,假设锚定 token 在裁剪与未裁剪轨迹中具有性质相似性。

动态调度与固定调度的初步观察

置信度调度器是第一个、最朴素的版本,我们也正是这样看待它的——它证明了该机制能够端到端地运作,而不是一个经过高度调优的结果。我们在两个接受率不同的示例工作负载上,将 compact(每步 SPS-argmax 预算)与 no-trim 进行对比——后者是 static 全块调度经过同一条不规则路径运行的结果。

图 3. compact(动态裁剪)对比 no-trim(全块),在 DP4 下 batch 从 1 到 256,基于两个接受率不同的示例。越高越靠右越好。

动态预算的优势主要是一种高 batch 效应。在 batch size 为 1 时,目标验证不会因为 token 增多而明显变慢,因此裁剪节省甚微,两条曲线打平。随着并发量增长、吞吐开始进入平台期,裁剪缩短了步长,compact 便取得领先。在接受率较低的示例上,差距更大、出现得更早——较低接受率留下了更多可裁剪的尾部,这恰好与成本模型的预测一致。

每个面板都是一次干净的 compact 对 no-trim 的 A/B 对比(同一面板内设置完全一致),但这两个示例并非严格意义上的单变量配对:除了接受率之外,它们在设置上也略有差异(提示词格式和每臂的轮数),因此我们读取的是它们之间的趋势,而非跨面板的绝对数值。

这些预算的优劣也取决于其背后的成本表。我们当前的 SPS(以及校准)拟合只是初步近似,可能尚未完全考虑步成本如何随上下文长度变化——因此调度器最终落到的确切工作点很可能仍有改进空间,我们在此呈现的是机制本身,而非一个调优后的数值。

混合流量下的按请求差异化

同质化的扫描掩盖了置信度调度的真正要点。同一批次中的两个请求,如果一个远比另一个更可预测,就不应该获得相同的验证窗口。混合流量正是这一点发挥关键作用的地方。

Per-dataset verify budget (left): ceiling/window/delivered tokens per verify step for gsm8k, arena-hard, and poetry under cap-accept; and per-step verify-length distribution (right) for the three workloads.

图 4. 按工作负载划分的预算(左)与逐步验证长度分布(右)。

举例来说,我们按接受难度混合了三种工作负载:gsm8k(高)、arena-hard(中)和 poetry(低)。窗口随难度收缩——分别为 5.24、3.78、2.91 个 token——而相对于上限(即该块在未裁剪情况下会接受的内容)的利用率仍保持高位(0.88–0.97)。调度器是在为每个请求单独定尺寸,而不是套用某个批次平均值。右图逐步展示了这一点:约 55% 的 gsm8k 步骤填满了六个的完整窗口,而约 80% 的 poetry 步骤只用了三个或更少。

性能优化与零开销调度(ZOS)

有两类工程把调度转化为实际墙钟时间:削减每一步的成本,以及把调度器隐藏在 forward 之后。二者结合,在 DeepSeek-V4-Pro、TP=8、B300 上,batch size 为 1 时,接受长度约 5 的情况下达到了 383.7 tok/s。

我们把大量微小算子组成的集群重写为融合的 Triton kernel,例如紧凑 scatter、SWA page-index、verify-length top-k 调度以及 ragged-window packing。block drafter 的采样路径被折叠进融合 kernel,其矩阵乘法也进行了分片。在一个示例 profile 中,目标 verify 之外的部分减少了 1.7 ms,而 verify 本身为 7.3 ms。

DSpark 几乎无需特殊处理即可直接接入 SGLang 的零开销(overlap)调度器,并加入论文中的两步回退置信度中继。这其中几乎没有多少是 DSpark 专属的管线。SGLang 的 spec-v2 运行时已经将下一步的调度与当前前向计算在独立 stream 上重叠执行,而 DSpark 作为一等 worker 加入:前向输出以异步 future 形式返回,跨迭代的顺序依赖运行时的设备端 barrier,设备端 page table 意味着无需每步的 host 同步。置信度中继使用同一通道,读取两步之前的数据。解码循环随后运行时没有每步气泡——比关闭调度器时紧凑约 1.5 倍。

Decode at batch size 1: overlap scheduler off (top) opens bubbles between run_batch iterations and between the draft-generate and target-verify phases; on (bottom) runs them all back-to-back.

图 5. batch size 为 1 时的解码,overlap 调度器关闭(上)与开启(下)。开启后,run_batch 迭代之间或一步内 block-draft-generate 与 target-verify 阶段之间都不存在气泡。

对成本表进行 profile

Additive SPS cost-table fit — raw step time vs. fit (a) and throughput (b) — and SPS-predicted vs. measured decode-step time (c), DeepSeek-V4 on H200.

图 6. 加性成本模型——原始值与拟合值(a)与吞吐量(b)——以及预测与实测的步时间(c)。

我们用加性模型来表达调度器对步时间的估计 T(bs, K) —— K 该批次的额外验证 token —— 即 T(bs, K) = bias + alpha(bs) + theta(M), M = bs + K,其中 alpha(bs) 是请求扩展的基线开销(草稿阶段加上部分注意力),不因裁剪而改变;theta(M) 是目标模型的验证 token 开销,也是裁剪唯一能回收的项。调度器的 argmax 在预期接受 token 数与真实边际成本之间做权衡,因此裁剪带来的余量只在 theta 较大时才会显现。图 6(c) 在实时服务器上验证了该模型的预测。

下一步

DSpark 目前已在 SGLang 中;我们在 sgl-project/sglang#30344 中跟踪路线图。下一步:

  • 成本模型与调度 —— 一个更强、日益在线/自适应的成本模型,以及对动态调度器的进一步改进。
  • 模型覆盖 —— 更多稠密与稀疏模型。
  • 并行 —— 覆盖更广泛的并行模式与服务拓扑。
  • 可观测性 —— 将块接受估计器和跨检查点的置信度校准等指标投入生产。
  • 鲁棒性 —— 加固全 CUDA 图路径,并开展更广泛的压力测试 / 回归测试。

感谢 DSpark 的作者们以及 DeepSeek 提供了算法和模型。

附录:复现

以下所有命令均在预构建镜像(docker pull lmsysorg/sglang:dev-dspark)内运行,或从 sgl-project/sglang#30261 的源码构建,固定于 commit 692c5f7d。

图 1、3 和 6——前沿服务器(DeepSeek-V4-Flash,H200,DP4)。启动 DSpark 分支:

SGLANG_ENABLE_METRICS_DEVICE_TIMER=1 \
python3 -m sglang.launch_server \
  --model-path deepseek-ai/DeepSeek-V4-Flash-DSpark \
  --speculative-algorithm DSPARK \
  --tp 4 --dp-size 4 --enable-dp-attention --enable-dp-lm-head \
  --moe-a2a-backend none --moe-runner-backend flashinfer_mxfp4 --disable-flashinfer-autotune \
  --swa-full-tokens-ratio 0.1 --chunked-prefill-size 1024 \
  --mem-fraction-static 0.8 --cuda-graph-max-bs 192 --max-running-requests 1024 \
  --disable-radix-cache --trust-remote-code --host 0.0.0.0 --port 30000

其中 --disable-radix-cache 是为了避免基准测试脚本命中缓存。其他分支仅更改推测配置:non-spec 去掉 --speculative-* 并加载 --model-path deepseek-ai/DeepSeek-V4-Flash;MTP 使用相同的 target,配合 --speculative-algorithm EAGLE --speculative-num-steps {1,3} --speculative-eagle-topk 1 --speculative-num-draft-tokens {2,4}(按 batch size 取两者中的最优);DSpark compact 或 static 设置 SGLANG_RAGGED_VERIFY_MODE=compact|static;在执行 compact 模式并使用 SPS 表时使用 --speculative-dspark-sps-table-path sps_table.json;而图 3 的 no-trim 分支为 SGLANG_RAGGED_VERIFY_MODE=compact,不带 SPS 表(全窗口下的 ragged 路径)。用固定提示词在 batch size 上扫描来驱动任意分支:

python3 -m sglang.benchmark.one_batch_server \
  --model None --base-url http://127.0.0.1:30000 \
  --batch-size 1 8 16 32 64 96 128 160 192 256 --output-len 1024 --temperature 0.7 \
  --fixed-prompt-file frontier_prompt.txt --fixed-prompt-apply-chat-template --show-report

固定提示词在 此处(frontier_prompt.txt),由 16 个拼接的 GSM8K 问题组成,以便生成内容是真实内容。用户可以在自己的数据上测试,因为推测解码在不同数据集上有不同的接受长度。

图 6 的成本表来自一次性能剖析运行:启动compact使用SGLANG_DSPARK_ENABLE_SPS_RECORD=1 SGLANG_SIMULATE_ACC_LEN=1.0,然后用以下方式拟合加性模型python3 -m sglang.benchmark.dspark_sps_profiler all(在输入长度为 512 时扫描 batch × verify-fraction 网格)。

图 4 —— 混合流量。与图 1 相同的服务器,在--mem-fraction-static 0.7块大小为六的情况下;运行全部三种模式(static / compact / cap-accept)通过SGLANG_RAGGED_VERIFY_MODE,并驱动一个混合的 gsm8k + arena-hard + poetry 请求集,以非流式 makespan 吞吐量来衡量。

图 5 — 零开销(DeepSeek-V4-Pro,B300,TP8)。

SGLANG_RAGGED_VERIFY_MODE=compact SGLANG_DSV4_FP4_EXPERTS=1 SGLANG_TORCH_PROFILER_DIR=./trace \
python3 -m sglang.launch_server \
  --model-path deepseek-ai/DeepSeek-V4-Pro-DSpark --speculative-algorithm DSPARK \
  --tp 8 --moe-runner-backend flashinfer_mxfp4 --disable-flashinfer-autotune \
  --mem-fraction-static 0.82 --chunked-prefill-size 4096 --cuda-graph-max-bs 4 \
  --trust-remote-code --host 127.0.0.1 --port 30000

捕获一个 batch-1 解码 trace,然后读取仅 GPU 通道:

python3 -m sglang.benchmark.one_batch_server \
  --model None --base-url http://127.0.0.1:30000 \
  --batch-size 1 --input-len 256 --output-len 256 \
  --profile --profile-activities GPU --profile-steps 20

来源:LMSYS:Blog(Chatbot Arena 团队) · lmsys.org