DSpark:半自回归生成与置信度调度的投机解码

笔记日期 / Review date: 2026-07-08 笔记作者 / Review author: Zhongzhu Zhou 论文标题 / Paper reviewed: DSpark: Confidence-Scheduled Speculative Decoding with Semi-Autoregressive Generation 作者 / Paper authors: Xin Cheng, Xingkai Yu, Chenze Shao, Jiashi Li, Yunfan Xiong, Yi Qian, Jiaqi Zhu, Shirong Ma, Xiaokang Zhang, Jiasheng Ye, Qinyu Chen, Chengqi Deng, Jiping Yu, Damai Dai, Zhengyan Zhang, Yixuan Wei, Yixuan Tan, Wenkai Yang, Runxin Xu, Yu Wu, Zhean Xu, Xuanyu Wang, Muyang Chen, Rui Tian, Xiao Bi, Zhewen Hao, Shaoyuan Chen, Huanqi Cao, Wentao Zhang, Anyi Xu, Huishuai Zhang, Dongyan Zhao, Wenfeng Liang arXiv: 2607.05147 状态 / Venue: 预印本(2026 年 7 月 — 北京大学、DeepSeek-AI)

一句话总结

并行投机解码的大草稿模型可以在一次前向传播里提出很多候选 token,但因为每个位置都是独立预测的,越靠后的位置越容易出现「接受率衰减」。DSpark 用一个半自回归草稿模型来修这个问题——保留一个快速的并行主干,再叠加一个极轻量的顺序修正头,只注入「刚好够用」的块内依赖来阻止衰减——然后再配上一个置信度调度的验证器,把「这一轮到底该把多少个草稿 token 真正送去目标模型验证」这个问题,严格建模成一个可证明无损、且能感知系统负载的吞吐最大化问题,并给出精确的贪心解。这套方案已经取代了 DeepSeek-V4 生产服务栈里原来的单 token MTP-1 草稿模型,在匹配的总吞吐水平下把单用户生成速度提升了 60-85%(V4-Flash)和 57-78%(V4-Pro)——更重要的是,它解锁了旧基线完全无法支撑的严格低延迟服务档位。

前置知识

这篇论文完全落在 LLM 推理系统这个方向里。下面从零开始搭建所有必要概念,让后面的架构和调度部分可以独立读懂。

为什么自回归解码天生慢

Transformer 语言模型是逐 token 生成文本的。要生成 token xn+1x_{n+1},模型需要在 x1,,xnx_1, \ldots, x_n 的条件下做一次完整的前向传播;采样出 xn+1x_{n+1} 后把它拼回上下文,再重复这个过程。这就形成了硬性的顺序依赖:你不可能在还不知道 xn+1x_{n+1} 之前就算出 xn+2x_{n+2}

在现代 GPU 上,这对硬件利用率是灾难性的。一次解码步要把整个模型的权重(前沿模型动辄几百 GB)从 HBM 搬到计算单元里,却只为每个序列产出一个新 token。算术强度——每搬运一个字节能做多少浮点运算——极低,所以解码是显存带宽瓶颈,而不是算力瓶颈。GPU 里真正花钱买的矩阵运算单元大部分时间是闲着的。这正是投机解码要利用的核心低效之处:如果能让模型只用一次昂贵的权重读取,就「敲定」好几个 token,就等于把闲置的算力转换成了有用功。

KV Cache

为了避免每一步都重新计算之前所有 token 的注意力 key 和 value,Transformer 会逐层缓存它们。随着上下文变长,KV Cache 也线性增长,占用的显存原本可以用来支撑更大的并发批量。这一点对 DSpark 很关键:正如第 5 节将看到的,论文的生产部署把「这块 GPU 现在还有多少解码批量容量可用」明确当作一种要在普通请求和投机解码验证之间公平分配的资源。

多 Token 预测(MTP)以及为什么 “MTP-1” 是要打败的基线

DeepSeek 自家的模型(从 DeepSeek-V3 开始)都用一个辅助的多 Token 预测(Multi-Token Prediction)目标训练:除了预测紧邻的下一个 token,模型还会用挂在主干上的几个轻量预测头,学习预测再往后几个 未来 的 token。推理时,这些 MTP 头可以直接被当作一个内置、随时可用的草稿模型——不需要单独训练或部署一个草稿模型,因为这个能力已经烤进了目标模型自己的 checkpoint 里。“MTP-1” 具体是指只用一个 MTP 头,每轮草拟恰好一个额外 token(即 γ=1\gamma=1)。这篇论文把 MTP-1 当作生产基线,是因为历史上部署一个静态的、更大 γ\gamma 的多 token 草稿模型(比如”MTP-3”或”MTP-5”,每轮无条件草拟 3 或 5 个 token)曾被发现在高并发下会拖累总吞吐——一旦很多并发请求争抢 GPU 批量容量,那个固定的、更大的验证代价就不划算了。这正是 DSpark 要避免重蹈的历史失败模式:与其挑一个在轻载和重载之间做妥协的静态 γ\gamma,DSpark 可以提出一个大的 γ\gamma同时根据实时负载动态收缩真正要验证多少——在轻载时拿到大块草稿的好处,又不用承担那种曾让静态 MTP-3/5 在生产环境中不可行的重载代价。

投机解码:先起草,再验证

投机解码(Chen et al., 2023;Leviathan et al., 2023)把便宜的提议和昂贵的验证拆开:

  1. 一个轻量的草稿模型 MdM_d 提议一个包含 γ\gamma 个候选 token 的块 x1,,xγx_1, \ldots, x_\gamma
  2. 昂贵的目标模型 MtM_t一次前向传播中验证整个块——因为注意力机制允许它并行处理全部 γ\gamma 个候选位置,就好像它们已经是上下文的一部分一样。
  3. 验证使用拒绝采样:在位置 kk,以概率 min(1,ptk(xk)/pdk(xk))\min(1, p_t^k(x_k)/p_d^k(x_k)) 接受 xkx_k,其中 ptkp_t^kpdkp_d^k 分别是目标模型和草稿模型在该位置的分布。第一次在位置 kk 被拒绝时,后面的一切都被丢弃,目标模型会重新采样一个”补偿” token 替换掉被拒绝的位置。
  4. 关键在于,这条接受规则的设计使得被接受输出的边缘分布在数学上与目标模型单独生成的分布完全一致。投机解码是纯粹的延迟优化——它是无损的。

τ\tau 为每轮实际被接受的 token 数(含补偿 token),TdraftT_{draft}TverifyT_{verify} 分别为起草和验证两个前向传播的墙钟时间。平均每 token 延迟为:

L=Tdraft+Tverifyτ(1)L = \frac{T_{draft} + T_{verify}}{\tau} \tag{1}

这一个公式就是全文的组织原则。让生成更快只有三条路:缩短 TdraftT_{draft}(起草更便宜)、增大 τ\tau(起草更准,也就是提高接受率)、或者缩短有效TverifyT_{verify}(只验证值得验证的部分)。DSpark 的每一个设计决策,都可以映射到这三个杠杆之一。

一个具体数值例子。 假设目标模型单独解码每 token 耗时 20 ms(自回归生成 100 个 token 需要 2000 ms)。现在假设一个草稿模型提议 γ=8\gamma = 8 个 token,起草耗时 Tdraft=3T_{draft} = 3 ms,验证这 8 个 token 的块耗时 Tverify=25T_{verify} = 25 ms(并行验证一小块的代价只比验证一个 token 略高,因为目标模型的前向传播主要被从显存加载权重的时间主导,而不是多验证几个位置带来的少量额外计算),平均每轮接受 τ=3.5\tau = 3.5 个 token(含补偿 token)。那么每 token 延迟为 L=(3+25)/3.58.0L = (3 + 25)/3.5 \approx 8.0 ms——相比纯自回归解码约有 2.5×2.5\times 的加速。现在假设草稿模型升级后 τ\tau 提升到 5.5,而 TdraftT_{draft}TverifyT_{verify} 不变:L=28/5.55.1L = 28/5.5 \approx 5.1 ms,加速比达到 3.9×3.9\times。这正说明了为什么论文如此聚焦于提高 τ\tau(而不是比如进一步压缩 TdraftT_{draft})——一旦 TdraftTverifyT_{draft} \ll T_{verify}(并行草稿模型正是这种情况),接受长度 τ\tau 就是式 (1) 中杠杆效应最大的量。

连续批处理,以及并发为何改变了整个计算方式

生产级 LLM 服务系统很少一次只服务一个请求。**连续批处理(Continuous Batching)**把许多并发请求的解码步骤打包进同一次前向传播,于是目标模型在一次 GPU 调用里同时处理,比如说,128 个不同用户的下一 token 预测。这正是 LLM 服务能做到经济实惠的原因:从 HBM 加载模型权重这一固定成本被摊到了这一步里全部 128 个用户的预测上,而不是每个用户单独付一次。

投机解码与批处理的交互方式非常具体、也非常重要:对一个请求验证 γ\gamma 个草稿 token,从目标模型的视角看,等价于在这一步给这个请求的批量多加了 γ\gamma插槽(每个候选位置一个插槽)。如果 RR 个并发请求每个都提议 γ\gamma 个 token,有效批量大小会从 RR 膨胀到大约 R×(1+γ)R \times (1+\gamma)。由于 GPU 吞吐量(以步/秒衡量)会随批量增大而下降——先是缓慢下降,超过 GPU 的算力瓶颈区间后下降更陡——盲目地为每个请求都最大化 γ\gamma,反而可能缩小 GPU 单位时间能服务的请求批量,哪怕单独看每一个被验证的块本身效率很高。这正是 DSpark 的硬件感知调度器要解决的张力:它把”值不值得为这个特定的草稿 token 多花一个批量插槽”当作一个一等公民的调度决策,而不是一个每请求固定的配置旋钮。

自回归草稿模型 vs. 并行草稿模型

自回归草稿模型(比如 EAGLE 系列,每个草拟位置都条件于前一个已采样的草稿 token 的小型 Transformer 头)能捕捉强烈的顺序依赖,但起草成本随块大小线性增长:TdraftγT_{draft} \propto \gamma。这迫使它们只能做得浅、块做得短,否则起草本身的开销会吃掉全部收益。

并行草稿模型(比如 Medusa、DFlash)在一次前向传播里预测全部 γ\gamma 个位置,只条件于目标模型的上下文,彼此之间互不依赖。这让 TdraftT_{draft} 几乎与 γ\gamma 无关,于是并行草稿模型在同样的延迟预算下可以负担更深的网络、更大的块。代价是:由于每个位置都是对所有可能的前驱边缘化预测的,而不是条件于实际采样出来的那一个 token,并行草稿模型容易出现多模态碰撞——比如在位置 1 独立预测出”的”,在位置 2 又独立预测出”问题”,拼出不连贯的短语”的问题”,而两个局部合理的延续本应是”的确”和”没问题”。这一节的洞察正是 DSpark 要解决的张力所在。

服务系统概念:吞吐、延迟与 SLA

在生产环境中,推理引擎会把许多并发用户请求打包在一起处理。两个指标需要同时兼顾:总吞吐(一块 GPU 上所有用户产出 token 的总速率)和单用户生成速度,通常以每用户每秒 token 数(TPS)报告。**服务等级协议(SLA)**规定了系统必须保证给每个活跃用户的最低 TPS。这两个指标彼此权衡:验证更长的投机块可以提高某个用户的 TPS,但会占用本可服务更多并发用户的 GPU 批量容量,从而拉低总吞吐。本笔记后面会展示 DSpark 的调度器是如何显式地管理这个权衡的,而不是回避它。

符号速查表

由于这篇论文在起草、校准、调度各节里用到了相当密集的符号集合,先把它们全部列在一处会更方便后面阅读:

符号含义
MtM_t, MdM_d目标模型(大而权威)与草稿模型(小而便宜)
γ\gamma每轮提议的草稿 token 数(块大小)
τ\tau每轮实际被接受的 token 数(含补偿 token)
TdraftT_{draft}, TverifyT_{verify}起草和验证两个前向传播的墙钟时间
LL每生成一个 token 的平均延迟,L=(Tdraft+Tverify)/τL = (T_{draft}+T_{verify})/\tau
ptkp_t^k, pdkp_d^k目标模型和草稿模型在草拟位置 kk 的概率分布
UkU_k并行主干在位置 kk 的基础 logits
BkB_k叠加在 UkU_k 之上的顺序修正偏置
hkh_k主干在位置 kk 的隐藏状态
x0x_0锚点 token(上一轮的补偿 token)
W1,W2W_1, W_2Markov 头 bigram 偏置的低秩分解矩阵
rr低秩分解的秩(默认 256)
sks_kRNN 头在位置 kk 的循环状态
ckc_k置信度头的原始输出:给定前缀已被接受时,位置 kk 存活的预测概率
ckc_k^*用作 ckc_k 训练标签的解析(真实)接受概率
ar,ja_{r,j}请求 rr 到位置 jj 为止的累积前缀存活概率,ar,j=ijcr,ia_{r,j}=\prod_{i\le j} c_{r,i}
r\ell_r请求 rr 被调度到的验证长度
RR当前批次中的并发请求数
BB(调度器语境下)总验证批量大小(token 数),B=r(1+r)B=\sum_r(1+\ell_r)
SPS(B)SPS(B)经过性能剖析得到的、随批量大小变化的引擎吞吐(步/秒)
Θ\Theta系统级期望 token 吞吐,Θ=τSPS(B)\Theta = \tau \cdot SPS(B)
wkw_k训练损失中的位置权重,wk=exp((k1)/γ)w_k = \exp(-(k-1)/\gamma)
Lce,Ltv,Lconf\mathcal{L}_{ce}, \mathcal{L}_{tv}, \mathcal{L}_{conf}交叉熵、全变差、置信度三种训练损失

问题所在:横亘在并行草稿模型与其理论上限之间的两个瓶颈

并行草稿模型看起来像是免费午餐:TdraftT_{draft} 开销近乎为零,那就应该把 γ\gamma 拉得越大越好。但有两个瓶颈挡在前面。

瓶颈一——生成质量(后缀衰减)。 因为并行草稿模型独立预测每个草拟位置、无法条件于前面位置实际采样出的 token,它的准确率会随着块内位置的深入迅速下降。论文自己的测量结果(复现为下文图 5)表明,这种衰减是真实存在的,而且因领域而异。

瓶颈二——系统效率(验证浪费)。 假设草稿模型提议了 16 个 token。验证整个块的代价,对目标模型来说大致等同于验证 16 个真实 token,无论实际接受了多少个。如果真实期望接受数只有 4 个 token,那么多验证的 12 个几乎没什么收益,却占用了本可服务其他并发请求的批量容量。这种浪费在高并发下最严重——而这恰恰是服务系统最需要榨干每一分 GPU 吞吐的时候。

