草稿模型很小,不等于它的耗时可以忽略。它可能与目标模型争用显存带宽,也可能部署在另一张卡上,带来传输和同步。只报告接受率,无法说明系统是否真的加速。
投机采样论文讨论了如何在保持目标分布的条件下加速生成;实际收益仍取决于草稿成本、验证成本与一次能够输出多少词元。原始论文把概率正确性与具体硬件上的速度区分开,才不会把算法条件误当成部署结果。
按每个最终输出 Token 摊成本
一轮投机的时间可以先拆成草稿、传输、目标验证和必要的后处理。它们是否能重叠,必须由实现证明。在没有测量前,先按串行相加能暴露隐藏成本;直接取各阶段的最大值,会提前假设完全重叠。
下面假设每轮输出数量已经由完整验证过程记录。输出数包含最终提交给用户的词元,不等于草稿长度,也不是只数被接受的候选。所有耗时都是人为输入的教学值,单位毫秒。
from fractions import Fraction as F def summary(rounds, baseline_ms_per_token): if not rounds or baseline_ms_per_token <= 0: raise ValueError("empty or invalid input") total_ms, outputs = F(0), 0 for r in rounds: if r["outputs"] <= 0 or any(r[k] < 0 for k in ("draft", "transfer", "verify")): raise ValueError("invalid round") total_ms += r["draft"] + r["transfer"] + r["verify"] outputs += r["outputs"] speculative = total_ms / outputs return speculative, baseline_ms_per_token / speculative rounds = [dict(draft=F(2), transfer=F(1), verify=F(5), outputs=2), dict(draft=F(2), transfer=F(1), verify=F(5), outputs=2)] per_token, speedup = summary(rounds, F(5)) assert per_token == 4 and speedup == F(5, 4) slower = [dict(draft=F(4), transfer=F(3), verify=F(5), outputs=2)] assert summary(slower, F(5))[1] == F(5, 6) print("toy ms/token:", float(per_token), "toy ratio:", float(speedup))相同的两词元输出,第一组得到 4 毫秒每词元,第二组变成 6 毫秒,反而慢于假设的 5 毫秒基线。接受率没有在这一步替你支付通信费用。
单请求加速可能牺牲整体吞吐
目标验证通常不是免费检查。候选长度增加,会改变临时张量、批处理形状和显存占用;草稿与目标模型共卡时,也会减少能同时服务的请求数。低并发下更快,不代表高并发下有效吞吐更高。
10 月 14 日 vLLM 的自适应验证文章讨论在线估计与接受行为。它提供的是如何做决策的相关方法,不能替代你对自身工作负载的端到端测量。Adaptive Verification
试验时应固定输入、采样规则和质量条件,记录每轮草稿数量、验证数量、提交数量、阶段耗时以及显存峰值。批量指标之外,还要观察 TTFT 与输出间隔:平均速度增加,但某些轮次长时间卡住,用户体验仍可能下降。
本文没有运行草稿模型、目标模型或设备间通信。代码只检验串行成本的记账方式;真实重叠、输出分布和吞吐收益都需要在目标引擎上另行验证。