想象你经营一家开在写字楼里的粤菜餐厅。灶台只有三平方米,但每晚要出三百道菜。厨师不可能把三百道菜的备料全堆在灶台上——放不下,火星还可能燎着菜叶。于是,最常用的十盘料放在手边灶台,用得少的放后厨冰箱,几乎不用的放地下冷库。真正让餐厅运转流畅的,是一位备菜员:他总能在厨师转身的一瞬间,把下一道菜需要的料从冷库端上灶台,绝不让人干等。 大模型的推理过程,就是一场“连续炒菜”:每生成一个新词(一道新菜),模型都要把此前所有词(已备好的料)都“看”一遍——这些料,就是 KV Cache。显存是灶台,CPU 内存是冰箱,SSD 是地下冷库。KV Cache 卸载(offloading)要解决的,正是“灶台放不下、又不让厨师停下”的难题。本文从“为什么要缓存”讲起,逐层拆解显存瓶颈、分层存储、卸载策略、预取机制、性能权衡和工程实践,让你完整看懂这套显存与内存的分层优化大法。

岗位关联

一句话职业价值:掌握显存/内存分层卸载与预取,你就能在固定预算的 GPU 上服务更长上下文、更高并发,直接降低推理成本与部署门槛。

目录
  1. 可视化演示
  2. 一、缓存缘起:为什么每道菜都要备料
  3. 二、显存瓶颈:灶台只有三平方
  4. 三、分层存储:灶台、冰箱与冷库
  5. 四、卸载策略:谁走谁留
  6. 五、预取机制:备菜员的远见
  7. 六、性能权衡:省地方还是省时间
  8. 七、工程实践:开一家不限量的餐厅
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

GPU 显存 CPU 内存 Offload ↓ ↑ Prefetch KV Cache 分层卸载:热数据驻留 GPU 显存,冷数据异步卸载至 CPU 内存,在推理计算间隙预取所需 KV 块,平衡显存占用与推理延迟。

一、缓存缘起:为什么每道菜都要备料

后厨做“白切鸡”,最费时的是杀鸡、褪毛、过冰水——真正下锅只要几十秒。聪明的厨师会一次性备好十份料,客人点单时只需下锅。大模型也一样:处理一段上千字的文档,最贵的一步是“预填充”(prefill),把每个词都过一次网络,算好所有中间表示。这就像头一天的备料,又慢又花工夫。

当模型逐词生成时(增量解码 decode),每生成一个新词,注意力机制都要把它与前面所有词“配对打分”。如果不缓存,模型就得把前面每个词的 Key 和 Value 重新算一遍——等于每上一道菜都把鸡重新杀一遍。KV Cache 就是把这些算好的 Key、Value 投影存起来,供后续 token 重复使用。这正是“键值重复利用”:用一份显存,换掉每次 O(序列长度) 的重复矩阵乘法。

KV Cache 有多大?它随序列长度线性膨胀,公式如下:

所以缓存是“省了重复劳动,占了宝贵空间”。备料越多,灶台越挤——KV Cache 帮我们省了算力,却把压力转嫁给了显存。下一节就来算这笔账。

SKV=2×Nlayer×Nhead×dhead×Ntok×b\displaystyle S_{KV} = 2 \times N_{layer} \times N_{head} \times d_{head} \times N_{tok} \times b
公式说明:KV Cache 总字节数 = K、V 两份 × 层数 × 头数 × 每头维度 × 已生成 token 数 × 每元素字节数。
  1. 系数 2(两份投影)每个 token 需要保存 Key 和 Value 两组矩阵,体积直接翻倍。
    2\displaystyle 2
  2. 层数 $N_{layer}$Transformer 每一层都有独立的注意力投影,层数越多缓存越厚。
    Nlayer\displaystyle N_{layer}
  3. 注意力头数 $N_{head}$多头注意力中每个头单独维护一组 K/V,头越多缓存条数越多。
    Nhead\displaystyle N_{head}
  4. 每头维度 $d_{head}$每个注意力头的投影向量维度,决定单个头存多少个数。
    dhead\displaystyle d_{head}
  5. 已生成 token 数 $N_{tok}$序列越长,堆在“灶台”上的备料盘越多——线性增长的来源。
    Ntok\displaystyle N_{tok}
  6. 每元素字节数 $b$FP16 精度下每个数占 2 字节,直接影响总字节数。
    b\displaystyle b

