推测解码有一个很容易被忽略的转折点:低并发时,多验证几个候选 token 可能几乎不增加延迟;并发升高后,同样的候选会占用本可服务其他请求的计算槽位。

这意味着“草稿模型一次能猜多少 token”并不是完整问题。真正影响系统收益的是:

  • 草稿前缀在每个深度上还有多大概率被目标模型接受;
  • 多验证一个 token 会让当前批次的目标模型前向增加多少成本;
  • 不同请求是否值得使用相同的验证长度;
  • 变长验证能否在 GPU 上真的变成更少的计算,而不是重新被 padding 补齐。

DSpark 把这些问题连在了一起。它先用半自回归 drafter 一次生成一个候选块,再估计候选前缀的存活概率,最后根据当前批次的边际成本,为每个请求选择不同的验证窗口。SGLang 则通过 ragged verification、分级 CUDA Graph 和重叠调度,让“少验证”真正落到执行层。

这篇文章不把 DSpark 简化成一种新的草稿模型,而是沿着一次完整推测循环,解释它为何要预测置信度、调度器究竟在优化什么,以及这套方法在哪些负载下才可能带来收益。

一次推测解码究竟节省了什么

标准自回归解码每轮只产生一个 token:

目标模型前向 -> 采样 1 个 token -> 更新状态 -> 下一轮

若输出长度为 (N),关键路径上通常需要约 (N) 次串行目标模型前向。推测解码引入更便宜的 drafter,让它先提出一段候选:

drafter:     y1, y2, y3, y4, y5, y6
                  │
                  ▼
target:      一次并行计算这些位置的真实分布
                  │
                  ▼
result:      接受连续前缀,首次拒绝后停止沿该路径接受

假设前 3 个候选通过,第 4 个被拒绝,那么这一轮能向前推进多个 token,却只执行了一次目标模型验证。它减少的不是总浮点运算,而是目标模型的串行轮数;额外工作包括 drafter 推理、候选验证以及接受或校正逻辑。

因此,推测解码成立需要一个基本不等式:

[ T_{draft}+T_{verify}(K) < E[A_K]\cdot T_{decode} ]

其中:

  • (K) 是送去验证的候选数;
  • (A_K) 是这一轮实际接受并推进的 token 数;
  • (T_{draft}) 是生成候选块的成本;
  • (T_{verify}(K)) 是目标模型验证候选的成本;
  • (T_{decode}) 是普通解码推进一个 token 的成本。

这个表达式只用于建立直觉。真实服务还要考虑批处理、KV Cache、MoE 路由、采样、通信和调度开销,但它已经指出一个事实:扩大 (K) 同时提高潜在收益和验证成本,最优值不一定是 drafter 能生成的最大长度。

接受是一件“前缀事件”

候选链不是若干互相独立的 token。要接受第 5 个候选,前 4 个必须先通过验证。设第 (i) 个 token 在此前缀已经通过的条件下,被接受的概率为 (c_i):

[ c_i=P(\text{第 }i\text{ 个 token 通过}\mid\text{前 }i-1\text{ 个已通过}) ]

那么候选前缀至少存活到深度 (k) 的概率约为:

[ S_k=\prod_{i=1}^{k}c_i ]

这条乘积曲线通常随深度下降。即使每一步的条件接受率都不算低,长前缀的整体存活概率也会快速衰减。例如:

深度 (k) 条件接受率 (c_k) 前缀存活率 (S_k)
1 0.92 0.920
2 0.88 0.810
3 0.82 0.665
4 0.73 0.485
5 0.61 0.296
6 0.48 0.142

验证第 1 个候选几乎总能产生价值,而验证第 6 个候选只有在前 5 个全部通过时才有机会被接受。第 6 个位置即使自身预测得不错,也可能因为前缀已经断裂而无法贡献输出。

期望接受长度可以由存活曲线表示:

[ E[A_K]\approx\sum_{k=1}^{K}S_k ]

把验证窗口从 (K) 扩到 (K+1) 时,新增的期望收益正是 (S_{K+1})。越靠后的候选,边际收益通常越小。这为“根据置信度截短验证”提供了直接依据。

为什么并行 drafter 容易在后半段失真