图 1 总结了这个设计空间,并展示了 DSpark 在其中的位置。

图 1:草稿模型设计空间与 DSpark 的定位

graph TD
    A[投机解码草稿模型设计] --> B[自回归草稿模型]
    A --> C[并行草稿模型]
    A --> D[DSpark:半自回归]
    B --> B1["单 token 质量高<br/>T_draft 随 gamma 线性增长<br/>被迫使用小块"]
    C --> C1["T_draft 几乎与 gamma 无关<br/>可用大块<br/>独立性导致后缀衰减"]
    D --> D1["并行主干:O(1) 起草延迟"]
    D --> D2["轻量顺序修正头:<br/>注入块内依赖"]
    D --> D3["置信度调度验证器:<br/>感知负载、无损截断"]
    style D fill:#4CAF50,color:#fff
    style D1 fill:#c8e6c9
    style D2 fill:#c8e6c9
    style D3 fill:#c8e6c9

DSpark 用两个互补的机制同时攻克这两个瓶颈:半自回归生成(论文 3.1 节,下文详解)在算法层面修复质量问题,置信度调度验证(论文 3.2 节,后文详解)在调度层面修复系统效率问题。这篇论文有意思的地方在于,这两个机制都不是简单缝在一起的启发式规则——调度器是作为吞吐最大化问题的精确解推导出来的,并附有严格的无损性证明,其中包括作者用来说明”为什么自己算法的一个朴素版本会悄悄破坏正确性”的反例,本文会详细走一遍。

架构第一部分:半自回归生成

回顾 DFlash 并行主干

DSpark 的并行主干建立在 DFlash(Chen et al., 2026)之上——一个当时最先进的并行草稿模型,所以有必要先理解 DFlash 的机制。预填充阶段,DFlash 从目标模型选定的若干层 {l1,,lm}\{l_1, \ldots, l_m\} 中提取隐藏状态,拼接起来,再投影进草稿模型的隐藏空间:

Hctx=RMSNorm(Wc[H(l1);;H(lm)])(2)H_{ctx} = \text{RMSNorm}\big(W_c [H^{(l_1)}; \ldots; H^{(l_m)}]\big) \tag{2}

这些上下文特征随后被注入草稿模型的每一个注意力层,方法是把它们沿序列维度和草稿块自身的 key、value 拼在一起:

Ki=[WiKHctx;  WiKHd],Vi=[WiVHctx;  WiVHd](3)K_i = [W_i^K H_{ctx};\; W_i^K H_d], \qquad V_i = [W_i^V H_{ctx};\; W_i^V H_d] \tag{3}

这样每个草拟位置都能双向地关注块内其余位置以及被注入的目标上下文。草稿模型共享目标模型(冻结的)嵌入表和语言模型头,输入是一个锚点 token 嵌入加上 γ\gamma 个 mask token 嵌入,一次前向传播就为所有 mask 位置输出 logits。由于起草成本几乎与块大小无关,DFlash 在同样的延迟预算下既能用更深的网络,也能用比自回归草稿模型更大的 γ\gamma——这正是式 (1) 中”TdraftT_{draft} 坍缩为 O(1)O(1)“这一让并行起草具有吸引力的性质。

DSpark 对这个主干做了一处小而实用的改动:不再是”输入一个锚点 token 加 γ\gamma 个 mask token,只预测这 γ\gamma 个 mask 位置”,而是把锚点本身也当作第一个预测位置。于是 γ\gamma 个输入 token(锚点 + γ1\gamma-1 个 mask)就能产出 γ\gamma 个草拟 logits,而不是 γ+1\gamma+1 个输入才产出 γ\gamma 个 logits——这是一个不损失质量的小型算力节省,因为锚点自身的概率分布本来就是已知信息,值得在同一次前向传播中一并预测出来。

为什么单纯并行还不够:多模态碰撞的形式化描述

考虑一个允许两种合理的两 token 延续的上下文:“的确”和”没问题”。并行草稿模型独立地对所有前驱边缘化,分别计算 p1()p_1(\cdot)p2()p_2(\cdot),于是第一个位置可能采样出”的”(在 p1p_1 下局部合理),而第二个位置由于从未真正见过”的”这个已采样 token,独立地采样出”问题”(在边缘分布 p2p_2 下也局部合理)。拼出的草稿”的问题”是不连贯的——不是因为任何一个单独的 token 预测得不好,而是因为联合序列从未被真正建模过。这种失败模式在非自回归生成文献中早已被记录(Gu et al., 2018),是并行模型把联合分布因子化为独立边缘分布所付出的代价。

顺序修正:在并行 logits 之上叠加一个自回归因子化

DSpark 的修法不是放弃并行生成,而是在并行主干的基础 logits UkU_k 之上叠加一个依赖前缀的转移偏置 Bk(x0,x<k,xk)B_k(x_0, x_{<k}, x_k)。作者没有选择定义一个新的、全局归一化的能量模型(正如下文相关工作一节所讨论的,那会破坏拒绝采样所需要的精确概率要求),而是让顺序阶段直接诱导出一个恰当的自回归因子化:

P(Xx0)=k=1γpk(xkx0,x<k),pk(vx0,x<k)=exp(Uk(v)+Bk(x0,x<k,v))uVexp(Uk(u)+Bk(x0,x<k,u))(4)P(X \mid x_0) = \prod_{k=1}^{\gamma} p_k(x_k \mid x_0, x_{<k}), \qquad p_k(v \mid x_0, x_{<k}) = \frac{\exp\big(U_k(v) + B_k(x_0, x_{<k}, v)\big)}{\sum_{u \in \mathcal{V}} \exp\big(U_k(u) + B_k(x_0, x_{<k}, u)\big)} \tag{4}

一步步拆开这个式子,因为它是整个方法的核心:

  • Uk(v)U_k(v) 是并行主干为候选 token vv 在位置 kk 计算出的基础 logit,对所有 kkvv 一次性并行算出——这部分依然便宜,因为只需一次前向传播。
  • Bk(x0,x<k,v)B_k(x_0, x_{<k}, v) 是一个逐位置顺序计算的小型修正项,把 logits 向与实际采样出的前缀 x<kx_{<k} 一致的 token 方向拉。
  • 把偏置加在 softmax 里面(而不是事后对概率做重新加权),保证 pkp_k 依然是词表上一个合法的归一化分布——这一点很关键,因为投机解码的拒绝采样规则需要对每个候选 token 都有一个精确、可计算的 pdk(x)p_d^k(x),而不仅仅是一个排序。
  • 连乘 kpk(xk)\prod_k p_k(x_k \mid \ldots) 就是标准的自回归链式法则因子化,只不过现在每一个因子都是”便宜的基础 logit + 便宜的修正”,而不是”以整个前缀为条件的完整 Transformer 前向传播”。

因为这个采样现在是顺序进行的(每个 pkp_k 都依赖实际采样出的 x<kx_{<k}),修正模块 BkB_k 必须在计算上非常小——论文的设计目标是 TsequentialTparallelT_{sequential} \ll T_{parallel},这样即使顺序阶段是一个循环,这个循环里跑的也只是一个轻量模块,整体起草延迟依然由那一次并行前向传播主导。这正是 DSpark 被称为”半自回归”的原因:完整的自回归结构,只作用在一个廉价的修正信号上,叠加在一个昂贵的并行主干之上。

实现一:Markov 头

最简单的修正把 BkB_k 限制为对紧邻前一个 token 的一阶 Markov 依赖,即 B(xk1,xk)B(x_{k-1}, x_k)。朴素地看,这需要一个完整的 V×VV \times V 矩阵(VV 为词表大小,通常在 10 万以上),存储和应用都会非常昂贵。DSpark 用一个低秩分解 B=W1W2B = W_1 W_2 来近似它,其中 W1RV×rW_1 \in \mathbb{R}^{V \times r}W2Rr×VW_2 \in \mathbb{R}^{r \times V},默认秩 r=256r = 256

B(xk1,)=W1[xk1]W2RV(5)B(x_{k-1}, \cdot) = W_1[x_{k-1}]\, W_2 \in \mathbb{R}^V \tag{5}

这里 W1W_1 相当于对前一个 token 做一次嵌入查表,W2W_2 再把这个嵌入投影回 logit 空间。设计理由是:在超过 10 万的词表上,一个满秩的 bigram 转移表既浪费(大多数 token 对都毫不相关)又慢(生成时每一步都要做一次 V×VV \times V 查表),而秩为 256 的分解能以极小的内存和计算代价捕捉到主要的局部共现结构(比如”的”会提升”确”、抑制”问题”),并且——关键的是——它可以用两次小矩阵乘法完成,而不是一次巨大的查表。

为什么是这个具体设计,以及它会在哪里失效: 显而易见的替代方案是让修正依赖不止一个前驱 token(比如完整的 n-gram 上下文),这能捕捉更多结构,但如果修正模块变重,就重新引入了并行起草本想消除的 O(γ)O(\gamma) 顺序代价。Markov 头的边界条件正是如此:它只能修复局部的模式碰撞(相邻一对不搭调的 token)。如果不连贯跨越了三个或更多 token——一个短语的第一个词和第三个词不一致,但相邻两两转移看起来都局部合理——Markov 头就完全无能为力。这促成了第二种实现。

实现二:RNN 头

RNN 头放松了一步记忆的限制,维护一个循环状态 sks_k,累积当前草稿块内到目前为止看到的整个前缀历史。每一步把前一状态 sk1Rrs_{k-1} \in \mathbb{R}^r、前一个 token 的 Markov 嵌入 W1[xk1]RrW_1[x_{k-1}] \in \mathbb{R}^r,以及主干的隐藏状态 hkRdh_k \in \mathbb{R}^d 拼接成一个向量 zk=[sk1;W1[xk1];hk]R2r+dz_k = [s_{k-1}; W_1[x_{k-1}]; h_k] \in \mathbb{R}^{2r+d},然后做一次门控循环更新(结构上是一个最简 GRU 风格的单元):

sk=σ(Wgzk)sk1+(1σ(Wgzk))tanh(Wczk),Bk(x<k,)=W2tanh(Wozk)(6)s_k = \sigma(W_g z_k) \odot s_{k-1} + \big(1 - \sigma(W_g z_k)\big) \odot \tanh(W_c z_k), \qquad B_k(x_{<k}, \cdot) = W_2^\top \tanh(W_o z_k) \tag{6}

其中 Wg,Wc,WoRr×(2r+d)W_g, W_c, W_o \in \mathbb{R}^{r \times (2r+d)} 全部打包进一次线性投影,再拆分成门/候选/输出三部分以提高效率,s0s_0 初始化为零。门 σ(Wgzk)\sigma(W_g z_k) 在”保留旧状态”和”写入新候选内容”之间插值——这正是 GRU 用来让梯度(以及推理时的信息)能跨多步流动而不消失的标准机制。

明确摆出来的设计权衡。 RNN 头原则上可以捕捉任意长程的块内依赖(只受 γ\gamma 限制,而 γ\gamma 本身很小,通常在 5-16 之间),这是 Markov 头做不到的。但论文自己的消融实验(4.3.2 节,后文详述)发现,RNN 头相对 Markov 头只带来边际收益,且集中在更长的提议长度上,而实现和部署起来更复杂。因此作者把 Markov 头作为默认配置——这是一个刻意的选择,用一点理论建模能力换取生产系统中的简洁性和可预测的延迟。这很好地体现了论文的整体设计哲学:每个组件的复杂度都恰好够用来弥补被明确识别出的那个缺口,而不是追求最大化的表达能力。

架构第二部分:置信度调度验证

生成一个高质量的大草稿块(第一部分)并不会自动转化为端到端加速,因为验证整个块并不是免费的。这一节是 DSpark 作为系统论文而非纯算法论文的地方。

置信度头

DSpark 挂了一个轻量置信度头,为每个草拟位置 kk 预测一个标量 ck(0,1)c_k \in (0,1),解读为:给定块内此前所有 token 都已被接受,位置 kk 的草拟 token 在目标验证中存活的条件概率。架构上它就是一个线性投影加 sigmoid:

ck=σ(w[hk;W1[xk1]])(7)c_k = \sigma\Big(w^\top [h_k;\, W_1[x_{k-1}]]\Big) \tag{7}

输入是主干的隐藏状态 hkh_k 和前一个草拟 token 的 Markov 嵌入。监督目标是解析的接受率——不是经验标签,而是直接从投机解码理论推导出的一个闭式量。由于某个位置的标准接受概率恰好等于 112pdpt11 - \tfrac{1}{2}\lVert p_d - p_t \rVert_1(草稿分布与目标分布之间总变差距离的一半——这一恒等式直接来自 Leviathan et al., 2023 中的拒绝采样构造),目标标签为:

ck=112pkdpkt1(8)c_k^* = 1 - \tfrac{1}{2} \lVert p_k^d - p_k^t \rVert_1 \tag{8}

这是一个巧妙的设计选择:作者没有用带噪声的经验接受/拒绝结果来训练置信度头(那需要每个位置采很多样本才能得到可靠标签),而是直接用理论所决定的精确量来监督。显而易见的替代方案——用二元接受/拒绝结果做交叉熵训练——虽然也可行,但收敛更慢、更嘈杂,因为每个训练样本只有一次接受/拒绝采样,是比精确解析率方差高得多的目标。

为什么原始置信度分数还不够:校准问题

DSpark 的置信度头与此前基于阈值的调度器(如 SpecDec++)有一个微妙的区别:那些方法只需要置信度分数能正确地排序草稿 token(分数越高,越可能存活)。而 DSpark 的硬件感知调度器(下一小节)需要累积乘积 ikci\prod_{i \le k} c_i绝对数值,用来计算一个可以在不同候选验证长度之间比较的期望吞吐数字。但神经网络的置信度估计早已被证明系统性地过度自信(Guo et al., 2017)——原始分数区分能力不错(论文报告 ROC-AUC 为 0.81-0.90),但校准很差(期望校准误差 ECE 为 3-8%),这会扭曲调度器的吞吐估计,导致系统性错误的调度决策。

顺序温度缩放(Sequential Temperature Scaling, STS) 是解决办法。因为每个 cic_i 建模的是一个条件概率,链式法则告诉我们前缀的联合存活概率因子化为累积乘积 ikci\prod_{i \le k} c_i。STS 在留出验证集上从左到右、逐位置校准这个乘积:在每个位置 k{1,,γ}k \in \{1, \ldots, \gamma\},对一个温度标量做简单的一维网格搜索,在固定住此前所有已校准位置的前提下,最小化累积乘积的期望校准误差。这里有两处设计细节值得注意:(1) 之所以要校准乘积而不是孤立地校准每个 ckc_k,是因为误差会沿链条乘性累积——一个略微过度自信的 c1c_1c2c_2 合在一起,会产出一个严重过度自信的 a2=c1c2a_2 = c_1 c_2;(2) 温度缩放是一个保序(单调)变换,所以它只修正数值大小,不会扰乱置信度头已经学到的排序——这一点很重要,因为它意味着校准是一个纯粹的”附加”步骤,不可能让底层的区分能力变得更差。

