- 引言 — 别去找表,把公式背下来
- VRAM 被什么填满 — 三大块
- 权重计算 — 「4 位」不是 4 位
- 为什么 Q4_K_M 是 4.89 而不是 4.5
- KV 缓存 — 随上下文成比例增长的那一块
- 那么你的卡里到底能装什么
- 量化削掉多少质量 — 只谈实测值
- tok/s 数字不带硬件就扔掉
- 位数再压就更快吗 — 只到某条线为止
- 计算会变样的情况 — MoE 与混合注意力
- 那么,你要放弃的是什么
- 结语
- 参考资料
引言 — 别去找表,把公式背下来
「跑一个 8B 模型需要几 GB VRAM?」对这个问题,互联网上的标准答案是一张不知从哪儿复制来的表。那张表通常没有出处,不注明上下文长度,量化是哪一档也含糊不清,而最重要的是 — 对你的情况并不适用。模型、上下文、量化都是你自己挑的,而表格不过是别人某一次挑好的组合的快照。
所以先把答案给你。VRAM 大体上分两块,各有一个公式。
(1) 权重 = 参数量 × bpw / 8 [字节]
bpw = bits per weight(量化档位的「真实」位数)
(2) KV 缓存 = 2 × 层数 × KV头数 × head_dim × 字节/元素 × token 数
2 = K 和 V,两份
字节/元素 = f16 为 2,q8_0 为 1
(1) 是常数。在你选定模型和量化的那一刻就固定了。(2) 则 随上下文成正比增长。而大多数人在本地 LLM 上撞墙的原因不是 (1),是 (2)。先把结论说在前面 — 把 Llama-3.1-8B 量化到 Q4_K_M 后权重是 4.58 GiB,可要是把这个模型支持的 128K 上下文用 f16 KV 缓存全部用满,光缓存就正好是 16 GiB。是权重的 3.5 倍。
本文先立起这两个公式,然后 直接对照 llama.cpp 的源代码和官方发布的表格来验证。接着处理诚实的部分 — 削减位数会损失多少质量(只用实测值来谈),以及再往下压真的会更快吗(不会)。
相关地,在浏览器里跑时的约束,我在 llama.cpp WebGPU 后端一篇里单独写过(在那边瓶颈不是 VRAM 而是标签页内存);量化技术本身的版图 — GPTQ、AWQ、FP8、MXFP4 各是什么 — 则在 LLM 量化总整理一篇里。本文只专注 内存算术 这一件事。
VRAM 被什么填满 — 三大块
先看地形。进入 GPU 内存的东西有三样。
- 权重。 模型文件本身。与上下文无关,固定不变。
- KV 缓存。 为每个已处理的 token 存下注意力的 K 和 V。与 token 数成正比。
- 其余。 计算用的临时缓冲区(compute buffer)、CUDA 上下文,如果桌面显示也接在同一张卡上,还得加上画面输出的那一份。
第 3 项没法写成公式。它取决于后端、批大小、有没有开 Flash Attention。我不会在这里编数字 — llama.cpp 在加载模型时会 直接打印给你。去日志里读 KV self size 和 compute buffer size 这两行就行。本文集中在把 (1) 和 (2),也就是下限,算准。知道了下限,你才能判断第 3 项到底吃掉了多少。
权重计算 — 「4 位」不是 4 位
公式 (1) 看起来简单,但陷阱藏在 bpw 里。「4 位量化」的 bpw 不是 4。
原因在量化的工作方式里。要把权重变成 4 位整数,就得把「把这些整数还原成真实值的缩放系数(scale)」一起存下来。那个缩放系数不是 4 位。所以真实位数总是大于名义值。
大多少不需要估算。ggml 的块结构体里原原本本写着。看 ggml/src/ggml-common.h 里最简单的 Q8_0:
#define QK8_0 32
typedef struct {
ggml_half d; // delta
int8_t qs[QK8_0]; // quants
} block_q8_0;
读法是这样的:32 个权重(QK8_0)组成一个块,每个块里放 32 个 int8(32 字节)和 1 个 ggml_half 缩放系数(2 字节)。于是,
Q8_0 = (32 × 8 + 16) 位 / 32 个权重
= 272 / 32
= 8.5 bits per weight
名义上是 8 位,实际是 8.5 位。每 32 个权重带一个 fp16 缩放系数,正好多出 0.5 位。
现在看 k-quant。Q4_K 把 256 个权重(QK_K)打包成一个超级块。
#define QK_K 256
#define K_SCALE_SIZE 12
typedef struct {
ggml_half d; // 超级块缩放系数 2 字节
ggml_half dmin; // 最小值用的超级块缩放系数 2 字节
uint8_t scales[K_SCALE_SIZE]; // 量化到 6 位的缩放系数与最小值 12 字节
uint8_t qs[QK_K/2]; // 4 位量化值 128 字节
} block_q4_K;
合计 144 字节,装 256 个权重,所以 144 × 8 / 256 = 4.5 bpw。而且这不是我算出来的,是源代码的注释亲口说的 — block_q4_K 的正上方就写着 // Effectively 4.5 bits per weight。同一个文件里有五处。
ggml-common.h 注释直接给出的「实效」bpw
Q2_K 2.625 Q3_K 3.4375 Q4_K 4.5
Q5_K 5.5 Q6_K 6.5625
(Q8_0 没有注释,但从结构体直接得出 8.5)
验证公式是否正确
到这里还只是推导。得确认它对不对。llama.cpp 的官方文档(tools/quantize/README.md)发布了 Llama-3.1-8B 各量化档位的 bpw 和文件大小。把公式倒过来反推参数量 — 参数量 = 大小 × 8 / bpw — 每一行都应该得出同一个值。
quant 发布 bpw 发布大小(GiB) 反推的参数量
F16 16.0005 14.96 8.031 B
Q8_0 8.5008 7.95 8.033 B
Q6_K 6.5633 6.14 8.036 B
Q5_K_M 5.7036 5.33 8.027 B
Q4_K_M 4.8944 4.58 8.038 B
Q3_K_M 3.9960 3.74 8.040 B
Q2_K 3.1593 2.95 8.021 B
IQ1_S 2.0042 1.87 8.015 B
全部聚在 8.02~8.04B。而 Llama-3.1-8B 的真实参数量,以整数写在 Hugging Face 的 safetensors 元数据里 — 8,030,261,248 个。公式是对的。在四舍五入误差范围内,每一行都命中了真实值。
还有一个更好的检验。把从块结构体推导出的值,和 llama.cpp 实际测量并发布的值并排放在一起。
从结构体推导 发布的实测 差值
Q8_0 8.5000 8.5008 +0.0008
Q6_K 6.5625 6.5633 +0.0008
Q5_K_S 5.5000 5.5704 +0.0704
Q5_K_M 5.5000 5.7036 +0.2036
Q4_K_S 4.5000 4.6672 +0.1672
Q4_K_M 4.5000 4.8944 +0.3944
Q3_K_S 3.4375 3.6429 +0.2054
Q3_K_M 3.4375 3.9960 +0.5585
Q2_K 2.6250 3.1593 +0.5343
Q8_0 和 Q6_K 吻合到小数点后第三位(8.5000 对 8.5008)。纯靠块算术就预测出了官方数字。可其余的档位最多超出 0.56 位。这个超出部分就是下一节的主题。
为什么 Q4_K_M 是 4.89 而不是 4.5
_S(small)和 _M(medium)不是量化类型,而是混合配方。它并不是把所有张量都压成 Q4_K,而是把某些张量留在更高的精度上。
配方明确写在 src/llama-quant.cpp 里。对 Q4_K_M 来说,挂着这样几条规则。
- 注意力的
attn_v张量 — 在一部分层被提升到Q6_K(use_more_bits为真的那些层) - FFN 的
ffn_down张量 — 同样在一部分层被提升到Q6_K - 注意力 QKV 张量 — 提升到
Q5_K
也就是说,Q4_K_M 是「大部分 4.5 位,但对质量敏感的一部分张量是 6.5 位」。所以平均值从 4.5 升到 4.8944。源代码的注释把这套设计哲学讲得很清楚 — 把关于 70B 模型 attn_v 的那条评论搬过来:因为 8 个头共享 attn_v 权重,那个张量比 attn_q 小 8 倍,因此「几乎不增加模型大小,就能把量化精度提高不少」。
这里有一条对实务很重要的结论。并不是说 _M 总是比 _S 好。 _M 是用更多的位去换质量的交易,这笔交易划不划算,要看后面的质量数字才知道。而 Q4_K_M 为什么固化成了本地阵营的默认值,答案也在后面。
KV 缓存 — 随上下文成比例增长的那一块
现在是真正的问题。
把公式 (2) 再写一遍。
KV 缓存 = 2 × L × n_kv_heads × head_dim × bytes × n_tokens
这个公式对不对,同样可以拿 llama.cpp 的源代码来确认。src/llama-kv-cache.cpp 为每一层这样创建 K 和 V 张量。
const uint32_t n_embd_k_gqa = hparams.n_embd_k_gqa(il);
ggml_tensor * k = ggml_new_tensor_3d(ctx, type_k, n_embd_k_gqa, kv_size, n_stream);
ggml_tensor * v = ggml_new_tensor_3d(ctx, type_v, n_embd_v_gqa, kv_size, n_stream);
而 n_embd_k_gqa 的定义在 src/llama-hparams.cpp。
uint32_t llama_hparams::n_embd_k_gqa(uint32_t il) const {
const uint32_t n_head_kv = this->n_head_kv(il);
return n_embd_head_k(il) * n_head_kv;
}
也就是说,一个 head_dim × n_kv_heads 大小的向量,按 token(kv_size)、K 和 V 两份、逐层分配。正是公式 (2)。
这里的关键是 n_kv_heads。不是注意力头数,而是 KV 头数。如今的模型几乎全都用 GQA(Grouped-Query Attention)把 KV 头砍掉了一大截。Llama-3.1-8B 有 32 个注意力头,KV 头却只有 8 个。在这里填 32,答案就错 4 倍。互联网上的表格频繁出错的地方恰恰就是这里。
用 Llama-3.1-8B 算一遍
配置值在 config.json 里 — 32 层、32 个注意力头、8 个 KV 头、hidden 4096、最大上下文 131,072。head_dim 没有单独给出,所以是 hidden / n_heads = 4096 / 32 = 128。(顺带一提,Meta 的官方仓库需要访问审批,匿名查询是被挡住的。上面这些值和后文的参数量读自镜像仓库,出处在参考资料里注明了。)
每个 token = 2 × 32层 × 8个KV头 × 128 × 2字节(f16)
= 131,072 字节
= 128 KiB / token
128K 上下文(131,072 个 token)全部用满:
131,072 字节 × 131,072 个 token = 17,179,869,184 字节 = 正好 16 GiB
一个 token 就是 128 KiB。好背。而把 128K 填满就是 16 GiB,一分不差。
现在把它和刚才那个数字并排放。
Llama-3.1-8B, Q4_K_M
权重 4.58 GiB (固定)
KV @ 4K 0.5 GiB
KV @ 32K 4.0 GiB ← 在这里已经追平权重
KV @ 128K 16.0 GiB ← 权重的 3.5 倍
拦住你的不是模型,而是上下文。 成功把 8B 模型压到 4 位,完全不等于你就能用上 128K 上下文。
别的模型不一样 — 所以才需要公式
同样是「8B」,数值却不同。Qwen3-8B 的 config.json 是 36 层、8 个 KV 头、head_dim 128、最大上下文 40,960。
Qwen3-8B: 2 × 36 × 8 × 128 × 2 = 147,456 字节 = 144 KiB / token
把最大上下文 40,960 个 token 全部用满 → 5.625 GiB
按每 token 算它比 Llama 多吃 12.5%(因为有 36 层),但最大上下文是 40,960,总量反而小得多。「8B」这个标签对 KV 缓存什么都没告诉你。 层数、KV 头数、head_dim,以及你实际要开的上下文 — 是这四样决定的。
KV 缓存也可以量化
llama.cpp 提供把 KV 缓存本身以更低精度存储的选项。照 tools/server/README.md 原样搬过来,
-ctk, --cache-type-k TYPE— 允许值:f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1(默认f16)-ctv, --cache-type-v TYPE— 允许值相同,默认值相同
换成 q8_0,每个元素从 2 字节降到 1 字节,KV 缓存正好减半。Llama-3.1-8B 的 128K 缓存从 16 GiB 降到 8 GiB。允许值列表里有一点值得留意 — k-quant(像 q4_K 这样的)不在列表里。 KV 缓存只能用传统(legacy)量化。
不过这里是有质量代价的,而那个代价没有像权重量化那样被充分测量和公开。这个方向的最新研究在 INT4 KV 缓存量化一篇里单独写过。这里只把 内存正好减半 这一条算术敲定,然后继续。
那么你的卡里到底能装什么
现在开始组装。假设把 Llama-3.1-8B 以 Q4_K_M(4.58 GiB)装上去,剩下的空间全部给 KV,能开多大的上下文。
注意 — 下面是 扣除计算缓冲区和画面输出之前的理论上限。实际会比这里更少。那一份如前所述我不会编造,你得从自己的日志里读。
VRAM 扣除权重后剩余 f16 KV 能开的上下文(上限) 换 q8_0 KV
16 GiB 11.4 GiB 约 93,000 token 约 187,000
24 GiB 19.4 GiB 约 159,000 token 约 318,000
32 GiB 27.4 GiB 约 224,000 token 约 449,000
读法很重要。
- 16GB 卡:8B 模型装得绰绰有余(4.58 GiB)。但 128K 上下文在 f16 下上限是 93,000 token,打不开。 再扣掉计算缓冲区就更打不开了。打开
-ctk q8_0 -ctv q8_0,128K 才总算进入射程。 - 24GB 卡(RTX 3090/4090):f16 的 128K 在上限之内(159,000),但余量不大。
- 32GB 卡(RTX 5090):f16 128K 轻松放下。
一句话总结 — 「模型装不装得进卡」是个错误的问题。 正确的问题是「模型 + 我实际要用的上下文装不装得下」。
量化削掉多少质量 — 只谈实测值
从这里开始我不主张,只引用。
llama.cpp 在 tools/perplexity/README.md 里测量并公开了各量化档位的质量损失。这张表好就好在 把条件写明了。
出处: llama.cpp tools/perplexity/README.md — "LLaMA 3 8b Scoreboard"
Revision : f364eb6f
Backend : CUDA
CPU : AMD Epyc 7742
GPU : 1x NVIDIA RTX 4090
数据集 : Wikitext-2 测试集
先说 一个大警告。用 GitHub API 查那个 revision f364eb6f,提交日期是 2024 年 4 月 30 日。也就是说,这张表虽然挂在当前 master 的 README 里,数据本身却出自一个 2 年多前的构建。这期间 llama.cpp 的量化实现一直在变。所以这些数字 要用来读量化档位之间的相对排序,不要把绝对值原样搬到今天的构建上。 不过,这是该项目亲自测量并公开、条件写明的唯一公开数据。
指标看两个。PPL(perplexity)越低越好;KLD(KL 散度)衡量概率分布偏离 fp16 有多远,0 表示完全相同。README 自己就把表格按更信任 KLD 的方向排好了序。
quant 大小(GiB) PPL ΔPPL KLD 平均 Δp
f16 14.97 6.233160 — 0.000551 0.001 %
q8_0 7.96 6.234284 0.002650 0.001355 -0.019 %
q6_K 6.14 6.253382 0.021748 0.005452 -0.007 %
q5_K_M 5.33 6.288607 0.056974 0.010762 -0.114 %
q4_K_M 4.58 6.407115 0.175482 0.031273 -0.596 %
q3_K_M 3.74 6.888498 0.656864 0.101913 -1.990 %
q2_K 2.96 9.751568 3.519934 0.445132 -9.123 %
iq1_S 1.88 58.097760 51.866126 2.211278 -32.471 %
(基准为不用 imatrix。仅 iq1_S 使用 "WT 1m" imatrix)
这张表的读法。
- q8_0 实际上是免费的。 PPL 从 6.233 到 6.234。大小减半,KLD 只有 0.0014。几乎没有理由死守 fp16。
- q6_K、q5_K_M 也非常便宜。 平均 Δp(正确 token 的概率变化)分别是 -0.007%、-0.114%。
- q4_K_M 是拐点。 大小只有 fp16 的 31%,平均 Δp 是 -0.596%。这就是 Q4_K_M 成为本地阵营默认值的原因。
- 从 q3_K_M 开始就明显疼了。 PPL 从 6.41 跳到 6.89。
- q2_K 已经是另一个模型。 PPL 9.75,平均 Δp 是 -9.1%。
- iq1_S 直接崩塌。 PPL 58.1。原版是 6.23,也就是 9.3 倍。
README 自己附上的警告,原样搬过来
在使用这些数字之前,文档亲自加了几条限定。按原文的意思搬过来,
- perplexity 不能在模型之间直接比较。分词器不同时尤其如此。
- 微调即便让人评的输出质量上升,perplexity 通常也会上升。
- llama.cpp 的数值强烈依赖实现细节,不能与其他项目的数值直接比较。
第三条尤其重要。不要把这张表的 PPL 和 GPTQ 或 AWQ 论文里的 PPL 摆在一起比。 它们是不同实现各自的自测值,不是共同基准。
而第二条警告的含义很深 — perplexity 只是质量的代理指标,不是质量本身。「q4_K_M 的平均 Δp 是 -0.596%」这句话,并不意味着「你的代码生成任务会变差 0.596%」。那个没有人替你测过。得用你自己的任务来测。
同一个量化,在不同模型上疼法不同
这是这张表里最不为人知、也最重要的事实。看同一份文档里的 "LLaMA 2 vs. LLaMA 3 Quantization comparison" 表(同一 revision、同一块 4090),
Mean PPL ratio (1.0 = 无损失)
L2 7b L3 8b
q8_0 1.000689 1.000425
q6_K 1.002406 1.003490
q4_K_M 1.014242 1.028160 ← 1.4% vs 2.8%,两倍
q2_K 1.107955 1.564849 ← 10.8% vs 56.5%,五倍
Mean KLD
q2_K 0.108903 0.445132 ← 4.1 倍
同一个 q2_K,在 Llama-2 7B 上把 PPL 拉高 10.8%,在 Llama-3 8B 上却拉高 56.5%。q4_K_M 也是 1.4% 对 2.8%,正好两倍。
这件事的含义很大。「Q4_K_M 的质量损失微乎其微」这句话,不加模型限定就不成立。 用更多 token 训练得越狠的模型,权重里的余量越少,同样的位预算下丢得就越多。2026 年的模型比 Llama-3 训练得还要多。所以上表的数字对今天的最新模型来说,很可能是往乐观的方向偏的。 除了对你自己的模型重新测一遍,没有别的办法。
顺带一提,关于 imatrix(importance matrix),README 也留下了诚实的观察 — 用更多 Wikitext token 去构建 imatrix,并没有带来一致的改进。imatrix 本身是有帮助的(q4_K_M 的 KLD 从 0.0313 降到 0.0282),但「更多 token = 更好的 imatrix」在这份数据里得不到支持。
tok/s 数字不带硬件就扔掉
接下来是速度。而这里我最想说的其实是方法论。
一个 tok/s 数字要有意义,至少得同时带上四样东西 — 精确的硬件(GPU 型号)、模型、量化,以及上下文长度(或批长度)。四样缺了任何一样,那个数字就无法向别人传递任何信息。
让我证明这不是唠叨。llama.cpp 的 官方 README(tools/quantize/README.md)里挂着这样一张表。
Measure Q4_K_M Q8_0 F16
bits/weight 4.8944 8.5008 16.0005
size (GiB) 4.58 7.95 14.96
prompt processing t/s @ 512 821.81 ±21.44 865.09 ±8.30 923.49 ±0.53
text generation t/s @ 128 71.93 ±1.52 50.93 ±0.08 29.17 ±0.04
模型写明了(Llama-3.1-8B)。量化写明了。上下文长度也写明了(@ 512、@ 128)。连误差范围都附上了。可是 哪里都没有硬件。 翻遍整份文档,GPU、CPU、后端、构建 revision,一个都没有。最后一次修改这张表的 PR(#24133,2026 年 6 月 5 日合并)的正文里也没有。
所以「Q4_K_M 是 71.93 tok/s」这句话 单独拿出来毫无用处。是在 3090 上、5090 上,还是 M4 Max 上? 3090 和 5090 的内存带宽差 1.9 倍。想想同一个项目在 perplexity 的 README 里把 CPU、GPU、后端、revision 全都写明了,这更像是文档的一处失误。
但是 — 有趣的部分来了 — 这张表里仍然有能读出来的东西。绝对值读不了,但所有行都出自同一(未知的)硬件,所以 行与行之间的比率 是有效的。下一节就讲这个。
位数再压就更快吗 — 只到某条线为止
先说量化为什么能提速。以批大小 1 生成 token 时,模型 每生成一个 token 都要把全部权重从内存里读一遍。计算量不大,要读的东西却很多。所以解码被内存带宽卡住(memory-bandwidth-bound)。天花板公式由此而来。
tok/s 上限 = 内存带宽 / 模型大小 (dense 模型,批大小 1)
用 NVIDIA 的 RTX Blackwell 架构白皮书里的带宽规格来算一算。照白皮书正文 — RTX 5090 搭载 28 Gbps GDDR7,给出 1.792 TB/sec 的峰值内存带宽。同一份白皮书的对照表写着 3090 是 936 GB/sec、4090 是 1008 GB/sec。
Llama-3.1-8B Q4_K_M (4.58 GiB) 的理论天花板
RTX 3090 (936 GB/s) → 190 tok/s
RTX 4090 (1008 GB/s) → 205 tok/s
RTX 5090 (1792 GB/s) → 364 tok/s
这是 天花板。实测永远低于它。但这个公式告诉你,对你的卡来说「不可能比这更快」,还能让你判定某个基准数字在物理上说不说得通。要是有文章说在 4090 上把 8B Q4_K_M 跑到了 300 tok/s,那要么不是批大小 1 的解码,要么量的是别的东西。
可是公式在低端碎掉了
现在用上刚才说的「比率是有效的」。如果解码纯粹被带宽卡住,那么 大小 × tok/s 在所有量化档位上都应该得出 一个恒定的值(= 有效带宽)。拿 llama.cpp 官方表格里的数字直接乘一遍。(下面的乘法是我用他们发布的数值算的。)
quant 大小(GiB) 生成 tok/s 大小 × tok/s
F16 14.96 29.17 436
Q8_0 7.95 50.93 405
Q6_K 6.14 58.67 360
Q5_K_M 5.33 67.23 358
Q4_K_M 4.58 71.93 329
Q3_K_M 3.74 71.68 268
Q2_K 2.95 79.85 236
IQ2_XXS 2.23 79.86 178
IQ1_S 1.87 79.73 149
不恒定。从 436 一路跌到 149。 意味着位数压得越低,带宽的利用率就越差 — 要读的数据是变少了,可把它解回来(dequantize)的计算变多了,瓶颈从带宽挪到了计算上。
把结果换成比率看,清楚得扎眼。
F16 → Q4_K_M : 大小缩小 3.27 倍,速度加快 2.47 倍 (交易成立)
Q4_K_M → Q2_K : 大小缩小 1.55 倍,速度加快 1.11 倍 (交易很差)
Q4_K_M → IQ1_S : 大小缩小 2.45 倍,速度加快 1.11 倍 (交易最差)
从 Q4_K_M 降到 IQ1_S,模型缩小 2.45 倍,速度却只快 11%。 与此同时质量 — 按前一节的表 — PPL 从 6.41 到 58.1(9.1 倍),KLD 从 0.031 到 2.21(71 倍)。这是一笔换来 9 倍差的模型、收下 11% 速度的交易。
限定条件诚实地写上。这张速度表(Llama-3.1-8B,硬件不明)和前面的质量表(Llama-3 8B、RTX 4090、2024 年 4 月的 revision)是 互不相同的两次运行。模型有微妙差异,构建也不同。所以横跨两张表去读,不是严格的比较,是看方向。即便如此,两张表各自内部的排序都很稳固,而且指向同一个地方。
所以降到极低位数的唯一正当理由不是速度,而是「除此之外装不下」。 为了速度选 IQ1_S,这笔算术算不平。
计算会变样的情况 — MoE 与混合注意力
两个公式都带着假设。如今的模型正在打破那些假设。
MoE(Mixture of Experts)。 照搬 OpenAI 的 gpt-oss-20b 模型卡的原话,这个模型是「21B 参数、活跃参数 3.6B」。在这里两个公式分道扬镳。
- 内存需要全部。 事先不知道哪个专家会被选中,21B 得全部装进去。
- 带宽只读活跃的部分。 每个 token 只读 3.6B 那么多。
也就是说,前面的天花板公式 带宽 / 模型大小 里,分母从 整体大小换成了活跃大小。MoE「重而快」的原因就在这里。内存付 21B 的价钱,速度拿 3.6B 的回报。同一份模型卡写明,gpt-oss-20b 用 MXFP4 量化 MoE 权重,能在 16GB 内存以内运行。
混合注意力。 看 gpt-oss-20b 的 config.json,layer_types 交替排列着 sliding_attention 和 full_attention,sliding_window 是 128。滑动窗口层只需要持有最近 128 个 token 的 KV,所以 那些层的 KV 不随上下文增长。公式 (2) 的 n_tokens 变成了每层各不相同。
重点在于这不是例外。llama.cpp 的源代码已经把这一点考虑进去了 — 前面看到的 n_embd_k_gqa(il) 之所以接受层索引 il 作为参数,正是因为每一层的 KV 头数可以不同。没有任何表格能替代打开模型的 config.json。
那么,你要放弃的是什么
来整理。在本地 LLM 上你放弃的东西有三样。
第一,质量。 用实测值来说 — q8_0 实际上免费(KLD 0.0014),到 q5_K_M 都很便宜(KLD 0.011),q4_K_M 是拐点(KLD 0.031),从 q3_K_M 开始疼(KLD 0.102),q2_K 以下是另一个模型(KLD 0.445+)。但这个排序是 2024 年 4 月构建的 Llama-3 8B 基准,而且训练得越充分的模型,在同样的位数下丢得越多。
第二,上下文。 这是被低估得最厉害的一项。把 8B 模型装进 16GB 的卡很容易。给那个模型配 f16 的 128K 上下文是不可能的。大多数人卡在「模型明明装进去了,为什么长文档塞不进去」。
第三,速度的可预测性。 别人的 tok/s 不是你的 tok/s。没写明硬件的数字直接扔掉,写明了的数字也要用你的卡的带宽重新算一遍天花板。
决策规则
我不会用「视情况而定」收尾。请这样做。
- 量化从 Q4_K_M 开始。 它是质量/大小的拐点,再往下压速度也几乎不涨。卡上有余量时,升到 Q5_K_M 或 Q6_K,总是好过降到 Q4 以下再把省出的空间空着。
- 定下权重之后,把剩余 VRAM 当作 KV 预算,反推上下文。 别反着来。如果上下文是先定死的,那它决定量化。
- KV 装不下时,先开
-ctk q8_0 -ctv q8_0,再考虑进一步削权重。 缓存正好减半。这通常比把权重从 Q4 降到 Q3 更划算。 - 每次换模型都打开
config.json。 层数、KV 头数、head_dim。30 秒的事。 - 用你的卡、你的模型、你的上下文测一次。 而且测完了,就把硬件写明再分享。
什么时候不该用本地
诚实地说 — 很多情况下本地不是答案。
- 质量是第一优先时。 4 位的 8B 不是前沿模型。那道差距量化填不平。
- 长上下文是本质需求时。 KV 算术会赢你。如果 128K 是常态,本地单卡就是选错了工具。
- 有并发用户时。 上面的公式全部以批大小 1 为准。同时接多个请求,KV 缓存就按用户数成倍增加。
- 偶尔才用时。 一张卡的钱等于非常多的 API token。
反过来,本地赢的情况也很明确 — 数据不能离开设备时、必须离线时、调用量大且可预测从而能摊销固定成本时、以及延迟比网络往返更重要时。 而且如 用一张 RTX 5090 亲手盘小模型的记录所示,小模型就够用的任务比想象中多。
结语
本地 LLM 的 VRAM 问题,正确答案不是一个数字,而是两个公式。
权重 = 参数量 × bpw / 8
KV 缓存 = 2 × 层数 × KV头数 × head_dim × 字节 × token 数
第一个是固定的,第二个会长。而且如本文所确认的,第二个轻轻松松就越过了第一个 — Llama-3.1-8B Q4_K_M 的权重是 4.58 GiB,128K 上下文的 f16 KV 是 16 GiB。
这些公式可以信,不是因为我说可以。是因为从 ggml 块结构体推导出的 Q8_0 的 8.5 bpw 与 llama.cpp 发布的 8.5008 吻合,把公式倒过来反推的参数量 8.02~8.04B 与真实值 8,030,261,248 吻合。公式能复现厂商自己发布的数字,它就也能在你的情况下工作。 表格做不到这一点。
最后再补一条方法论。写这篇文章时我纠缠得最久的问题,是 llama.cpp 官方 README 的速度表里没有硬件。那张表写明了模型、量化、上下文长度,甚至连误差范围都给了,却没有 GPU 的名字。于是「71.93 tok/s」成了一个无法引用的数字。当你分享基准的时候 — GPU、模型、量化、上下文长度。四样都写上。缺了这四样的数字,谁都用不了。
参考资料
- llama.cpp — tools/quantize/README.md(Llama-3.1-8B 的 bpw、大小、速度表)
- llama.cpp — tools/perplexity/README.md(LLaMA 3 8b Scoreboard,PPL、KLD 实测值与条件)
- llama.cpp — ggml/src/ggml-common.h(块结构体与 "Effectively N bits per weight" 注释)
- llama.cpp — src/llama-quant.cpp(Q4_K_M 等混合配方规则)
- llama.cpp — src/llama-kv-cache.cpp(KV 张量分配)
- llama.cpp — src/llama-hparams.cpp(n_embd_k_gqa 的定义)
- llama.cpp — tools/server/README.md(--cache-type-k / --cache-type-v 允许值)
- llama.cpp PR #24133 — Update quantization readme(2026-06-05 合并)
- NVIDIA RTX Blackwell GPU 架构白皮书(3090/4090/5090 内存带宽)
- openai/gpt-oss-20b 模型卡(21B 总参数、3.6B 活跃、MXFP4)
- Qwen/Qwen3-8B config.json
- NousResearch/Meta-Llama-3.1-8B — Llama-3.1-8B 的 config.json 与参数量 8,030,261,248(Meta 官方仓库被门控,故使用镜像)
- The Llama 3 Herd of Models (arXiv 2407.21783)
- LLM 量化总整理 — GPTQ、AWQ、FP8、MXFP4(相关文章)
- 把 KV 缓存压到 4 位 — SAW-INT4(相关文章)
- llama.cpp 进了浏览器 — WebGPU 后端(相关文章)
- 用一张 RTX 5090 亲手盘小模型(相关文章)
현재 단락 (1/246)
「跑一个 8B 模型需要几 GB VRAM?」对这个问题,互联网上的标准答案是一张不知从哪儿复制来的表。那张表通常没有出处,不注明上下文长度,量化是哪一档也含糊不清,而最重要的是 — **对你的情况并...