二、显存瓶颈:灶台只有三平方

假设餐厅只有一张一米二的小灶台,却接到一个三百人的婚宴订单。备料充足,但灶台放不下三十盘菜,厨师连转身都难,火候一乱,整桌菜全废。大模型的处境一模一样:GPU 显存就是那张灶台,KV Cache 的盘子正一盘接一盘端上来。

技术侧的具体数字更触目惊心。以 32 层、32 头、每头 128 维、FP16 精度(每数 2 字节)的模型为例,每个 token 的 KV 缓存约 512 KB。当上下文达到 128K 时,单条序列的 KV Cache 就接近 64 GB——一块 80 GB 的 A100 刚够装,但模型权重和激活值也要占显存,直接爆掉。KV Cache 随序列长度的线性增长,是“上下文限制”的根源。

显存满了会怎样?批大小(batch size)被压缩到 1——一桌只能坐一位客人;GPU 算力大量闲置——灶台空转;上下文被截断——菜单砍半。出路不是把灶台变大,而是给冰箱和冷库派活:分层存储,这正是 KV Cache offloading 的核心思想。

MKV(T)=α×T,α=2×Nlayer×Nhead×dhead×b\displaystyle M_{KV}(T) = \alpha \times T, \qquad \alpha = 2 \times N_{layer} \times N_{head} \times d_{head} \times b
公式说明:KV Cache 总量与序列长度 T 成正比,斜率 α 是每个 token 的“存储单价”。
  1. 总占用 $M_{KV}(T)$序列长度为 T 时 KV Cache 的总字节数。
    MKV(T)=α×T\displaystyle M_{KV}(T) = \alpha \times T
  2. 存储单价 $\alpha$每增加一个 token 平均增加的字节数,是“线性增长”的斜率。
    α=2×Nlayer×Nhead×dhead×b\displaystyle \alpha = 2 \times N_{layer} \times N_{head} \times d_{head} \times b
  3. 层数与头数相乘层数 × 头数决定一份 K/V 需要存多少份副本。
    Nlayer×Nhead\displaystyle N_{layer} \times N_{head}
  4. 维度乘字节$d_{head} \times b$ 决定单份投影向量占几个字节。
    dhead×b\displaystyle d_{head} \times b
  5. 线性增长后果T 翻倍则 M 翻倍,这是长序列把显存打爆的根本原因。
    MKV(2T)=2×MKV(T)\displaystyle M_{KV}(2T) = 2 \times M_{KV}(T)

三、分层存储:灶台、冰箱与冷库

餐厅有一条铁律:今晚要出的菜放灶台,明天要用的放冰箱,一个月后用的放冷库。取料速度完全不同——灶台秒取,冰箱十几秒,冷库要五分钟。KV Cache 也一样:GPU 显存最快但最小,CPU 内存次之但大得多,SSD 最慢却几乎无限。所谓“分层存储”,就是把缓存按访问热度放进不同层。

offloading 的基本动作很朴素:热数据(最近生成的几块 KV)驻留显存,冷数据(早期 token 的 KV)卸载到 CPU 内存甚至 SSD。KV 以“块”为单位管理,而不是逐 token 搬运——这就是“块粒度管理”,每个块包含固定数量的 token 键值对,便于定位、搬运和驱逐。分层越多,能装的上下文越长,代价是跨层访问变慢。

于是需要一个指标衡量分层方案的好坏——平均访问延迟:

命中率越高,整体越接近显存速度。所以分层存储的命门,是“谁住灶台、谁下冷库”的决策——这就是卸载策略要回答的问题。