推导式 (8) 背后的接受率恒等式

正文中式 (8) 直接给出了 ck=112pkdpkt1c_k^* = 1 - \tfrac12\lVert p_k^d - p_k^t\rVert_1 而没有证明,但这个恒等式短到值得完整推导一遍,因为它是把”置信度”从任意的神经网络分数,变成一个可校准、有物理含义的量的关键支撑事实。

从拒绝采样规则本身出发:抽取候选 xpdx \sim p_d,以概率 min ⁣(1,pt(x)/pd(x))\min\!\big(1, p_t(x)/p_d(x)\big) 接受它。对这次抽样求边缘化,得到”新起草的草稿 token 被接受”的无条件概率:

P(accept)=xVpd(x)min ⁣(1,pt(x)pd(x))=xVmin(pd(x),pt(x))(8a)P(\text{accept}) = \sum_{x \in \mathcal{V}} p_d(x)\,\min\!\Big(1, \frac{p_t(x)}{p_d(x)}\Big) = \sum_{x \in \mathcal{V}} \min\big(p_d(x),\, p_t(x)\big) \tag{8a}

(第二个等号只是把 pd(x)p_d(x) 分配进 min\min 里,用到 pd(x)1=pd(x)p_d(x)\cdot 1 = p_d(x)pd(x)pt(x)/pd(x)=pt(x)p_d(x) \cdot p_t(x)/p_d(x) = p_t(x))。现在对求和号里的每一项,逐项使用初等的双变量恒等式 min(a,b)=12(a+bab)\min(a,b) = \tfrac12(a + b - |a-b|)(对任意实数 a,ba, b 都成立):

xmin(pd(x),pt(x))=12(xpd(x)+xpt(x)xpd(x)pt(x))(8b)\sum_{x} \min(p_d(x), p_t(x)) = \frac{1}{2}\left(\sum_x p_d(x) + \sum_x p_t(x) - \sum_x |p_d(x) - p_t(x)|\right) \tag{8b}

pdp_dptp_t 都是概率分布,所以 xpd(x)=xpt(x)=1\sum_x p_d(x) = \sum_x p_t(x) = 1,而根据定义,总变差距离为 pdptTV=12xpd(x)pt(x)=12pdpt1\lVert p_d - p_t\rVert_{TV} = \tfrac12\sum_x |p_d(x)-p_t(x)| = \tfrac12\lVert p_d-p_t\rVert_1。代入:

P(accept)=12(1+1)12pdpt1=112pdpt1(8c)P(\text{accept}) = \frac12(1 + 1) - \frac12\lVert p_d - p_t\rVert_1 = 1 - \frac12\lVert p_d - p_t\rVert_1 \tag{8c}

这正是式 (8) 的目标标签 ckc_k^*用论文自己的算例做一次验算(后面在”非预见性”反例中还会复用这组数字):pt(A)=0.7,pt(B)=0.3p_t(A)=0.7, p_t(B)=0.3pd(A)=pd(B)=0.5p_d(A)=p_d(B)=0.5。直接计算式 (8a) 的左边:min(0.5,0.7)+min(0.5,0.3)=0.5+0.3=0.8\min(0.5,0.7) + \min(0.5,0.3) = 0.5 + 0.3 = 0.8。通过 TV 距离恒等式 (8c):pdpt1=0.50.7+0.50.3=0.2+0.2=0.4\lVert p_d-p_t\rVert_1 = |0.5-0.7| + |0.5-0.3| = 0.2+0.2=0.4,所以 112(0.4)=10.2=0.81 - \tfrac12(0.4) = 1-0.2=0.8。两条路径结果一致,说明这个恒等式不仅代数上正确,数值上也与本文其他地方用到的数字自洽。

为什么这不只是一个好看的事实,而是有实际意义。 它告诉我们,DSpark 里的”置信度”不是一个主观的、和模型相关的概念——它恰好就是决定拒绝采样多久保留一个 token 的那个量,用一种(1 减去两个分布之间的距离)对称、有界于 [0,1][0,1]、当 pd=ptp_d = p_t(草稿完美)时恰好等于 1、当 pd,ptp_d, p_t 支撑集不相交(最差起草)时恰好等于 0 的形式表达出来。训练置信度头去预测这个具体的闭式量,而不是一个经验接受/拒绝频率,正是让下一小节的 STS 校准步骤良定义的原因:你在校准一个网络对一个已知的、良定义的统计距离的估计,而不是在追逐一个受采样噪声主导的移动目标。

用数字看 STS:可靠性图上的逐位置校准

论文的可靠性图(图 6,Alpaca 数据集)在块内四个采样位置分别报告了 STS 前后的 ROC-AUC 和 ECE。把具体数字复现出来,比之前引用的”AUC 0.81-0.90,ECE 3-8%“这个区间更有信息量,因为跨位置的模式本身就很有诊断价值:

图 5b(对应论文图 6 局部):逐位置区分能力与校准误差。

位置ROC-AUCSTS 前 ECESTS 后 ECE
10.8185.7%2.0%
30.8128.2%1.7%
50.8645.8%0.8%
70.9073.3%0.4%

有两个模式值得明确指出,因为它们都不是不言自明的。第一,区分能力(AUC)随位置提升(0.818 → 0.907),尽管直觉上越靠后的位置应该越”难”预测,因为它依赖更长、更复杂累积的上下文。可能的解释是一种选择效应:根据构造,位置 7 只会在位置 1-6 都已经存活的 rollout 上被评估(回想前文”为什么并行胜过纯自回归”一节的逐位置条件接受方法论)——条件于一个长的已接受前缀,本身就筛选出了一个更容易、更可预测的延续子群体,所以置信度头在位置 7 的任务,在被问到的前提下其实隐含地变简单了。第二,校准前 ECE 是非单调的,在位置 3 达到峰值(8.2%)而不是随深度稳步上升——这提醒我们原始的失校准并不是序列深度的简单函数,STS 逐位置的网格搜索(而不是单一全局温度)确实在做有实际意义、因位置而异的工作:一个针对位置 1 调好的共享温度,很可能会对位置 3、5、7 造成欠校准或过校准,因为它们一开始失校准的方式差异很大。STS 之后,ECE 不仅数值小得多,跨位置也平坦得多(2.0%、1.7%、0.8%、0.4%),这正是”按顺序校准累积乘积,而不是套用一次统一修正”这一做法的实践依据。

硬件感知前缀调度器

这是论文最具新意的贡献,值得完整推导一遍。调度器要回答的问题是:给定一批 RR 个并发请求,每个请求都有自己一套逐位置存活概率,这一轮到底应该把每个请求草稿块的多少个 token 真正送去目标模型验证,才能最大化系统级吞吐?

设定。 对请求 rr,设 cr,1,,cr,γc_{r,1}, \ldots, c_{r,\gamma} 为逐位置置信度估计,r{0,,γ}\ell_r \in \{0, \ldots, \gamma\} 为被选定的验证长度。由于投机解码只会接受连续前缀,位置 jj 的 token 的存活概率就是累积乘积 ar,j=ijcr,ia_{r,j} = \prod_{i \le j} c_{r,i}。如果这批请求总共验证 B=r(1+r)B = \sum_r (1 + \ell_r) 个 token(+1+1 对应每个请求的锚点/补偿 token 插槽),目标模型会以某个实测吞吐 SPS(B)SPS(B)(每秒步数,在引擎启动时一次性剖析成一张随批量变化的查找表)运行。这一轮的期望接受 token 数τ=r(1+j=1rar,j)\tau = \sum_r \big(1 + \sum_{j=1}^{\ell_r} a_{r,j}\big),于是要最大化的系统级期望 token 吞吐为:

Θ=τSPS(B)(目标函数)\Theta = \tau \cdot SPS(B) \tag{目标函数}

为什么这看起来像组合优化,但实际上不是。 独立地为 1,,R\ell_1, \ldots, \ell_R 选值,看起来像是要在一个指数级大的长度分配空间里搜索。能把这个问题坍缩下来的关键结构性洞察是:由于 ar,ja_{r,j} 关于 jj 单调不增(更长的前缀完全存活的概率只会更低或相等),把请求 rr 的验证长度从 j1j-1 延伸到 jj 带来的边际增益恰好就是 ar,ja_{r,j}——这是一个单一的、可比较的数字,与其他请求在做什么无关。这意味着”再多验证一个 token”的增量价值可以在不同请求之间直接比较,而不只是在一个请求自己的块内部比较。于是 DSpark 不是逐请求分配长度,而是把批次中每一个候选 token 扩展 (r,j)(r, j) 池化到一张全局列表里,按 ar,ja_{r,j} 降序排序,从头开始贪心录取:

算法 1——硬件感知前缀调度器(带编号步骤重述):

输入:活跃请求 r in {1,...,R};置信度序列 c_{r,1},...,c_{r,gamma};
      经剖析得到的吞吐曲线 SPS(B)
输出:每请求验证长度 l*_1, ..., l*_R

1.  对每个请求 r:计算前缀存活概率
        a_{r,j} = product_{i<=j} c_{r,i}    对 j = 1,...,gamma
2.  构建候选池 E = { (r,j) : a_{r,j} > 0 };按 a_{r,j} 降序排序
3.  初始化所有 r 的 l_r = 0;批量大小 B = R;期望接受数 tau* = R
4.  初始化目前最优:Theta_best = R * SPS(R);所有 r 的 l*_r = 0
5.  对排好序的 E 中每个 (r,j):
5a.     l_r = j;  B = B + 1;  tau* = tau* + a_{r,j}
5b.     Theta = tau* * SPS(B)
5c.     若 Theta > Theta_best:
            Theta_best = Theta;l*_r = l_r(对所有 r 做快照)
        否则:
            跳出循环                                    # 提前终止 —— 见下文因果性证明
6.  返回达成 Theta_best 的 (l*_1, ..., l*_R)

算法的文字化走读。 第 1 步把每个请求原始的逐位置置信度,转换成累积存活概率——这与 STS 校准用的是同一个量。第 2 步是关键一招:它把逐请求、逐位置的问题拍扁成一个全局的、按”每一个大概率有回报”排序的”验证机会”队列。第 3-4 步把初始状态设为每个请求除了保底的锚点 token 之外都不多验证任何额外草稿 token(这始终是一个可行的安全基线——等价于纯单 token 解码)。第 5 步是对全局排好序的候选列表做一次线性扫描:每一轮都试探性地录取下一个最有希望的扩展,在新的(略微更大的)批量大小下重新计算期望系统吞吐 Θ\Theta,如果吞吐上升就把这个更大的前沿接受为新的最优,如果吞吐下降就立即停止。第 6 步返回这次单趟排序扫描中达成最优吞吐的那个配置。

一个双请求的具体演算。 为了让算法具体化,考虑 R=2R=2 个请求,每个 γ=2\gamma=2。请求 1 的置信度为 c1,1=0.9,c1,2=0.5c_{1,1}=0.9, c_{1,2}=0.5,给出存活概率 a1,1=0.9a_{1,1}=0.9a1,2=0.45a_{1,2}=0.45。请求 2 的置信度为 c2,1=0.6,c2,2=0.6c_{2,1}=0.6, c_{2,2}=0.6,给出 a2,1=0.6a_{2,1}=0.6a2,2=0.36a_{2,2}=0.36。假设剖析出的容量曲线为 SPS(2)=1.0SPS(2)=1.0SPS(3)=0.8SPS(3)=0.8SPS(4)=0.55SPS(4)=0.55SPS(5)=0.5SPS(5)=0.5SPS(6)=0.3SPS(6)=0.3

  1. 初始化: B=2B=2(每个请求一个锚点插槽),τ=2\tau^*=2Θbest=2×SPS(2)=2.0\Theta_{best} = 2 \times SPS(2) = 2.0
  2. 排序后的候选池(按存活概率降序):(1,1):0.9(1,1){:}0.9(2,1):0.6(2,1){:}0.6(1,2):0.45(1,2){:}0.45(2,2):0.36(2,2){:}0.36
  3. 录取 (1,1)(1,1) B=3B=3τ=2+0.9=2.9\tau^*=2+0.9=2.9Θ=2.9×SPS(3)=2.9×0.8=2.32>2.0\Theta = 2.9\times SPS(3) = 2.9\times0.8=2.32 > 2.0 → 接受,新的最优 Θbest=2.32\Theta_{best}=2.32,快照 1=1,2=0\ell_1=1,\ell_2=0
  4. 录取 (2,1)(2,1) B=4B=4τ=2.9+0.6=3.5\tau^*=2.9+0.6=3.5Θ=3.5×SPS(4)=3.5×0.55=1.925<2.32\Theta=3.5\times SPS(4)=3.5\times0.55=1.925 < 2.32这个候选单独看确实不划算,但为了在演算中说明问题,我们暂不在此立即停止、先看看再往后一个候选会怎样;而在严格的因果性算法 1 中,循环会在此刻立即跳出,返回 1=1,2=0\ell_1^*=1, \ell_2^*=0Θbest=2.32\Theta_{best}=2.32
  5. 接下来的候选本会展现什么(仅作直觉说明,因果性算法并不会执行到这里): 如果接着录取 (1,2)(1,2),会得到 B=5B=5τ=3.5+0.45=3.95\tau^*=3.5+0.45=3.95Θ=3.95×0.5=1.975\Theta=3.95\times0.5=1.975——依然低于 2.32——再录取 (2,2)(2,2) 会得到 B=6B=6τ=3.95+0.36=4.31\tau^*=3.95+0.36=4.31Θ=4.31×0.3=1.293\Theta=4.31\times0.3=1.293,更差。在这个具体的玩具例子里,第 4 步的提前停止恰好也是全局最大值,这是一个有用的合理性检验,但在一条锯齿状、非单峰的 SPS(B)SPS(B) 曲线上并不能保证成立——这正是第 5 节生产环境改造要弥补的缺口。

最终决策——请求 1 多验证 1 个 token,请求 2 一个都不多验证——展示了核心行为:即使请求 2 第一个位置的置信度(0.6)单独看还算不错,一旦把共享的批量成本纳入考虑,延伸它带来的系统级边际价值也低于请求 1。这正是一个朴素的”每个请求各自独立设阈值”方案做不到的跨请求比较。

