DSpark 是 DeepSeek 提出的一套大模型推理加速方案,在 DeepSeek-V4 线上把单用户生成速度提升 60%–85%,且输出分布严格无损(一个字都不改)。核心两招——让草稿写得更准,让验证花得更省。

这篇论文言简意赅,直接奔着两个硬核点去:半自回归生成的局部 softmax 设计,以及硬件感知调度器里那个绕不开的无损性证明。读完之后不得不感叹Deepseek团队的牛!


0. 为什么这篇论文值得逐字读

大多数投机解码的论文,要么只优化「草稿写得准不准」,要么只优化「该验多长」,而且基本都停在离线 benchmark 上刷接受长度。

DSpark 的不同在于三点:

  1. 它把算法和系统打通了——半自回归解决草稿质量,置信度调度解决高并发吞吐,两者是耦合设计的;
  2. 它有严格的无损性论证,并且诚实地指出了自己调度算法里一个会破坏无损性的坑,再用异步工程把坑填上;
  3. 它真上了生产(DeepSeek-V4-Flash / Pro),而且对线上数据的解读相当克制,那个 +661% 的数字作者自己说别当真。

下面从最底层讲起。


1. 先把问题说清楚:自回归的原罪

LLM 生成文字是自回归的:每吐一个 token,都要把前面所有 token 重新过一遍整个模型做一次完整前向传播。

形式化地说,推理延迟和输出长度成正比。后果是 GPU 利用率低、用户等待时间长。在实时对话、多轮 Agent 这类延迟敏感场景里,这是生产部署的头号瓶颈。


2. 投机解码:白嫖加速的把戏

2023 年的投机解码 (Speculative Decoding) 给了一个优雅解法:用一个又小又快的草稿模型 MdM_dMd 一口气猜出 γ\gammaγ 个候选 token,再用真正的目标模型 MtM_tMt 一次前向传播把整块批改掉,接受与目标分布一致的最长前缀,并补一个 bonus token。

2.1 接受规则与无损性

在第 kkk 个草稿位置,目标模型算出自己的分布 pktp^t_kpkt,与草稿分布 pkdp^d_kpkd 比较,token xkx_kxk 被接受的概率是:

accept(xk)=min⁡(1, pkt(xk)pkd(xk)) \text{accept}(x_k) = \min\left(1,\ \frac{p^t_k(x_k)}{p^d_k(x_k)}\right) accept(xk)=min(1, pkd(xk)pkt(xk))

直觉:目标模型觉得这个字比草稿模型更该写,就直接收;觉得草稿写过头了(草稿太自信),就按比例折扣概率收。 验证严格从左往右,第一个被拒的位置 kkk 一出现,后面 xk+1,…,xγx_{k+1},\dots,x_\gammaxk+1,,xγ 全部作废,无论它们质量多高。

这个规则在数学上保证最终输出分布精确等于目标模型自己自回归生成的分布。这是投机解码的灵魂:不是用质量换速度,是白赚的加速。

2.2 加速的核心公式与三个杠杆

τ\tauτ 为每轮真正被接受的 token 数,TdraftT_{\text{draft}}TdraftTverifyT_{\text{verify}}Tverify 分别为草稿与验证的墙钟时间,则平均每 token 延迟:

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

想加速,只有三条路:

  • TdraftT_{\text{draft}}Tdraft:草稿写得更快
  • τ\tauτ:草稿写得更准
  • 降有效 TverifyT_{\text{verify}}Tverify:验证花得更聪明

DSpark 三条都动了。记住这个公式,后面所有设计都在围着它转。


3. 两类草稿模型,各有各的死穴

草稿模型的设计,决定了 TdraftT_{\text{draft}}Tdraftτ\tauτ 怎么权衡。

① 自回归草稿(如 Eagle3):也是一个字一个字猜,每个位置都条件于前面已采样的字。建模能力强、连贯,但 Tdraft∝γT_{\text{draft}} \propto \gammaTdraftγ——猜的字越多越慢。所以它被迫用很小的 γ\gammaγ、很浅的网络。

② 并行草稿(如 DFlash):一次前向传播把 γ\gammaγ 个字同时全猜出来,TdraftT_{\text{draft}}Tdraft 几乎与块大小无关。这就允许它用更深的网络、更长的块(如 γ=16\gamma=16γ=16)。