生成候选块大致有两条路线。

自回归起草

drafter 逐 token 生成候选:

y1 -> y2 -> y3 -> y4 -> ...

后一个 token 能看到前一个 token,序列依赖完整,深层候选通常更稳定。但候选长度越大,drafter 的串行延迟越高。如果起草本身占据关键路径,目标模型节省的轮数会被小模型的多轮前向抵消。

全并行起草

另一条路线让 drafter 一次预测所有位置:

context -> [y1, y2, y3, y4, y5, y6]

它把起草延迟压缩到一次前向,却削弱了块内依赖:预测 (y_6) 时,模型未必真正知道前面最终会选择 (y_1\ldots y_5) 中的哪些 token。位置越靠后,条件信息缺口越大,容易出现论文所讨论的 suffix decay——候选块后半段的接受质量持续下降。

两种路线的矛盾可以概括为:

起草方式 块内依赖 起草关键路径 常见风险
自回归 随候选长度增长 drafter 延迟过高
全并行 接近固定 后缀接受率衰减

DSpark 没有在两端二选一,而是采用半自回归的块 drafter。

半自回归块:重计算并行,轻依赖串行

DSpark 的核心拆分是:让昂贵的 backbone 并行处理整个候选块,再用很轻的序列输出头恢复块内依赖。

                         ┌─> position 1 representation ─┐
context -> parallel backbone -> position 2 representation ─┼-> lightweight sequential head
                         ├─> position 3 representation ─┤       y1 -> y2 -> y3 -> ...
                         └─> position K representation ─┘

轻量输出头可以使用 Markov 或 RNN 风格的状态传递。生成第 (i) 个位置时,它不仅读取 backbone 为该位置准备的表示,还条件于先前选出的 token 或短状态。因此:

  • 大部分参数和矩阵计算只运行一次;
  • 块内仍保留从前向后的 token 依赖;
  • 串行部分足够轻,不至于退化为完整的自回归 drafter;
  • 同一轮还能额外输出每个位置的接受置信度。

这里的目标不是让 drafter 单独生成更好的文本。草稿永远要经过目标模型验证。更准确地说,drafter 要在固定起草延迟下,尽量提高“前缀被目标模型接受”的概率。

置信度头预测的不是语言模型概率

语言模型给某个 token 的 softmax 概率,并不等于它会通过推测验证的概率。两者至少受到以下因素影响:

  • 目标模型与 drafter 的分布差异;
  • temperature、top-p、top-k 等采样设置;
  • 接受/拒绝算法的具体规则;
  • 候选位置与前缀是否已经存活;
  • 训练语料与在线请求的分布偏移。

所以 DSpark 训练独立的 confidence head,估计每个候选在验证路径上的存活可能性。调度器真正需要的是 (c_i) 或由它得到的 (S_i),而不是“drafter 对自己选出的 token 有多自信”。

这个区别很重要。一个过度自信的 drafter 可能为错误候选给出很高的 token 概率;若调度器直接相信它,就会把低价值后缀继续送入目标模型,最终只增加验证负担。

为什么置信度必须校准

排序正确还不够。假设两个位置的预测接受概率分别是 0.8 和 0.6,调度器不仅要知道前者更可靠,还要把这些值与真实成本比较。若模型说“0.8”的样本实际只有 0.55 通过,成本决策就会系统性偏长。

DSpark 使用 Sequential Temperature Scaling(STS)对序列置信度进行校准。校准的目标可以描述为:

在预测存活概率接近 (p) 的样本中,真实前缀存活频率也应接近 (p)。

常见检查方式是把预测值分桶,比较每个桶的平均置信度与真实接受率,并计算 Expected Calibration Error(ECE):

[ ECE=\sum_{b=1}^{B}\frac{|I_b|}{n} \left|\operatorname{acc}(I_b)-\operatorname{conf}(I_b)\right| ]

其中 (I_b) 是第 (b) 个置信度区间。ECE 低不代表 drafter 的接受率高,它只说明“报出的概率更可信”。一个接受率一般但校准良好的模型,反而可能比高估自己的模型更适合成本调度。

