上一章 Transformer 与自注意力 Agent 的一切都跑在注意力机制之上。这一章只做三件事:说清 token、张量、投影、打分这些名词,跟着数据走一遍形状变化,再把 n² 的来源与工程推论讲透。 I · 大模型原理 · 第 01 章 · 入门 · 约 35 分钟
Agent 工程学习站 做棵大树 出品 主站 beatree.cn 免费 · 无需登录 · 进度存本机
I 版 · 大模型原理 ◆ 第 02 章 ◆ The Hidden Bill of Multi-turn
I · 大模型原理 02 / 22 进阶

KV Cache:多轮对话的隐性账单

The Hidden Bill of Multi-turn
预计阅读 26 分钟
难度 进阶
关键词 KV Cache · 前缀稳定性
本机状态 未读

一个 Agent 的单次任务往往要发十几轮请求,每一轮都带着前面所有轮次的完整历史。如果服务端每次都从头算一遍,你付的钱里有一大半是重复劳动。KV Cache 把这份重复劳动变成缓存命中,而命中率取决于一个平时根本不会被注意的细节:前缀稳定性。

编辑部注 · 本章是全站最重要的成本一章。读完后请务必把「前缀稳定性破坏点清单」写进你自己的笔记。

先看一个真实的账单形态。同一个 Agent 任务,第一轮请求 2,000 token 的输入,第二轮变成 2,400,第三轮 2,800……输入长度线性增长,而如果每一轮都要从头算一遍全部前缀,总成本就会呈平方级增长。这不是假设,这是绝大多数「感觉 Agent 很贵」的真实原因。

一、重复计算发生在哪

生成式模型是自回归(Autoregressive)的:生成第 t 个 token 时,需要前面所有 token 的信息。而在 Transformer 里,每一层的信息都浓缩成两个张量——K(键)和 V(值)。

关键点在于:这两个张量只依赖已经出现过的前缀,与后面要生成什么完全无关。

所以第 2 轮请求里,那 2,000 个 token 的历史算出来的 K/V,和第 1 轮里算出来的完全一样。既然一样,就没有理由重算。把每一层的 K/V 存下来,下一轮直接复用,这就是 KV Cache。

同一个前缀,两种命运 灰色 = 缓存命中复用 深色 = 必须重新计算 A · 前缀稳定 → 命中 第 1 轮:system + 历史 [2,000] 全部符合 第 1 轮无缓存 第 2 轮:完全相同的前缀 [2,000] 仅 +400 ✓ 只算新增 第 2 轮输入成本 ≈ 400 token 量级,而非 2,400 B · 前缀被改动 → 未命中 第 1 轮:system + 历史 [2,000] 第 1 轮无缓存 改 system 开头多了一句「今天是周三」 全量 ✗ 整段前缀作废 第 2 轮输入成本 ≈ 2,400 token 量级,缓存的 2,000 全部浪费 缓存是按 token 逐位比对前缀的:从第一个 token 开始,一旦有一位不同,其后全部失效。 所以改动永远要加在尾部(追加),不要动头部(系统提示、工具定义、历史顺序)。
图 1 A 与 B 的区别只在于「前缀有没有被改动」。同样把输入从 2,000 加到 2,400,A 只需处理新增的 400,B 需要处理全部 2,400。成本差异不是百分比,而是数量级。

二、前缀稳定性:被忽略的成本开关

KV Cache 是按前缀逐位比对(Prefix Stability)命中的。从序列的第一个 token 开始,一旦某一位不同,其后全部失效。这个机制带来一个反直觉但极其重要的结论:

在中间插入内容,比在末尾追加内容昂贵得多。

常见的「插在中间」的操作,在工程里几乎随处可见:

常见写法为什么破坏前缀正确做法
System Prompt 里塞 当前时间:{now}每轮时间都变,第一个 token 就不同把动态信息放到最后一条 user 消息里,或只精确到天/小时并接受偶发失效
每轮重新序列化工具列表,顺序不稳定字典遍历顺序或 JSON 键序变化 → 字节不同工具定义做稳定排序,序列化后缓存字符串而非重复生成
把最新用户消息插到历史前面历史整体后移,全部前缀作废永远追加到尾部
每轮重新注入「记忆」到 system 里记忆内容变化 → 开头就不同记忆放在尾部或独立的一条消息;或作为工具按需检索
历史消息每次重新渲染(重新拼 Markdown)换行、空格、引号形式细微变化即失效保持历史消息字节级不变,原始字符串原样回传
把工具执行结果截断长度随轮次变化被截断位置之后的内容全变截断策略要稳定(固定头尾保留量)
最容易被忽略的一条