为什么贪心在这里是可证明最优的(而不只是一个启发式)。 这是贪心算法给出精确全局最优解的一个少见情形,原因就在于上面提到的单调性:由于每个请求自身的边际增益 ar,1ar,2ar,γa_{r,1} \ge a_{r,2} \ge \ldots \ge a_{r,\gamma} 内部本就已经是非递增排序,而目标函数中请求之间唯一的依赖只通过共享的批量大小项 SPS(B)SPS(B),按边际价值(ar,ja_{r,j})全局降序录取候选,等价于——对每一个可能的批量大小 BB——都已经选出了那个批量下价值最大的扩展子集。如果真正的最优批量大小是 BB^*,那么贪心排序截断出的列表,根据构造就是达到任意给定 BB 的、τ\tau 最高的方式——所以从小到大扫描 BB,在 Θ\Theta 停止增长的地方停下来,就能恢复全局最大值,前提是 Θ\Theta 关于 BB 是单峰的(论文明确说明,这一假设——一条平滑递减的硬件容量曲线——正是提前终止规则得以成立的前提;下文第 5.2 节会重新审视真实硬件违反这一假设时会发生什么)。

正式证明梗概:为什么排序列表能达到全局最优

上面的直觉值得严格化一下,因为”贪心即最优”这类主张说起来容易,在细微不同的问题设定下却经常出错。下面用三步给出论证。

引理 1(前缀闭包是自动满足的)。E={(r,j):r{1,,R},j{1,,γ}}E = \{(r,j) : r \in \{1,\ldots,R\}, j \in \{1,\ldots,\gamma\}\}ar,ja_{r,j} 降序排序,GmG_m 表示这个排好序的列表的前 mm 个元素(m{0,,Rγ}m \in \{0, \ldots, R\gamma\} 任意)。断言:对任意请求 rr,若 (r,j)Gm(r,j) \in G_m,则(当 j>1j>1 时)必有 (r,j1)Gm(r, j-1) \in G_m证明: 因为 ar,j1ar,ja_{r,j-1} \ge a_{r,j}(存活概率沿前缀非递增——一个 token 只有在它之前的一切都存活时才可能存活),所以 (r,j1)(r,j-1) 在降序排列中排在 (r,j)(r,j) 之前或同位,于是如果 (r,j)(r,j) 位列前 mm,排名至少同样靠前的 (r,j1)(r,j-1) 也必然位列其中。这意味着贪心列表自动不会出现”空洞”(验证位置 jj 却不验证位置 1,,j11,\ldots,j-1),不需要额外规则来显式强制前缀约束。

引理 2(按价值取前 mm 个是该规模下唯一的价值最大化选择)。 在所有大小为 mm 的子集 SES \subseteq E 中,GmG_m 使 (r,j)Sar,j\sum_{(r,j)\in S} a_{r,j} 最大。证明: 标准交换论证——如果存在某个最优 SGmS^\star \ne G_m,它必然包含某个价值严格小于 GmG_m 中某个不在 SS^\star 里的元素;交换两者会弱增总和,与 SS^\star 最优矛盾,除非 S=GmS^\star = G_m 本身。

第三步(拼合两条引理)。 结合引理 1 和引理 2:GmG_m 同时满足 (a) 对每个请求都前缀一致(因此对应一个真实可执行的长度选择 1,,R\ell_1,\ldots,\ell_R)和 (b) 是规模为 mm 的价值最大化选择。于是定义 τ(B):=R+(r,j)GBRar,j\tau^*(B) := R + \sum_{(r,j)\in G_{B-R}} a_{r,j}——在总验证批量为 BB真正可达到的最佳期望接受长度。由于按定义 Θ(B)=τ(B)SPS(B)\Theta(B) = \tau^*(B)\cdot SPS(B),而 τ(B)\tau^*(B) 恰好是通过逐个元素增量式地扩大 GmG_m 实现的(这正是算法 1 第 5 步循环所做的事),扫描排好序的列表并在每次录取后计算 Θ\Theta,就精确地描出了整条曲线 {Θ(B)}B=RR(1+γ)\{\Theta(B)\}_{B=R}^{R(1+\gamma)} 在每一个 BB 处的真实最优值。在这条曲线上求 maxBΘ(B)\max_B \Theta(B)——如果不确定 Θ\Theta 是否单峰就全程扫描,或者如果已知单峰就在第一次下降处停止——因此能找到所有可行长度分配中的全局最优,而不仅仅是局部最优。单峰性假设只用于证成提前终止这一步;“按价值排序的顺序在每个批量大小处都给出真正最优 τ\tau“这一核心主张(引理 1-2)是无条件成立的。

双请求算例的完整 Θ(B)\Theta(B) 曲线。τ(B)\tau^*(B) 应用到上面 R=2R=2γ=2\gamma=2 的例子上,能让第三步具体化——不再只逐个检查候选,而是把整条扫描过程隐含探索的曲线完整列出:

BB录取集合 GB2G_{B-2}τ(B)\tau^*(B)SPS(B)SPS(B)Θ(B)\Theta(B)
2\emptyset2.001.002.00
3{(1,1)}\{(1,1)\}2.900.802.32
4{(1,1),(2,1)}\{(1,1),(2,1)\}3.500.551.925
5{(1,1),(2,1),(1,2)}\{(1,1),(2,1),(1,2)\}3.950.501.975
6{(1,1),(2,1),(1,2),(2,2)}\{(1,1),(2,1),(1,2),(2,2)\}4.310.301.293

Θ(B)\Theta(B) 这一列往下读能同时确认两件事:(1) B=3B=3 在这整张表上确实是无约束的全局最大值(而不仅仅相对邻居是局部峰值),所以提前终止的因果性调度器在这个实例上给出的答案完全正确;(2) 这条曲线在此处是单峰的(先升后降,单调变化),这正是引理 1-2 的最优性保证需要提前终止这个捷径成立的条件——下文第 5.2 节会讨论真实 GPU 吞吐曲线不这么规矩时会发生什么。

非预见性:一个具体反例

调度器必须满足一个论文称之为**非预见性(non-anticipating)**的微妙正确性约束:是否录取草拟 token kk 用于验证的决策,只能依赖于 token kk 被采样之前可获得的信息,绝不能依赖于 xkx_k 本身的实现值。如果违反这一点,调度器会悄悄破坏投机解码得以安全部署的无损性保证——一项服务优化就不再是”免费的”,而是开始真正改变用户看到的输出。

论文用一个具体的数值反例(附录 A)证明了这一点确实重要,这里完整复现一遍,因为这类正确性论证在文字里很容易被一笔带过,但完整算一遍会很有说服力。

考虑单个请求(R=1R=1),最大块长 γ=2\gamma = 2,第一位置存活概率 a1=0.8a_1 = 0.8,剖析出的容量曲线 SPS(1)=1.0SPS(1)=1.0SPS(2)=0.5SPS(2)=0.5SPS(3)=0.45SPS(3)=0.45。验证 0 个或 1 个 token 的候选吞吐为:

Θ0=1SPS(1)=1.0,Θ1=(1+0.8)SPS(2)=0.9(9)\Theta_0 = 1 \cdot SPS(1) = 1.0, \qquad \Theta_1 = (1+0.8)\cdot SPS(2) = 0.9 \tag{9}

到这里为止,Θ1<Θ0\Theta_1 < \Theta_0——一个正确的、带提前终止的因果性调度器就在此刻停下,决定 =0\ell = 0,根本不会再往后看。但假设调度器被允许再多看一步(即不做提前终止),也评估了 Θ2\Theta_2。由于 Markov 置信度头下一个分数 c2c_2 依赖于实际被采样出的 token x1x_1x1x_1 的两种实现会给出两个不同的 a2=a1c2a_2 = a_1 c_2

  • x1x_1 恰好使得 c2=0.9c_2 = 0.9 a2=0.8×0.9=0.72a_2 = 0.8 \times 0.9 = 0.72,于是 Θ2=(1+0.8+0.72)×0.45=1.134\Theta_2 = (1+0.8+0.72)\times 0.45 = 1.134——此时成为全局最大值,于是不做提前终止的调度器会追溯性地决定 =2\ell=2录取 x1x_1
  • x1x_1 恰好使得 c2=0c_2 = 0 a2=0a_2 = 0,于是 Θ2=(1+0.8+0)×0.45=0.81<Θ0\Theta_2 = (1+0.8+0)\times 0.45=0.81 < \Theta_0,调度器决定 =0\ell=0拒绝 x1x_1

要害在于:x1x_1 是否被送去验证,现在竟然取决于 x1x_1 自身的值——这是教科书式的非预见性违反。论文把这个例子转化为一个分布论证:设 pt(A)=0.7,pt(B)=0.3p_t(A)=0.7, p_t(B)=0.3pd(A)=pd(B)=0.5p_d(A)=p_d(B)=0.5(于是位置 1 的诚实接受概率为 min(0.7,0.5)+min(0.3,0.5)=0.8\min(0.7,0.5)+\min(0.3,0.5)=0.8,与假设的 a1a_1 相符),假设(如例中所述)x1=Ax_1=A 触发高置信度路径(=2\ell=2,被录取),而 x1=Bx_1=B 触发低置信度路径(=0\ell=0,被拒绝并从 ptp_t 重新采样)。那么:

Pr(Y=A)=Pr(x1=A)1+Pr(x1=B)pt(A)=0.5+0.5×0.7=0.85(10)\Pr(Y=A) = \Pr(x_1=A)\cdot 1 + \Pr(x_1=B)\cdot p_t(A) = 0.5 + 0.5\times 0.7 = 0.85 \tag{10}

不等于目标模型真实的 pt(A)=0.7p_t(A) = 0.7——追溯性调度器悄悄污染了输出分布。这正是为什么算法 1 的提前终止跳出不是一个性能捷径,而是一项正确性要求:通过在 Θ\Theta 一停止改善就立即停止搜索,位置 kk 的录取决策永远不会触碰依赖于 xkx_k 实现值的信息(ck+1c_{k+1} 或更晚位置)。

图 2 端到端地可视化了整个调度流程,包括校准和因果性约束所处的位置。

图 2:DSpark 解码周期——从起草到调度验证

sequenceDiagram
    participant T as 目标模型
    participant P as 并行主干
    participant S as 顺序修正头
    participant C as 置信度头 + STS
    participant Sch as 硬件感知调度器

    T->>T: 生成锚点 token D(上一轮的补偿 token)
    T->>P: 以锚点 D 作为输入
    P->>P: 一次前向传播 -> 基础 logits U_1..U_gamma
    P->>S: 隐藏状态 h_1..h_gamma
    loop k = 1 到 gamma(轻量、顺序)
        S->>S: 从 p_k(基础 logit + 偏置 B_k) 采样 x_k
        S->>C: h_k、前一 token 嵌入
        C->>C: 计算原始 c_k,应用 STS 校准
    end
    C->>Sch: 本请求及其他请求的校准后置信度 c_1..c_gamma
    Sch->>Sch: 池化全部 (r,j) 候选,按 a_{r,j} 排序,贪心录取并提前终止
    Sch->>T: 每请求调度出的前缀长度 l*_r
    T->>T: 只验证被调度出的前缀(一次并行传播)
    T->>T: 接受最长有效前缀,为下一轮产出补偿 token

复杂度分析:量化”半”自回归中的”半”

后文的消融结果(“延迟开销”一节)测得,把顺序循环从 4 个草拟位置扩展到 16 个,只给整轮延迟增加了 0.2-1.3%。这个数字凭直觉接受也不难,但如果能从一次显式的 FLOP 计算中得出来,会更有说服力——不只是这一次测量碰巧便宜,而是这个顺序修正在构造上就注定便宜。

一步 Markov 头的代价。 回想式 (5):B(xk1,)=W1[xk1]W2B(x_{k-1}, \cdot) = W_1[x_{k-1}]\,W_2,其中 W1RV×rW_1 \in \mathbb{R}^{V\times r}W2Rr×VW_2 \in \mathbb{R}^{r \times V},默认秩 r=256r=256。查一行 W1W_1 是一次免费的内存读取(无浮点运算);随后这一长度为 rr 的行与 W2W_2 相乘,是一次规模为 r×Vr \times V 的向量-矩阵乘法,代价为 2rV2rV 次浮点运算(因子 2 计入每个元素一次乘法和一次加法)。在整个 γ\gamma 个位置的块上顺序运行,顺序头的总代价为:

FLOPsseq=2γrV(15)\text{FLOPs}_{seq} = 2\,\gamma\, r\, V \tag{15}

代入生产规模的数字。 取默认 r=256r=256,一个具有代表性的前沿模型词表 V1.28×105V \approx 1.28\times10^5,生产环境的块大小 γ=5\gamma=5FLOPsseq=2×5×256×128,0003.3×108\text{FLOPs}_{seq} = 2 \times 5 \times 256 \times 128{,}000 \approx 3.3\times10^8——每请求每轮约 0.33 GFLOPs。按现代 LLM 服务的标准,这确实是一个很小的数字。

并行主干代价,作为对比。 一个 Transformer(类)前向传播的标准量级估计大致是 2×Nactive×ntok2 \times N_{active} \times n_{tok} 次浮点运算,其中 NactiveN_{active} 是某个 token 触达的活跃参数数(对 MoE 层而言,是被路由到的稀疏子集,而不是全部参数),ntokn_{tok} 是该次传播处理的 token 数。论文没有公开三层协同部署的 MoE 主干层的确切参数量,但即便刻意保守地估计——假设主干每个 token 触及量级为几亿到十几亿的活跃参数,每请求每轮处理 γ=5\gamma=5 个位置——主干的前向代价也在每请求每轮几十 GFLOPs 甚至更多的量级:比上面算出的约 0.33 GFLOP 顺序头代价大三到四个数量级

为什么应该看这个”数量级差距”,而不只是笼统地说”很小”。 这个差距源于两者把浮点运算花在了哪里:顺序头的代价随 rr(一个刻意选得很小的秩,比任何现代隐藏维度 dd 都小一到两个数量级)伸缩,而主干的代价随 Transformer/MoE 模块的完整隐藏宽度和深度伸缩。因为 rdr \ll d 大约相差一到两个数量级,而且主干还额外要付出顺序头不需要承担的深度乘数,总差距是乘性叠加而非加性叠加。这正是为什么实测的经验开销(来自一个真实剖析系统的 0.2-1.3%)落在这个区间,而不是比如 10-20%:架构被设计成”半自回归”里”自回归”的那一半,永远只触碰一个秩为 256 的瓶颈,从不触碰完整的模型宽度。

