本文目录
推测解码有一个很容易被忽略的转折点:低并发时,多验证几个候选 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 不是一个固定配置:
相同请求 + 不同系统负载 -> 可能得到不同验证深度
不同请求 + 相同系统负载 -> 也可能得到不同验证深度
截短为什么不会改变目标分布
“根据置信度少验一些”容易让人担心输出质量。这里要区分两个动作:
- 调度器决定把候选前缀中的哪些位置交给目标模型;
- 目标模型对被提交的候选执行标准验证和接受/校正。
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?
这三种模式最好成组使用:
- 用
static建立完整块的性能与接受率基线; - 用
compact测量动态调度的真实端到端收益; - 抽样运行
cap-accept,判断调度器是否过早截断。
只比较 static 和 compact 的平均接受长度,很容易得出错误结论。后者主动放弃了低性价比的候选,接受长度下降可能正是吞吐上升的原因。
截短会制造观测盲区
一旦 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 随后的工程文章复现了动态截短的机制和趋势,并明确指出硬件与流量不同,不追求逐位复现论文数字。
这类结果应按三层语义阅读:
- 论文结果证明所给模型、checkpoint、数据集和硬件上,方法达到了报告值;
- 框架复现证明核心机制能进入真实推理栈,并观察到相同方向的曲线;
- 本地结论仍必须由自己的模型、并行拓扑、请求分布和 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”,而是一个更贴近在线系统的问题:在当前请求与当前负载下,验证到哪里最划算。
它的完整逻辑可以压缩成五步:
- 半自回归 block drafter 用一次重前向提出完整候选块,并用轻量序列头保留块内依赖;
- confidence head 估计各位置的条件接受概率,STS 把置信度校准为可用于决策的概率;
- 调度器把前缀存活收益与当前批次的边际 verify 成本比较,为每个请求选择窗口;
- SGLang 用紧密打包、ragged verification 和分级 CUDA Graph,让短窗口真正减少目标模型计算;
cap-accept、校准指标和完整验证抽样弥补截短带来的观测盲区。
最值得保留的判断标准也很简单:平均接受长度不是最终目标。只有当较少的验证工作转化为更低步时、更高 SLO goodput,并且目标分布与模型状态仍保持正确时,置信度调度才完成了从论文算法到推理系统的闭环。
参考资料
觉得有帮助?
分享给同样关注系统性能的人。