「重新序列化历史」是隐蔽的前缀杀手。很多 Harness 会在每轮把消息数组拼成一次性的提示字符串,中间经过模板渲染、去空行、合并连续换行等「美化」步骤。这些步骤哪怕只差一个空格,前缀就废了。

判断方法很简单:把你的请求体打印出来,对比第 1 轮与第 2 轮的字节前缀,看从第几个字符开始不同。如果第一个差异出现在前 1% 的位置,说明你的缓存命中率基本上是 0。

三、显存估算:缓存的代价

缓存不是免费的,它要占相当可观的KV Cache 显存(KV Cache Footprint)。估算公式不长,记住它就能在面试里立刻给出数量级。

kv_cache_footprint.pypython
def kv_cache_bytes(
    batch: int,        # 并发请求数
    seq_len: int,      # 序列长度(含历史)
    n_layers: int,     # 层数
    n_kv_heads: int,   # K/V 头数(注意:不一定等于注意力头数)
    head_dim: int,     # 每个头的维度
    bytes_per_num: int = 2,   # fp16=2, fp8=1
) -> int:
    # 每层缓存 K 和 V 两份,所以乘 2
    return 2 * batch * seq_len * n_layers * n_kv_heads * head_dim * bytes_per_num
# 一个典型的 70B 级模型(GQA,kv_heads 远少于 query_heads)
total = kv_cache_bytes(
    batch=32, seq_len=32_000, n_layers=80, n_kv_heads=8, head_dim=128
)
print(f"{total / 1024**3:.1f} GiB")   # 大约 312 GiB —— 仅缓存,不含权重
# 关键观察:这个数是随 seq_len 线性长的
for s in (8_000, 32_000, 128_000):
    n = kv_cache_bytes(32, s, 80, 8, 128)
    print(f"seq_len={s:>7}: {n/1024**3:6.1f} GiB")

三点必须记住:

  1. KV Cache 显存随序列长度线性增长,与注意力的算力平方增长不是同一回事。算力是平方问题,显存是线性问题——两者要分开讲。
  2. GQA(Grouped-Query Attention) / MQA(Multi-Query Attention) 的工程意义就在这里:把 K/V 头数(n_kv_heads) 从 h 降到 h/8 甚至 1,每个头的 维度(head_dim) 不变,缓存直接缩小同样倍数。这是长上下文能跑起来的关键手段之一。
  3. 缓存有淘汰策略(Cache Eviction)。服务端不会让你无限占显存,通常按 LRU 或分页(PagedAttention)管理。这意味着你的前缀如果排在很久不用的位置,可能已经被淘汰——命中率还受并发与调度影响,不只看你自己。这也就是缓存命中率(Cache Hit Rate)不完全由你决定的原因。

3.1 缓存也有一本账:命中 / 未命中怎么计价

显存讲的是「技术代价」,真正决定上不上缓存的往往是钱。多数按 token 计费的服务里,输入 token 因为能复用前缀缓存,价格会分档:

  • 缓存命中(cache hit):这段前缀此前已经算过、还在缓存里,这次只做增量计算,单价最低;
  • 缓存未命中(cache miss):这段前缀要么是新的、要么已被淘汰,要完整重算,单价最贵(往往是命中的数倍)。

所以同样的输入 token,因为「前缀有没有命中」会落到完全不同的价位。这带来两个反直觉但必须算清的结论:

  1. 「输入便宜、输出贵」不一定成立。 传统说法是输出 token 更贵,但一个长前缀如果每次都未命中,输入端的累计成本可能反过来成为大头。
  2. 压缩上下文可能在「省钱」上适得其反。 为了省 token 而压缩/改写历史,一旦改动发生在前缀部分,缓存就从命中变未命中——token 数省了,单价却翻了数倍,总价可能不降反升。
cache_billing.pypython
def estimate_cost(input_tokens: int, output_tokens: int,
                  hit_ratio: float,          # 输入里缓存命中的比例 0~1
                  input_hit_price: float,    # 每 token 命中价($)
                  input_miss_price: float,   # 每 token 未命中价($)
                  output_price: float) -> float:
    hit = input_tokens * hit_ratio
    miss = input_tokens * (1 - hit_ratio)
    return (hit * input_hit_price
            + miss * input_miss_price
            + output_tokens * output_price)