DFlash 是 SOTA 并行草稿,它把草稿模型条件于从目标模型多层抽取的上下文特征(KV injection):

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

再把这些上下文特征沿序列维拼进每个草稿层的 K/V:

Ki=[WiKHctx; WiKHd],Vi=[WiVHctx; WiVHd](3) K_i = [W^K_i H_{\text{ctx}};\ W^K_i H_d],\quad V_i = [W^V_i H_{\text{ctx}};\ W^V_i H_d] \tag{3} Ki=[WiKHctx; WiKHd],Vi=[WiVHctx; WiVHd](3)

块内所有位置双向互相注意,并注意到目标上下文。

但并行草稿有个致命病——多模态碰撞 (multi-modal collision)。 因为每个位置是独立预测的,互相看不见。论文的例子很经典:上下文后面合理的接法有「of course」和「no problem」两种,并行草稿第 1 位可能采「of」、第 2 位可能采「problem」,拼出「of problem」这种鬼话。原因是第 2 位对所有可能的前驱求边缘,而非条件于第 1 位实际采样的那个字。

结果是后缀衰减 (suffix decay):越往后接受率掉得越快,草稿和验证算力双双浪费。


4. 第一刀:半自回归生成

核心思想:计算量大头继续用并行(保速度),只在输出端加一个极轻的串行头,让每个位置偷看一眼前面刚定下来的字(补依赖)。

4.1 因子分解 + 局部 softmax(这是关键)

并行阶段:并行骨干(本文用 DFlash)一次前向产出隐状态 h1,…,hγh_1,\dots,h_\gammah1,,hγ 和基础 logits U1,…,UγU_1,\dots,U_\gammaU1,,Uγ。(一个小改动:把 anchor 本身当成第 1 个预测位置,即 anchor + γ−1\gamma{-}1γ1 个 mask 产出 γ\gammaγ 个 logits,省一点算力。)

串行阶段:给基础 logits 叠加一个前缀依赖的转移偏置 Bk(x0,x<k,xk)B_k(x_0, x_{<k}, x_k)Bk(x0,x<k,xk),通过自回归因子分解诱导出一个块内因果分布:

P(X∣x0)=∏k=1γpk(xk∣x0,x<k),pk(v∣x0,x<k)=exp⁡(Uk(v)+Bk(x0,x<k,v))∑u∈Vexp⁡(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}),\quad 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 V}\exp\big(U_k(u) + B_k(x_0, x_{<k}, u)\big)} \tag{4} P(Xx0)=k=1γpk(xkx0,x<k),pk(vx0,x<k)=uVexp(Uk(u)+Bk(x0,x<k,u))exp(Uk(v)+Bk(x0,x<k,v))(4)

注意这个 softmax 是逐位置局部归一化的,不是全局归一化的能量模型。 这一点是整个设计的命门,原因在第 6 节相关工作里——投机解码的拒绝采样规则要求草稿模型给出精确的逐 token 概率 pkd(xk)p^d_k(x_k)pkd(xk)

历史上很多「在并行隐状态上叠加序列模块」的方案都栽在这:

  • CRF-NAT 用全局归一化的配分函数,算不出精确逐 token 概率;
  • CTC-drafter 因为对齐路径的隐变量边缘化,只能退化成贪心验证。

DSpark 靠把序列修正保持在局部,让每个 token 的概率仍是一次精确的 softmax 求值,干净地绕开了这个坑。这是它能做无损投机解码的根本前提。

4.2 两种串行头

Markov 头(默认):最省,BkB_kBk 只依赖前一个 token,退化为一阶转移 B(xk−1,xk)B(x_{k-1}, x_k)B(xk1,xk)。理论上它是个 V×VV\times VV×V 矩阵,太大,用低秩分解 B=W1W2B=W_1 W_2B=W1W2,其中 W1∈RV×rW_1\in\mathbb{R}^{V\times r}W1RV×rW2∈Rr×VW_2\in\mathbb{R}^{r\times V}W2Rr×V:

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

W1W_1W1 是查表(embedding lookup),W2W_2W2 是 logit 投影。默认 r=256r=256r=256,存储和每步计算都很小,大词表也扛得住。回到例子:第 1 位采出「of」后,Markov 头在第 2 位给「course」加分、给「problem」减分,碰撞被压住。

