想象你经营着一家专做“多道菜宴席”的餐厅:客人一坐下,后厨就要一道接一道地上菜,而且每一道新菜都必须“回味”之前所有菜的味道,才能决定这道菜怎么做。大模型生成文字就是这个过程——它一次只能吐一个词(token),但每吐一个新词,都要“回头”参考之前的所有词。如果每上一道菜都把前面的菜全部重做一遍,厨师再能干也撑不住。聪明的主厨会为每道菜留下一张“配方卡”——记录这道菜的关键用料和成品模样,下次上菜只需瞄一眼卡片。在 AI 世界里,这些配方卡就叫 KV Cache(键值缓存)。 可是配方卡越攒越多,厨房的储物柜(显存)很快就满了。传统做法是每个客人一进门就预留一整排柜子,结果许多柜子空着,新客人又挤不进来——这就是“碎片化浪费”。而新一代的 PagedAttention(分页注意力) 像一位训练有素的仓库管理员:把所有配方卡装进统一规格的收纳盒,见缝插针散放在各个空位,再用一张索引卡记住每个盒子在哪里。于是同一间厨房能服务更多客人、接更长的菜单。本文将带你走进这座厨房,看懂显存背后的收纳智慧。
掌握 KV Cache 与 PagedAttention,你就能精准估算推理显存、消除碎片浪费,把一张 GPU 的吞吐压榨到极致——这是大模型部署工程师必须拿下的核心硬技能。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
自回归推理中KV Cache的增长过程 GPU显存 (24GB) 每个token生成后追加K/V向量到缓存 KV Cache计算公式 Size = 2 x L x S x H x dtype L=层数,S=序列长度,H=头维度 KV Cache与PagedA...
自回归推理
宴席开始上菜了。这家餐厅有个铁规矩:菜必须一道一道上,绝不允许一次性把整桌菜端齐。更麻烦的是,主厨每做一道新菜,都要把前面每一道菜的味道放进嘴里“回味”一遍——酱油放多少、辣度几何、客人哪道菜动过筷子,全部要参考。大模型的“自回归推理”正是如此:模型一次只输出一个 token,而每输出一个新的 token,都要和之前所有 token 做一次“注意力计算”,就像新菜要重新翻阅全部旧菜。
这种“翻阅”在数学上叫 Self-Attention(自注意力)。假设现在已经生成了 \(T\) 个词,那么新词的计算需要同时看到这 \(T\) 个词各自的三张“卡片”:Q(查询)、K(键)、V(值)。Q 是新词自己的“问题清单”,K 是每个旧词的“标签”,V 是每个旧词的“内容档案”。新词拿自己的 Q 去和所有旧词的 K 匹配,匹配度越高,就越该参考该词的 V。
1. 三个输入的角色:Q 来自当前正在生成的新词,K 和 V 来自包括新词在内的全部 \(T\) 个位置;\(Q,K,V\in\mathbb{R}^{T\times d_k}\)。
2. 点积打分:用新词的 Q 与每个旧词的 K 做点积,得到“这道旧菜值不值得细看”的相似度分数。
3. 缩放防溢出:除以 \(\sqrt{d_k}\),防止向量维度大时分数爆炸,像厨师把火候调小避免炒糊。
4. 归一化成权重:softmax 把所有分数压成总和为 1 的权重,表示每道旧菜该被参考多少。
5. 加权汇总:按权重把各旧位置的 V 加权求和,得到融合了全部历史信息的新词表示。
问题来了:每上一道新菜就“回味”一遍所有旧菜,如果没有留下任何记录,就得把旧菜全部重做一次才能回味——第 \(T\) 道菜要重做 \(T-1\) 道旧菜,整桌菜下来工作量是平方级爆炸。宴席越到后面,后厨就越绝望。聪明的厨师当然不会这么干,下一节我们看看他留下的“配方卡”。
KV Cache 原理
主厨想出了一个办法:每做完一道菜,就立刻把这道菜的“配方卡”留下——卡片正面写 K(风味标签:咸甜、香料、火候关键词),背面贴 V(成品照片:这道菜最终的样子)。有了配方卡,之后做任何新菜想“回味”旧菜,只需要扫一眼卡片,而不必把旧菜重新端回厨房重做。在 Transformer 里,这个“边做边留卡”的动作,就是 KV Cache(键值缓存):把已经计算出的每个 token 的 K 向量和 V 向量暂存在显存里。
具体流程分两个阶段。预填充阶段(Prefill):用户输入一整段提示词,模型并行算出每个 token 的 K/V,并全部写入缓存;解码阶段(Decode):每生成一个新 token,只计算它自己的 Q,用它和缓存中所有旧 token 的 K 做注意力,最后把这个新 token 的 K/V 追加进缓存。这样一来,计算量从“每一步重算全部历史”降为“每一步只算一个新位置”——就像 10 位客人每人点 20 道菜,主厨不再疯狂重做,只是不停地往配方卡柜子里添新卡。
1. 下标 \(i\) 遍历历史:\(i\) 从 1 数到当前已生成的 token 数 \(t\),每生成一个 token 就多一张卡。
2. 每张卡的内容:第 \(i\) 个 token 的 K 向量和 V 向量,维度都是模型的隐藏维度 \(d_{\text{model}}\)。
3. 缓存的整体形态:这是一个不断变长的集合,每步生成后追加一条,容量随序列长度线性增长。
不过天下没有免费的午餐。配方卡柜子也是要占地方的:每个 token 的 K/V 卡在每一层都要存一份,层数越多、词越长、同时就餐的客人(批大小)越多,柜子就膨胀得越快。这给我们提出了一个必须精确回答的问题:显存到底会被吃掉多少?
内存占用模型
现在我们来当一次“厨房面积规划师”:要给配方卡柜子预留多大的空间?把各因素摆上桌:模型有 \(L\) 层(每层都要存一套 K/V 卡)、每张卡是 \(d_{\text{model}}\) 维的向量、当前序列长 \(S\)、同时服务的请求数(批大小)为 \(B\)。K 一份、V 一份,所以先乘 2;现代显卡惯用 FP16 半精度存储,每个数值占 2 字节,再乘一个 2。于是:
1. 两份向量:每个 token 每层都要缓存 K 和 V 两份向量,所以先乘 2。
2. 层数 \(L\) 与维度 \(d_{\text{model}}\):模型越深、每层向量越宽,配方卡就越多,这是模型结构决定的常数。
3. 序列长度 \(S\) 与批大小 \(B\):每个请求各自的词都在累积,同时处理的请求越多,总卡数成倍增长。
4. FP16 每元素 2 字节:半精度存储的每个数值占 2 字节,最终单位是字节。
把这个公式套到现实中会非常震撼。以 LLaMA-7B 为例:\(L=32\) 层、维度 \(d=4096\),当序列长度 \(S=2048\)、批大小 \(B=16\) 时,KV Cache 占用 \(2\times32\times4096\times2048\times16\times2\approx17.2\) GB——而模型权重本身只有约 14 GB!也就是说,长序列 + 大批量时,配方卡柜子比菜谱本(权重)还要占地方。部署工程师必须养成“先算 4LHSB,再定方案”的职业习惯。
碎片化问题
如果柜子规划得不好,再大的厨房也会“看起来满满当当,实际没装多少”。传统的推理服务采用 预留式分配(pre-allocation):每个请求一进来,就按系统允许的最大序列长度给它划一整排柜子。比如系统上限是 2048 个 token,那就给每个请求预留 2048 格。可实际情况是,大部分请求聊几句就结束了,可能只用掉 300 格。预留 2048 格、实际用 300 格,其余 1700 格全在吃灰——这就是 内部碎片(internal fragmentation)。
更糟的是 外部碎片(external fragmentation):请求结束后柜子被回收,但各个请求长度不同,回收出来的空隙长短不一、东一块西一块。新请求想要一段连续的大空间,却怎么也拼不齐;而总空闲量明明够用。就像几个客人拼桌,剩下的座位全是零散单座,坐不下一个 6 人家庭。这种低效可以用利用率来衡量:
1. 分子:实际使用的总量:\(s_i\) 是第 \(i\) 个请求实际用掉的缓存格数,把所有 \(N\) 个请求加总。
2. 分母:预留出来的总量:每个请求都按最大长度 \(s_{\max}\) 预留,\(N\) 个请求共预留 \(N\times s_{\max}\) 格。
3. 比值 \(\eta\):实际用量占预留用量的百分比,预留越狠、实际越短,\(\eta\) 越低。
不少线上服务实测发现,\(\eta\) 常常只有 40%~60%——意味着近一半显存在白白浪费。碎片像厨房里被遗忘的死角,谁都不想要,但传统分配方式注定会产生。要根治,就不能再“一进门就预留一整排”,而要把收纳方式彻底换掉。
分页机制
新来的仓库管理员推翻了“每人一整排柜子”的旧规矩,改为“统一收纳盒 + 散放”:所有配方卡(K/V)都装进规格完全相同的收纳盒,一个盒子固定装 16 个 token 的 K/V。盒子可以放在任何空位上——柜子 A 的第三格、柜子 B 的第一格,完全不用连续。管理员手里有一张“索引卡”(block table),记录着“逻辑盒号 → 物理位置”的对应关系。这就是 PagedAttention(分页注意力机制),它把操作系统虚拟内存的分页思想搬进了显存管理。
解码时,当前 token 的 Q 只需按照索引卡,去读取本请求已占用的那几张物理盒即可;生成完继续装盒,盒子满了就再申请一个空闲盒,填到哪算哪。因为盒子规格统一、随用随取,房间里再也不会出现“凑不齐的整排座位”——这正是虚拟内存页表的核心逻辑:逻辑上连续,物理上散落,由映射表兜底。
1. 逻辑块号 \(\ell_i\):第 \(i\) 个逻辑块,按序列顺序编号,从 0 到总块数减 1。
2. 总块数:序列长度 \(S\) 除以每块容量 \(B_{\text{size}}\)(如 16),并向上取整——多一个 token 也要多占一整盒。
3. 映射 \( \mapsto \) 到物理块号 \(p_i\):块表把每个逻辑块指向物理内存里的任意空闲物理块,物理块不必相邻。
分页还有个隐藏彩蛋:前缀共享。如果多个请求(如多人问同一份文档、beam search 的多个分支)拥有相同的前缀,它们可以共享同一个物理块,只需在块表里给该块加引用计数——相当于多位客人共享同一批“前菜配方卡”,省下的显存相当可观。管理员不必再担心碎片,要担心的只剩一件事:厨房总面积到底划多少给收纳盒。
vLLM 配置
这位聪明的仓库管理员,在开源世界里有个响亮的名字——vLLM。它把上面这套分页思想做成了生产级系统。部署工程师要做的,就是给管理员定好几条“厨房规矩”。第一条规矩:收纳盒容量(block_size),vLLM 默认每个盒装 16 个 token 的 K/V。盒子越小越省空间但索引开销大,盒子越大访存越高效但碎得更细,默认 16 是经过权衡的甜点值。第二条规矩:显存利用率(gpu_memory_utilization),默认 0.9,意思是把 90% 的显存交给 vLLM 统一支配(包括权重、激活和 KV 块池),留 10% 作为缓冲。
第三条规矩藏在调度里:连续批处理(continuous batching)。传统“动态批处理”是一桌客人全部吃完才让下一批入座,只要有一个人慢,整桌人陪着等;连续批处理则是谁吃完谁走,空位立刻让新客人坐下——翻台率大幅提升。合理配置这些参数前,先要算清有多少显存能留给 KV 盒:
1. 工作区总预算:显卡总显存 \(M_{GPU}\) 乘以利用率 \(u\)(如 0.9),这是管理员可支配的总面积。
2. 减去权重和激活:模型权重 \(M_{\text{weights}}\) 和前向计算的中间激活 \(M_{\text{activations}}\) 必须优先保证。
3. 保留安全垫:减去 \(M_{\text{reserve}}\) 的预留缓冲,防止瞬时峰值导致 OOM,剩余的就是 KV 块池。
配置时还要盯住监控指标:num_kv_blocks(空闲块数)、gpu_cache_usage_perc(KV 池占用率)等,让管理员始终有“余盒可用”。此外 enable_prefix_caching 能自动复用重复前缀,量化权重则能压缩权重占用从而给 KV 腾出更多空间——一切都围绕“用最少的浪费服务最多的请求”。
部署效果
搬进新厨房后,效果立竿见影。先看吞吐:想象原来一晚上只能接待 8 桌客人,现在同样的灶台、同样的储物面积,却能接待 20 桌——因为不再有人占着整排柜子却只吃两三道菜。PagedAttention 把显存利用率从约 50% 拉高到 90% 以上,配合连续批处理,线上实测吞吐往往能提升 2~10 倍。再看长序列:以前 2048 token 就到头了,因为预留 8192 的柜子太奢侈;现在盒子按需增长,轻松支持 32K、128K 的长文档问答。
1. 分子:生成的总 token 数:\(N_j\) 是第 \(j\) 个请求最终生成的 token 数,跨 \(R\) 个请求求和。
2. 分母:总耗时:\(T_{\text{total}}\) 包含排队、预填充和解码的端到端时间。
3. 吞吐量:单位时间产出的 token 数,是衡量推理服务性价比的核心指标,分页管理直接提升这个数。
回望整个故事:从“每道菜重做”到“配方卡”,从“整排预留”到“统一收纳盒”,大模型推理的显存管理完成了一次厨房革命。今天,vLLM、SGLang 等主流推理框架都已内置分页缓存;理解 4LHSB 的计算、碎片化的成因和块表的映射,你就掌握了这座现代化厨房的核心图纸——无论面对多长的序列、多大的并发,都能从容规划、精准扩容。
AI 应用场景
高并发在线对话机器人的吞吐瓶颈
阶段:部署推理(Serving)
解决的问题:传统推理服务在几十路并发下频繁 OOM——每个会话都按最大长度预留 KV 显存,空闲时浪费、高峰时崩溃。使用 vLLM 的 PagedAttention 后,KV 按需分配成 16 token 的小块,碎片几乎归零,单卡能稳定承载数百路会话。
具体案例:某客服公司用 Qwen2-7B 部署了 200 路并发对话,原方案在 64 路时便显存溢出;接入 vLLM 并设置 gpu_memory_utilization=0.9 后,吞吐提升约 4 倍,P95 首字延迟保持在 500ms 以内。
长文档智能问答的超长上下文支持
阶段:长序列推理(Long-context Inference)
解决的问题:阅读几百页合同或论文时,序列长度动辄上万 token。传统预留式缓存会让显存瞬间爆掉;PagedAttention 按需增长 + 前缀共享,让同一文档的前缀块被多个提问者复用。
具体案例:法务团队用 32K 上下文的模型审阅尽调报告,部署时开启 enable_prefix_caching,多轮追问共享文档前缀块,显存占用比逐个独立推理降低约 60%,单张 A100 即可支撑 5 名律师并行提问。
小显存显卡上的低成本微服务
阶段:资源规划与容量评估
解决的问题:创业团队只有一张 24GB 的 3090,既要跑 7B 模型权重又要服务多路用户。先用公式 \(4LHSB\) 估算 KV 需求,再用连续批处理让请求“吃完就走”,把单卡的接待能力用到极致。
具体案例:某 AI 写作插件团队将 LLaMA-7B 部署在单张 3090 上,通过 vLLM 分页缓存把并发路数从 12 路提升到 48 路,月成本节约 70%,用户平均等待时间反而缩短了三成。
Python 演示
# -*- coding: utf-8 -*-"""分页KV缓存与朴素预留在显存利用率上的对比模拟"""import random class BlockAllocator: """把显存看作固定大小的物理块池, 按需分配与回收""" def __init__(self, total_blocks): self.total_blocks = total_blocks self.free_blocks = set(range(total_blocks)) def allocate(self, num_blocks): """申请 num_blocks 个物理块, 可以来自任意散落位置""" blocks = [] for _ in range(num_blocks): if not self.free_blocks: raise MemoryError("显存不足") blocks.append(self.free_blocks.pop()) return blocks def free(self, blocks): for b in blocks: self.free_blocks.add(b) def usage(self): return self.total_blocks - len(self.free_blocks) def naive_reserve(total_cells, requests, max_len): """朴素方案: 每个请求一进来就预留 max_len 个缓存单元""" served = used = reserved = 0 for length in requests: if reserved + max_len > total_cells: break reserved += max_len used += length served += 1 return served, used, reserved def paged_serve(total_cells, requests, block_size): """分页方案: 按实际长度向上取整申请块, 块可散落任意物理位置""" allocator = BlockAllocator(total_cells // block_size) served = used_blocks = 0 for length in requests: need = (length + block_size - 1) // block_size try: allocator.allocate(need) used_blocks += need served += 1 except MemoryError: break return served, used_blocks * block_size, total_cells def main(): random.seed(42) requests = [random.randint(8, 28) for _ in range(40)] total_cells = 192 s1, u1, r1 = naive_reserve(total_cells, requests, 32) s2, u2, t2 = paged_serve(total_cells, requests, 8) print("朴素预留: 服务 %d 个请求, 利用率 %.1f%%" % (s1, u1 * 100.0 / r1)) print("分页管理: 服务 %d 个请求, 利用率 %.1f%%" % (s2, u2 * 100.0 / t2)) if __name__ == "__main__": main()
self.free_blocks = set(range(total_blocks))初始化空闲物理块集合,模拟一整片可散落分配的显存。blocks.append(self.free_blocks.pop())每次从集合中随机取一个空闲块,体现“物理块可以不相邻”。if reserved + max_len > total_cells: break朴素方案累积预留空间,空间不够就停止接待,造成大量浪费。need = (length + block_size - 1) // block_size按实际长度向上取整算出需要的块数,多一个 token 也要多占一盒。allocator.allocate(need)分页方案只按需申请,申请失败才停止,因此能接待更多请求。used_blocks * block_size分页方案的实际占用是“块数 × 块容量”,块内若有空余属于块级粒度损耗,但远小于预留损耗。
常见误区
误区既然 KV Cache 只是保存历史 token 的向量,放哪里不都一样吗?放 CPU 内存还能省显存,反正顺序生成,慢慢搬呗。我见过有人把 KV Cache 挂到 CPU 上,觉得“显存不够就借内存”。
正解解码阶段每一步都要用全部历史 K/V 做注意力计算,属于高频率、高带宽访问。如果在 CPU 内存,每一步都要先 PCIe 搬回 GPU,传输时延会让吞吐崩塌。这就像主厨做菜要“回味”配方卡,结果卡片放在仓库、每做一道菜都要跑去仓库翻一遍——灶台全在空等。
为何常见:看到“缓存”二字就联想到磁盘缓冲、浏览器缓存,忽略了 Transformer 推理中 KV 是每步 decode 的热路径数据。
误区很多人以为 KV Cache 只是“一个序列”的缓存,算显存时只考虑序列长度,把批大小完全忘掉,导致线上并发一高立刻 OOM“猝死”。
正解批大小 \(B\) 与序列长度 \(S\) 是乘法关系:同时服务的每个请求都有自己的 KV Cache,10 个请求就会放大 10 倍。公式 \(4LHSB\) 中 \(S\) 和 \(B\) 是平等相乘的,正如 10 位客人每人都有自己的配方卡柜,而不是共用一张。
为何常见:学习阶段通常用单条样本演示,脑子里“了一条序列”的图景根深蒂固,忽略了真实服务的并发维度。
误区有人觉得分页管理就是把显存物理切成固定大小的小格,跟“给柜子加隔板”没本质区别,换谁来做都一样。
正解分页的精髓是“逻辑连续、物理散落”的块表映射,加上按需分配、引用计数和前缀共享。切片只是手段,块表才是灵魂——它让一个请求的缓存可以散落在任意物理位置,并让多请求共享相同前缀块。没有映射关系,单纯切块无法解决外部碎片。
为何常见:把“分页”直觉理解为“分块”,忽略了虚拟内存页表的映射机制这一核心思想。
误区反正是自家显存,利用率设成 0.99 甚至 1.0,KV 池不就最大了吗?设置越大越“榨干”显卡,不是吗?
正解还要给模型权重、激活、CUDA context 和碎片留空间。利用率设过高,KV 池预分配时就会把显存吃满,一旦权重加载或激活峰值到来直接 OOM。默认 0.9 正是“留好安全垫”的经验值,而不是贪婪的上限。
为何常见:只盯着“KV 池越大越好”的局部目标,忽视了整个显存生态里还有权重与激活这些“固定房租”。
误区一听 7B 模型就以为 14GB 权重是显存大头,推理时只算权重的账,对 KV Cache 的膨胀毫无防备。
正解权重是静态的“菜谱本”,放一次就完事;KV Cache 是动态增长的“配方卡”,随每次对话持续累积。序列 2048、批量 16 时 7B 模型的 KV 能达到约 17GB,超过权重本身。长序列 + 大批量下,KV 才是真正的显存吞金兽。
为何常见:训练时代的“显存 ≈ 模型大小”经验深入人心,忽略了推理阶段动态缓存的线性增长特性。
面试题
Q1. KV Cache 缓存的内容是什么?
- A.每层所有历史位置的 K/V 向量← 正确答案
- B.每层所有历史位置的 Q 向量
- C.每层所有模型参数的权重
- D.每层所有注意力得分矩阵
答案:A。缓存的是已生成历史 token 在各层的 K 和 V 向量,供后续 token 做注意力时反复使用。Q 只需当前 token 现算;权重是静态参数无需“缓存”;注意力矩阵只是中间结果。好比配方卡只留“风味标签”和“成品照片”,不必留“点菜问题”。
Q2. 若不使用 KV Cache,每生成一个新 token 会怎样?
- A.需重新计算全部历史词 KV← 正确答案
- B.需重新加载全部模型权重
- C.需重新训练全部模型参数
- D.需重新采样全部候选词
答案:A。没有缓存,解码第 \(T\) 步就要把前 \(T-1\) 个 token 的注意力全部重算一遍,总开销呈平方级爆炸。权重本来就在显存里,无需“重新加载”;训练和采样都不涉及。这正是配方卡存在的意义——避免每道新菜都重做旧菜。
Q3. 公式 \(M = 4 \times L \times H \times S \times B\) 字节中的系数 4 如何而来?
- A.2 份 K/V 乘 2 字节 FP16← 正确答案
- B.4 个注意力头的并行度
- C.每词 4 字节的嵌入总数
- D.权重占 4 倍显存开销
答案:A。每个 token 每层要缓存 K、V 两份向量(乘 2),FP16 半精度每元素占 2 字节(再乘 2),合起来是 4。注意力头数、词嵌入字节数都与系数来源无关。
Q4. 下列哪些属于显存碎片化问题?
- A.预留缓存远大于实际使用量← 正确答案
- B.缓存回收后留下零散空隙← 正确答案
- C.模型权重与激活间的间隙
- D.缓存块被多个请求瓜分
答案:AB。A 是内部碎片——每请求按最大长度预留,用不满就浪费;B 是外部碎片——回收后空隙长短不一,新请求凑不齐连续空间。权重与激活的间隙属于正常布局;多请求共享块恰是分页想达成的目标,不是问题。
Q5. PagedAttention 中 block table 的作用是什么?
- A.记录逻辑块对应的物理块位置← 正确答案
- B.存储每个块的平均注意力权重
- C.压缩每个块的 K/V 向量维度
- D.保存每个块的哈希签名数值
答案:A。块表是分页的灵魂,把逻辑上连续的块号映射到物理上散落的空闲块,实现“逻辑连续、物理任意”。它不存注意力权重、不压缩向量、也不做主存哈希,而是像仓库的索引卡一样回答“第 i 盒放在哪”。
Q6. vLLM 中默认的 block_size 是多少?
- A.每个块容纳 16 个 token← 正确答案
- B.每个块容纳 8 个 token
- C.每个块容纳 32 个 token
- D.每个块容纳 64 个 token
答案:A。vLLM 默认每个 KV 块装 16 个 token 的 K/V。块太小则块表索引开销大,块太大则块内碎片增多,16 是工程权衡后的默认值。记住“16 个 token 一盒”就记住了默认配置。
Q7. 连续批处理(continuous batching)的核心改进是?
- A.请求完成就腾位并接入新请求← 正确答案
- B.固定批大小以稳定输出速度
- C.每步全部请求同时结束
- D.所有请求共享同一前缀
答案:A。传统批处理像“整桌吃完才翻台”,慢请求拖累全批;连续批处理让完成的请求立刻退出、新请求马上进入,GPU 永不空等。它不要求批大小固定,更不要求请求同步结束,共享前缀是分页的另一项能力。
Q8. PagedAttention 引入的收益有哪些?
- A.消除预留导致的显存碎片← 正确答案
- B.支持多序列共享前缀块← 正确答案
- C.减少模型参数的冗余度
- D.支持超长序列按需分配← 正确答案
答案:ABD。分页按需分配小块,消除了大块预留造成的碎片;引用计数让相同前缀的请求共享物理块;序列多长就申请多少块,不再按上限预留。模型参数量完全没变,C 是干扰项。
Q9. KV Cache 显存大小与哪些因素成正比?
- A.模型层数与隐藏维度← 正确答案
- B.序列长度与批大小← 正确答案
- C.学习率与训练轮数
- D.采样温度与解码方式
答案:AB。公式 \(4LHSB\) 表明 KV 显存只由层数 \(L\)、隐藏维度 \(H\)、序列长度 \(S\)、批大小 \(B\) 决定。训练超参和采样参数只在推理之外起作用,完全不影响缓存占用。
Q10. 缓解 KV 显存压力的正确做法是?
- A.提高 gpu 显存利用率上限
- B.降低批大小或使用分页缓存← 正确答案
- C.增加模型层与注意力头数
- D.提高采样温度放大随机性
答案:B。降低批大小直接按乘法关系缩小 \(B\),分页缓存则消除碎片并支持前缀共享。把利用率调到接近 1.0 反而可能让权重和激活无地容身而 OOM;加深模型只会让 KV 更大;采样温度与显存完全无关。