顺序校准还需要保留前缀条件。第 5 个位置的标签只有在前 4 个位置存活时才有正确语义,不能简单把所有位置混成独立二分类样本。

调度器要比较期望收益与边际成本

有了存活曲线,调度器还缺少另一半信息:多验证一个 token 到底有多贵。

SGLang 的 DSpark 集成用一个可测量的加性成本模型描述单步时间:

[ T(bs,K)=bias+\alpha(bs)+\theta(M),\qquad M=bs+K ]

这里:

  • (bs) 表示当前批次中的请求数,也对应不使用额外候选时的基础 token 数;
  • (K) 表示整个批次额外加入的验证 token 数;
  • (alpha(bs)) 表示随请求规模变化、但无法通过截短候选消除的成本;
  • ( heta(M)) 表示与目标模型实际处理 token 总数相关、能够通过少验证而降低的成本。

对单个请求而言,保留第 (k+1) 个候选带来的期望收益约为 (S_{k+1})。整个批次则要联合考虑所有请求:每加入一个验证位置,都会改变 (K),也就可能改变当前 GPU 步的执行时间。

调度器的直观规则是:

若新增候选的期望接受收益
    > 它给当前批次带来的边际时间成本
则继续扩大该请求的验证窗口;
否则在当前位置截断。

实际目标会使用吞吐、延迟或二者组合后的归一化效用,不能直接把“token 概率”和“毫秒”比较。但核心始终是边际决策:不是问第 6 个候选能否通过,而是问它的通过机会是否值得让整个批次承担更多成本。

GPU 成本不是一条平滑直线

如果每多验证一个 token 都增加固定时间,那么调度器只需一个简单阈值。真实 GPU 的边际成本更像阶梯:

step time
   │                 ┌──────────
   │          ┌──────┘
   │    ┌─────┘
   │────┘
   └──────────────────────── total verify tokens

在同一个 kernel 形状、CUDA Graph 档位或执行 wave 内增加少量 token,可能几乎不改变步时;一旦越过边界,就要启用更大的图、更多 thread block 或额外通信,时间会突然上升。

因此,成本表需要针对实际组合测量:

  • GPU 型号与数量;
  • 模型结构和精度;
  • tensor、data、expert parallelism 拓扑;
  • KV Cache 布局与可用显存;
  • CUDA Graph 的捕获档位;
  • 典型 batch size 和上下文长度。

把一台机器上的 cost table 复制到另一套部署,可能让调度器持续选择错误窗口。论文中的调度思想可以迁移,成本曲线不能脱离硬件直接照搬。

为什么每个请求都需要自己的验证窗口

同一批次里的请求难度并不相同。代码补全可能有很强的局部规律,事实问答可能很快收敛,开放式写作则有更多合理续写。即使属于同一任务,请求也会在生成过程中经历高、低置信度片段。

假设 drafter 一次都提出 8 个候选,调度器可能给出:

request A: verify 7 tokens  ███████
request B: verify 4 tokens  ████
request C: verify 2 tokens  ██
request D: verify 5 tokens  █████

如果只允许全局固定 (K=8),低置信度请求会浪费大量验证槽位;若全局固定 (K=2),高置信度请求又失去一次推进更远的机会。逐请求窗口让系统把验证预算分配给更可能产生接受 token 的位置。

窗口还应随负载变化。在 batch size 1 时,目标模型常受权重读取或固定启动开销支配,多验证几个 token 未必明显变慢;高并发时,额外验证 token 会与其他请求争夺计算资源,截短才更容易转化为吞吐收益。

所以 DSpark 不是一个固定配置:

相同请求 + 不同系统负载 -> 可能得到不同验证深度
不同请求 + 相同系统负载 -> 也可能得到不同验证深度

截短为什么不会改变目标分布

“根据置信度少验一些”容易让人担心输出质量。这里要区分两个动作:

  1. 调度器决定把候选前缀中的哪些位置交给目标模型;
  2. 目标模型对被提交的候选执行标准验证和接受/校正。

DSpark 改变的是候选集合,不是目标验证规则。被截掉的后缀不会被直接接受,也不会绕过目标模型写入输出;系统只是放弃了本轮可能多推进几个 token 的机会,随后仍由目标分布继续生成。

