返回博客

桌面上的 128k 上下文是个谎言。KV 缓存把它吃干了。

2026年9月22日

本地模型的卡片现在都吹 128k 上下文。在桌面上,这个数字是你的内存没看条款就签下的租约。决定你能推进多长文档的不是权重——是 KV 缓存,它随上下文增长的样子,像一笔用别人的货币结算的税。

上下文窗口不是能开关的功能。它是内存租约,服务器跑着的每一秒你都在付。

128k 上下文实际吃多少内存?

算法是公开的,而且很短。如 Transformer Inference Arithmetic 所述,KV 缓存为每层、每个 KV 头、每个 token 存两个张量——key 和 value:

cache_bytes = 2 × 层数 × 上下文 × kv头数 × head_dim × 每元素字节数

拿 Llama 3.1 8B 来算:32 层、8 个 KV 头、head_dim 128,直接来自 Hugging Face 上的 config.json。fp16(2 字节)下 131,072 个 token:

2 × 32 × 131072 × 8 × 128 × 2 = 17,179,869,184 字节 ≈ 16 GiB

同一个模型的 fp16 权重约 16 GiB。满上下文时,缓存和它所属的模型一样大。「16 GiB 装得下的 8B 模型」,在你把 num_ctx 拉到 128k 的那一刻,悄悄变成 32 GiB 的问题。

缓存为什么跟着窗口长?

因为注意力必须把每个 token 和它前面所有 token 比较,而推理是自回归的——什么都不重算,什么都记着。让生成变快的全部机关就在这:每个 token 的 key 和 value 只算一次然后存起来。每个 token 的存储是常数,所以总量随上下文线性增长。128k 不是「更大的数字」——它是 1k 窗口缓存的 128 倍,整个会话期间都占着。

Grouped-query attention(GQA)是模型侧的折扣:KV 头更少,缓存更薄。Qwen2.5 7B 用 28 层配 4 个 KV 头(config 在这里),同样的 128k 窗口 fp16 下只要约 7 GiB——实打实的减负,也是这些模型在小机器上显得友好的原因之一。

模型 (fp16)层数 × KV 头KV 缓存 @ 128k权重缓存对权重
Llama 3.1 8B32 × 8~16 GiB~16 GiB~100%
Qwen2.5 7B28 × 4~7 GiB~15 GiB~47%

自己算一遍——全部验证就在这里:

def kv_cache_gib(layers, kv_heads, head_dim, ctx, bytes_per=2):
    return 2 * layers * ctx * kv_heads * head_dim * bytes_per / 2**30

print(kv_cache_gib(32, 8, 128, 131_072))  # Llama 3.1 8B  -> 16.0
print(kv_cache_gib(28, 4, 128, 131_072))  # Qwen2.5 7B    -> 7.0

营销数字没说的部分

这是这套论点的诚实账本——包括它弯掉的地方:

  • prefill 是第二张账单。 第一个回答 token 出来之前,整个 prompt 要一口气处理完。100k token 的 prompt 意味着嚼完 100k token 才见到第一个字。在 3060 上,那是分钟级,不是毫秒级。
  • sliding-window 模型会打破这笔账——向着你这边。 Mistral 一类的架构把每层注意力限制在固定窗口内,缓存不再无限长。简单公式会高估它们。本文说的是全注意力 transformer——大多数人跑的就是这类。
  • KV 量化是真的,但只省一半。 llama.cpp 可以把 KV 放进 q8_0,缓存大约减半,代价是些质量风险。16 GiB 的一半也还是 8 GiB。
  • 规格页很少写 KV 头数。 你得去挖 config.json——这件事本身就说明了那个数字希望你怎么读。

这些都不说明长上下文没用。RAG 之所以存在,正是因为塞满窗口是「读一下第 4 节」最贵的方式。但一张只印「128k」不印内存账单的卡片,就是拿极速卖车却绝口不提油箱。

没有人读完过 128,000 个 token。你的内存照样全额买单。

药方很无聊:把上下文设成你的工作负载真正用到的量。32k 窗口只要 128k 缓存的四分之一,已经盖住独立开发者实际会发的几乎所有 prompt。这和本地 LLM 的内存算术是同一套账本纪律——模型大小只是预算的一半,而量化等级缩得了权重,动不了缓存的账。

如果缓存才是上下文的真实价格,为什么卡片只卖窗口、从不卖账单?还有忏悔环节:你真正填满到最后一个 token 的最大上下文是多少——输出配得上那几个 GB 吗?评论区开着;本系列的下一篇会站到输掉的那一边。

FAQ

128k 上下文窗口要用多少内存?

Llama 3.1 8B 的 fp16 版本,光是 KV 缓存在 131,072 token 时就要约 16 GiB——和模型权重一样大。公式:2 × 层数 × 上下文 × KV 头数 × head_dim × 字节数。

为什么长上下文更吃内存?

缓存里的每个 token 都要为每层注意力存 key 和 value 张量。上下文翻倍,缓存翻倍。就算你从不填满窗口,分配也照样存在。

本地 LLM 怎么减少 KV 缓存内存?

把上下文设成真正用到的量,选 grouped-query attention 的模型(KV 头更少),运行时支持的话把 KV 缓存量化到 q8,或者用 sliding-window 和混合架构。

— mrsaynothing

— mrsaynothing

观点上线前都经过负载测试。大多数时候。

下一篇观点,直达邮箱

每篇一封。同意也好,拆解也好。

self-hosted · 无第三方 · 一键退订

这是什么?

git stash:只收起一个文件,不惊动其余

喜欢这些文章?我的本职工作就是这样的工程。 雇用我