推测解码 KV 滑动窗口¶
推测解码草稿模型通常在短上下文上训练 (例如 4-8k token)。在推理时,当运行上下文远远超过训练长度时, 草稿模型对整个 KV 缓存的注意力会偏离分布,接受率崩溃:在 GLM-5.2 上使用早期 DSpark 草稿时,平均接受长度在 32k 上下文下从约 5 降到约 1,使 推测解码成为净损失。
KV 滑动窗口将草稿模型的注意力限制在最近
draft_window_size 个 token 内。目标模型不受影响——它始终关注
完整上下文,因此生成质量得以保留;只有草稿读取
更短、处于分布内的窗口。在接受率崩溃的草稿上,这
可以恢复接受率(同一个 GLM-5.2 草稿在 32k 下从约 1.0 恢复到约 4.7-5.5,
在原始评估中端到端吞吐量最高提升 +381%)。
配置¶
窗口通过 additional_config 启用,并且可用于默认(MRV1)模型运行器上的
EAGLE3 / DFlash / DSpark 草稿路径:
默认禁用(键不存在)。MTP 会强制禁用它(MTP 复用目标模型自身的层,因此对草稿进行窗口化会同时窗口化目标模型);对于与 DeepSeek-V4 目标模型原生一起训练的 DSpark 草稿,会记录一条警告,因为草稿本身已经对长上下文稳定,窗口化会降低接受率。
窗口计算¶
设 W 为窗口大小,b 为内核块大小,K = future_offset
为适配器看到的 seq_lens 值之外的草稿查询位置数量。适配器完全在设备上计算:
start = floor(clamp(seq_lens + K - W, min=0) / b) * b # block-aligned window start
windowed_len = seq_lens - start # <= W + b - 1
start_block = start // b
needed = ceil(windowed_len / b) (capped at max_window_blocks = W//b + 1)
future_offset 因草稿家族而异,因为它们的 seq_lens 语义不同:
| 方法 | future_offset | 原因 |
|---|---|---|
| EAGLE3 | num_speculative_tokens |
seq_lens 仅表示上下文;K 个草稿位置位于其之后,因此窗口末端必须覆盖 seq_lens + K。 |
| DFlash / DSpark | 0 |
输入准备内核已将查询扩展(bonus + mask tokens)烘焙到 seq_lens 中,因此 final = seq_lens。 |
块表通过从完整表中收集列
start_block[i] + [0, max_window_blocks) 到零填充克隆中进行裁剪;超过 needed(以及超过完整表宽度)的列被置零。置零的列永远不会被读取,因为每个 FIA KV 长度输入都被限制在相同的 windowed_len 内。
两个不变量始终成立:
- slot_mapping 永远不会被窗口化。 草稿 KV 写入仍然通过完整块表在绝对位置进行,因此缓存对目标模型以及步骤之间变化的窗口大小保持连贯。
- 只有 FIA 读取的内容被限制。 FIA 内核的 KV 长度输入是 NPU
seq_lens张量以及 CPU 镜像(seq_lens_cpu、_seq_lens_cpu、seq_lens_cpu_upper_bound);它们全部使用相同算术进行窗口化,因此任何路径都不能读取超出窗口。
MRV1 实现¶
SlidingWindowAdapter(vllm_ascend/spec_decode/utils.py)拥有上述计算
和一个持久的 [max_num_reqs, max_window_blocks] 克隆缓冲区。当 draft_window_size
被设置时,它在 AscendSpecDecodeBaseProposer.__init__
(vllm_ascend/spec_decode/llm_base_proposer.py)中构造,然后在构建每层元数据之前应用到草稿的 common_attn_metadata。
由于草稿构建元数据的方式,存在两个应用点:
- EAGLE3 / DFlash:在
_propose中对共享的common_attn_metadata(llm_base_proposer.py)应用一次。 - DSpark:该路径被绕过——
build_draft_attn_metadata的 dspark 分支在构建之前用每组的完整表覆盖common_attn_metadata.block_table_tensor。因此适配器在每组覆盖之后、在builder.build_for_drafting之前应用,使 FIA 读取每组表的窗口化克隆而不是完整表。
MRV2 状态¶
Model Runner V2 尚未被此功能覆盖。MRV2 推测器系统
在 build_attn_metadata 内部将 seq_lens / 块表烘焙到最终每层元数据中
——构建之后没有可修改的共享 common_attn_metadata,因此移植窗口需要将其应用到
构建器输入(窗口化的 input_buffers.seq_lens、收集的每组块表克隆、受限的 seq_lens_cpu_upper_bound),位于
vllm_ascend/worker/v2/spec_decode/dspark/speculator.py 中
_build_draft_attn_metadata 调用周围。窗口计算和上述两个不变量保持不变。
预期行为¶
- 在短上下文上训练、在长输入上崩溃(接受率随上下文长度下降)的草稿:窗口可将接受率恢复到草稿的短上下文水平。收益随输入长度增长。
- 在长上下文上已经稳定的草稿(最近训练的草稿,或具有自身 SWA 结构的草稿):窗口没有收益,如果设置低于草稿的训练窗口则可能有害——草稿期望看到比窗口允许的更多上下文。经验法则:保持
draft_window_size等于或高于草稿的训练/原生窗口,或者在接受率已经跨上下文长度平稳时关闭它。 - 端到端效果将接受率变化与草稿降低的 KV 读取成本相结合;如果没有接受率收益,两者大致抵消。
相关文件¶
- 共享窗口适配器:
vllm_ascend/spec_decode/utils.py - MRV1 提议器(应用点、配置验证):
vllm_ascend/spec_decode/llm_base_proposer.py
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 vl/vllm-ascend