这个估计会在哪里失效。 这个分析还准确地告诉我们这个设计会在哪里不再便宜。如果某个部署使用了前文”实现一:Markov 头”讨论中明确否决的满秩替代方案(r=V1.28×105r=V\approx1.28\times10^5 而不是 r=256r=256),式 (15) 会爆炸到 2γV22×5×(1.28×105)21.6×10112\gamma V^2 \approx 2\times5\times(1.28\times10^5)^2 \approx 1.6\times10^{11} 次浮点运算——与主干自身的代价相当甚至更高,彻底摧毁”在昂贵主干之上叠加廉价修正”这一整个前提。类似地,在保持 rr 不变的情况下把 γ\gamma 从 5 提高到 20,只会让顺序代价线性增长(式 (15) 对 γ\gamma 是线性的),所以大块大小依然是安全的——这个推导明确揭示的真正危险点在秩 rr 上,而不是块长 γ\gamma,这也正是为什么 4.3.2 节的消融实验自由地改变 γ\gamma,却从未把 rr 探索到远大于 256 的范围。

训练目标

DSpark 在从目标模型生成的序列中随机采样的 γ\gamma-token 锚点块上训练,目标模型全程冻结,草稿模型共享(并冻结)目标模型的嵌入表和语言模型头——只有主干草稿模型、顺序头和置信度头会被更新。总损失由三项组成,每一项都按 wk=exp((k1)/γ)w_k = \exp(-(k-1)/\gamma)(沿用 Chen et al., 2026 的做法)做位置加权,把训练信号前置在块内更早的位置——这是一个刻意的选择,因为在基于前缀的验证机制下,位置 1 的错误会作废后面的一切,所以一个在各位置上均匀分布的训练信号,会过度投资于那些期望上很少真正被到达的 token 的正确预测。

为什么具体选用指数衰减权重,而不是更简单的方案? 显而易见的替代方案是线性衰减 wk=1(k1)/γw_k = 1 - (k-1)/\gamma,同样把权重前置在位置 1,而且推理起来也更简单。指数形式 wk=exp((k1)/γ)w_k=\exp(-(k-1)/\gamma) 有一处重要的不同:它是乘性衰减的,所以任意两个相邻位置之间的权重比 wk+1/wk=exp(1/γ)w_{k+1}/w_k = \exp(-1/\gamma) 是一个常数,与你处在块内哪个位置 kk 无关;而线性方案相邻位置的比值在整个块内是变化的(越靠近末尾,比例上收缩得越快)。这个常数比值的性质,恰好映照了基于前缀的接受过程的真实统计规律:回想一下,到达位置 kk 的概率本身就是此前各位置存活概率的乘积,也就是说,在各位置接受率大致平稳的假设下,它大致是关于 kk 的几何衰减——一个指数训练权重,恰好与一个本身就是运行乘积的量(调度器一节的 ar,ka_{r,k})自然对应,而线性权重与底层过程之间没有这样的对应关系。这套推理会在哪个边界情形下失效:如果逐位置接受率不是大致平稳的(比如某个领域里块的最后一个位置系统性地比中间位置容易得多,这在结构化的、定长的输出格式里是可能发生的),一个固定的指数衰减方案依然会对这个”容易但靠后”的位置赋予比其真实到达概率所应得的更低权重——论文没有探索按领域调整 wkw_k 衰减速率,而是把 γ\gamma 本身(出现在指数分母中)当作唯一能调整该方案陡峭程度的旋钮。

交叉熵损失——标准的下一 token 预测,对照真实采样出的 token xkx_k^*

Lce=k=1γwklogpkd(xk)(11)\mathcal{L}_{ce} = -\sum_{k=1}^{\gamma} w_k \log p_k^d(x_k^*) \tag{11}

分布匹配(全变差)损失——直接惩罚草稿分布与目标分布之间的差距:

Ltv=k=1γwkpkdpkt1(12)\mathcal{L}_{tv} = \sum_{k=1}^{\gamma} w_k \lVert p_k^d - p_k^t \rVert_1 \tag{12}

额外加入这一项的直觉是:交叉熵只会把概率质量推向那唯一一个被采样出的 token,但真正决定投机解码接受率的量,是草稿分布与目标分布之间完整的总变差距离(回想式 (8):接受概率 =112pdpt1= 1-\tfrac12\lVert p^d-p^t\rVert_1)。直接最小化 Ltv\mathcal{L}_{tv},优化的是论文真正关心的那个量,而不是一个只与接受率松散相关的代理量(下一 token 准确率)。

置信度损失——二元交叉熵,把置信度头训练向来自式 (8) 的解析软标签 ckc_k^*

Lconf=k=1γwk[cklogck+(1ck)log(1ck)](13)\mathcal{L}_{conf} = -\sum_{k=1}^{\gamma} w_k \Big[c_k^* \log c_k + (1-c_k^*)\log(1-c_k)\Big] \tag{13}

组合目标函数,默认权重 αce=0.1\alpha_{ce}=0.1αtv=0.9\alpha_{tv}=0.9αconf=1.0\alpha_{conf}=1.0(注意 Ltv\mathcal{L}_{tv} 按设计远大于 Lce\mathcal{L}_{ce},与前面”TV 距离是更直接相关的量”这一推理一致):

L=αceLce+αtvLtv+αconfLconf(14)\mathcal{L} = \alpha_{ce}\mathcal{L}_{ce} + \alpha_{tv}\mathcal{L}_{tv} + \alpha_{conf}\mathcal{L}_{conf} \tag{14}

实验:离线基准测试

设置

目标模型:Qwen3-4B/8B/14B 和 Gemma4-12B。基线:Eagle3(自回归,训练时测试)和 DFlash(并行)。为公平起见,所有草稿模型都在同一框架、同一数据上重新训练——Open-PerfectBlend(130 万条指令样本:39.4% 数学、38.9% 代码、17.6% 聊天、4.1% 指令遵循),响应由各自的目标模型自己重新生成(这样草稿模型学到的是目标模型自身的实际输出分布,而不是某个其他模型的分布)。评测覆盖三个领域:数学(GSM8K、MATH500、AIME25)、代码(MBPP、HumanEval、LiveCodeBench)和聊天(MT-Bench、Alpaca、Arena-Hard),测量的是每个解码轮次的接受长度 τ\tau,此时置信度调度器被关闭,因此这一节隔离出了纯粹的草稿质量,排除了调度效应的影响。

主要结果

表 1(复现自论文表 1):每解码轮次的接受长度 τ\tau

目标模型草稿模型GSM8KMATH500AIME25MBPPHumanEvalLCBMT-BenchAlpacaArena-Hard
Qwen3-4BEagle35.144.623.923.694.163.772.392.262.55
Qwen3-4BDFlash5.404.854.154.404.744.183.072.962.83
Qwen3-4BDSpark6.115.704.895.135.384.863.643.543.29
Qwen3-8BEagle35.304.773.913.964.334.172.662.542.54
Qwen3-8BDFlash5.334.914.074.364.644.393.112.982.81
Qwen3-8BDSpark6.175.785.015.165.525.173.723.583.21
Qwen3-14BEagle35.244.603.713.814.144.012.622.472.48
Qwen3-14BDFlash5.414.843.984.444.594.333.102.942.72
Qwen3-14BDSpark6.215.744.945.265.435.023.703.583.13
Gemma4-12BEagle35.875.464.834.725.374.163.193.062.72
Gemma4-12BDFlash5.455.044.224.394.953.702.982.842.59
Gemma4-12BDSpark6.055.785.125.115.644.513.493.352.92

DSpark 相对 Eagle3 把宏平均接受长度提升了 30.9%/26.7%/30.0%(4B/8B/14B),相对 DFlash 提升了 16.3%/18.4%/18.3%,而且这个收益迁移到了一个不同的模型家族(Gemma4-12B)上,说明这一机制是架构无关的,而不是针对某一个目标模型的特性调出来的。同样值得注意的是每一行都能看到的领域效应:数学和代码的接受长度始终高于聊天(例如 Qwen3-4B:数学平均 5.57,聊天只有 3.49)——结构化任务本来就更可预测,这正是”不该在不同领域间用统一固定验证长度”这一观点的直接论据。

为什么并行(以及半自回归)在这里胜过纯自回归?一个逐位置分析

这个结果表面上有点反直觉——一个显式条件于此前已采样 token 的模型(Eagle3)不应该总是打败一个不这样做的模型(DFlash)吗?论文用逐位置条件接受分析来回答这个问题:对位置 kk,只在位置 1,,k11, \ldots, k-1 已经被接受的那部分 rollout 子集上测量接受率——这样就把位置 kk 自身的内在预测质量,从此前拒绝带来的复合效应中分离出来了。

图 3(对应论文图 2):分领域的逐位置条件接受率。

graph LR
    subgraph Math["数学领域"]
    M1["位置 1:DFlash 0.88,Eagle3 0.81"] --> M2["位置 7:DFlash 衰减,Eagle3 稳定/上升"]
    end
    subgraph Chat["聊天领域"]
    C1["位置 1:DFlash 0.72,Eagle3 0.53"] --> C2["位置 7:DFlash 降至约 0.63-0.72,<br/>Eagle3 升至约 0.74"]
    end
    style M1 fill:#4CAF50,color:#fff
    style C1 fill:#4CAF50,color:#fff

两个效应解释了这个悖论:

  1. 位置 1 的容量优势。 在草拟的第一个位置上,两种架构都纯粹依据目标上下文预测,还没有任何块内依赖起作用——差异纯粹来自模型容量。因为并行草稿模型只付出 O(1)O(1) 起草代价,它们能负担比自回归草稿模型(付出 O(γ)O(\gamma)、只能做浅)更深的网络,所以 DFlash 从一开始就明显领先:数学上 0.88 对 0.81,聊天上 0.72 对 0.53。由于投机解码是严格的前缀匹配过程,位置 1 的错误会抹掉整个块的潜力——所以这个第一 token 优势,会不成比例地决定全局接受长度,哪怕 DFlash 在后面的位置会失去优势。
  2. 后续位置独立性的局限。 越深入块内,Eagle3 的显式条件依赖让它能利用这样一个事实:一旦早期 token 锁定了某条语义路径,后面的 token 就会变得可预测——它的条件接受率保持稳定甚至上升(在聊天领域从 0.53 升到 0.74)。DFlash 的独立边缘化无法利用这一点,反而会衰减(代码领域从 0.87 降到 0.78;聊天领域从 0.72 降到 0.63)——这正是多模态碰撞失败模式在实验数据中的直接体现。

DSpark 被明确设计成同时拿到这两个效应:它继承了 DFlash 在位置 1 的高容量(数学领域起始于 0.93),同时轻量顺序头抑制了后续位置的衰减,给出一条既起点高、又能维持高位的曲线——是两种范式中最好的部分的结合,而不是二者之间的折中。

DSpark 与既有草稿模型的直接对比

沿着真正决定端到端加速的几个维度,把 DSpark 与此前两大类草稿模型摆在一起对比会很有价值,而不是把”接受长度”当作唯一重要的数字。

图 3b:草稿模型家族的对比总结。

属性自回归(Eagle3)并行(DFlash)半自回归(DSpark)
TdraftT_{draft}γ\gamma 的伸缩O(γ)O(\gamma)——线性O(1)O(1)——常数O(1)O(1) 主干 + 极小的 O(γ)O(\gamma) 修正循环
位置 1 接受质量较低(浅网络,如数学 0.81)较高(深网络,如数学 0.88)较高(继承并行主干,如数学 0.93)
后缀(末段位置)行为稳定或提升衰减(多模态碰撞)顺序头很大程度上抑制了衰减
最大实用块大小小(受延迟限制)
验证策略通常固定/基于树通常固定长度置信度调度、感知负载
调度下的正确性不适用(通常没有动态调度器)不适用可证明非预见性(附录 A)
相对 Eagle3 的宏平均接受长度提升(Qwen3-4B)基线+6.6% 至 +8.3%(由表 1 逐行数据推算)+30.9%
相对 DFlash 的宏平均接受长度提升(Qwen3-4B)基线+16.3%

这张表里有两点很容易在只读论文正文时被忽略。第一,DFlash 相对 Eagle3 自身的提升(在 Qwen3-4B 上大约 6-8% 的宏平均接受长度,可由表 1 推算得出)远小于 DSpark 相对 Eagle3 的提升(30.9%)——意味着顺序修正头对总收益的贡献,比当初从自回归换成并行起草本身的贡献还要大。第二,这一对比中此前的草稿模型都完全不包含任何调度机制;DSpark 在线上生产环境(第 5 节)中的优势,是离线接受长度收益再叠加一个完全独立、正交的额外收益来源(验证长度调度)——这正是为什么端到端生产加速(60-85%)比单凭离线接受长度数字通过式 (1) 所能预测的还要大。

消融实验:一点点自回归带来很大收益

草稿模型深度。 固定块大小为 7,把 DSpark Transformer 深度从 1 层变化到 5 层(对照一个 5 层的 DFlash 基线),接受长度随深度单调提升,从 1→2 层的边际增益最陡峭。值得注意的是,一个 2 层的 DSpark 已经在全部三个领域上超过了 5 层的 DFlash 基线——也就是说,一点点顺序修正比额外三层纯并行容量更值钱。这对任何在紧张参数或延迟预算下部署的人都是一个有意义的结果:它说明”更多并行深度”的边际回报低于”一点点顺序依赖建模”的边际回报。

提议长度。 固定深度为 5 层,把块大小 γ+1{4,8,12,16}\gamma+1 \in \{4,8,12,16\} 逐步放大,DSpark 相对 DFlash 的优势随之扩大:在 γ=7\gamma=7 时为 16%/15%/18%(数学/代码/聊天),扩大到 γ=15\gamma=15 时的 30%/26%/22%。结合前面的逐位置分析,这是合理的——DFlash 每多提议一个草稿 token 带来的边际效用会随后缀衰减的累积而缩水,而 DSpark 的顺序修正在块内更深处依然持续见效。RNN 头相对 Markov 头只带来边际额外收益,且主要集中在最长的提议长度上——与前文讨论一致:RNN 头额外的长程记忆能力是”锦上添花”而非主导因素,这也是 Markov 头依然是出厂默认配置的原因。

延迟开销。 在批量大小 128、上下文长度 {512, 1024, 2048, 4096} 下测量,把草拟长度从 4 扩大到 16,相对 DFlash 只给整轮延迟增加 0.2-1.3%,因为在这个批量下目标模型的验证传播主导了总计算量——顺序循环的代价在绝对意义上确实微不足道,尽管它能带来最高 30% 的接受长度提升。

图 4(对应论文图 4):接受长度 vs. 提议长度,以及延迟开销。

graph TD
    A["提议长度 gamma = 4"] --> A1["DSpark 相对 DFlash 提升约 16pct(数学)"]
    B["提议长度 gamma = 15"] --> B1["DSpark 相对 DFlash 提升约 30pct(数学)"]
    C["相对 DFlash 的延迟开销"] --> C1["gamma=4 时 +0.2pct"]
    C --> C2["gamma=16 时 +1.3pct"]
    style B1 fill:#4CAF50,color:#fff
    style C1 fill:#c8e6c9
    style C2 fill:#c8e6c9

验证得更聪明,而不是更长:单独验证置信度头