Tavg=phit×Tvram+(1−phit)×Tmem\displaystyle T_{avg} = p_{hit} \times T_{vram} + (1 - p_{hit}) \times T_{mem}
公式说明:平均读取耗时 = 命中显存的概率乘以显存延迟 + 未命中的概率乘以内存搬运读取延迟。
  1. 平均访问延迟 $T_{avg}$把命中显存与未命中(去内存/SSD)两种情况按概率加权。
    Tavg=phit×Tvram+(1−phit)×Tmem\displaystyle T_{avg} = p_{hit} \times T_{vram} + (1 - p_{hit}) \times T_{mem}
  2. 命中率 $p_{hit}$注意力读取 KV 时,目标块恰好驻留显存的比例。
    phit\displaystyle p_{hit}
  3. 显存延迟 $T_{vram}$直接从显存读 KV 的耗时,纳秒级。
    Tvram\displaystyle T_{vram}
  4. 未命中概率 $(1 - p_{hit})$目标块不在显存、需要跨层搬运的概率。
    1−phit\displaystyle 1 - p_{hit}
  5. 内存开销 $T_{mem}$跨 PCIe 搬运并读取的耗时,是显存的数十到数百倍。
    Tmem\displaystyle T_{mem}

四、卸载策略:谁走谁留

厨师长立下规矩:灶台固定放十盘,新菜上来了,就把最久没动过的那盘收进冰箱;冰箱也满,再把次久没动的推到冷库。这个“最久没用先走”的直觉,在系统里叫 LRU(Least Recently Used,最近最少使用)。KV Cache 卸载同样遵循这个原则:近处常有新 token 来抢占显存,总得有块让位。

但实践中仅看“新旧”不够。系统提示词(system prompt)可能生成后被反复长距离关注,虽然“不新”却“高频”;有些块刚被访问过一次就再也没人碰。因此工程实现会用“优先级打分”综合两个因素:最近使用时间 recency 和累计访问频率 freq,分数最低的块最先被驱逐。

打完分还要设一道“容量预算”:显存中 KV 驻留总量不得超过预算(例如显存的 40%),超了就从分数最低的块开始驱逐。算完新 token 的 KV 后按需卸载、按预算驱逐,这套联动就是“按需卸载 + 优先级驱逐 + 块粒度管理”的经典架构。

不过,光有“赶人”还不够——被赶走的冷块,每隔一阵还会被长距离注意力“点名”。为了不让厨师干等,需要一位有远见的备菜员:预取机制。

scorei=λ×recencyi+(1−λ)×freqi\displaystyle score_i = \lambda \times recency_i + (1 - \lambda) \times freq_i
公式说明:每个 KV 块的驻留资格分 = 权重 λ 调节的“新近度”与“频次”的加权和,分低者先被卸载。
  1. 优先级得分 $score_i$每个 KV 块留在显存的“资格分”,越低越先被驱逐。
    scorei=λ×recencyi+(1−λ)×freqi\displaystyle score_i = \lambda \times recency_i + (1 - \lambda) \times freq_i
  2. 最近使用 $recency_i$越新被访问,分数越高,保护刚用过的热块。
    recencyi\displaystyle recency_i
  3. 访问频率 $freq_i$长期高频使用的块(如系统提示词)即使不新也值得留下。
    freqi\displaystyle freq_i
  4. 权重 $\lambda$手工调节“新”与“热”哪个更重要,一般取 0.5~0.8。
    λ\displaystyle \lambda

五、预取机制:备菜员的远见

从冷库取料要五分钟,如果等客人点了才去拿,厨师就得干瞪眼五分钟。好餐厅的备菜员会盯着订单屏幕:下一道菜要什么,提前五分钟去拿。等厨师伸手时,料已经摆在灶台边。KV Cache 预取的本质,就是这位备菜员。