RNN 头:Markov 头只记得前一步。RNN 头维护一个递归状态 sks_ksk,累积块内整段前缀历史。每步把状态、前一 token embedding、骨干隐状态拼成 zk=[sk−1;W1[xk−1];hk]z_k=[s_{k-1};W_1[x_{k-1}];h_k]zk=[sk1;W1[xk1];hk],做一次门控更新(类 GRU):

sk=σ(Wgzk)⊙sk−1+(1−σ(Wgzk))⊙tanh⁡(Wczk),Bk(x<k,⋅)=W2⊤tanh⁡(Wozk)(6) \begin{aligned} 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),\\ B_k(x_{<k}, \cdot) &= W_2^\top \tanh(W_o z_k) \end{aligned} \tag{6} skBk(x<k,)=σ(Wgzk)sk1+(1σ(Wgzk))tanh(Wczk),=W2tanh(Wozk)(6)

实验结论:RNN 头只在很长的块上比 Markov 头好一点点,实现更复杂、部署性更差,所以默认用 Markov 头。 这印证了标题那句——「一点点自回归就够用了」。


5. 第二刀:置信度调度验证(系统侧精华)

第一招让块可以做长。但**「猜得多」≠「端到端就快」**。高并发下无脑验整块,反而拖垮系统吞吐。

理想验证长度沿两个轴变化:

  • 数据轴:代码这类结构化任务接受率天然高,开放聊天接受率低;
  • 系统轴:系统闲时多验几个字几乎免费,系统忙时每多验一个大概率被拒的字,就占了一个本该服务别人的 batch 名额。

DSpark 用「置信度头 + 硬件感知调度器」解决。

5.1 置信度头

让草稿每写一个字额外输出一个标量 ck∈(0,1)c_k\in(0,1)ck(0,1),含义是「在前面 token 全部过审的前提下,第 kkk 个 token 能过审的条件概率」。结构极轻:

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

监督信号用一个有解析解的目标——逐步接受率 ck∗c^*_kck,由草稿分布与目标分布的全变差距离 (TV distance) 决定:

ck∗=1−12 ∥pkd−pkt∥1(8) c^*_k = 1 - \frac{1}{2}\,\|p^d_k - p^t_k\|_1 \tag{8} ck=121pkdpkt1(8)

这恰好就是投机解码逐步接受概率的精确公式。两分布越接近,接受率越高。

5.2 序列温度缩放 STS:为什么不能直接用原始置信度

这里有个微妙但致命的点:下游调度器需要接受概率的绝对数值(要拿去算期望接受长度 τ\tauτ),而不只是排序。可神经网络的置信度天生过度自信 (overconfident)。直接用原始分数,吞吐估计会全错,调度就崩。

阈值类方法不在乎这个(它们只需要排序对),但 DSpark 的调度需要精确的累积接受概率,所以必须校准。

STS 的做法:因为一个前缀整体被接受的联合概率,按链式法则因子分解为各位置置信度的累积乘积 ∏i≤kci\prod_{i\le k} c_iikci,所以在验证集上从左到右逐位做一维网格搜索,找最优温度系数,最小化累积乘积的期望校准误差 ECE,同时保持前面已校准位置不变。温度缩放是保序变换,只改概率数值、不破坏置信度头学到的排序。

效果:校准后平均 ECE 从 3%–8% 降到约 1%,ROC-AUC 在 0.81–0.90,既有判别力又准。

5.3 硬件感知调度器:全局吞吐最大化

这是全篇最漂亮的部分。把「该验多长」formalize 成一个全局吞吐最大化问题

设 batch 内有 RRR 个请求,给请求 rrr 分配验证长度 ℓr∈{0,…,γ}\ell_r\in\{0,\dots,\gamma\}r{0,,γ}。因为投机解码只接受连续前缀,位置 jjj存活概率是累积乘积:

ar,j=∏i≤jcr,i a_{r,j} = \prod_{i\le j} c_{r,i} ar,j=ijcr,i

一步验证里:

  • 丢给目标模型的总 token 数:B=∑r=1R(1+ℓr)B = \sum_{r=1}^{R}(1+\ell_r)B=r=1R(1+r)
  • 期望被接受的 token 数:τ=∑r=1R(1+∑j=1ℓrar,j)\tau = \sum_{r=1}^{R}\Big(1 + \sum_{j=1}^{\ell_r} a_{r,j}\Big)τ=r=1R(1+j=1rar,j)
  • SPS(B)\text{SPS}(B)SPS(B) = 引擎在前向 batch 为 BBB 时每秒能跑多少步,开机时 profile 一次,存成轻量 cost table,查表 O(1)O(1)O(1)