在部署完整的硬件感知调度器之前,作者用一次静态阈值扫描(图 5,复现如下)单独验证了置信度头本身:随着置信度阈值升高,被验证的 token 中的接受率相应升高,说明这个估计器确实正确地过滤掉了那些最终会被拒绝的 token。这个效应因领域而异,与领域方差的论点完全一致:在聊天领域,提高阈值能把实际接受率从 45.7% 提升到 95.7%(因为聊天领域有大量低置信度的后缀 token 可供修剪),而数学和代码——本身接受率就已经很高的领域——提升幅度较温和(分别为 76.9%→92.5% 和 67.6%→92.0%,因为一开始就没有太多”浪费”可以修剪)。

图 5(对应论文图 5):置信度阈值扫描——接受率 vs. 修剪程度。

领域阈值 0(不修剪)阈值较高(大力修剪)
数学76.9% 接受率92.5% 接受率
代码67.6% 接受率92.0% 接受率
聊天45.7% 接受率95.7% 接受率

校准可靠性图(论文图 6)证实了引入 STS 的动机:原始置信度区分能力强(ROC-AUC 0.81-0.90)但过度自信(ECE 3-8%);STS 之后,平均 ECE 降到约 1%,恢复了调度器吞吐计算所需要的、可信赖的绝对概率估计。

DeepSeek-V4 中的真实部署

这一部分是这篇论文从”一个好想法”迈向”一套经过验证的生产系统”的地方,值得细读,因为其中的工程改造揭示了整洁的理论算法 1 与真实大规模 GPU 上实际运行的东西之间的真实差距。

可扩展的训练基础设施

生产环境的草稿模型与 DeepSeek-V4-Flash 和 V4-Pro(预览版)协同部署。并行主干使用三层 MoE,配合 mHC 和窗口大小为 128 的滑动窗口注意力,块大小 γ=5\gamma=5,使用 Markov 头。有两处系统级训练优化值得关注:

  • 用隐藏状态通信取代全词表 logit 通信。 朴素地看,训练草稿模型需要每个位置目标模型的输出分布——但在并行 worker 之间传输全词表 logits(V105V \approx 10^5)是一个严重的带宽瓶颈。DSpark 转而缓存目标模型的、送入语言模型头之前的隐藏状态,只在草稿模型 worker 上、只针对被采样的位置本地投影出 logits。这把每 token 的通信复杂度从 O(V)O(V) 降到 O(d)O(d)(隐藏维度)——用一点点冗余的本地计算(重新做一次语言模型头投影)换取网络流量的大幅下降,这在一个大型训练集群里,当算力相对节点间带宽而言更便宜时,正是正确的取舍。
  • 锚点有界的序列打包。 与其对变长的训练序列做填充(这会在填充 token 上浪费计算),DSpark 从每条序列中采样固定数量的锚点位置,用 token 级别的注意力索引而不是二维掩码,把由此产生的独立 γ\gamma-token 块紧密打包在一起——在一个批次里保留跨多个打包好的、互不相关的块的精确因果掩码,而不需要付出填充带来的内存和计算开销。

让算法 1 适配真实硬件:两处冲突

直接部署整洁的算法 1,暴露出与生产基础设施的两处不匹配:

冲突一——锯齿状、不平滑的硬件容量曲线。 算法 1 的最优性证明假设了一条平滑递减、单峰的 SPS(B)SPS(B)。真实的 GPU 吞吐曲线是离散且阶梯状的(由于内核分块、显存 bank 冲突等原因,在特定批量大小处会出现性能悬崖),这可能让朴素的贪心提前终止陷入一个局部最小值,而真正的最优就在下一个”悬崖”之后不远处。

冲突二——与连续 CUDA graph 重放不兼容。 现代高吞吐服务依赖 CUDA graph 重放和零开销调度(Zero-Overhead Scheduling, ZOS),二者都要求在当前步执行完成之前就知道下一步的批量大小。但算法 1 按原文写法是用当前这一步刚采样出来的置信度分数来计算调度方案的——这是一个同步依赖,会让 GPU 流水线停下来等待调度方案算出来。

异步修正方案,以及为什么它依然无损

DSpark 用一处改造同时解决了这两处冲突:它用两步之前的置信度头输出,来近似即将到来的验证容量上限 KK,同时仍然按当前步真实的、最新的累积置信度分数,对当前步的实际候选 token 排序。只有截断长度 KK(批量容量上限)是根据陈旧(两步之前)的信息决定的;具体哪些 token 填满这个预算,依然由当前、准确的置信度排序来决定。这是一次干净的关注点分离:有多少预算可用,是一个能容忍短暂预测滞后的硬件调度问题;而获得这份预算,是一个对正确性敏感、必须用实时数据的排序问题。

为什么去掉了提前终止跳出(为了在冲突一那条锯齿曲线上做无约束的全局搜索、避免困在局部最小值)之后,这个方案依然能恢复因果安全性?因为截断长度 KK 现在只依赖于两步之前可获得的信息,结构上就不可能依赖于当前步草拟 token xr,kx_{r,k} 的实现值——而这正是上面反例中造成非预见性违反的那个量。换句话说,两步的滞后本身就是那道因果屏障,取代了同步提前终止,成为让调度器保持无损的机制,同时也让调度方案可以提前算好,供 CUDA graph 重放使用。

物理执行:无填充浪费的变长批次

一个动态调度出来的批次,会产出每个请求各不相同的已验证前缀长度,这与针对定长 query 深度优化的解码内核相冲突。DSpark 的解决办法是把所有请求的全部 token 拍扁成一条物理上同质的流(内核层面每个 token 都被一视同仁地对待),把逻辑上的序列内结构(哪些 token 属于哪个请求、它们的因果顺序)编码进一个单独的、由稀疏注意力内核消费的标记张量。具体到 DeepSeek-V4 架构,只有索引注意力和压缩内核需要修改——对于支持完全动态的、逐请求验证长度这样一个功能而言,这是一个相当克制的工程改动范围。

图 6:端到端生产流水线。

graph TD
    A[线上流量:R 个并发请求] --> B[并行主干 + 顺序头<br/>产出草稿 token 与置信度]
    B --> C[两步之前的置信度<br/>决定批量容量 K]
    C --> D[对当前步置信度排序,<br/>每请求录取前 K 个]
    D --> E[拍扁成变长批次<br/>标记张量编码结构]
    E --> F[目标模型验证<br/>索引注意力 + 压缩内核]
    F --> G[接受最长有效前缀<br/>产出补偿 token,进入下一轮]
    G --> A
    style C fill:#ffe0b2
    style D fill:#c8e6c9

生产环境结果

相对此前生产环境的单 token MTP-1 基线(之所以曾是生产默认,正是因为静态多 token 草稿模型如 MTP-3/5 曾被发现在高并发下因验证开销过大而拖累吞吐——这是一个真实存在的先前失败模式,正说明为什么需要的是一个动态调度器,而不只是一个更大的静态块):

图 7(对应论文图 7):总吞吐 vs. 单用户 TPS——帕累托前沿。

模型SLA 锚点总吞吐增益备注
V4-Flash80 tok/s/用户(适中)+51%稳定对比区间
V4-Flash120 tok/s/用户(严格)名义上 +661%MTP-1 在此几乎崩溃;应理解为”拓展了可行边界”,而非字面上的倍数
V4-Pro35 tok/s/用户(适中)+52%稳定对比区间
V4-Pro50 tok/s/用户(严格)名义上 +406%与上同样的警示
两者匹配吞吐水平下单用户速度 +60-85%(Flash)/ +57-78%(Pro)最具代表性的核心数字

作者明确指出,这些在严格 SLA 下出现的巨大”名义”百分比应该被理解为”DSpark 让一个基线几乎无法运转的交互性档位变得可行了”,而不是正常运行条件下字面意义上的 4-7 倍加速——对一篇系统论文而言,这种在报告最吸睛数字时表现出的自我克制相当难得。

图 8(对应论文图 8):负载自适应调度行为。 在中等并发以下(V4-Flash 约 200 并发请求以内,V4-Pro 约 150 以内),调度器把验证预算从 MTP-1 静态的 2 个 token 扩展到每请求大约 4-6 个 token,直接带来了上面的吞吐收益。随着并发趋于饱和,调度器平滑地收缩这个预算,在低置信度草稿 token 消耗掉其他并发请求所需的批量容量之前就把它们修剪掉——这正是”感知负载”这一半设计发挥作用的地方,而且是直接在生产遥测数据中可见的,而不仅仅是在仿真中。

graph TD
    A["低并发<br/>例如 50 个请求以内"] --> A1["验证预算:约 5-6 token/请求<br/>富余 GPU 算力 -> 花在投机上"]
    B["中等并发<br/>例如 100-150 个请求"] --> B1["验证预算:约 4 token/请求<br/>验证长前缀依然净收益为正"]
    C["高并发<br/>例如 200+ 个请求,接近饱和"] --> C1["验证预算:收缩至接近 MTP-1 的 2 token<br/>为更多请求保留批量容量"]
    A1 --> D["调度器输出:平滑、单调的<br/>预算-负载曲线,无需人工调参"]
    B1 --> D
    C1 --> D
    style A1 fill:#c8e6c9
    style C1 fill:#ffe0b2
    style D fill:#4CAF50,color:#fff

论文自述的局限

论文坦诚地指出了一个局限:前缀调度器能把验证浪费降到最低,但无法回收生成初始 γ\gamma-token 块所花费的、固定且不可挽回的草拟侧计算(通过并行主干)。对于那些天然接受率较低的查询(比如非常开放式或不寻常的聊天轮次),无论后面调度器如何激进地修剪验证,这份前期起草成本都是要照付不误的。作者建议未来在草稿模型内部引入难度感知的提前退出机制,让这类请求可以跳过完整的块生成。

相关工作:DSpark 在这个领域里的位置

投机解码算法。 这个方向已经从早期的分块方法和独立的小模型草稿模型(Chen et al., 2023;Leviathan et al., 2023),发展到集成进目标模型本身的多 token 头(Medusa、EAGLE 系列、MTP)。另一条线索追求彻底消除顺序起草瓶颈的并行/分块生成(Medusa、P-EAGLE、PARD、DART、DFlash)——这正是 DSpark 主干的血脉所在。

系统感知调度。 另一条并行的工作线用置信度启发式或习得的接受率预测器动态调整起草/验证长度(SpecDec++ 等),或者把问题建模为实时负载下的 goodput/延迟优化。DSpark 在这里的独特贡献是那个可证明无损、全局最优的贪心形式化及其明确的非预见性证明——此前大多数基于置信度的调度器都是没有配套正确性论证的启发式阈值规则。

并行/非自回归生成。 并行生成速度与联合序列连贯性之间的张力,可以追溯到非自回归 Transformer(Gu et al., 2018),它率先尝试独立预测所有位置,也立即撞上了 DSpark 要解决的同一个模式平均化问题。更广泛的 NAT 文献探索过两大类补救办法:要么用潜变量把所有位置引导向一个一致的输出,要么重新引入有限的顺序结构(迭代精化、块级自回归,或者 CRF/CTC/HMM 之类的结构化输出层)。论文小心地区分了 DSpark 的独特之处:投机解码额外要求精确的逐 token 概率用于拒绝采样,这直接排除了大多数这类 NAT 补救办法——全局配分函数模型(CRF-NAT)无法给出精确的逐 token 概率,基于潜变量边缘化的模型(CTC-drafter)只能局限于贪心验证。DSpark 局部的、因子化的修正(式 4)被专门设计成在每个位置都保持一个精确的 softmax,这正是它能与无损拒绝采样兼容的原因——一个很好的例子说明了一个设计约束(需要精确概率)如何直接排除了一整类看似合理的架构选择。

为了把这种定位说得更具体,下面列出 DSpark 各个具体设计选择在更广泛文献中的谱系归属:

  • 草稿模型主干血脉: DFlash(Chen et al., 2026)→ 带 KV 注入目标上下文的并行分块生成;DSpark 把它整体作为自己”昂贵、并行”的那一半。
  • 顺序修正血脉: 在概念上与 Domino 的 CausalEncoder(Huang et al., 2026a)以及 CRF-NAT/CTC-drafter 在并行隐藏状态之上叠加顺序层的思路相邻——但 DSpark 的局部因子化(式 4)刻意比一个全局归一化的 CRF 更简单,用一点建模能力换取精确、可行的逐 token 概率。
  • 置信度估计血脉: 借鉴 SpecDec++(Huang et al., 2024)以及此前基于置信度的自适应长度工作,但增加了大多数基于阈值的既有工作所缺乏的解析全变差监督(式 8)和校准步骤(STS)。
  • 调度血脉: 与追求 goodput 的系统工作(TurboSpec,Liu et al., 2024c)相关,后者把投机解码建模为系统级资源分配问题,但 DSpark 的独到之处在于推导出一个带有形式化非预见性证明的精确贪心最优解,而不是一个启发式或习得的赌博机策略(对照 Liu et al., 2026b 基于赌博机的草稿模型选择)。
  • 基于树的替代方案: DDTree、TAPS 和 JetSpec(Hu et al., 2026a;Ringel and Romano, 2026;Wang et al., 2026a)把草稿链延伸成一个可验证的树结构,而不是一条线性块——这是 DSpark 在本文中没有探索的一个正交扩展轴(DSpark 全文使用基于链的起草,见 4.1 节),把树结构半自回归起草留作未来工作的一个开放组合方向。

局限与边界条件(笔记作者的评估)

除了论文自述的那个局限(无法回收的草拟侧计算)之外,还有几处边界条件值得明确指出:

  • 单峰性假设承担了真正的重任。 算法 1 的可证明最优性建立在 Θ\Theta 关于 BB 单峰这一假设之上。生产系统的解决办法是允许无约束搜索,加上一道两步滞后的因果屏障——但这意味着 3.2.2 节的理论最优性保证,和 5.2 节实际部署的算法,已经不完全是同一个算法了;部署版本经验上是有效的,但在 SPS(B)SPS(B) 锯齿状的情况下,其最优性已不再是形式化可证明的。
  • 置信度调度的收益是有条件的,前提是真正存在富余容量。 论文自己的表述(5.3 节)指出,在他们的具体部署中,由于 KV Cache 和流量池的限制,有效批量大小始终”远低于 GPU 算力饱和的阈值”——这是一个有利的区间,在这里吞吐和单用户延迟碰巧是正相关而非对立的。运行在更接近算力饱和状态的运营者,可能会在这两个目标之间看到一个不那么有利的权衡。
  • Markov 头的边界情形。 如前所述,默认的 Markov 头只能修复一阶碰撞;跨越 3 个或更多位置的多 token 语义不一致,在架构上完全超出了它的能力范围,而论文自己的消融实验表明,本可以捕捉这些问题的 RNN 头在实践中只带来边际收益——这或许说明这类长程碰撞在被测试的领域里比较罕见,但未必在所有部署场景下都如此(例如带有僵化多 token 惯用语的高度结构化格式)。
  • 词表大小是 Markov 头成本上一个隐藏的乘数。 上面的复杂度分析(式 15)表明,顺序修正的 FLOP 成本随词表大小 VV 线性伸缩。DeepSeek 系列模型使用的词表较大但并不极端;如果部署到一个词表规模明显更大的目标模型上(比如某些多语言或字节级分词方案会把 VV 推到远超 10510^5),修正模块的成本会成比例膨胀,可能侵蚀生产消融实验所报告的”开销可忽略”这一性质——论文的成本测量应被理解为与 DeepSeek-V4 具体的分词器绑定,而不是一个与词表无关的普遍保证。
  • 调度器的保证是逐轮的,而不是逐对话的。 算法 1 是孤立地为当前解码轮次优化系统级吞吐;这套形式化里没有任何东西考虑跨轮次的公平性或饥饿问题(例如,同一小撮低置信度请求是否会一轮又一轮地被压到接近 MTP-1 的验证预算下限,而高置信度请求则一直在贪心录取中获胜)。论文的生产遥测数据(图 8)报告的是聚合的、按负载分桶的行为,而不是逐请求的预算随时间演变轨迹,所以对任何承诺个体用户最低体验、而不仅仅是聚合数字好看的 SLA 部署方而言,这是一处真实(尽管可能不大)的疏漏。

批判性分析:不足与可改进之处

(a) 不足与不够有说服力/缺失的对比。

  1. 线上部署里没有与其他置信度调度或 SLO 感知基线做对等比较。 生产环境对比(5.4 节)是 DSpark 对 MTP-1,一个静态、无调度的单 token 草稿模型。论文自己的相关工作一节点名了几个基于置信度或追求 goodput 优化的调度器(SpecDec++、AdaSpec、类 TurboSpec 的 goodput 优化),这些显然是更相关的线上基线——哪怕只是离线地、用 4.3.3 节验证置信度头时用过的同一套静态阈值协议来对比,也能把 60-85% 的收益拆分成”半自回归草稿模型本身贡献了多少”和”调度器贡献了多少”,因为 MTP-1 在这两个维度上都很弱,把两种贡献混在了一起。
  2. “名义 661%/406%” 这样的数字即便带了警示语,也不应该被列出来。 论文自我意识很强,坦承这些数字夸大了真实效果(MTP-1 在这些点上”几乎崩溃”),但这么大的头条百分比几乎必然会在二次报道中被断章取义地引用。更干净的呈现方式是只把匹配吞吐水平下的对比(60-85%/57-78%)作为主要结论,把边界拓展的数字降级成一句定性的”基线在此处变得不可行”的表述,而不是给出一个具体倍数。
  3. 离线结果(表 1)或线上生产图表里都没有任何方差或置信区间。 接受长度在每个基准上都只报告了一个单点估计;鉴于有些基准(AIME25、Arena-Hard)本身规模小、方差大,仅凭论文本身无法判断诸如 30.9% vs. 26.7% vs. 30.0% 这种跨模型规模(4B/8B/14B)的提升模式究竟是真实趋势还是基准噪声。
  4. “平滑递减、单峰的 SPS(B)“这一假设在实践中被论文自己承认是不成立的(5.2 节),但论文从未量化真实硬件曲线偏离单峰性的程度,也没有报告异步、两步滞后的近似方案相对一个拥有完美前瞻能力的假想 oracle 调度器,到底在吞吐上损失了多少。给出部署算法相对(可证明最优的)理论算法 1 的”最优性差距”的量化数字,才能真正支撑”实际改造版本几乎和可证明最优版本一样好”这一说法。
  5. 生产环境只部署了一种草稿模型配置γ=5\gamma=5、Markov 头、3 层 MoE)。论文丰富的离线消融空间(深度 1-5、γ{4,8,12,16}\gamma \in \{4,8,12,16\}、Markov 头 vs. RNN 头)从未与为什么选定这些具体生产超参数联系起来——尤其是离线消融本身就表明 DSpark 相对 DFlash 的相对优势会随 γ\gamma 增大而扩大

(b) 被淡化或省略的局限。

  1. 论文没有讨论当置信度头校准随时间漂移时(比如底层模型被更新,或者流量分布偏离了 STS 校准集)调度质量会发生什么——STS 是在一个留出集上一次性校准的,但生产流量构成(聊天/代码/数学的比例)恰恰是论文自己展示出的、驱动接受率巨大行为差异的那个轴,所以一个不再匹配线上流量构成的校准集,可能会在没有任何明显失败信号的情况下悄悄拉低调度器质量。
  2. 训练数据(Open-PerfectBlend)严重偏向数学(39.4%)和代码(38.9%),聊天相对较少(17.6%)——然而聊天恰恰是论文自己展示出、置信度头修剪空间最大(图 5)、且草稿模型绝对接受长度最低(表 1)的领域。很有可能专门增加聊天领域的训练数据,比继续在数学/代码上调优,更能改善真实(明显更偏聊天)生产工作负载的表现。
  3. 论文没有报告草稿模型自身的算力/参数成本(训练 FLOPs、协同部署草稿模型额外占用的 GPU 显存、或者它自身作为目标模型服务成本的一部分占比),而这对任何想要评估采用这套方案总成本的团队来说都很关键,不能只看它带来的加速。

(c) 具体、可执行的改进建议。

  1. 做一次只替换调度器(固定 DSpark 草稿模型不变)的离线消融:不调度(验证整个块)、静态阈值调度(如 4.3.3 节)、以及完整的硬件感知调度器——这能干净地分解出线上 60-85% 的收益中,有多少归功于草稿模型架构本身,有多少归功于调度器,而当前的实验设计做不到这一点。
  2. 报告一次受控的”置信度漂移”压力测试:在第 tt 月的数据上重新校准 STS,然后在第 t+kt+k 月的流量上测量 ECE 和实际调度器吞吐(kk 逐渐增大),刻画生产环境中实际需要多久重新校准一次,以及论文隐含的”校准集静态不变”这一假设在现实部署时间尺度上是否安全。
  3. 增加一次针对训练数据领域配比的显式消融(比如一个聊天占比更高的 Open-PerfectBlend 变体),报告它是否能缩小聊天与数学/代码之间的接受率差距,直接检验上面 (b.2) 提出的假设,而不是把它留作一个开放问题。
  4. 公布已部署的异步调度器(5.2 节)相对一个拥有 1 步前瞻能力(能看到 ck+1c_{k+1},虽然线上不可部署,但可以离线算出来做对比)的 oracle 调度器的经验最优性差距,以证实两步滞后近似在实践中代价很小,而不是仅仅依赖”它恢复了因果性”这一定性论证。
  5. 报告协同部署草稿模型(主干 + 顺序头 + 置信度头)额外占用的 GPU 显存和服务成本开销,用目标模型自身开销的百分比表示,这样 DeepSeek 自身基础设施之外的采用者才能评估在自己的服务栈上是否可行。
  6. 公布所有三个评测领域(而不只是 Alpaca)的逐位置 ECE/AUC 可靠性分解(本笔记的图 5b 就复现了 Alpaca 那一份)——由于本笔记上面自己的分析发现校准前 ECE 跨位置呈现出论文没有解释的非单调模式,一份跨领域的同类表格能澄清这种非单调性究竟是置信度头的稳定属性,还是 Alpaca 特有聊天风格流量的一个特有现象。

可复现性说明

作者发布了 DeepSeek-V4-Flash(预览版)和 DeepSeek-V4-Pro(预览版)的 DSpark checkpoint,以及 DeepSpec——一个开源、算法驱动的训练代码库,在同一个框架下实现了 Eagle3、DFlash 和 DSpark。这对可复现性是一个实实在在的贡献,因为它意味着表 1 中对比的全部三个基线都是用一套共享的、可检视的代码库训练出来的,而不是各自独立重新实现(在投机解码文献里,各自独立重新实现基线是一个常见的、造成不公平对比的源头,这篇论文明确避开了这一点)。训练方案(Open-PerfectBlend、10 轮训练、式 (14) 中给出具体权重的位置加权损失)在论文中被完整给出,置信度调度算法(算法 1)也足够精确、可以直接复现,包括那个非预见性反例——任何复现工作都应该把它当作正确性的单元测试来用。

估算你自己场景下的加速比:一个粗略计算器

对于想知道”这对我自己的服务栈有没有帮助”的读者,式 (1)(L=(Tdraft+Tverify)/τL = (T_{draft}+T_{verify})/\tau)也可以当作一个粗略的计算器,只要你为自己的部署准备好四个数字:目标模型纯自回归解码每 token 的延迟 L0L_0(也就是 γ=0\gamma=0、完全不起草时的 TverifyT_{verify})、你的草稿模型每轮的延迟 TdraftT_{draft}、在你选定的块大小下验证传播的延迟 Tverify(γ)T_{verify}(\gamma),以及在该块大小下你期望的接受长度 τ(γ)\tau(\gamma) 的估计值(这需要你实测得到,比如用上文”实验”一节中的离线接受长度方法论,因为它取决于你的草稿模型与目标模型匹配得有多好)。

用本文前面自己的数字做一个具体例子。 回想式 (1) 的示例:Tdraft=3T_{draft}=3 ms,γ=8\gamma=8Tverify=25T_{verify}=25 ms,给出 τ=3.5\tau=3.5(类 DFlash 的并行草稿模型,带后缀衰减)对比 τ=5.5\tau=5.5(类 DSpark 的草稿模型,衰减被抑制)。在固定 TdraftT_{draft}TverifyT_{verify} 不变的前提下(考虑到上面的复杂度分析表明顺序修正带来的延迟可以忽略,这是一个合理的近似),采用 DSpark 式半自回归相对纯并行草稿模型的相对加速比,就是接受长度之比:

LDFlashLDSpark=(Tdraft+Tverify)/τDFlash(Tdraft+Tverify)/τDSpark=τDSparkτDFlash=5.53.51.57(16)\frac{L_{DFlash}}{L_{DSpark}} = \frac{(T_{draft}+T_{verify})/\tau_{DFlash}}{(T_{draft}+T_{verify})/\tau_{DSpark}} = \frac{\tau_{DSpark}}{\tau_{DFlash}} = \frac{5.5}{3.5} \approx 1.57 \tag{16}

也就是说,仅凭接受长度的提升就能带来 57% 的单 token 延迟下降——与论文自己报告的 60-85% 生产环境收益处在同一量级,而生产环境的收益还额外受益于置信度调度验证器降低了负载下有效TverifyT_{verify}(本文图 3b 那张表突出的第二个独立杠杆)。这个计算器的关键局限值得明确说出来:它假设你能在自己的模型/领域组合上测出 τ\tau,而这在实践中需要在你自己的工作负载上跑一遍同样的离线接受长度基准方法论(上文”实验:离线基准测试”一节)——论文中 30-85% 的具体数字是与 DeepSeek-V4 及其训练/服务栈绑定的,应当被当作一个上限参考点,而不是一个保证可迁移的数字,尤其考虑到论文自己关于领域方差的发现(数学/代码接受长度始终高于聊天)暗示你的结果会高度依赖于你的流量构成。

常见问题解答

DSpark 会不会输出和目标模型纯自回归解码不一样的结果? 不会——这正是投机解码拒绝采样接受规则(见上文”投机解码:先起草,再验证”一节)的全部意义所在,而 DSpark 在每一层都明确保留了这一点:顺序头产出精确的 softmax 概率(式 4),置信度头的校准(STS)是保序的,因此不会改变 token 相对其真实排名的录取结果,调度器的非预见性(通过附录 A 反例证明)保证了”到底验证多少个 token”这个选择永远不会依赖那些会泄露尚未验证的草稿 token 身份的信息。论文中的每一项优化都是延迟/吞吐优化,不是对输出分布的近似。

既然验证”几乎免费”,相比一次完整的自回归步骤,为什么不干脆总是验证整个草稿块? 对孤立的单个请求而言这确实成立,但在并发条件下从系统层面看是错的(回想前面”连续批处理”那节前置知识):每多验证一个 token,实际上就等于给共享批量多加了一个插槽,而 GPU 每步吞吐会随批量增大而下降。图 8 里的数字把这一点具体化了——随着并发趋于饱和,调度器把验证预算从低负载时的约 5-6 token/请求收缩到接近 MTP-1 静态的 2 个 token,正是因为一旦 GPU 本身成为瓶颈而不是任何单个请求,“几乎免费”就不再成立了。

能不能把并行主干换成一个完全不同的模型? 半自回归公式(式 4)里没有任何东西是 DFlash 专属的——这套配方只需要一个能在一次前向传播里产出逐位置隐藏状态 hkh_k 和基础 logits UkU_k 的主干,任何并行草稿模型(比如 Medusa 风格的多头预测)都能提供。论文选择 DFlash 是因为它在当时是并行草稿模型中的最先进水平,但顺序修正头和置信度调度验证器最好被理解为与主干无关的即插即用组件。

硬件感知调度器是不是 DeepSeek 服务栈专属的,还是能移植到其他推理引擎(vLLM、SGLang、TensorRT-LLM)上? 核心算法(算法 1)只需要引擎提供两个原语:(1) 来自草稿模型的逐位置置信度估计,(2) 一张经过剖析得到的、吞吐随批量变化的查找表 SPS(B)SPS(B)——任何服务引擎都能通过离线基准测试得到这些,无论其内部内核实现如何。生产环境专属的部分(5.2 节针对 CUDA graph 重放的两步滞后改造,以及 5.3 节拍扁变长内核路由)与引擎实现更紧密相关,因为它们直接与某个特定引擎实现连续批处理和图捕获的方式交互——想在另一个引擎上采用这套方案的团队,很可能需要为自己的内核和调度栈重新推导出等价的这两处改造,而不是直接照搬 DeepSeek 专属代码。

既然 RNN 头严格意义上表达能力更强,论文为什么还要费心保留 Markov 头? 因为即便延迟代价可以忽略(4.3.2 节表明两种顺序循环的开销都很小),表达能力也不是免费的:RNN 头在生产训练/服务流水线中要正确实现、验证、维护起来更复杂,而实测相对 Markov 头的准确率收益只是边际的,且集中在长提议长度上,这未必代表大多数真实生产流量的主体。这是论文明确选择简洁性、放弃一点点额外余量的一个例子——对生产系统而言是一个合理的取舍,尽管这也意味着 RNN 头更大的潜力(比如在 Open-PerfectBlend 未充分代表的、有更长程块内依赖的领域里)在已部署系统中基本没有被探索过。