# 例:同样 80k 输入 + 2k 输出,命中率 0% vs 90% 的差别
LOW  = estimate_cost(80_000, 2_000, 0.0, 0.50, 2.50, 10.0)
HIGH = estimate_cost(80_000, 2_000, 0.9, 0.50, 2.50, 10.0)
print(f"命中 0%:${LOW/1e6:.2f}/M-token 折算")    # 未命中为主,输入占大头
print(f"命中 90%:${HIGH/1e6:.2f}/M-token 折算")  # 命中为主,输入成本骤降

面试里能把「缓存命中率是一条成本线、不只是延迟线」讲清,比背显存公式更能体现你真正在生产里管过这块。判断标准一句话:改任何「省 token」的优化(压缩、去重、重排历史),都要先问一句——它会不会把前缀从「命中」打成「未命中」? 会的话,省的 token 可能远抵不上单价的上涨。

同样的序列长度,两种代价 横轴 = 序列长度 n 纵轴 = 相对成本(示意斜率,非对数刻度) 相对成本 ↑ 序列长度 n → n=32k KV Cache 显存 ∝ n(线性) 注意力算力 ∝ n²(平方) decode 每步只读取已有缓存 → 显存随长度线性;prefill 要算完 [n,n] → 算力随长度平方。两者是不同瓶颈,要分开优化。
图 2 把序列长度翻倍,KV Cache 显存大致翻倍,注意力算力却翻四倍。所以「长上下文很贵」有两个独立的物理来源,优化时不能混为一谈。
减少 K/V 头数 = 直接缩小缓存 假设 query 头 = 64、单层;柱高 ∝ KV Cache 大小(以 MQA 为 1×) MHA 每 query 头各持一组 K/V …×64 …×64 GQA 多 query 头共享一组 K/V ×64 8 组 K/V MQA 全部共享 ×64 1 组 K/V 64× 8× 1× MHA GQA MQA GQA 把 K/V 头数从 64 降到 8(缓存 ÷8);MQA 降到 1(再 ÷8)。注意力平方复杂度不变。
图 3 MHA 每个 query 头各存一组 K/V,缓存最大;GQA/MQA 让多个 query 头共享 K/V 头,缓存随之缩小。缩小的是 n_kv_heads 这个因子,不是注意力复杂度,也不是权重体积。

四、动手:把「前缀稳定性」变成可检查的东西

下面这套做法用一个前缀指纹(Prefix Fingerprint),把「相邻两轮是否共享同一前缀」从「肉眼比对」落成「可自动跑的检查」——上一节讲的那些前缀破坏点,用它能被断言出来,而不是靠人记得。脚本可以直接接进你自己的 Harness 里做日常检查。

prefix_guard.pypython
import json, hashlib
from typing import Any
def serialize_stable(obj: Any) -> str:
    """稳定序列化:键排序、分隔符固定、禁用 ascii 转义以保证同一对象字节一致"""
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def prefix_fingerprint(messages: list[dict], tools: list[dict]) -> str:
    """对 tools + 前 N-1 条消息取指纹,验证相邻两轮是否共享同一前缀"""
    stable_part = {
        "tools": tools,                 # 工具定义必须在 messages 之前,且顺序稳定
        "history": messages[:-1],       # 最后一条是新增内容,不参与指纹
    }
    return hashlib.sha256(serialize_stable(stable_part).encode()).hexdigest()[:16]
def append_only_messages(system: str, history: list[dict], user_input: str) -> list[dict]:
    """唯一推荐的组装方式:头部固定,只在尾部追加"""
    fixed_head = [{"role": "system", "content": system}]
    return fixed_head + history + [{"role": "user", "content": user_input}]
# --- 使用示例:连续两轮,观察指纹是否一致 ---
tools = [{"name": "get_weather"}, {"name": "get_time"}]   # 注意:顺序必须写死
h1 = [{"role": "user", "content": "北京天气"}]
fp1 = prefix_fingerprint(h1, tools)
h2 = h1 + [{"role": "assistant", "content": "正在查询"},
           {"role": "tool", "content": "晴 24℃"}]
fp2 = prefix_fingerprint(h2, tools)   # 注意这里的「-1」语义:最后一轮的新输入不入指纹
print(fp1, fp2)   # 这里的差异是因为 history 长度变了,实际检查时应比较「相同长度部分」
实操任务
  • 写一个函数,输入是两轮的完整请求体,输出第一个不同字符的偏移量;
  • 用它在你的 Harness 上跑一遍,看第 1→2 轮的偏移量是不是接近 0;
  • 把 system prompt 里所有动态变量(时间、用户名、记忆片段)挪到尾部,再测一次,记录偏移量的变化。