目标:最大化系统级 token 吞吐:

Θ=τ⋅SPS(B) \Theta = \tau \cdot \text{SPS}(B) Θ=τSPS(B)

为什么这个看似组合爆炸的问题能贪心解? 关键是单调性。存活概率关于 jjj 单调不增:

ar,j=ar,j−1⋅cr,j≤ar,j−1 a_{r,j} = a_{r,j-1}\cdot c_{r,j} \le a_{r,j-1} ar,j=ar,j1cr,jar,j1

而把请求 rrr 的验证长度从 j−1j{-}1j1 延到 jjj,期望多接受的 token 数恰好等于 ar,ja_{r,j}ar,j(边际增益 = 存活概率)。这个单调性保证:把全局所有候选 token 按 ar,ja_{r,j}ar,j 从高到低排序、贪心往里收,天然尊重块内前缀依赖(不可能在收第 jjj 位前先收第 j+1j{+}1j+1 位)。

于是算法 1 就是:全局降序排序所有合法前缀扩展 → 沿这条贪心录取路径逐个 admit → 每 admit 一个就用 O(1)O(1)O(1) 查表更新 Θ\ThetaΘ一旦 Θ\ThetaΘ 不再上升立刻 break

5.4 无损性的坑 + 附录 A 反例完整推导(重头戏)

投机解码无损有个铁律——非预期性 (non-anticipating):「要不要录取第 kkk 个 token」的决定,绝不能依赖第 kkk 个 token 实际采样成了什么。

麻烦来了:置信度头要看前一个 token(W1[xk−1]W_1[x_{k-1}]W1[xk1]),所以算下一个存活概率 ar,k+1a_{r,k+1}ar,k+1 必须先知道当前 token xr,kx_{r,k}xr,k 的实例。如果做一次事后回顾式 (retrospective) 的全局搜索,就会把 xr,kx_{r,k}xr,k 偷偷泄漏进对第 kkk 步的录取决定,引入选择偏差。

下面把附录 A 的反例逐个数字走完

设单请求 R=1R=1R=1,最大长度 γ=2\gamma=2γ=2,第一位接受概率 a1=0.8a_1=0.8a1=0.8,profile 的硬件曲线:

SPS(1)=1.0,SPS(2)=0.5,SPS(3)=0.45 \text{SPS}(1)=1.0,\quad \text{SPS}(2)=0.5,\quad \text{SPS}(3)=0.45 SPS(1)=1.0,SPS(2)=0.5,SPS(3)=0.45

先算验 0 个、1 个草稿 token 的吞吐:

Θ0=1⋅SPS(1)=1.0 \Theta_0 = 1\cdot\text{SPS}(1) = 1.0 Θ0=1SPS(1)=1.0
Θ1=(1+0.8)⋅SPS(2)=1.8×0.5=0.9 \Theta_1 = (1+0.8)\cdot\text{SPS}(2) = 1.8\times0.5 = 0.9 Θ1=(1+0.8)SPS(2)=1.8×0.5=0.9

如果不早停,调度器会继续评估 Θ2\Theta_2Θ2。但 c2c_2c2 依赖 x1x_1x1,于是 a2=a1c2a_2=a_1 c_2a2=a1c2 也依赖 x1x_1x1。看 x1x_1x1 的两种实现:

情况 1(x1x_1x1 恰好导致高续接置信,c2=0.9c_2=0.9c2=0.9):a2=0.8×0.9=0.72a_2 = 0.8\times0.9 = 0.72a2=0.8×0.9=0.72

Θ2=(1+0.8+0.72)×0.45=2.52×0.45=1.134 \Theta_2 = (1+0.8+0.72)\times0.45 = 2.52\times0.45 = 1.134 Θ2=(1+0.8+0.72)×0.45=2.52×0.45=1.134

1.1341.1341.134{1.0, 0.9, 1.134}\{1.0,\,0.9,\,1.134\}{1.0,0.9,1.134} 中最大 → 返回 ℓ=2\ell=2=2第一个 token 被录取进验证前缀

情况 2(x1x_1x1 导致低续接置信,c2=0c_2=0c2=0):a2=0a_2=0a2=0

Θ2=(1+0.8+0)×0.45=0.81 \Theta_2 = (1+0.8+0)\times0.45 = 0.81 Θ2=(1+0.8+0)×0.45=0.81