只要以下约束成立,调度就是性能选择而不是近似采样:

  • 所有提交的草稿 token 都经过目标模型验证;
  • 接受、拒绝与残差采样遵循对应推测算法;
  • 截短位置不会污染已提交位置的 attention 或状态;
  • KV Cache 和其他模型状态只提交已接受路径;
  • 随机数使用与批次压缩不会破坏采样语义。

换句话说,短窗口可能降低这一轮的平均接受长度,却不应改变目标模型定义的最终输出分布。工程上的难点正是让变长、压缩和异步执行仍满足这些状态约束。

Ragged Verification:不等长必须真的少算

调度器输出逐请求窗口后,最直接的实现是把所有请求 padding 到批次最大长度:

A: 7 real tokens
B: 4 real tokens + 3 padding
C: 2 real tokens + 5 padding
D: 5 real tokens + 2 padding

这样虽然逻辑上只验证 18 个候选,kernel 仍按 (4\times7=28) 个位置执行,调度器节省的大部分工作被 padding 吃掉。

SGLang 的 compact 路径会把每个请求选中的 token 前向紧密打包:

[A1 A2 A3 A4 A5 A6 A7 | B1 B2 B3 B4 | C1 C2 | D1 D2 D3 D4 D5]

再用类似 cu_seqlens 的边界元数据描述每段属于哪个请求。attention、位置编码、KV 索引和输出回写都依据这些边界恢复逻辑序列。

这种 ragged batch 有两个层面的收益:

  • 张量中不再保留逐请求的尾部 padding;
  • 目标模型的 attention 和 MLP 只处理真实选中的行。

但它也增加了实现复杂度。系统必须保证紧密打包前后的 token、请求、位置、KV slot 和采样状态一一对应,任何索引错位都会造成难以察觉的错误输出。

变长批次如何与 CUDA Graph 共存

CUDA Graph 依赖相对稳定的执行形状,而 ragged verification 每轮的总 token 数都可能变化。若为每个精确长度捕获一张图,图数量和显存开销会失控;若只捕获最大形状,又会重新引入大量空算。

SGLang 采用分级档位:先计算紧密打包后的总 token 数,再向上取整到最近的已捕获 graph tier。例如:

真实 token 总数  37 -> replay 40-token graph
真实 token 总数  58 -> replay 64-token graph
真实 token 总数  65 -> replay 80-token graph

少量档位内 padding 换来稳定的 graph replay,同时仍远小于“每个请求都补到最大窗口”的二维 padding。档位设计要在以下目标之间平衡:

  • 档位更密:空算更少,但捕获和常驻显存更多;
  • 档位更疏:图管理简单,但边界附近浪费更大;
  • 高负载档位:决定系统吞吐;
  • 低负载档位:更影响单请求延迟。

在 Data Parallel Attention 等多 rank 执行中,各 rank 还需要以共同档位同步前进,通常由本轮需求最大的 rank 决定 graph tier。否则不同 rank 选择不同执行路径,集合通信可能无法对齐。

static、compact 与 cap-accept 三种模式

SGLang 为 DSpark 验证提供了三个有不同用途的模式:

模式 实际验证范围 实际提交范围 主要用途
static 完整候选块 标准接受前缀 固定窗口基线
compact 调度器选择的窗口 该窗口内的接受前缀 生产执行与真实降算
cap-accept 完整候选块 只提交调度窗口以内 观测截短后的反事实上限

compact 才是把较短窗口转成较少目标计算的路径。cap-accept 故意保留完整验证成本,因此不用于获得主要加速;它的价值在于回答一个观测问题:如果刚才没有截短,后面还能接受多少 token?

这三种模式最好成组使用:

  1. static 建立完整块的性能与接受率基线;
  2. compact 测量动态调度的真实端到端收益;
  3. 抽样运行 cap-accept,判断调度器是否过早截断。

只比较 staticcompact 的平均接受长度,很容易得出错误结论。后者主动放弃了低性价比的候选,接受长度下降可能正是吞吐上升的原因。

截短会制造观测盲区

一旦 compact 在第 4 个位置结束验证,系统就不知道原始 8-token 块在第 5 至第 8 个位置的真实接受情况。这是典型的 censored feedback:调度行为改变了能够观察到的数据。

