KV Cache 是什么?为什么长上下文推理显存爆炸?
谁在问:一二面;算法背景面试官、自建推理团队、做长文档/长上下文产品的团队
口语化问法
- KV Cache 是干什么用的?为什么要有它?
- 为什么上下文一长,显存就顶不住了?这个显存到底被谁吃掉了?
- 我们想把上下文从 8K 提到 128K,你估算一下需要几张卡?
考察意图
面试官在判断三件事:
- 你能不能把它算出来,而不是只会描述。 这道题的分水岭极其明确:能报出"每 token 多少 KB"的人和只会说"缓存了 K 和 V"的人,一句话就分开了。
- 你知不知道瓶颈其实有两个。 显存容量限制的是能并发多少,显存带宽限制的是每一步多快。只答"显存不够"是及格,能把两个拆开才是有实践的。
- 你会不会给出错误的扩容方案。 把"长上下文慢"一律归因于注意力的平方复杂度,就会推出"加卡加算力"——这个方向从一开始就是错的,也是这道题最有价值的负向筛子。
参考答案
60 分答案(及格线)
大模型是自回归生成的,每生成一个 token 都要对前面所有 token 做注意力计算。如果每一步都重算,前面那些 token 的 Key 和 Value 会被反复算很多遍,总计算量是平方级的。KV Cache 就是把每个 token 算出来的 K、V 向量缓存下来,下一步直接查表用,本质是用显存换算力。
显存爆炸是因为 KV Cache 的大小和序列长度成正比,而且和并发数也成正比。上下文从 8K 变成 128K,单条请求的 KV Cache 就涨 16 倍;再乘上并发数,很快就把显存吃光了。缓解手段主要有 GQA(多个查询头共享一组 KV 头)、KV Cache 量化(比如用 FP8 存),以及各种 KV 压缩和驱逐的方法。
为什么只有 60 分:机制说对了,成正比也说对了,但一个数字都没有——面试官没法判断你是算过还是背过。而且"显存爆炸"背后其实有两个不同的瓶颈(容量和带宽),这里只答了一个。
90 分答案(有生产经验的回答)
先给公式,因为这道题最后一定会落到估算上。
KV_bytes = 2 × 层数 × KV头数 × 头维度 × 序列长度 × 并发数 × 精度字节
这里最容易写错的是"KV 头数"——不是注意力头数。 很多资料写的是注意力头数,因为它们举的例子是老的 MHA 模型,两个数恰好相等。换成现代 GQA 模型就会错得离谱:Llama-3.1-8B 有 32 个注意力头但只有 8 个 KV 头,写错就是 4 倍;70B 是 64 比 8,写错就是 8 倍。
代入具体数字(bf16):Llama-3.1-8B 每 token 约 128 KB,70B 每 token 约 320 KB。 由此推出两个很有说服力的对照:
- 8B 的模型权重是 16.1 GiB,而它在 128K 上下文下单条请求的 KV Cache 就是 16 GiB——一条长请求的缓存顶得上一整份模型权重;
- 70B 在 128K 下单条请求约 40 GiB。
然后是瓶颈,这里要分两段答。
第一段是容量,它限制的是能并发多少。 一张 80 GB 的卡跑 8B,刨掉权重 16.1 GiB 和激活与临时缓冲,能留给 KV Cache 的大约 54 GiB。8K 上下文时装得下 50 多条并发,128K 时只装得下三四条。 这句话是我讲这个问题时最爱用的一句——序列长 16 倍,并发掉 16 倍,吞吐跟着掉 16 倍,而算力单元一直是闲的。所以"加卡"是最贵也最低效的一条路,它只能线性地多加那三条。
第二段是带宽,它限制的是每一步多快。 解码阶段每生成一个 token,都要把模型权重和整个 KV Cache 从显存里读一遍。batch=32、32K 上下文时,权重是 16 GiB 而 KV 是 128 GiB——KV 占了显存流量的近九成。此时加算力毫无意义。
这里有个我觉得最值得说的细节:"解码是内存带宽受限"其实是一句关于 batch size 的陈述,不是解码的本质属性。判断卡算力还是卡带宽看算术强度,而它约等于这一步参与计算的 token 总数,H100 的临界值大约是 295。预填充一步塞进几千个 token,所以卡算力(单个足够长的请求就能把 GPU 打满);解码每条请求每步只出一个 token,算术强度约等于并发数,batch=32 时远低于临界值——但并发推到 500 以上,解码同样会变回卡算力。这也顺带解释了为什么投机解码只在中低 QPS 有效。
最后是缓解手段,我按成熟度排:
- 结构级(GQA / MLA)——选型时就定了。GQA 的压缩比就是查询头数除以 KV 头数;MLA 的缓存量等效于只有 2.25 组的 GQA,比 GQA-8 再压约 3.5 倍。代价是 MLA 结构侵入性强、用不了现成权重、内核生态窄。
- FP8 KV Cache——存储直接减半,解码每 token 成本降到 bf16 的约 54%(不是 50%,反量化有开销)。但有约 7000 token 的盈亏平衡线,短上下文下反而更慢;某些头维度较大的模型首字延迟还会恶化。默认不开,我会先在长上下文流量上灰度。
- 前缀复用与 KV 卸载——命中时省的是重算预填充。有带宽临界点:互联带宽低时,从远端拉 KV 比重算还慢。
- KV 驱逐(H2O、SnapKV 这类)——纸面数字很漂亮,但我不会上生产,理由见追问。
追问链
公式里是 KV 头数 —— GQA 到底省了什么,代价是什么?
期望多查询头共享一组 KV 头,压缩比 = 查询头数 ÷ KV 头数:Llama-3.1-8B 是 32/8 = 4 倍,70B 是 64/8 = 8 倍 —— 差别在查询头数,两者 KV 头数都锁死在 8。代价:MQA 极端到 1 组掉质量,MHA 转过来还需少量 uptrain信号解释得出「8B 4 倍而 70B 8 倍」的原因 → 真去查过 config,不是背结论显存不够时,你第一个动的旋钮是什么?
期望次序:先确认瓶颈 → 零风险 → 有质量风险。① 看 KV Cache 占用率与 preemption 警告 → ② 调gpu_memory_utilization→ ③ 限最大上下文 / 超长请求单开队列 → ④ FP8 KV 只对长上下文开 → ⑤ 换 MLA 模型信号上来就说「上量化」→ 跳过了前三步免费的动作;能把「零质量风险」和「有质量风险」分开 → 真调过为什么说解码阶段是内存带宽受限?什么时候它不是?
期望每步只算 1 个 token 却要读一遍权重和全部 KV Cache,读远多于算;算术强度≈并发数,远低于 H100 约 295 的临界值。「什么时候不是」才是分水岭:并发推到几百上千就跨过临界值、变回卡算力 —— 这是关于 batch size 的陈述,非本质属性信号只会说「解码是 memory-bound」→ 背过;能补「高并发下会变回 compute-bound」→ 真理解 rooflineH2O、SnapKV 能压到 6%–25%,为什么你说不上生产?
期望三条硬伤:① 拿不到打分信号 —— FlashAttention 从不把 N×N 分数矩阵写进显存;② 释放不了显存 —— 分页下块全空才回收,驱逐 90% 后幸存者散落各 block;③ 评测口径偏差 —— 公平设定下 SnapKV 输给「开头 + 最近窗口」这个朴素基线信号说得出前两条工程硬伤任一条 → 读过实现;只说「可能有质量损失」→ 泛泛而谈32K 提到 128K,并发不许降、SLO 不许放宽、不加卡,你怎么答?
期望先说清不可能:序列长四倍 = KV Cache 四倍 = 并发变四分之一,是算术不是调优。再按代价分层:零成本(前缀缓存、固定模板前置、重复文档跨请求复用)→ 低风险(长上下文开 FP8 KV,存储减半等于并发补回一倍)→ 有产品代价(128K 分级、长请求独立队列)→ 需时间(MLA)。缺口只有加卡 / 降并发 / 放宽 SLO 三个出口,选择权交回业务信号第一动作是当场算出并发变四分之一 → 有量化能力;把缺口明确交回业务而不是偷偷放宽 SLO → 有职业成熟度
评分要点
- 给出完整公式,且用的是 KV 头数而不是注意力头数
- 能报出至少一个具体数字(如 8B 每 token 128 KB、70B 每 token 320 KB)
- 把瓶颈拆成容量和带宽两段:容量限制并发数,带宽限制单步延迟
- 用并发数而不是显存 GB 来讲吞吐塌陷
- 知道解码是内存带宽受限,且知道这取决于并发数、高并发下会变回卡算力
- 能说出 GQA 的压缩比就是查询头数除以 KV 头数,并解释同系列内不同规模的差异
- 知道 FP8 KV Cache 有盈亏平衡线(约 7000 token),短上下文下反而更慢
- 知道 KV 驱逐类方法在生产上的工程硬伤,而不只是"可能掉点"
- 压力面下先算账、再分层给方案、最后把剩余缺口交回业务