验收标准:能说出你的 Harness 里至少五个前缀破坏点,并指出各自的修法。

五、常见误区与追问

5.1 误区:命中率只由自己请求的写法决定

错在哪:以为只要自己两轮请求前缀一致,KV Cache 就一定能命中。为什么自然:前缀确实按字节比对,一致就该命中。正确做法:服务端还有 LRU 或分页(PagedAttention)淘汰策略,并发高、显存紧时你的长前缀可能已被换出。给个数字:70B 级模型在 32k 上下文下单条缓存约 10 GiB,32 路并发就是 300 GiB 量级。判据:高频复用的前缀要短而稳定,别指望超长前缀一直驻留;命中率不完全由你,要按最坏情况设计。

5.2 误区:GQA / MQA 缩小的是「模型」或「算力」

错在哪:把 KV Cache 显存、权重体积、注意力算力混为一谈。为什么自然:它们都叫「省资源」,直觉上是一回事。正确做法:GQA/MQA 只缩小 KV Cache 里的 K/V 头数,从 64 降到 8 甚至 1;它不改变注意力 O(n²) 复杂度,也不改变权重体积。判据:被问「GQA 解决了公式里哪个因子」时,答案必须是 n_kv_heads,而不是 n_layers、head_dim 或注意力复杂度。长上下文能跑起来,靠的是这一项的缩小。

5.3 误区:动态信息放 system prompt 开头无所谓

错在哪:以为模型能「理解」就不影响成本。为什么自然:从语言理解角度,加一句时间确实无伤大雅。正确做法:KV Cache 按前缀逐位比对,第一个 token 不同,其后全部失效,命中率直接掉到 0。判据:把第 1 轮与第 2 轮请求体打印出来,比对字节前缀,看第一个不同字符的偏移量;如果偏移量落在前 1% 内,说明缓存基本作废。纪律:时间、用户名、记忆等动态内容一律放尾部。

5.4 误区:KV Cache 显存和注意力算力都是「随长度增长」,一起优化即可

错在哪:把两种增长当成同一种瓶颈。为什么自然:都和序列长度正相关,容易合并思考。正确做法:前者线性(∝n),后者平方(∝n²),是两套独立瓶颈、两套独立手段。判据:算 seq_len 翻倍时两者各自变化倍率——显存约 ×2,算力约 ×4;优化时一个靠减 n_kv_heads / 量化 / 分页,另一个靠 FlashAttention / 分块 / 稀疏。把「长上下文优化」说成单一手段,是面试里最容易被追问的点。

5.5 误区:缓存越大越好,把整个历史都留着

错在哪:认为多留历史只会更准,没有代价。为什么自然:上下文越长,模型可利用的信息越多,看似有益。正确做法:KV Cache 显存 = 2·B·L·n_layers·n_kv_heads·head_dim·bytes,是硬预算;超了就 OOM 或被淘汰。判据:单条 32k、fp16 约 10 GiB,64 并发就 600 GiB 量级,还不算权重。结论:历史要外置(放文件 / 工具),上下文只留指针;「全留着」既不经济也可能因淘汰而失效。

5.6 误区:前缀缓存(Harness 侧)和 KV Cache 是一回事

错在哪:把两个层次的复用混为一谈。为什么自然:名字都带「缓存」,效果都是省重算。正确做法:前缀缓存是 Harness 在请求级复用相同前缀的计算结果(如 system + 工具定义),KV Cache 是推理引擎在层内缓存 K/V 张量;前者靠字节稳定命中、由应用侧保证,后者由引擎管理、还受淘汰影响。判据:前者你能完全控制(稳定序列化),后者只能间接影响(短前缀 + 高命中)。两者协同,但归属不同层。

六、自测

本章自测本章为 DeepSeek 类岗位高频考点
Q1为什么在 system prompt 里加一句「当前时间:14:03」会让成本显著上升?
C。多出的 token 量可以忽略,真正的问题是位置。前缀比对从第一个 token 开始,头部一处改动就让其后全部作废。通用的纪律是:动态内容一律放尾部,静态内容一律放头部。
Q2某模型 80 层、K/V 头数 8、head_dim 128、fp16 存储。单条 32k 上下文的 KV Cache 大约多大?
A。代入公式:2 × 1 × 32000 × 80 × 8 × 128 × 2 = 1.05×10¹⁰ 字节 ≈ 9.8 GiB。两个最容易漏的因子:最前面的 2(K 和 V 各一份,漏了就得到 5 GiB),以及 n_layers(每一层都要各存一份)。面试时能当场笔算出这个数,比说出「很大」有价值得多。
Q3GQA(分组查询注意力)主要解决什么问题?
D。GQA 不改变注意力的平方复杂度,也不改变权重体积,它压的是 K/V 的头数——也就是缓存大小。n_kv_heads 从 64 降到 8,缓存直接缩到 1/8。这解释了为什么长上下文模型几乎都用 GQA 或 MQA。

七、小结

结论依据你应该做的动作
历史不该被重算K/V 只依赖前缀,与后文无关确保前缀字节稳定,最大化命中
头部改动代价最高前缀逐位比对,一错全废动态内容(时间、记忆)一律放尾部
缓存占显存,随长度线性增长2 · B · L · n_layers · n_kv · d_head · bytes长上下文要为缓存留预算,不只是权重
命中率不完全由你决定服务端有淘汰与调度策略高频复用的前缀要短且稳定,别指望超长前缀一直驻留

前缀稳定性是那种「懂了就能立刻省一半钱、不懂也完全不知道自己在亏」的知识。面试里能主动提起它,说明你真的在为自己的系统付过账。本刊编辑部

下一章换一个角度:既然总参数可以很大而激活参数可以很小,成本结构还能再动一次手脚。

八、参考与延伸

本章的机制部分只讲到「够用」为止。想往下深挖,下面这几份材料按「先看图、再看代码、最后读论文」的顺序排好了。全站不做原文转载,这里只登记链接与「为什么值得读」。

先看图(建立直觉)

  • vLLM:用 PagedAttention 把 LLM 服务做到又快又省 —— 这篇博客把 KV Cache 碎片问题讲得最直白,还配了显存占用的对比图。读本章第三节「淘汰策略」卡住时,来这儿看 PagedAttention 怎么消灭碎片最快。
  • Lilian Weng · The Transformer Family v2.0 —— 把各种注意力变体(稀疏、线性、GQA/MQA)按「改了什么」整理成谱系。想理解「长上下文优化都有哪几条路」,读这一篇的目录结构就够了。

再看代码(动手实现)

最后读论文(对齐一手定义)

◇ 面试官会怎么问

共 7 条 · 其中 5 条高频 · 先自己答一遍,再展开对照

KV Cache 是什么?为什么多轮对话能省下来钱? 高频
自回归解码时,每生成一个 token 都要重新做一遍注意力计算,而每一层的 K(键)和 V(值)只依赖已经出现过的前缀。既然前缀没变,重算出来的 K/V 就是一样的——于是把它们缓存起来,下一步直接复用,只计算新 token 的那一份。
多轮对话之所以能省,是因为第二轮的 prompt 里,绝大部分是第一轮已经算过的内容。前缀完全一致时,这段直接命中缓存,不重复计算,在按 token 计价的 API 上直接体现为折扣。

追问链

  1. 那缓存命中率取决于什么?
  2. 显存占用怎么估?和 batch size 是什么关系?
缓存命中率取决于什么?—— 这道题几乎决定你懂不懂成本。 高频
取决于前缀稳定性。缓存的匹配方式是"从头开始逐 token 比对,直到第一个不同点",也就是说:前缀里任何一处变化,都会让从该点之后的所有缓存全部失效。
常见破坏点包括:system prompt 里塞了当前时间戳或随机 ID、把工具列表按使用频率每次重排、把检索结果按时间排序每次都变、few-shot 示例顺序随机、会话 ID 注入到 prompt 开头。这些写法看起来无害,实际每一轮都在让缓存重置。

追问链

  1. 那你系统里具体哪些地方在破坏缓存?你怎么改的?
  2. 改成稳定前缀之后,命中率大概提升了多少?
