AI 推理 KV Cache 淘汰:别让长会话吃掉所有显存
AI 推理 KV Cache 淘汰别让长会话吃掉所有显存一、KV Cache 是吞吐的朋友也是显存的敌人自回归模型推理里KV Cache 可以避免重复计算历史 token是流式输出性能的基础。但 KV Cache 会随着上下文长度和并发数增长显存压力非常明显。长会话如果没有淘汰策略短请求可能连进入队列的机会都没有。KV Cache 管理不是内存小优化而是推理服务能否稳定承载多用户的关键。二、先看 KV Cache 生命周期flowchart TD A[Prefill 写入 KV] -- B[Decode 持续追加] B -- C[会话保持] C -- D{是否继续使用} D --|是| B D --|否| E[释放或淘汰]Cache 的价值来自复用但复用也意味着资源被占住。调度器要知道哪些会话活跃哪些会话只是暂时挂着哪些已经没有继续保留的必要。kv_cache_policy: max_session_idle_seconds: 120 max_tokens_per_session: 8192 evict_low_priority_first: true空闲时间和最大 token 都要限制否则长尾会话会慢慢把显存吃完。三、淘汰不能只看时间struct CacheEntry { session_id: u64, tokens: usize, last_access_ms: u64, priority: u8, }LRU 简单但不一定适合推理服务。一个低优先级长会话和一个高价值在线会话占用资源和业务价值不同。淘汰策略应综合 tokens、空闲时间、租户优先级和重建成本。还要区分可重建和不可重建。某些会话可以从历史 prompt 重新 prefill成本只是延迟某些会话状态来自工具调用或临时上下文丢失后可能影响连续性。策略不能只看内存。淘汰策略在工程实现上可以借鉴操作系统的页面置换思想但需要针对推理场景定制。multi-queue 是一种实用方案维护两个 FIFO 队列和一个 LRU 队列新请求先进 FIFO 1快速淘汰区短时间内有第二次访问则晋升到 LRU 队列长会话中超过 token 阈值但仍活跃的进入 FIFO 2长会话保护区。三个队列按不同速率淘汰FIFO 1 最快、LRU 按传统逻辑、FIFO 2 最慢。在 Rust 中可以用VecDeque和定时器驱动的 eviction worker 协作每次 tick 按 water level 比例从各队列取目标页数避免集中淘汰造成延迟抖动。还有一个关键细节淘汰过程中对被淘汰会话的后续请求应返回特殊错误码告知上下文已过期而不是假装正常继续生成否则用户会看到不连贯的输出。四、内存压力要提前暴露等显存分配失败才淘汰通常已经太晚。推理服务应该持续监控 KV Cache 占用接近阈值时主动压缩、淘汰或拒绝低优先级新请求。kv_cache_guard: warn_at_percent: 75 evict_at_percent: 85 reject_at_percent: 95三段式阈值比一个硬失败点更稳定。警告阶段可以减少最大输出长度淘汰阶段清理低优先级缓存拒绝阶段保护服务不崩。如果支持 paged KV cache还要关注碎片。显存总量看起来够但页碎片太多仍然可能影响分配效率。监控指标里应包含页使用率、碎片率和搬迁次数。最后用户体验也要设计。缓存被淘汰后如果需要重新 prefill应让上层知道延迟可能变长。不要把所有额外等待都伪装成模型慢否则定位问题会很困难。服务内部最好维护 KV Cache 账本。每个会话占用多少页、属于哪个租户、最后访问时间、是否可重建都要能查询。账本不只是调试工具也是限流和成本归因的输入。kv_cache_accounting: track_tenant: true track_page_count: true track_rebuild_cost: true expose_admin_query: true如果某个租户长期占用大量 cache却请求频率很低平台就应该降级保留策略。缓存资源和计算资源一样都需要配额意识。淘汰策略上线前还要做回放测试。用真实请求日志模拟不同策略比较命中率、淘汰次数、首 token 延迟和显存峰值。没有回放策略参数很容易只适合某一天的流量。五、总结AI 推理 KV Cache 淘汰要结合空闲时间、token 占用、优先级、重建成本和显存压力阈值。长会话值得优化但不能让它吃掉所有显存。Cache 管理越清楚多租户推理服务越稳定。