预取发生在“计算间隙”:生成下一个 token 时,GPU 要跑注意力计算,这段时间 PCIe 总线和 CPU 内存通道是闲着的。预取调度器就在这间隙里,把下一轮注意力需要的 KV 块从内存/SSD 异步搬回显存,传输和计算两条流水线重叠进行。只要预取指令在计算开始前发出,传输时间就能被“藏”在计算背后。

注意“约等于”三个字——预取要准。预测错了(prefetch miss)就白搬一趟,还挤占带宽;重叠不充分时,传输时间仍会漏进关键路径。备菜员不是万能的,下单预测错了,一样白跑腿。

Tstep≈max⁡(Tcompute,Ttransfer)\displaystyle T_{step} \approx \max(T_{compute}, T_{transfer})
公式说明:理想流水线重叠下,生成一个 token 的耗时约等于“计算时间”与“预取传输时间”中的较大者,而不是两者相加。
  1. 单步耗时 $T_{step}$生成一个 token 的平均端到端时间。
    Tstep≈max⁡(Tcompute,Ttransfer)\displaystyle T_{step} \approx \max(T_{compute}, T_{transfer})
  2. 计算时间 $T_{compute}$GPU 做注意力计算的时间。
    Tcompute\displaystyle T_{compute}
  3. 传输时间 $T_{transfer}$把一块 KV 从 CPU/SSD 搬到显存的时间。
    Ttransfer\displaystyle T_{transfer}
  4. max 而非加两条流水线并行,总耗时由较慢的一环决定。
    max⁡(Tcompute,Ttransfer)\displaystyle \max(T_{compute}, T_{transfer})
  5. 无预取对比串行执行时是两者相加,预取的核心收益正在于此。
    Tcompute+Ttransfer\displaystyle T_{compute} + T_{transfer}

六、性能权衡:省地方还是省时间

用冰箱料做的菜更新鲜,还是冷库料更省电?没有免费的午餐:卸载腾出了灶台空间,却多了搬运开销。系统设计者要算三笔账:第一,延迟隐藏——预取能把传输藏多少;第二,带宽瓶颈——PCIe 是有限通道,搬运本身占用资源;第三,命中率与调度开销——管理块列表、打分、驱逐都要消耗 CPU 时间。

一条现实数据:PCIe 4.0 ×16 约 32 GB/s,GPU 显存带宽却高达 2 TB/s 量级。把 20 GB 冷 KV 从 CPU 内存拉回显存需要接近一秒——如果预取没藏住,这段延迟会直接暴露给用户。因此“把热窗口做小、把命中率做高”是卸载调优的第一原则;命中率达到 90% 以上时,绝大多数请求根本感知不到卸载的存在。

权衡到最后,这是一道经营题:灶台够用就别开冰箱,冰箱不够用才开冷库。工程上由显存预算、目标延迟和请求并发共同决定 p 值(驻留比例)与 β 值(重叠率)的设定。

Sspeedup=Tcompute+TtransferTcompute+(1−β)×Ttransfer\displaystyle S_{speedup} = \frac{T_{compute} + T_{transfer}}{T_{compute} + (1 - \beta) \times T_{transfer}}
公式说明:预取相对无预取的加速比 = 串行总耗时 ÷ 重叠后总耗时,β 是传输被计算覆盖的重叠率。
  1. 加速比 $S_{speedup}$预取比无预取快多少倍。
    Sspeedup=Tcompute+TtransferTcompute+(1−β)×Ttransfer\displaystyle S_{speedup} = \frac{T_{compute} + T_{transfer}}{T_{compute} + (1 - \beta) \times T_{transfer}}
  2. 分子(无预取)没有重叠时,计算完再传输,串行相加。
    Tcompute+Ttransfer\displaystyle T_{compute} + T_{transfer}
  3. 分母(有预取)只有没被盖住的传输部分 $(1 - \beta) T_{transfer}$ 暴露在关键路径上。
    Tcompute+(1−β)×Ttransfer\displaystyle T_{compute} + (1 - \beta) \times T_{transfer}
  4. 重叠率 $\beta$被计算时间覆盖的传输比例,取值 0~1。
    β\displaystyle \beta
  5. 极限情形β=1 时加速比最大;β=0 时无提升。
    Smax=1+TtransferTcompute\displaystyle S_{max} = 1 + \frac{T_{transfer}}{T_{compute}}