如果直接使用线上已验证样本继续训练置信度模型,会出现选择偏差:

  • 高置信度位置被频繁验证,标签充足;
  • 低置信度尾部被频繁截断,几乎没有真实标签;
  • 调度器可能因为缺少尾部数据而越来越确信“截断是对的”。

可行的观测策略包括:

  • 对少量请求运行 cap-accept,直接取得完整块标签;
  • 使用未来目标 token 或 logprob 估计被截尾部的潜在接受结果;
  • 为估计值报告区间,而不是把它当成已观测事实;
  • 按任务、深度、负载和时间分别监控校准误差;
  • 保留固定比例的无截短流量,防止长期反馈闭环失真。

需要同时报告两个量:实际接受长度,以及相对于完整块可接受上限的利用率。前者决定当前执行效率,后者用于判断调度器是否留下了过多未兑现收益。

调度不能重新制造 CPU 气泡

动态验证每轮都需要读取置信度、查询成本表、选择窗口、建立紧密批次。如果这些动作要求 GPU 把结果同步回 CPU,主线程做完决策再启动下一轮,省下的目标计算可能被 host bubble 抵消。

SGLang 的实现把调度与当前 GPU 前向重叠,并使用异步 future、设备侧屏障和设备上的 page table 减少同步。其思路可以简化为:

time ─────────────────────────────────────────────>

GPU:       forward(t) -------- forward(t+1) --------
CPU:             schedule(t+1)      schedule(t+2)
confidence:   older result ─────> next usable decision

置信度 relay 使用两步之前可获得的信息,使 CPU 不必在每轮等待最新 GPU 输出。这样做引入了“信息略旧”的问题,但避免了每步同步造成的确定性停顿。

工程上必须明确时间语义:

  • 这份置信度对应哪个请求、哪个生成位置;
  • 请求在等待期间是否被取消、抢占或重新入批;
  • KV block 是否发生迁移;
  • 旧决策是否仍适用于当前 batch cost;
  • 异步异常是否会让请求退回安全的固定窗口。

动态算法若不能与调度器时间线对齐,理论上的边际收益无法转成稳定尾延迟。

Dense、MoE 与非 KV 状态带来的差别

对 dense 模型而言,增加验证 token 通常是在相同权重上增加矩阵行数。对 MoE 模型,额外候选还可能激活更多专家,引起 token dispatch、负载不均与跨设备通信。此时仅用总 token 数 (M) 建模可能不够,还要观察:

  • 每层激活的 unique experts;
  • 每个专家收到的 token 数;
  • 最慢 rank 的通信与计算时间;
  • 验证窗口变化是否跨过新的专家并集或通信档位。

DSpark 已被用于 dense 与 sparse 模型,但“支持”不代表同一成本表或调度参数可以通用。MoE 部署更需要在线测量真实步时,而不是按理论 FLOPs 推断。

另一个边界来自带循环状态的模型。普通 Transformer 的回滚主要围绕 KV Cache;如果模型还有线性注意力或状态空间模块的 recurrent state,推测路径被拒绝后也必须恢复这些状态。

SGLang 在 Kimi K3 的 DSpark 集成中使用 ReplaySSM:不为每条候选路径保存完整状态快照,而是保留必要的原始输入,在确认接受前缀后重放以恢复正确状态。这说明推测解码的“无损”不仅是概率公式问题,还取决于所有隐状态都能按已接受路径提交或回滚。

从 checkpoint 到服务是一份联合契约

一个 DSpark drafter 不是可随意挂载的小模型。至少以下对象必须保持一致:

  • 目标模型权重与版本;
  • tokenizer、词表和特殊 token;
  • chat template 与 reasoning 模式;
  • drafter 的候选块长度和输出头结构;
  • confidence head 的标签定义;
  • STS 校准参数及其数据分布;
  • 服务端的采样与接受实现;
  • 成本表对应的硬件和并行拓扑。

训练流程可以分成四个相互校验的阶段:

目标模型轨迹与隐藏信息
          │
          ▼
训练半自回归 block drafter
          │
          ├── token / block prediction objective
          └── prefix acceptance labels
          │
          ▼