加分点:能当场列出一份"破坏点清单"并给出改造后的数字,是这道题的最强答法。
Agent 场景下,token 成本的大头在哪? 高频
不在单次输入,而在多轮累积的历史。一个十轮的 Agent 任务,第 n 轮要把前面 n−1 轮的全部消息重新送进去,输入 token 的总量大致是平方级累积的——这和注意力复杂度是两个独立但叠加的成本问题。
所以成本杠杆的优先级通常是:① 前缀缓存友好(提升命中率,直接对应 API 折扣);② 工具结果裁剪(只回关键字段,不要把整页 JSON 塞进去);③ 历史摘要化(保留决策与结论,丢掉过程);④ 状态外置(大块数据落盘,按需回读);⑤ 模型分级路由(简单环节用便宜档位)。
加分点:把成本按"输入 / 输出 / 命中缓存"三段分别报价再谈优化,能立刻显出你算过账。
长上下文和 RAG 该怎么选?
判据是召回精度需求与成本结构,不是"哪个更先进"。
适合长上下文:全集较小且必须整体把握(例如一次代码评审要把整个文件读完)、需要跨段落的推理与全局一致性。
适合 RAG:库很大、每次只需要极少相关片段、需要精确引用来源。
真实系统几乎都是混合:用检索把候选收窄到可控范围,再把候选完整放进上下文,这样既控制了 token 又保住了精度。反过来做——把整个库塞进长上下文——成本和延迟都不可接受,而且要面对"中间内容被忽略"的注意力衰减问题。
把同一个请求的序列长度从 8k 提到 32k,KV Cache 显存和注意力算力各变成原来的几倍?这两个倍率分别对应什么工程手段? 高频
显存约 ×4,算力约 ×16。
依据是两条完全不同的增长律:KV Cache 显存 ∝ n(公式 2 · batch · seq · n_layers · n_kv_heads · head_dim · bytes),而注意力算力 ∝ n²(中间那张 [n, n] 分数矩阵)。
对应的手段也完全不同:显存这条线靠 GQA / MQA 降 n_kv_heads、量化降 bytes、PagedAttention 降碎片;算力这条线靠 FlashAttention / 分块计算 把单次前向算便宜。
  • 判据一:显存是「装得下多少并发」的容量问题,算力是「要算多久」的时长问题。
  • 判据二:把「长上下文优化」说成一个笼统手段,几乎一定会在追问里露馅。

追问链

  1. 这两个倍率里,哪一个会先成为你在线上遇到的瓶颈?
  2. 如果显存够、算力也够,但并发就是上不去,你会先查什么?
加分点:加分点:先把两个瓶颈分开,再各自给出对准的手段——而不是笼统地说「要优化长上下文」。
你的 Agent 每轮都往 system prompt 里写「当前时间:…」,前缀缓存命中率几乎为 0。除了把时间挪到末尾,还有哪些更稳的改法?各自代价是什么? 高频
三条路,按推荐度排:
① 做成工具——把「取当前时间」变成 get_current_time,模型需要时才调,system prompt 一个字节都不动;代价是多一次工具往返。
② 降精度并接受偶发失效——只写到「天」甚至「周」,命中率大幅回升;代价是少数跨天边界的请求会拿到略过期的时间。
③ 挪到尾部——放进最后一条 user 消息,动态内容不再破坏前缀。
判据:任何时候都问一句「这段字节跨轮会变吗」——会变的,一律不许出现在前缀里。

追问链

  1. 你项目里还有哪些「跨轮会变」的字段被写在了头部?
  2. 你怎么量化这次改动带来的收益?
加分点:加分点:能从「时间戳」这一个例子抽象出「前缀里只放跨轮不变的字节」这条不变量,并举出自己系统里的多个破坏点。
应用侧的前缀缓存和推理引擎侧的 KV Cache 是一回事吗?这两者里,你能完全控制哪一个?
不是一回事,属于两个层次。
前缀缓存(应用侧)指你的请求前缀保持字节稳定,从而命中服务端已有的计算结果——你能完全控制,靠稳定序列化保证(键排序、分隔符固定、不做「美化」式重排)。
KV Cache(引擎侧)是推理框架在层内缓存的 K/V 张量,由引擎按 LRU 或分页管理,还受并发与显存压力影响——你只能间接影响,做法是把高频复用的前缀做短、做稳。
判据:能被你的 JSON 序列化规则左右的,是前者;会被别人抢占显存而失效的,是后者。两者协同,但归属不同层。

追问链

  1. 如果服务端把你的长前缀淘汰了,应用侧还能做什么?
  2. 「稳定序列化」具体要固定哪几件事?
加分点:加分点:分清「我完全控制」与「我只能间接影响」,并说出后者必须按最坏情况设计(短前缀 + 可降级)。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes
    下一章 MoE 与稀疏激活 总参数 671B 的模型为什么能比 70B 的稠密模型还便宜?答案在"激活参数量"和"总参数量"之间的那条缝里。这一章讲清 MoE 的收益、代价,以及面试里最容易答错的那道题。 I · 大模型原理 · 第 03 章 · 进阶 · 约 24 分钟