桌机上的 128k 上下文是一纸租约 —— KV cache 才是账单,实时计费
每层每 token 两个张量:16 GiB 把一个 8B 的满窗撑起来。选个模型,拧一下上下文,看租客账本把公式实时算给你看。
■ space · 机器车间 · fig 1
Llama 3.1 8B at 131,072 tokens in fp16: KV cache ≈ 16 GiB —— 和权重一样大。
那个「装得进 16 GiB 的 8B」变成了 32 GiB 的问题。
cache 是 随上下文线性增长、开工前就全额划走的 ——不管你填不填满,RAM 都要为 128k 买单。
解法很无聊: 把上下文设成你真正用到的量 —— 32k 的窗口只要 128k cache 的四分之一。
01计量台
租客是 kv cache —— 轮子转出那张显卡从不打印的账单
CACHE = 权重满窗时 cache 和它所属的模型一样大 —— 一个 32 GiB 的租约就此生效
租客 LLAMA 3.1 8B · 窗口 128k · 131,072 TOKENS · CACHE 16.00 GiB · 权重 16.0 GiB · 份额 100% · 已喂 0
轮子在里程表动之前就把账记了 —— 划款在前,填满是可选项
02被卡片们省略的公式
一条公开公式转动了这些轮子上的每一个数字
cache_bytes = 2 × layers × context × kv_heads × head_dim × bytes_per_element
「上下文窗口不是一个可以拨动的开关功能。它是一纸内存租约,服务器每活着一秒,你就付一秒的账。」
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
verify: kv_cache_gib(32, 8, 128, 131_072) → 16.0 · kv_cache_gib(28, 4, 128, 131_072) → 7.0
03两个租客,一张台面
公式只是算术,直到它并排给两张真实的模型卡片标出价格
| 模型 (fp16) | 层数 × KV 头 | KV cache @ 128k | 权重 | cache 对权重 |
|---|---|---|---|---|
| Llama 3.1 8B | 32 × 8 | ~16 GiB | ~16 GiB | ~100% |
| Qwen2.5 7B | 28 × 4 | ~7 GiB | ~15 GiB | ~47% |
按台面上的 1 键 —— 同一个 128k 窗口在 qwen2.5 7b 上重新计量成 7.0 GiB
config.json 里挖,这也说明了这个数字本来该怎么读04账单在哪里弯折
计量器对 full attention 很诚实 —— 在你签字之前有四个弯折点
没人真的读满 128,000 个 token。但每个 token 的 RAM 你照付不误。
config.json 上跑出来的(层数、KV 头、head_dim),如 transformer inference arithmetic 所述Transformer Inference Arithmetic —— 台面上的计量器就是同一条公式的 JavaScript 版;一个 bench 都不用买,离你自己的终端只有一个 print 之遥。
租客账本到此为止 · 零 fetch · 全程本地