训练 confidence head + STS calibration
          │
          ▼
离线 acceptance / calibration 评估
          │
          ▼
在目标硬件测 cost table,接入 compact verification

校准集应接近线上请求,却不能与训练样本完全重合。若服务侧更换 temperature、模板、模型 checkpoint 或量化方式,接受概率和校准关系都可能漂移,需要重新评估,而不是只确认模型能够加载。

一组数字不能证明调度有效

评估 DSpark 至少要把四个层次分开。

草稿质量

  • 各深度的条件接受率 (c_i);
  • 前缀存活率 (S_i);
  • 完整块的平均可接受长度;
  • 与固定块长 drafter 的 draft latency 对比。

平均值会掩盖 suffix decay,必须按深度画曲线。还应按代码、数学、对话、长文本等请求类型切分,确认收益是否只来自某一类数据。

置信度质量

  • reliability diagram;
  • ECE、Brier score 或 log loss;
  • 不同深度、负载、采样配置下的误差;
  • 调度窗口与完整块接受上限的差距。

高接受率和好校准是两个维度。调度器更依赖后者,因为它要把概率放入成本函数。

执行层成本

  • 每轮真实验证 token 数;
  • ragged packing 后落入的 graph tier;
  • draft、schedule、verify、sample 各阶段耗时;
  • padding 比例与 host/device 同步次数;
  • KV、循环状态与临时 buffer 的显存占用。

若日志只显示“选中了更短窗口”,却没有显示目标模型实际处理的行数和步时,就不能证明 compact 路径真的省下了计算。

端到端服务

  • TTFT、TPOT、inter-token latency;
  • 每秒完成 token 数与每 GPU goodput;
  • P50、P95、P99 延迟;
  • 在给定 SLO 下可承载的并发;
  • 请求取消、抢占与长短请求混合时的稳定性。

评测应覆盖从 batch size 1 到饱和负载的完整曲线。动态截短本来就主要解决高并发验证竞争,只报告单请求数据会错过它的设计目标。

如何解读论文与公开系统结果

DSpark 论文在 Qwen3 目标模型和 DeepSeek-V4 预览版本上报告了接受长度与端到端性能提升;SGLang 随后的工程文章复现了动态截短的机制和趋势,并明确指出硬件与流量不同,不追求逐位复现论文数字。

这类结果应按三层语义阅读:

  1. 论文结果证明所给模型、checkpoint、数据集和硬件上,方法达到了报告值;
  2. 框架复现证明核心机制能进入真实推理栈,并观察到相同方向的曲线;
  3. 本地结论仍必须由自己的模型、并行拓扑、请求分布和 SLO 测得。

Kimi K3 的 SGLang 集成给出了一个很有代表性的例子:drafter 提出 8-token 块,但对话负载的平均接受长度远低于 8,意味着完整验证会检查许多最终无法提交的位置。公开测试中,动态调度在较高 batch 下扩大了吞吐收益,而低 batch 收益较小。这个案例支持“按负载调整窗口”的方向,却不能推出其他模型也应使用 8-token 块或相同阈值。

对任何百分比提升,都应同时记录:

  • 基线是普通解码、MTP-1,还是另一种 drafter;
  • 是单用户速度、总吞吐,还是满足 SLO 的 goodput;
  • batch 是固定值还是在线动态批处理;
  • 输出长度、上下文长度和采样参数;
  • 是否包含 drafter、调度和状态回滚的全部成本。

缺少这些条件的“加速比例”不具备可迁移性。

一条稳妥的上线顺序

如果要把置信度调度引入现有推测解码服务,可以按风险由低到高推进。

先建立固定窗口基线

固定 drafter checkpoint、采样参数和候选块长度,记录不同并发下的:

  • 完整块接受曲线;
  • 目标 verify 步时;
  • 各阶段 GPU 与 CPU 时间;
  • 端到端 SLO 和 goodput。

没有这条基线,动态模式出现更短接受长度时无法判断是合理取舍还是质量回退。

影子计算窗口,不改变执行

