本地部署 llama.cpp,3.9G 盒子只能开 5.8 万
结论先行:按官方 12.8 万买盒子的人,实测只有 5.8 万,差 818 MiB 就是开不了
这句判断来自一次实测量出来的数:688 MiB 换成字节是 721,554,688,除以 12,339.5,得 58,474 个 token,也就是 5.8 万。
环境、命令、量出来的数都在下面,可以直接照着重跑。
697 MB 的模型文件,官方模型卡写着最大上下文 128,000。一台 3.9 GB 内存、没有 swap 的 ARM 盒子,能不能开到官方那个数?
我把这句「能不能」拆成了能量的东西:固定权重、固定线程,只改 -c,量六档上下文下的进程常驻内存,取斜率。斜率就是每 token 的 KV 字节数。
结果是 12,339.5 字节。按 config 逐层算出来的理论值是 12,288,两者相差 0.4%。所以官方那个 12.8 万不是营销数字,它确实是从这个模型的结构推出来的。
但 12.8 万 token 的 KV 要 2.90 GB。那台盒子可用内存 2,121 MB。差 847 MB。
847 MB 在别的地方是「再挤挤」,在这台机器上是开不了 —— 它连 swap 都没有,超了就是进程被杀。
下面这条链子是我实际跑的顺序:先认结构、再量斜率、最后把数落回那台盒子。
① 先认结构:16 层里只有 6 层真的存 KV
我干的第一件事不是下模型,是读 config。因为「12.8 万上下文」这个数字能不能成立,取决于模型有没有偷偷把注意力层砍掉。
LiquidAI/LFM2.5-1.2B-Instruct 的 config.json 是这么写的:
| 字段 | 值 |
|---|---|
num_hidden_layers | 16 |
layer_types | 6 层 full_attention + 10 层 conv |
| 全注意力层位置(0 起) | 2, 5, 8, 10, 12, 14 |
hidden_size / num_attention_heads | 2048 / 32 |
num_key_value_heads | 8 |
head_dim | 64 |
max_position_embeddings | 128,000 |
16 层里只有 6 层存 KV,剩下 10 层是卷积层,压根没有 KV cache。这一条决定了后面所有的数。
按这个结构手算每 token 的 KV:
2(K 和 V)× 8(KV 头)× 64(head_dim)× 2(FP16 两字节)× 6(只有 6 层)= 12,288 字节。
一个 token 12 KB。12.8 万 token 就是 1.47 GiB。这个数比很多人脑子里「128K 上下文 = 几十 GB」的印象小得多,原因就在那 10 层卷积上。
但手算只是纸面。我要的是量出来的。
② 量斜率:只改 -c,看进程内存涨多少
方法很土,但结论很硬:固定模型、固定线程数、固定 prompt 长度,只改 -c,量进程的最大常驻内存(Maximum resident set size)。llama.cpp 启动时就把 KV 一次性预分配好,所以 RSS 会随 -c 单调上升,斜率就是每 token 的 KV 字节数。
# 绑 8 个核,跑完立刻看 RSS 峰值
export GGUF=lfm2.5-1.2b-q4_k_m.gguf
for C in 4096 8192 16384 32768 65536 131072; do
printf "ctx=%s " "$C"
/usr/bin/time -v taskset -c 0-7 llama-cli \
-m "$GGUF" -c "$C" -n 2 -p 8 -st --no-warmup \
2>&1 | grep "Maximum resident set size"
done
六档跑下来:
| ctx | RSS(KB) | 比上一档多(MiB) |
|---|---|---|
| 4,096 | 1,509,012 | — |
| 8,192 | 1,558,316 | 48.1 |
| 16,384 | 1,657,576 | 145.1 |
| 32,768 | 1,855,024 | 337.9 |
| 65,536 | 2,248,784 | 722.4 |
| 131,072 | 3,039,116 | 1,494.2 |
端点相减:从 4,096 到 131,072 一共涨了 1,566,826,496 字节,除以 126,976 个 token,12,339.5 字节/token。
手算 12,288,实测 12,339.5,比值 1.004。差的那 0.4% 是块对齐和分配器碎片。这说明 config 里那 6 层不是写给人看的,它真的决定了内存。
接着我又用 Q8_0 跑了一遍同一组 -c。结果是 12,338.8 字节/token,跟 Q4 差 −0.01%。
这一条值得单独说:你把权重从 4bit 换成 8bit,文件大了 70.5%,KV 一个字节没动。 因为 KV 是以固定 FP16 存的,跟权重量化档位完全无关。想省 KV,只有两条路 —— 改层结构,或者单独量化 KV cache。换权重量化档是最没用的一招,很多人却指望它。
③ 落回那个 3.9 GB 的盒子
数出来了,现在把它压到一台真实的机器上。
这台是端侧的 OneThingCloud OEC,4 核 ARM,总内存 3,900 MB,零 swap。free 读出来:available 2,121 MB,free 只剩 134 MB,buff/cache 占着 2,265 MB。
先把纯权重的开销量准:-c 512(KV 几乎为零)时,Q4_K_M 文件 697.0 MiB,进程 RSS 1,432.6 MiB。
RSS 是文件的两倍多。这里必须说清楚 —— 不是「模型要两倍内存」,是 llama.cpp 把 mmap 的权重页和一份工作副本都算进了 RSS。所以下面用的是实测 RSS,不是「文件大小 × 2」这种拍脑袋公式。
拿 RSS 上限 = available 来推:
| 档位 | 文件 | 实测 RSS | 剩余 | 能开的上下文 |
|---|---|---|---|---|
| Q4_K_M | 697.0 MiB | 1,432.6 MiB | 688 MiB | 约 5.8 万 token |
| Q8_0 | 1,188.5 MiB | 2,415.7 MiB | −295 MiB | 装不下 |
也就是说:官方标 12.8 万,这台盒子只能开到 5.8 万,不到一半。
想开满 12.8 万,KV 要 1.47 GiB,加上 1.40 GiB 的权重,得 2.87 GiB 可用内存。这台机器 available 只有 2.07 GiB。差 818 MiB。
818 MiB 在台式机上是「再插一条内存」,在这台 3.9 GB 无 swap 的盒子上是「进程被杀」。它没有 swap,超了不会变慢,会直接 OOM。
再补一个更紧的口径:如果只给它 1,500 MB 可用(buff/cache 被别人抢走一部分),Q4 只剩 67 MiB,上下文掉到约 5,700 个 token。
所以这台机器的真实区间是 5 千 7 到 5.8 万,取决于你给不给它清缓存。官方那个 12.8 万,在这台机器上不存在。
换成钱和行不行:这台盒子二手两百多块,加 4 GB 内存的钱不如直接买一台 8 GB 的。8 GB 那档能开到 5.8 万以上还有余量,而 3.9 GB 这档连官方标称的一半都到不了。
④ 反过来说:省下来的到底是哪一部分
上面一直在算「不够」,但结构上它其实是省了的。做个反事实:如果这 16 层全是全注意力层,每 token 就要 32,768 字节,12.8 万 token 是 3.91 GiB。
实测结构只要 1.47 GiB,省了 62.3%,约等于 10/16 —— 那 10 层卷积一点 KV 都不占。
这个 62.5% 不是优化出来的,是模型设计出来的。你选一个混合卷积的模型,等于白得 62.5% 的上下文空间;选一个全注意力的同参数量模型,同样的内存只能开四成。
这也是我建议端侧优先看 layer_types 字段的原因。参数量、文件大小都能在模型卡上看到,但「有几层真的存 KV」只有 config 里有,而它决定你能开多长的上下文。
⑤ 三个我没做成的地方
写清楚,免得你照着我的结论去踩。
一、另一个 0.8B 的模型今天完全没跑成。 qvac/BotPsy-0.8B 我在 modelscope 拿,返回 {"Code":10990101007,"Message":"获取模型文件失败,文件内容为空"};换 hf-mirror,拉到 13,272,062 字节就断了,而它应该 529,296,960 字节。所以它的 12,288 字节/token 是我按 config 算的,不是量的,本文没有引用它任何 tok/s。
二、这台盒子到今天为止还是零 tok/s,连续第四天。 原因不在内存,在 glibc。我把 DGX 上能跑的官方 arm64 预编译包经内网分发到盒子上,直接报:
llama-b12128/llama-bench: /lib/aarch64-linux-gnu/libc.so.6:
version `GLIBC_2.38' not found
ldd (Debian GLIBC 2.36-9+deb12u13) 2.36
包 18,753,070 字节,架构是 aarch64,文件完整,差 0.02 个版本就是跑不起来。这条我上次写过一次,今天第二次独立确认。分发路径不是瓶颈,glibc 才是墙。
三、x86 对照今天没做。 我只跑了两台 aarch64,手里那台 80 核 x86 和 4 核 SSE4.2 的老机器都没参与。所以「跨架构同模型差多少」这一半仍然是空的。
还有一组数据我判废了:20 线程那台机器在测试期间跑着生产容器,loadavg 从 1.04 涨到 4.08,16 线程的 tg 反而比 8 线程高,20 线程又掉回去 —— 物理上不可能,整组不作为结论引用。不绑核的 tok/s 在这台机器上一律不可信,这是我上次踩过、今天又验了一遍的坑。
把这几条并起来看,结论只有一句 —— 按官方 12.8 万买盒子的人,实测只有 5.8 万,差 818 MiB 就是开不了。
参考来源:
LiquidAI/LFM2.5-1.2B-Instruct与-GGUF两仓库逐文件字节数(2026-11-15 00:1x 用 hf-mirror API 求和,共 24 + 12 个文件)- LFM2.5-1.2B 官方
config.json原值(layer_types/num_key_value_heads/max_position_embeddings) - 本机实测:DGX Spark #1(aarch64 20 核 / 122G,llama.cpp b12128 原生 arm64,
taskset绑核)与端侧主机(aarch64 4 核 / 3,900 MB / 零 swap) - 数据落盘:
site_matrix/runs/J_20261115_D3/d3_ctx_kv_20261115.json、d3_summary_20261115.json、d3_clean_20261115.json
验证等级:L1 | KV 斜率为今日实机测量(六档 -c,每档取 Maximum resident set size 峰值,端点相减后除以 token 增量),Q4_K_M 与 Q8_0 两档独立复跑;权重加载 RSS 为 -c 512 实测值。config 结构为官方原值逐字段读取,非转述。端侧那台机器的 tok/s 为零 —— 本体从未跑起来,全文不含它任何速度数字;12.8 万上下文所需内存为按实测斜率外推,不是在该机器上真开到 12.8 万。
数据说明:1 MiB = 1,048,576 字节,1 GiB = 1,024 MiB,文中不把 GB 与 GiB 混用。KV 字节数由进程 RSS 斜率反推,未读 llama.cpp 自报的 KV self size,两者本应交叉验证但今天没做。20 线程那台机器的 tg 数据因生产容器污染(loadavg 1.04→4.08)已整组判废,未引用。
声明:本文为个人实机测试记录,机器与模型均自购自装,不含任何厂商合作。测试环境与你的环境可能不同,数字仅供参考。
本文所有数字均为本机实测,非引用。环境与脚本版本见文末参考来源。 作者:丘山录 · 记录本地部署与算力实测