最大值仍是 Θ0=1.0\Theta_0=1.0Θ0=1.0 → 返回 ℓ=0\ell=0=0第一个 token 不被录取

矛盾点暴露:第一个 token 到底录不录,竟取决于第一个 token 自己是什么。 这就是选择偏差——调度器偷偷偏爱「后续看起来顺」的那些 token。

论文进一步把输出分布算出来,证明它真的改变了结果。设词表 {A,B}\{A,B\}{A,B},第一位的分布:

pt(A)=0.7, pt(B)=0.3;pd(A)=0.5, pd(B)=0.5 p^t(A)=0.7,\ p^t(B)=0.3;\qquad p^d(A)=0.5,\ p^d(B)=0.5 pt(A)=0.7, pt(B)=0.3;pd(A)=0.5, pd(B)=0.5

第一位标准接受概率(应等于 a1a_1a1):

∑x∈{A,B}min⁡(pt(x),pd(x))=min⁡(0.7,0.5)+min⁡(0.3,0.5)=0.5+0.3=0.8 ✓ \sum_{x\in\{A,B\}}\min\big(p^t(x), p^d(x)\big) = \min(0.7,0.5)+\min(0.3,0.5) = 0.5+0.3 = 0.8\ \checkmark x{A,B}min(pt(x),pd(x))=min(0.7,0.5)+min(0.3,0.5)=0.5+0.3=0.8 

假设回顾式调度器如上行为:x1=Ax_1=Ax1=A 触发高置信(故 ℓ=2\ell=2=2)、x1=Bx_1=Bx1=B 触发低置信(故 ℓ=0\ell=0=0)。则首位输出 token 的分布:

  • x1=Ax_1=Ax1=A:被录取,接受概率 min⁡(1,0.7/0.5)=1\min(1, 0.7/0.5)=1min(1,0.7/0.5)=1,输出即 AAA;
  • x1=Bx_1=Bx1=B:不被录取,目标模型从 ptp^tpt 重新采一个新 token。

于是:

Pr⁡(Y=A)=Pr⁡(x1=A)⋅1⏟0.5×1+Pr⁡(x1=B)⋅pt(A)⏟0.5×0.7=0.5+0.35=0.85 \Pr(Y=A) = \underbrace{\Pr(x_1=A)\cdot 1}_{0.5\times1} + \underbrace{\Pr(x_1=B)\cdot p^t(A)}_{0.5\times0.7} = 0.5 + 0.35 = 0.85 Pr(Y=A)=0.5×1 Pr(x1=A)1+0.5×0.7 Pr(x1=B)pt(A)=0.5+0.35=0.85
Pr⁡(Y=B)=0.15 \Pr(Y=B) = 0.15 Pr(Y=B)=0.15

得到输出分布 (0.85, 0.15)(0.85,\,0.15)(0.85,0.15),与目标分布 (0.7, 0.3)(0.7,\,0.3)(0.7,0.3) 不符——无损性被打破。证毕。

早停为什么能救? 用同样数字:初始 Θbest=Θ0=1.0\Theta_{\text{best}}=\Theta_0=1.0Θbest=Θ0=1.0,试录第一个 token 得 Θ1=0.9\Theta_1=0.9Θ1=0.9,判断 0.9>1.00.9>1.00.9>1.0?不成立 → 立刻 break,返回 ℓ=0\ell=0=0。整个过程只用到了采样前就已知的 a1=0.8a_1=0.8a1=0.8,根本没去计算需要 x1x_1x1c2c_2c2。录取决定因此只依赖前缀信息,非预期性保住。

注:早停取到全局最优,前提是 Θ\ThetaΘ 单峰,即隐含假设硬件容量曲线平滑下降。真实硬件不满足这点——见第 8 节。


6. 训练目标(公式 9–12)

三个损失,全部用位置权重 wk=exp⁡ ⁣(−(k−1)/γ)w_k = \exp\!\big(-(k-1)/\gamma\big)wk=exp((k1)/γ) 加权,越靠前权重越大(前面的字一旦被拒整块作废,杠杆最高):

交叉熵(教草稿预测正确下一字):

Lce=−∑k=1γwklog⁡pkd(xk∗)(9) \mathcal{L}_{\text{ce}} = -\sum_{k=1}^{\gamma} w_k \log p^d_k(x^*_k) \tag{9} Lce=k=1γwklogpkd(xk)(9)

分布匹配(惩罚 TV 距离):

Ltv=∑k=1γwk ∥pkd−pkt∥1(10) \mathcal{L}_{\text{tv}} = \sum_{k=1}^{\gamma} w_k\,\|p^d_k - p^t_k\|_1 \tag{10} Ltv=k=1γwkpkdpkt1(10)

因为逐步接受概率 =1−12∥pd−pt∥1= 1-\tfrac{1}{2}\|p^d-p^t\|_1=121pdpt1,压低 Ltv\mathcal{L}_{\text{tv}}Ltv 直接等价于拉高期望接受率

置信度损失(BCE,教置信度头预测软标签 ck∗c^*_kck):

Lconf=−∑k=1γwk(ck∗log⁡ck+(1−ck∗)log⁡(1−ck))(11) \mathcal{L}_{\text{conf}} = -\sum_{k=1}^{\gamma} w_k\Big(c^*_k\log c_k + (1-c^*_k)\log(1-c_k)\Big) \tag{11} Lconf=k=1γwk(cklogck+(1ck)log(1ck))(11)

合成:

L=αceLce+αtvLtv+αconfLconf,(αce,αtv,αconf)=(0.1, 0.9, 1.0)(12) \mathcal{L} = \alpha_{\text{ce}}\mathcal{L}_{\text{ce}} + \alpha_{\text{tv}}\mathcal{L}_{\text{tv}} + \alpha_{\text{conf}}\mathcal{L}_{\text{conf}},\quad (\alpha_{\text{ce}}, \alpha_{\text{tv}}, \alpha_{\text{conf}}) = (0.1,\ 0.9,\ 1.0) \tag{12} L=αceLce+αtvLtv+αconfLconf,(αce,αtv,αconf)=(0.1, 0.9, 1.0)(12)

注意 αtv≫αce\alpha_{\text{tv}}\gg\alpha_{\text{ce}}αtvαce——目标是「骗过目标模型」而非「写对答案」,这两件事并不完全等价。训练时目标模型全程冻结,草稿模型共享其 embedding 与 LM head(也冻结),只更新骨干、串行块、置信度头。


7. 实验:为什么并行打赢了自回归

主结果:在 Qwen3-4B/8B/14B 上,DSpark 的宏平均接受长度比自回归 Eagle3 高 30.9% / 26.7% / 30.0%,比并行 DFlash 高 16.3% / 18.4% / 18.3%,在 Gemma4-12B 上同样成立。

最反直觉的现象:并行/半自回归普遍打赢了「理应更连贯」的纯自回归 Eagle3。 论文用「逐位条件接受率」拆开,给了漂亮解释。

这个指标的定义很关键:统计第 kkk 位时,分母只数那些前 k−1k{-}1k1 位全部被接受的实例,从而剥离「被前缀拖累」的惩罚,暴露每一步的纯预测质量。

三个发现:

① 第 1 个字是胜负手。 第一位大家都只看上下文,拼的纯是网络容量。自回归被 O(γ)O(\gamma)O(γ) 延迟逼成浅网络,并行能用深网络,所以 DFlash 在第一位就明显更高(Math 0.88 vs 0.81,Chat 0.72 vs 0.53)。而投机解码是严格前缀存活过程,第一位一旦被拒整块全废,杠杆最大,这点优势被指数级放大——这就是并行全局打赢自回归的根因。

② 后面靠依赖建模。 第 2 位之后,自回归利用「前文已定、后文更好猜」维持甚至上升条件接受率(Chat 从 0.53 升到 0.74);DFlash 因独立预测后缀快速衰减(Code 0.87→0.78,Chat 0.72→0.63),这正是多模态碰撞。

③ DSpark 两头通吃。 既继承并行的高起点(Math 起步 0.93),又用串行头压住衰减,全程高而稳。

其他值得记的:

  • 一点点自回归就够:2 层 DSpark 打赢 5 层 DFlash;块越长优势越大,从 γ=7\gamma{=}7γ=7 的 +16%/+15%/+18%(math/code/chat),拉大到 γ=15\gamma{=}15γ=15 的 +30%/+26%/+22%。
  • 串行头几乎不要钱:batch=128 下草稿长度 4→16,整轮延迟仅增 0.2%–1.3%,却换来最高 30% 的接受长度。
  • 置信度头确实能剪枝:阈值调高后,Chat 接受率从 45.7% 飙到 95.7%(剪掉注定被拒的尾巴),Math/Code 从 76.9%/67.6% 升到 92.5%/92.0%。

8. 落地工程:异步调度如何把第 5.4 节的坑天然填上

线上比离线多两个现实冲突:

  1. 真实硬件的 SPS(B)\text{SPS}(B)SPS(B) 曲线是锯齿状跳变的,不是平滑单峰(早停的全局最优前提失效);
  2. 动态变化的验证长度会和 CUDA Graph 重放、零开销调度 (ZOS) 打架——ZOS 要求「下一步 batch size 在本步结束前就得确定」,同步调度会卡死 GPU 流水线。

DSpark 的解法是异步调度。两步之前的置信度输出来估算即将到来的验证容量上限 KKK,把录取过程 cast 成动态 top-KKK 选择;而当前这一步的候选 token 仍按最新的累积置信度严格排序。近似容量带来轻微时间偏移,但选择机制本质保序——最有把握的 token 永远被优先验证。这样既隐藏了调度延迟,又无缝兼容 ZOS。

最妙的一步:正因为容量 KKK 只依赖两步之前的信息,这个异步设计本身就形成一道因果屏障。于是 DSpark 可以大胆去掉早停、做无约束全局搜索(去翻越锯齿悬崖找全局最优),却不会泄漏当前 token、不会破坏无损性——第 5.4 节那个会破坏无损性的坑,被异步天然填上了。这是算法洞察和系统工程咬合得极漂亮的一处。

变长 kernel 问题:不同请求验证长度不一,标准 decode kernel 只擅长定长 query,硬塞会因 padding 和负载不均严重浪费 GPU。解法是解耦物理执行与逻辑序列追踪——把所有请求的 token 拍平、当独立元素一视同仁处理,复杂的序列内依赖用一个 marker tensor 集成进稀疏注意力实现。在 DeepSeek-V4 架构上只需改 index-attention 和 compress 两个 kernel。


9. 线上数据 + 那个诚实的 661%

DSpark-5(γ=5\gamma=5γ=5)对比老基线 MTP-1(单 token 草稿):

  • V4-Flash:中等 SLA(80 tok/s/用户)下吞吐 +51%;匹配吞吐下单用户生成速度快 60%–85%;
  • V4-Pro:中等 SLA(35 tok/s/用户)下 +52%;匹配吞吐下快 57%–78%

机制(图 8):并发不高时调度器把验证预算从 MTP-1 固定的 2 个 token 放大到 4–6 个,吃满空闲算力;并发一高就平滑收紧,把低置信 token 在占用 batch 名额前剪掉。这就是它能稳住高并发的根本。

这里要给作者点个诚实分。 论文里那个 +661%(V4-Flash 在 120 tok/s 严苛 SLA 下)的数字,他们自己明确说别当成真实加速倍数——那是因为在极严 SLA 下,单 token 的 MTP-1 已濒临崩溃、只能撑极小并发,分母太小才显得比值巨大。作者把它解读为「DSpark 把以前根本做不到的交互档位变得可行」,即把服务的帕累托前沿往外推,而非吹一个 6 倍速。这种克制在如今的论文里不算常见,值得尊重。


10. 局限与总结

局限:即便调度器把验证浪费压到最低,DSpark 仍需先花固定草稿成本用并行骨干生成那 γ\gammaγ 个 token。遇到本身接受率极低的复杂请求,这笔前期算力不可回收。作者提出未来可在草稿模型里做难度感知的提前退出,让这类请求绕过整块生成。

总结:DSpark 的价值不在颠覆,而在缝合与落地

  • 算法上,它用一个极轻的局部归一化串行头,把「并行写得快」和「自回归写得准」缝在一起,既保住了无损投机解码必需的精确逐 token 概率,又压住了后缀衰减;
  • 系统上,它把验证长度选择 formalize 成全局吞吐最大化,用单调性证明了贪心可解,诚实地指出了无损性的坑,再用异步因果屏障优雅填坑;
  • 工程上,它真上了 DeepSeek-V4 生产,数据扎实、解读克制。

一句话总结:这是一篇「能落地」的论文,不是「刷榜」的论文。 半自回归骨架、置信度调度、异步无损保证这三件套,大概率会成为后续高并发投机解码方案的标配组件。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