七、工程实践:开一家不限量的餐厅

现实中,vLLM、DeepSpeed Zero-Inference、FlexGen 等方案已经把“备菜员”武装到了牙齿:分页 KV(像操作系统管理虚拟内存一样按页管理)、异步拷贝队列、多级卸载流水线。它们的共同目标是:在资源受限的显卡上跑更长上下文,以更高并发服务更多请求,并摊薄每一 token 的推理成本。

工程上最核心的收益公式是并发上限。假设显存总量减去模型占用后,剩余空间能装多少条请求的 KV:

举个例子:单条请求 KV 完整体积 10 GB,可用显存 40 GB,不卸载(p=1)只能并发 4;卸载到 p=0.3 后并发提升到约 13,吞吐翻了三倍。离线批处理场景可以把 p 压得更低搏吞吐,在线对话则保持高 p 保延迟。经营账由此算清:同样的卡,接更多客人,摊薄每单成本——这就是 KV Cache offloading 的商业价值。

Cmax=⌊MVRAM−Mmodelp×Sseq⌋\displaystyle C_{max} = \left\lfloor \frac{M_{VRAM} - M_{model}}{p \times S_{seq}} \right\rfloor
公式说明:可同时处理的请求数 = 可用显存 ÷ 单请求驻留显存量,其中 p 是卸载后的驻留比例。
  1. 并发上限 $C_{max}$显存能同时容纳的请求条数。
    Cmax=⌊MVRAM−Mmodelp×Sseq⌋\displaystyle C_{max} = \left\lfloor \frac{M_{VRAM} - M_{model}}{p \times S_{seq}} \right\rfloor
  2. 可用显存 $M_{VRAM} - M_{model}$显存总量减去模型权重与激活占用的剩余空间。
    MVRAM−Mmodel\displaystyle M_{VRAM} - M_{model}
  3. 单请求驻留量 $p \times S_{seq}$卸载后单条请求平均只占 p 比例的完整 KV 体积。
    p×Sseq\displaystyle p \times S_{seq}
  4. 驻留比例 $p$热数据占比,p 越小腾出的空间越多,但冷数据搬运越频繁。
    p\displaystyle p
  5. 向下取整 $\lfloor \cdot \rfloor$并发条数必须是整数。
    ⌊⋅⌋\displaystyle \lfloor \cdot \rfloor

AI 应用场景

场景一:长上下文客服机器人

阶段:推理服务部署 / 显存优化

解决的问题:传统方案下,128K 上下文的客服机器人要把全部对话历史的 KV Cache 放在显存,动辄几十 GB,24 GB 消费级显卡直接 OOM,只能忍痛截断历史,导致用户前面说过的话“被遗忘”。采用分层卸载后,最新几轮对话的 KV 驻留显存,早期轮次卸载到 CPU 内存甚至 SSD,再配合预取,机器人可以完整记住 128K 上下文而不爆显存。

具体案例:某电商客服团队用 30B 模型加 KV offloading,上下文从 16K 扩展到 96K,单卡吞吐提升约 3 倍,晚高峰 P95 延迟仅增加 8%,用户满意度回升明显。

场景二:离线批量文档总结

阶段:批处理推理调度

解决的问题:定时跑上千份长文档摘要时,延迟不敏感但吞吐和成本敏感。传统全显存驻留需要大量显卡;FlexGen 式策略把冷 KV 放 SSD,用流水线预取最大化带宽利用,让四张卡干八张卡的活。

具体案例:某金融公司每周生成 500 份研报摘要,原需 8 张 A100,采用 offloading 后 4 张 A100 完成,硬件成本减半;单篇耗时从 40 秒升到 55 秒,完全在批处理的容忍范围内。

场景三:多租户在线推理平台

阶段:在线推理服务 / 并发调度

解决的问题:SaaS 平台在固定 GPU 上服务大量用户,显存被 KV Cache 占满导致并发上不去。卸载腾出显存空间,让更多请求的 KV 块“挤”进显存,配合同步的预取调度,并发数和单位成本同时优化。

具体案例:某 MaaS 平台用 vLLM + KV offload,把驻留比例 p 设为 0.4,单卡并发从 8 提升至 18,每 token 成本下降约 55%,并通过 QoS 调度保证在线请求的关键路径延迟不超标。

Python 演示

python71 行
"""KV Cache 分层卸载与预取策略的简化模拟对比"无预取"与"带预取"两种方案的总耗时。"""  class OffloadSim:    def __init__(self, vram_cap=3, cpu_cap=8, bandwidth=2.0, compute=1.0, prefetch=False):        self.vram_cap = vram_cap        self.cpu_cap = cpu_cap        self.bandwidth = bandwidth        self.compute = compute        self.prefetch = prefetch        self.vram = set()        self.cpu = set()        self.last_use = {}        self.clock = 0        self.total_time = 0.0     def load(self, bid):        """把块搬入显存;超过容量则按 LRU 驱逐到 CPU/SSD"""        if bid in self.vram:            return 0.0        if bid in self.cpu:            self.cpu.discard(bid)            cost = 1.0        else:            cost = 1.5  # 从 SSD 读取更慢        self.vram.add(bid)        while len(self.vram) > self.vram_cap:            victim = min(self.vram, key=lambda b: self.last_use.get(b, -1))            self.vram.discard(victim)            if len(self.cpu) < self.cpu_cap:                self.cpu.add(victim)        return cost     def step(self, need, prefetch_bids=()):        """处理一个 token:访问 need 中的块,并尝试预取 prefetch_bids"""        transfer = 0.0        for b in need:            transfer += self.load(b)            self.clock += 1            self.last_use[b] = self.clock        for b in prefetch_bids:            if b not in self.vram:                transfer += self.load(b)                self.last_use[b] = self.clock        if self.prefetch:            self.total_time += max(self.compute, transfer / self.bandwidth)        else:            self.total_time += self.compute + transfer / self.bandwidth  def main():    times = {}    for prefetch in (False, True):        sim = OffloadSim(prefetch=prefetch)        for t in range(240):            bid = t % 12            need = {bid, (t - 1) % 12}            if t % 4 == 0:                need.add((t - 6) % 12)            future = {(t + 1) % 12} if prefetch else ()            sim.step(need, future)        times[prefetch] = sim.total_time    print('无预取总耗时: %.1f 单位' % times[False])    print('带预取总耗时: %.1f 单位' % times[True])    print('预取加速比:   %.2fx' % (times[False] / times[True]))  if __name__ == '__main__':    main()
目的:用一个小型模拟器对比“无预取”和“带预取”两种 KV Cache 卸载方案的总耗时,直观看到流水线重叠带来的加速比。
  1. self.vram = set()用集合管理显存中驻留的 KV 块,相当于“灶台上放了哪几盘料”。
  2. victim = min(self.vram, key=lambda b: self.last_use.get(b, -1))LRU 驱逐:优先赶走最久没用过的块,对应“最久没用先走”的厨师长规矩。
  3. cost = 1.5 # 从 SSD 读取更慢从冷库取料比冰箱贵,模拟 SSD 与内存的带宽差异。
  4. transfer += self.load(b)每次访问冷块都产生搬运成本,这是 offloading 绕不开的开销。
  5. future = {(t + 1) % 12} if prefetch else ()备菜员提前把下一轮要用的块列入预取清单。
  6. self.total_time += max(self.compute, transfer / self.bandwidth)预取时传输与计算重叠,耗时取 max 而非加,这正是核心收益来源。

常见误区

误区把 KV 搬到内存/SSD,模型不就“记忆模糊”了吗?生成质量肯定会变差,所以卸载只适合不重要的场景。

正解不会。卸载移动的是缓存数据本身,不涉及压缩或量化,K/V 数值完全不变,注意力计算结果和全驻留显存时逐位一致。变的只是读取速度:命中显存快,命中内存慢。质量不变,速度有差异——这就像备料从冷库拿还是灶台拿,菜的味道一样,只是上菜快慢不同。

为何常见:人们把“系统变慢”误当成“模型变笨”,又看到“卸载”这个词联想到降质,实际二者毫无关系。

误区显存访问最快,KV Cache 当然全部放显存最好;卸载只是显存不够的无奈妥协。

正解这是典型的“只算延迟、不算吞吐”。很多场景显存“刚好能装下”全部 KV,但装下后 batch=1,GPU 算力闲置 90%。主动把冷 KV 卸载一部分,腾出的空间容纳更多请求,总吞吐反而更高。卸载不仅是“装不下”的兜底,更是“提高资源利用率”的主动优化——就像灶台放不下时才开冰箱?不,好厨师的冰箱永远是开着的。

为何常见:初学者只盯着单请求延迟,忽略了并发和单位成本这两个更大的变量。

误区加了预取,传输时间就变成 0 了,跟全驻留显存一样快。

正解预取只能让传输与计算重叠,把延迟“藏”在计算后面,并不能让传输资源凭空消失。带宽仍被占用,可能挤压其他作业;预测不准还会白搬一趟(prefetch miss)。当传输时间超过计算时间时,每步耗时仍由传输主导。备菜员跑得再快,也变不出第二条 PCIe 通道。

为何常见:把“重叠”误解为“消失”,忽略了物理带宽的上限。

误区KV Cache 和模型权重一样,是训练好的参数,应该保存下来长期复用。

正解KV Cache 是推理中间产物:针对某条输入序列,把它的 Key/Value 投影缓存下来避免重复计算。换一条输入,缓存完全失效;请求结束,缓存随之释放。模型权重才是跨请求复用的持久化参数。就像备好的菜料只属于今晚这场宴席,明天换菜单就得重新备。

为何常见:两者都叫“Cache“,有人便类比预训练权重,混淆了”持久参数“和”会话中间态”。

误区7B 这种小模型显存装得下 KV Cache,根本不用学卸载。

正解是否需要卸载取决于“KV Cache 相对显存是否紧张”,而不是模型大小。7B 模型读 200K token 的长篇小说,KV 照样几十 GB;再加上多请求并发,KV 总量急剧膨胀,小模型一样打爆 24 GB 显卡。offloading 是显存资源管理的通用手段,与模型参数量无必然关系。

为何常见:只按“单请求 × 单模型”估算显存,漏掉了上下文长度和并发数这两个乘数。

面试题

Q1. KV Cache 在推理中缓存的主要内容是?

单选题 · easy

  • A.模型的全部权重参数
  • B.已生成 token 的键值投影← 正确答案
  • C.优化器里的梯度状态
  • D.词表的嵌入向量表

答案:B。KV Cache 缓存的是已生成 token 的 Key 和 Value 投影矩阵,用于后续 token 的注意力计算,避免重复投影。模型权重是持久参数,梯度只在训练中出现,词表嵌入是输入映射,都与“缓存历史 token 中间结果”无关。就像备好的料是“菜料”而非“菜刀”。

Q2. KV Cache 总大小随序列长度怎样变化?

单选题 · easy

  • A.线性增长← 正确答案
  • B.对数增长
  • C.平方级增长
  • D.保持不变

答案:A。每个新 token 都会新增一份 K/V,总量与序列长度成正比,即线性增长。不是平方(注意力计算量才是平方级),也不是对数或恒定。灶台上的备料盘是“每道菜多一盘”,而不是“每道菜多一桌”。

Q3. 卸载策略中“热数据”通常指?

