本地模型的卡片现在都吹 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 8B | 32 × 8 | ~16 GiB | ~16 GiB | ~100% |
| Qwen2.5 7B | 28 × 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
观点上线前都经过负载测试。大多数时候。
下一篇观点,直达邮箱
每篇一封。同意也好,拆解也好。
这是什么?喜欢这些文章?我的本职工作就是这样的工程。 雇用我