当两个候选在边际价值 ar,ja_{r,j} 上恰好打平时会怎样? 上文的正式证明梗概(引理 2)只要求被录取候选的总和最大化,并不要求任何特定的打平规则——交换两个打平的候选,在任意 BB 处都不会改变 τ(B)\tau^*(B),从而也不会改变 Θ(B)\Theta(B)。打平规则在操作上唯一可能有影响的地方,恰好就是 Θ\Theta 从递增转为递减的那个批量大小处;一个确定性的打平规则(比如请求 ID 较小者优先)就足以让算法可复现,且不影响 Θbest\Theta_{best} 本身的最优值保证。

SPS(B)SPS(B) 在生产引擎里实际怎么测出来,会不会过期? SPS(B)SPS(B) 是在引擎启动时一次性离线剖析出来的:在目标硬件和内核配置上,扫过一系列批量大小、测量解码步的墙钟时间,产出算法 1 要查询的那张查找表。它是服务栈本身的属性(GPU 型号、内核实现、张量/流水线并行布局),不是任何特定请求的属性,所以不需要按请求更新——但在内核升级、硬件更换,或并行配置变化之后需要重新剖析,而论文没有把这些当作运营层面的关注点来讨论。

在贪心(温度为 0)解码下,这一切会不会有变化? 上面推导的接受率恒等式(式 8c)建立在随机拒绝采样之上,在温度为 0 时并不直接有意义——此时”接受”退化为草稿 token 与目标模型唯一 argmax token 之间的精确匹配检查。通用的投机解码框架在这个极限下依然成立(Leviathan et al., 2023 把它当作一种特殊情形处理),但 DSpark 解析的置信度标签 ckc_k^* 会相应地从一个平滑的、基于距离的量,退化成一个更硬的 0/1 argmax 一致性指示器——这是论文没有单独分析的一种情形,STS 校准(建立在平滑变化的概率之上)的有效性在这种情形下需要独立验证,而不能直接信任。

公式索引:本文全部方程交叉引用

由于本文跨越了起草、校准、调度和训练多个部分,且多处方程在后文多次被复用(仅式 8 就在置信度头、校准讨论、常见问题和粗略计算器中都被援引),有一份统一的索引会比在正文中来回翻找方便得多。

编号定义内容首次出现于后续复用于
1每 token 延迟 L=(Tdraft+Tverify)/τL=(T_{draft}+T_{verify})/\tau“投机解码:先起草,再验证”粗略计算器、结论
2DFlash 上下文特征提取 HctxH_{ctx}“回顾 DFlash 并行主干”
3DFlash 上下文注入 K/V”回顾 DFlash 并行主干”
4基础 logits + 偏置之上的自回归因子化”顺序修正”伪代码第 5 步、常见问题
5低秩 Markov bigram 偏置”实现一:Markov 头”复杂度分析(式 15)
6RNN 头循环更新”实现二:RNN 头”
7原始置信度头 ckc_k“置信度头”伪代码第 6 步
8 / 8a-c解析接受标签 ckc_k^* 及其 TV 距离推导”置信度头” / 推导小节训练损失(式 13)、常见问题、计算器
9-10非预见性反例的吞吐与输出分布”非预见性:一个具体反例”
11-14训练损失(Lce,Ltv,Lconf\mathcal{L}_{ce}, \mathcal{L}_{tv}, \mathcal{L}_{conf}、组合 L\mathcal{L}“训练目标”
15顺序头 FLOP 代价”复杂度分析”常见问题(词表伸缩局限)
16比值形式的加速比计算器”估算你自己场景下的加速比”结论

全景拼合:完整解码循环的伪代码

论文把起草、校准和调度分成几个独立小节来介绍;把它们融合成一个端到端循环会很有帮助,因为这正是 GPU 上每一轮真正在执行的东西:

算法:DSpark 端到端解码轮次(每批 R 个请求)
需要:锚点 token x0_1..x0_R(各请求上一轮的补偿 token),
      冻结的目标模型 M_t,DSpark 草稿模型(主干 + 顺序头 + 置信度头),
      已校准的 STS 温度,剖析得到的 SPS(B) 表,
      来自两步之前置信度输出的容量估计 K

1.  对 1..R 中每个请求 r(一起打包进一次并行前向传播):
2.      在锚点 x0_r 上运行并行主干 -> 基础 logits U_1..U_gamma,隐藏状态 h_1..h_gamma
3.      对 k = 1 到 gamma:                                   # 轻量的顺序循环
4.          从 Markov 头(式 5)或 RNN 头(式 6)计算偏置 B_k
5.          从 softmax(U_k + B_k) 采样 x_{r,k}                 # 式 4
6.          计算原始置信度 c_{r,k}(式 7);应用 STS 校准
7.      计算累积存活概率 a_{r,j} = prod_{i<=j} c_{r,i},j = 1..gamma
8.  把 R 个请求的全部 (r,j) 候选池化在一起;按 a_{r,j} 降序排序
9.  在 Theta = tau* x SPS(B) 持续改善的前提下贪心录取候选,
    使用来自两步之前置信度得出的容量上限 K(见 5.2 节)
10. 组装拍扁后的变长验证批次(标记张量编码结构)
11. 目标模型 M_t 在一次并行前向传播中验证被调度出的前缀
12. 对每个请求 r:接受与 M_t 自身分布一致的最长前缀;
    在第一次拒绝处重新采样一个修正 token(或在整块被完全接受后,采样一个补偿 token)
13. 新锚点 x0_r <- 最后被接受/重新采样的 token;回到第 1 步进入下一轮

这个完整循环把一件容易被逐节阅读所忽略的事情说清楚了:第 2-7 步(起草 + 置信度估计)全部发生在草稿模型自己的计算路径上,在构造上就很便宜;第 8-10 步(调度 + 批次组装)纯粹是簿记工作,不涉及任何矩阵乘法;只有第 11-12 步真正触碰昂贵的目标模型——也就是说,让 DSpark”聪明”起来的全部机制(半自回归修正、校准、调度)几乎不给这一轮增加任何浮点运算量;它改变的是哪些token、多少token 到达那个昂贵的步骤,而不是那个步骤本身每 token 的成本。

一次贯穿全流程的三请求数值演算

前面的双请求调度器例子只单独展示了第 8-9 步。现在值得完整走一遍每一个编号步骤,用三个性格不同的并发请求(一个自信的数学请求、一个中等的代码请求、一个发散的开放式聊天请求),看看整条流水线的各个部件在实践中是如何组合起来的。

设定。 R=3R=3γ=3\gamma=3。假设置信度头(经 STS 校准后)给出:

  • 请求 1(数学类,高置信度):c1,1=0.95,c1,2=0.85,c1,3=0.70c_{1,1}=0.95, c_{1,2}=0.85, c_{1,3}=0.70
  • 请求 2(代码类,中等置信度):c2,1=0.80,c2,2=0.55,c2,3=0.30c_{2,1}=0.80, c_{2,2}=0.55, c_{2,3}=0.30
  • 请求 3(聊天类,低置信度,陡峭衰减):c3,1=0.65,c3,2=0.25,c3,3=0.10c_{3,1}=0.65, c_{3,2}=0.25, c_{3,3}=0.10

第 7 步——累积存活概率 ar,j=ijcr,ia_{r,j} = \prod_{i\le j} c_{r,i}

rrar,1a_{r,1}ar,2a_{r,2}ar,3a_{r,3}
1(数学)0.9500.8080.565
2(代码)0.8000.4400.132
3(聊天)0.6500.1630.016

第 8 步——池化并降序排序。 把全部九个 (r,j)(r,j) 候选池化在一起,按存活概率 ar,ja_{r,j} 排序,得到:(1,1):0.950>(1,2):0.808>(2,1):0.800>(3,1):0.650>(1,3):0.565>(2,2):0.440>(3,2):0.163>(2,3):0.132>(3,3):0.016(1,1){:}0.950 > (1,2){:}0.808 > (2,1){:}0.800 > (3,1){:}0.650 > (1,3){:}0.565 > (2,2){:}0.440 > (3,2){:}0.163 > (2,3){:}0.132 > (3,3){:}0.016

第 9 步——贪心录取。 假设剖析出的容量曲线为 SPS(3)=1.0,SPS(4)=0.85,SPS(5)=0.70,SPS(6)=0.60,SPS(7)=0.42,SPS(8)=0.30,SPS(9)=0.20,SPS(10)=0.12,SPS(11)=0.07,SPS(12)=0.04SPS(3)=1.0, SPS(4)=0.85, SPS(5)=0.70, SPS(6)=0.60, SPS(7)=0.42, SPS(8)=0.30, SPS(9)=0.20, SPS(10)=0.12, SPS(11)=0.07, SPS(12)=0.04(一条平滑递减、相对 Θ\Theta 单峰的曲线,为便于说明而选取)。从 B=3B=3(每请求一个锚点插槽)出发,τ=3\tau^*=3Θbest=3×SPS(3)=3.00\Theta_{best}=3\times SPS(3)=3.00

录取BBτ\tau^*SPS(B)SPS(B)Θ\Theta决策
(1,1)(1,1)43.9500.853.358接受(新的最优)
(1,2)(1,2)54.7580.703.331低于最优 → 跳出

因果性提前终止调度器在此停下:1=1,2=0,3=0\ell_1^*=1, \ell_2^*=0, \ell_3^*=0Θbest=3.358\Theta_{best}=3.358。纯粹为了直觉,继续把这张表算下去(因果性算法不会真正执行到这里)能确认这条曲线持续下降:接下来录取 (2,1)(2,1) 会给出 B=6,τ=5.558,Θ=5.558×0.60=3.335B=6, \tau^*=5.558, \Theta=5.558\times0.60=3.335;再录取 (3,1)(3,1) 会给出 B=7,τ=6.208,Θ=6.208×0.42=2.607B=7,\tau^*=6.208,\Theta=6.208\times0.42=2.607——单调变差,所以在 B=4B=4 处提前停止,实际上就是这条特定曲线上的全局最优。

解读。 这一轮除了保底的锚点之外,只有数学请求的第一个草拟 token 被验证——尽管代码请求自己第一位置的置信度(0.800)单独看相当不错,聊天请求的第一位置(0.650)也不算无关紧要。这正是调度器正确地认识到:考虑到扩大批量带来的共享成本(体现在下降的 SPS(B)SPS(B) 曲线上),再多花一个验证插槽在请求 1 的第二个 token 上,其系统级回报,一旦把它对所有人的 SPS(B)SPS(B) 造成的影响算进去,就不如乍看之下那么好。这也说明了一个值得单独指出的微妙之处:贪心扫描可能在考虑某个竞争请求的第一个 token 之前,就先录取了同一个请求的第二个 token(比如 (1,2)(1,2)),恰恰是因为排序依据的是绝对边际价值 ar,ja_{r,j},而不是按请求或按位置分组——一个请求内部的第二个 token 候选,完全可能合理地排在另一个请求的第一个 token 候选之前,只要它的存活概率更高,而这正是这里 (1,2):0.808(1,2){:}0.808(2,1):0.800(2,1){:}0.800 之间几乎发生的情况(两者足够接近,只要置信度估计稍有不同就会翻转相对顺序,很好地说明了为什么校准的准确性、而不仅仅是排序的准确性,对做好这种边际比较至关重要)。

第 10-13 步。 调度器把 =(1,0,0)\ell^* = (1, 0, 0) 交给批次组装阶段,把总共四个待验证 token(请求 2 和请求 3 各一个锚点插槽,请求 1 一个锚点加一个草拟插槽)拍扁进一条带标记张量的物理同质流,记录每个 token 属于哪个请求(第 10 步);目标模型在一次并行传播中验证这个批次(第 11 步);每个请求接受各自最长的有效前缀,并从目标模型自身的分布中获得一个补偿/重新采样的 token(第 12 步);新接受的 token 成为下一轮的锚点(第 13 步)——请求 1 这一轮可能推进最多 2 个 token(1 个草拟 + 1 个补偿),而请求 2 和请求 3 各推进恰好 1 个 token(仅补偿 token),与纯单 token 解码完全一样,对二者的正确性都是零代价的。

设计决策一览

把前面各节贯穿始终的”为什么选这个,而不是显而易见的替代方案”这条推理线索,汇总成一张表:

决策DSpark 的选择显而易见的替代方案替代方案为何不足
草稿模型结构半自回归(并行主干 + 极小顺序头)纯并行,或纯自回归纯并行有后缀衰减;纯自回归付出 O(γ)O(\gamma) 起草代价,被迫做浅
顺序修正的秩低秩(r=256r=256)Markov bigram 偏置完整的 V×VV \times V 转移矩阵满秩矩阵在 V105V\approx10^5 时内存和计算上都不可承受
默认修正模块Markov 头(1 步记忆)RNN 头(完整块内记忆)RNN 只带来边际额外收益,但实现/维护成本更高
置信度监督解析 TV 距离标签 ckc_k^*(式 8)经验接受/拒绝二元标签解析标签精确、低方差;经验标签每个位置需要大量样本
置信度校准顺序温度缩放(保序)未校准的原始 sigmoid 输出原始输出过度自信(ECE 3-8%),会污染调度器的吞吐计算
验证长度策略基于池化逐 token 边际价值的全局贪心调度器独立的静态/逐请求阈值静态阈值忽略系统负载,无法跨请求比较边际价值
生产调度时序两步滞后容量估计,当前步排序全同步逐步调度同步调度会让 GPU 流水线停滞,破坏 CUDA graph 重放/ZOS
内核执行拍扁的变长 token + 标记张量定长填充批次填充浪费计算,在动态长度下造成 GPU 利用率不均

结论

DSpark 的核心洞察一旦说出来其实相当简单:并行草稿模型的独立性假设是质量瓶颈,静态验证长度是系统瓶颈,但这两个瓶颈都不需要用重量级方案去解决。一个微小的顺序修正模块(默认配置下只是一个秩 256 的 bigram 偏置)就能挽回并行生成损失掉的大部分连贯性,而一个可证明无损的贪心调度器——建立在”逐 token 边际价值可以跨请求比较”这个简单却并不显然的观察之上——就能挽回盲目定长验证损失掉的大部分吞吐。真正让这篇论文从”一个聪明的点子”升级为”值得细读的工作”的,是它对调度器正确性所投入的严谨程度(非预见性,用一个明确的反例证明),以及生产部分坦诚地交代了整洁的理论算法 1 在与真实的、锯齿状硬件容量曲线和基于 CUDA graph 的服务引擎正面接触时,究竟在哪里不得不做出让步。60-85% 的生产加速本身就是一个强有力的结果,但更持久的贡献或许是那个更一般化的方法论:只要服务流水线中的某个组件能产出一个经过校准、定义良好的置信度分数,这个分数就有潜力被转化为一个可证明安全、感知负载的调度决策,而不是一个手工调出来的阈值——这是一个很可能会在投机解码之外反复出现的模式。