单选题 · mid

  • A.离当前生成位置较近的 KV← 正确答案
  • B.序列最开头的 KV 块
  • C.词频较高的词汇投影
  • D.显存中的所有缓存块

答案:A。热数据指最近生成、马上还要被注意力多次读取的 KV 块,通常就是离当前生成位置最近的若干块。序列开头的 KV 往往已变冷,词频是文本统计概念,与缓存热度无关;显存中并非所有块都热,否则就不需要分层了。

Q4. KV Cache 分层卸载常见的存储层级有?

多选题 · mid

  • A.GPU 显存← 正确答案
  • B.CPU 内存← 正确答案
  • C.SSD 磁盘← 正确答案
  • D.路由器缓存

答案:ABC。主流分层是 GPU 显存(灶台)、CPU 内存(冰箱)、SSD 磁盘(冷库)三级。路由器缓存不属于推理服务器的存储体系,不参与 KV Cache 的驻留与卸载。层级选择原则是“越快越小、越慢越大”。

Q5. 预取机制的主要收益来自哪里?

单选题 · mid

  • A.传输与计算在时间上重叠← 正确答案
  • B.把 KV Cache 压缩得更小
  • C.减少模型参数的计算量
  • D.提高注意力计算的精度

答案:A。预取在“计算间隙”异步搬运 KV 块,让传输时间和 GPU 计算时间重叠,从而使单步耗时从“计算+传输”降为“两者取 max”。它不压缩缓存、不减少计算量,也不改变精度——只是把备菜员的取料动作藏进厨师炒菜的时间里。

Q6. 理想流水线重叠时,单步生成耗时近似为?

单选题 · hard

  • A.计算时间加传输时间
  • B.两者中较大的那个值← 正确答案
  • C.两者中较小的那个值
  • D.传输时间的两倍数值

答案:B。传输与计算完全并行时,关键路径由较慢的环节决定,因此单步耗时 ≈ max(计算时间,传输时间)。相加是“无预取”的串行情形;取较小值或两倍都没有物理意义。好比备菜员和厨师同时干活,总工时看谁先干完。

Q7. 下列哪些因素决定单个 token 的 KV 占用?

多选题 · hard

  • A.Transformer 的层数← 正确答案
  • B.注意力头的数量← 正确答案
  • C.每个头的向量维度← 正确答案
  • D.词表的整体大小

答案:ABC。单 token 的 KV 体积 = 2 × 层数 × 头数 × 每头维度 × 字节数。层数、头数、维度都直接乘进体积公式。词表大小影响的是输入输出嵌入层,与 KV 缓存无关——它决定“菜名有多少种叫法”,不决定“一盘菜备多少料”。

Q8. 显存容量不足时,通常优先驱逐哪类块?

单选题 · mid

  • A.最久未被访问的块← 正确答案
  • B.最近被访问的热块
  • C.体积最大的 KV 块
  • D.随机挑选的任意块
  • E.计算时间加传输时间之和
  • F.计算时间减传输时间之差
  • G.只算传输时间忽略计算
  • H.只算计算时间忽略传输

答案:A。无预取时,注意力要先等冷块从 SSD 搬进显存才能计算,两步串行,总耗时 ≈ 计算时间 + 传输时间。且 SSD 带宽远低于显存,传输时间可能占大头。这正解释了为什么没有预取的 naive offloading 会让人“干等冷库取料”。

Q9. KV Cache 卸载的典型收益包括?

多选题 · easy

  • A.支持更长的上下文← 正确答案
  • B.提高并发请求数← 正确答案
  • C.降低单卡显存门槛← 正确答案
  • D.减少模型的推理计算量

答案:ABC。卸载让 KV 不占满显存,从而支持更长上下文、容纳更多并发请求,也降低了单卡显存门槛。但它不减少推理计算量——注意力矩阵乘法该算还是得算。卸载优化的是“存储布局”,不是“算术量”。好比换更大的仓库不改变做菜的工序,只改变料放哪里。