[Bug/Perf]: hybrid-SWA prefix caching collapses to zero for ALL requests in multi-session round-robin at ~25% pool occupancy (Gemma-4-31B; e

该问题发生在 vLLM 混合注意力(hybrid-SWA)模型的多会话轮询负载中,当 KV 缓存池占用率约达 25% 时,跨请求前缀缓存复用会突然降为零,表现为“全部命中”或“全部未命中”的断崖式切换。优先排查 SWA 组的尾部块是否被提前回收,可借助 CPU offloading(48GiB+)验

快速结论:该问题发生在 vLLM 混合注意力(hybrid-SWA)模型的多会话轮询负载中,当 KV 缓存池占用率约达 25% 时,跨请求前缀缓存复用会突然降为零,表现为“全部命中”或“全部未命中”的断崖式切换。优先排查 SWA 组的尾部块是否被提前回收,可借助 CPU offloading(48GiB+)验证并绕过。

适用环境:vLLM v0.24.0 和 v0.25.0(vllm-openai 镜像),Gemma-4-31B 混合注意力模型(10 层全注意力 / 50 层 sliding-window(1024)),1× RTX 5090 32GB,驱动 580.x,KV cache dtype fp8,max-model-len 65536。

最快修复方案:暂无确认的一步修复方案。可优先尝试使用 --kv-transfer-config ... cpu_bytes_to_use=48GiB+ 配置 CPU offloading 层来完全恢复缓存驻留(152k 五会话场景下 TTFT 从 27s 冷启动降至 0.4–0.6s 预热),但该方案在 32GiB 下无效,需至少 48GiB。

注意事项:CPU offloading 仅是验证机制的 workaround,并未修复根本问题。代码层面定位到根因是 FreeKVCacheBlockQueue 的“尾部块优先逐出”策略对 SWA 组不适用(SWA 组的尾部块恰恰是唯一可缓存块),但社区尚未合并正式修复补丁。

问题场景

在 vLLM 中运行支持混合注意力(hybrid-SWA)的模型(如 Gemma-4-31B,10 层全注意力 + 50 层 sliding-window(1024)),多个独立聊天会话(seats)以 round-robin 方式轮询触发。每个会话持有不同提示词,每轮追加相同前缀和短新后缀(agentic-session 形态)。当合并工作集超过某个远低于池容量的临界阈值(约 34–38k tokens,池容量 153k,约 25% 占用)时,所有请求的跨请求前缀缓存复用率精确降为零,且是完全不命中(0% hit)而非部分退化。

报错原文

[Bug/Perf]: hybrid-SWA prefix caching collapses to zero for ALL requests in multi-session round-robin at ~25% pool occupancy (Gemma-4-31B; eager-freed SWA tails recycled tail-first)

原因分析

问题根因是四个独立合理行为相互作用产生的涌现效应(已确认在 main 分支 4c81772e8 上可复现):

1. SWA 命中是全有或全无的交叉点:请求的缓存命中是各 KV cache 组的交集;SWA 组通过 SlidingWindowManager.find_longest_cache_hit 要求缓存块的尾部存在 cdiv(window−1, block_size) 个连续块——任何一块缺失则整组未命中,无部分命中概念。

2. SWA 组仅哈希边界尾部块:一旦混合对齐超过 SWA 块大小,reachable_block_mask 使 SWA 组只哈希段边界尾部块,稀疏命中语义下没有任何部分可命中对象。

3. 窗口外块被提前释放:remove_skipped_blocks / _remove_blocks_in_range(single_type_kv_cache_manager.py:558)在请求进行中即刻将窗口外 SWA 块释放到共享空闲队列。

4. 空闲队列尾部优先逐出:FreeKVCacheBlockQueue 明确以“尾部链块优先逐出”为策略(kv_cache_utils.py:194),这对全注意力共享前缀树是正确的,但对 SWA 组恰好相反——尾部窗口块正是 SWA 命中唯一需要的块。

在 round-robin 场景下,其他会话的分配会在会话两次轮询之间回收其 SWA 尾部块;丢失一个块便通过机制(1)使整个请求(含全注意力块)全部未命中。低于阈值时分配需求不触及哈希尾部;超过阈值后每个会话的尾部每轮都被回收,因此出现尖锐断崖而非渐进退化。

环境排查

  • vLLM 版本:v0.24.0 / v0.25.0,或当前 main(4c81772e8);注意同一代码路径在后续版本中可能仍未修复
  • GPU:1× RTX 5090 32GB,驱动 580.x
  • 模型:google/gemma-4-31B-it-qat-w4a16-ct(10 全注意力层 + 50 sliding-window(1024) 层)
  • 内存配置:--gpu-memory-utilization 0.93,KV cache 池大小 153,374 tokens
  • 编码 dtype:--kv-cache-dtype fp8;可确认混合对齐后 SWA 页与全注意力页大小是否不同(Gemma-4 确实如此)
  • 对比基线:GDN-hybrid 控制模型(Qwen3.5-27B,align mode)无此现象;单会话 64k 立即重发有 99.96% 命中率可作对照组

解决步骤

  1. 确认问题特征:检查 prefix_cache_hits_total 指标是否在预热轮之后突然完全停止增长(非部分退化),并在多会话轮询场景中触发约 25% 池占用率的请求。
  2. (可优先尝试)验证 CPU offloading 绕过:添加 --kv-transfer-config ... cpu_bytes_to_use=48GiB+(注意 32GiB 不足),确认命中率恢复、TTFT 降回亚秒级。该方案仅验证机制,不建议生产长期使用。
  3. 检查代码路径:查看 single_type_kv_cache_manager.pySlidingWindowManager.find_longest_cache_hit(约 858 行)和 reachable_block_mask(约 956 行)确认 SWA 组仅哈希稀疏尾部块;检查 remove_skipped_blocks(约 558 行)的窗口外提前释放逻辑。
  4. 检查逐出策略:查看 kv_cache_manager.py(约 492 行)KVCacheManager.freekv_cache_utils.py(约 194 行)BlockPool.free_blocks,确认尾部链块是否被标记为“无哈希”优先逐出,而 SWA 组恰恰依赖这些块。
  5. (若需二次开发)短期 patch 方向:对启用稀疏哈希的组(reachable_block_mask 激活)豁免“尾部优先逐出”,将其视作全注意力哈希块按普通 LRU 保护。社区提出的 Option A(targeted fix)为小中量改动,可参考 #40270、#42799、#23083 相关讨论。
  6. (长期方案需评估)general eviction priority:FreeKVCacheBlockQueue 改为基于块的复用价值做优先级排序,而非简单的二分类 FIFO。此改动触及热路径核心数据结构,范围大、风险高,实施前需与相关 PR 作者协调。

验证方法

使用多会话 round-robin 复现脚本:对比冷启动 TTFT 与预热轮 TTFT。修复生效的标准为预热轮 TTFT 降至 0.4–0.6s(152k 五会话场景),同时 prefix_cache_hits_total 在连续多轮中持续增长(多会话同时发生完整命中)而非突然归零。亦可使用无 GPU 单元级测试:直接驱动 KVCacheManager / BlockPool / SlidingWindowManager,以混合 full-attn/SWA 配置 + 合成 4 会话 round-robin 负载验证是否出现“全部同时从满命中坍缩到零命中”的特征。

参考来源

vllm-project/vllm #48435

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 21677

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注