让置信度头和调度器产生建议窗口,但仍执行完整验证。这样可以检查:

  • 建议窗口是否随负载合理缩短;
  • 置信度是否校准;
  • 建议截断点之后实际还会接受多少 token;
  • 调度计算是否引入 CPU 同步或异常。

这相当于持续的 cap-accept 观察阶段,成本较高,但最容易定位算法问题。

启用 compact 并保留观测流量

逐步放量 compact verification,同时让一小部分请求继续完整验证,用于监控反事实上限和分布漂移。应设置自动回退条件,例如:

  • ECE 超过阈值;
  • P99 延迟恶化;
  • graph fallback 或 shape miss 激增;
  • 接受上限利用率持续下降;
  • 状态回滚校验失败。

最后再做在线自适应

成本表先离线测量,再考虑根据线上步时缓慢更新。在线模型更新太快会把短期噪声反馈给调度器,造成窗口振荡;更新太慢又无法适应流量和并行拓扑变化。生产系统需要版本化 cost table,并保留可一键切回的静态配置。

常见失败模式

现象 更可能的原因 优先检查
窗口变短,但步时不降 仍按最大长度 padding 或总 token 未进入更小 graph tier 实际执行行数、tier 命中
接受长度高,吞吐反而下降 draft 或 verify 边际成本过大 分阶段耗时、饱和 batch
离线校准好,线上持续过度验证 请求分布或采样配置漂移 分桶 ECE、模板与 temperature
单请求更慢,高并发更快 固定调度开销无法在低负载摊薄 batch 分段策略
平均值改善,P99 恶化 graph fallback、同步或最慢 rank 拖尾 尾部 trace、rank 时间
compact 输出偶发不一致 token/KV/state 索引或随机数语义错误 与 static 的确定性对照
MoE 成本表不稳定 expert scattering 与通信未建模 active experts、all-to-all
线上校准数据越来越偏 截短造成 censored feedback 完整验证抽样比例

其中最危险的不是“没有加速”,而是 compact 与 static 在相同随机条件下产生语义不一致。这类问题必须先按正确性缺陷处理,不能通过调低窗口掩盖。

DSpark 没有消除的成本

DSpark 每轮仍要先生成完整的初始候选块。若请求本身很难预测,调度器可能只选择很短窗口,但 drafter 已经付出了生成整块的固定成本。此时系统形成了:

生成 8 个草稿 -> 只值得验证 1 到 2 个 -> 草稿成本难以回收

这也是论文和 SGLang 路线图仍在讨论的方向:能否根据请求难度提前绕过推测解码,或者在 drafter 内部早停,而不是先生成完整块再截短。

其他尚不能由一次集成自动解决的问题包括:

  • cost model 是否能随负载在线更新且保持稳定;
  • 更多模型结构与并行拓扑的覆盖;
  • CUDA Graph 在复杂 shape 下的鲁棒性;
  • 长期流量漂移下的校准和可观测性;
  • 极低延迟与最大吞吐目标之间如何切换策略。

因此,DSpark 更像一套联合优化框架:drafter 决定候选质量,confidence head 给出可调度的概率,cost model 描述硬件代价,ragged execution 把选择转成真实降算,观测系统再防止反馈闭环失真。缺失其中任何一环,动态窗口都可能只停留在算法层。

总结

DSpark 解决的不是“怎样永远接受更多 token”,而是一个更贴近在线系统的问题:在当前请求与当前负载下,验证到哪里最划算。

它的完整逻辑可以压缩成五步:

  1. 半自回归 block drafter 用一次重前向提出完整候选块,并用轻量序列头保留块内依赖;
  2. confidence head 估计各位置的条件接受概率,STS 把置信度校准为可用于决策的概率;
  3. 调度器把前缀存活收益与当前批次的边际 verify 成本比较,为每个请求选择窗口;
  4. SGLang 用紧密打包、ragged verification 和分级 CUDA Graph,让短窗口真正减少目标模型计算;
  5. cap-accept、校准指标和完整验证抽样弥补截短带来的观测盲区。

最值得保留的判断标准也很简单:平均接受长度不是最终目标。只有当较少的验证工作转化为更低步时、更高 SLO goodput,并且目标分布与模型状态仍保持正确时,置信度调度才完成了从论文算法到推理系统的闭环。

参考资料