上下文窗口与 KV 缓存
上下文窗口有价签,以显存支付。上下文里的每个词元都迫使模型在每层记住两个小向量,而那块内存——KV 缓存——随每个词元增长,悄悄吃掉你以为拥有的余量。它解释了本地 LLM 最奇怪的一批失败:第四十轮的 OOM、随着聊天变长似乎变慢的模型、以及只在宣传册里能跑 “128k 上下文” 的 8 GB 卡。
缓存存储什么
注意力需要每个过去词元的键和值投影才能关注它。它们在预填充期间计算一次,并在请求的生命周期内保留:每层、每词元、各一个 K 向量和一个 V 向量,每个含 kv_heads × head_dim 个数。那块存储就是 KV 缓存,也是上下文对每个被服务的请求永远线性花费内存的原因。
公式
bytes per token = 2 (K and V) × layers × kv_heads × head_dim × 2 bytes (fp16)
算例,llama-3-8B 级 GQA(32 层、8 个 KV 头、head_dim 128):
2 × 32 × 8 × 128 × 2 = 131,072 字节 = 每词元 128 KB。
32k 上下文时是 32,768 × 128 KB ≈ 4 GB 的 KV 缓存——与旁边 Q4 权重相当。4 个 KV 头的更精简配置落在每 32k 约 2 GB。8B 级模型 fp16 KV 的数量级:按架构每 32k 预算 0.5–4 GB。检查点的精确数字直接来自其 config.json——num_key_value_heads、num_hidden_layers 和头尺寸。
GQA 是长上下文变便宜的原因
分组查询注意力让多个查询头共享一小撮 KV 头——比如 32 个查询头由 8 个 KV 头服务——以可忽略的质量代价把缓存相对完整多头注意力缩小 4 倍。没有 GQA,下面的表会大 4–8 倍,24 GB 卡上的长上下文就是幻想。它是对本地长上下文工作最大的单项架构馈赠,也是现代 7–9B 模型根本能提供 32k–128k 窗口的原因。
它长得多快
8B 级 GQA 模型、fp16 KV、每词元 128 KB:
| 上下文 | KV 缓存大小 | 在 8 GB 卡上 |
|---|---|---|
| 4k | ~0.5 GB | 不可见 |
| 16k | ~2.1 GB | 在 5 GB 权重旁可感 |
| 64k | ~8.4 GB | 装不下,句号 |
| 128k | ~16.8 GB | 模型本身的 3 倍 |
内存随词元线性增长,但注意力计算随上下文平方增长——所以长上下文同时花费显存和每词元时间。
KV 爆炸时的杠杆
- 量化缓存。 q8_0 减半、q4_0 取四分之一,质量代价温和——常常是整个栈里最好的容量交易。llama.cpp 中 KV 量化需要 flash attention,见下。
- 服务更短的上下文。 如果你的提示词 2k,
-c 8192一分钱不花。按宣传的 128k“以防万一”分配,是在保留你永远用不上的显存。 - 滑动窗口模型。 一些架构不管名义上下文、按层封顶 KV 窗口——没有线性账单的长上下文行为,代价是对旧材料的一些召回损失。
- Flash attention。 根本上是计算和内核效率的胜利——精确注意力而没有二次激活爆炸——而且在 llama.cpp 里它也是解锁 KV 量化的门。
llama-server -m model-8b-q4_k_m.gguf -c 65536 -fa --cache-type-k q8_0 --cache-type-v q8_0
宣传上下文 vs 有效上下文
宣传 128k 的模型会运行在 128k——缓存装得下、数学成立。它是否推理在 128k 是另一个主张。拉伸的 RoPE 和长上下文微调让针检召回分数保持高位,而更广泛的理解常常远在极限之前就垮。把宣传窗口当作物理最大值,假设质量在后半段变软,并在把工作流押上去之前寻找该检查点的社区长上下文评估。
迷失在中间,与卫生习惯
位置偏差是真的:模型对提示词的开头和结尾关注最强、在中段下垂。把指令和关键事实放开头或结尾;绝不把模型必须遵守的规格埋进粘贴的文档里。保持上下文小而锐利的习惯: