本地部署到底看哪些本机参数:同一台机器上,GPU 让 prefill 快 38 倍、让 decode 只快 2.5 倍

在 24GB M5 Pro 上实测 llama.cpp 的容量、带宽、算力、核数、上下文与并发六组参数,给出一张「参数 → 它管哪一段 → 一行换算公式」的表,和一个 15 分钟自测流程。

同一台机器、同一个 7B 模型、同一条 llama-bench 命令,只把 -ngl 0 换成 -ngl 99(把权重从 CPU 挪到 GPU):prefill 从 40.42 tok/s 涨到 1541.65 tok/s,快了 38 倍;decode 从 21.37 tok/s 涨到 53.33 tok/s,只快了 2.5 倍

这两个数字是我今天在自己这台 24GB 的 M5 Pro 上量出来的。它们同时成立,而且它们就是「本地部署该看哪些本机参数」这个问题的全部答案的入口。

因为它说明:没有哪个本机参数是笼统地「决定性能」的——每个参数只对推理的某一段有杠杆。 于是看规格表的正确顺序不是「哪个数字大」,而是先确定你卡在哪一段,再去看管那一段的那个数字。GPU 核心数对着 decode 使劲是白使劲,内存带宽对着 prefill 使劲也是白使劲。

本文的交付物是一张表(参数 → 它管哪一段 → 一行换算公式)和一个 15 分钟的自测流程。所有带小数点的数字都是我这次亲手跑出来的,测法在文中写明;标称参数和论文结论单独标注来源。

名词速查

术语一句话解释
prefill / decode推理的两个阶段:把你输入的整段提示一次性算完 vs 之后逐个 token 往外蹦
tok/s每秒处理多少个 token;prefill 和 decode 的 tok/s 不是一回事,不能混着比
-ngl Nllama.cpp 参数:把模型的前 N 层放到 GPU 上算,-ngl 0 是纯 CPU
量化 / Q4_K_M把每个权重从 16 位压到约 4 位存,模型文件变小、搬运变快,代价是精度损失
KV cache注意力对已生成 token 的中间结果缓存,省掉重复计算,代价是占内存(本站旧文算过这笔账
可达带宽实际能跑出来的内存读写速度,通常明显低于规格表上的标称带宽
Roofline / ridge point一张「算力天花板 + 带宽斜坡」的图;两条线的交点叫 ridge point,交点左边是带宽卡住、右边是算力卡住
统一内存 working setApple Silicon 上 CPU/GPU 共用一池内存,但系统只允许 GPU 用其中一部分,这个上限就是真正的显存

结论先行:四个预算,各管一段

本机参数它管哪一段一行换算我这台实测
GPU 可用内存(不是内存条容量)能不能跑起来权重 + KV + 激活 ≤ 预算18186 MiB / 24576 MiB(74%)
可达内存带宽decode 有多快tok/s ≈ 可达带宽 ÷ 模型字节数反推 261 GB/s(标称 307 的 85%)
算力(GPU 后端是否启用)prefill 与长上下文有多快与模型大小几乎无关,与上下文长度强相关prefill 38× / decode 2.5×
核数 / 线程数只在纯 CPU 路径上是杠杆,且会过冲有最优点,不是越多越好14 线程最优,18 线程掉 49%

三个常被误读的「不是闸门」的数字:GPU 核心数(对 decode 几乎不兑现)、CPU 核数(GPU 路径上只需要 6 个线程喂数据)、标称带宽(我这台只能吃到 72%~85%)。

下面逐个参数拆,每节先给测法再给数。

参数一:可用内存 ≠ 内存条容量,差了 26%

这是唯一一个「不满足就直接跑不起来」的硬闸,也是最容易看错的一个。

24GB 的机器,llama.cpp 加载时报出来的 Metal 设备预算是:

- MTL0 : Apple M5 Pro (18186 MiB, 18185 MiB free)
- CPU  : Apple M5 Pro (24576 MiB, 24576 MiB free)
ggml_metal_device_init: recommendedMaxWorkingSetSize = 19069.67 MB

18186 MiB / 24576 MiB = 74.0%。 剩下的 26% 归操作系统和其他进程,GPU 拿不到。所以「我有 24G 内存」这句话在选模型时的正确读法是「我有 17.8 GiB 显存」。

容量账要算三项,只有第一项写在模型文件名上:

权重      = GGUF 文件大小                    Q4_K_M 7B → 4.36 GiB
KV cache  = n_layer × (k_gqa + v_gqa) × 每元素字节 × 上下文长度
激活/图    = 几百 MiB 量级,随 batch 和 ubatch 变

这个模型的元数据是 n_layer = 28n_embd_k_gqa = n_embd_v_gqa = 512(都是 llama-bench -v 打印的)。代进去:

28 × (512 + 512) × 2 bytes = 57344 B = 56.0 KiB / token   (f16)
→ 32K 上下文 = 1.75 GiB
→ 128K 上下文 = 7.00 GiB

于是 4.36 + 7.00 = 11.36 GiB,小于 17.76 GiB 预算,满 128K 上下文放得下。这不是我推的——llama-fit-params(这个 build 里自带的自动配参工具)对这个模型直接吐出 -c 0 -ngl -1,意思是「全上下文、全层上 GPU,都装得下」。我的手算和它的结论一致。

值得注意的是 GQA 的杠杆有多大:如果这个模型不是 4 个 KV head 而是 28 个(标准 MHA),56 KiB/token 会变成 392 KiB/token,128K 上下文要 49 GiB——同一台机器直接判死。「能跑多长上下文」这个问题,模型的注意力结构比你的内存条更有话语权。

参数二:带宽只看可达值,我这台是标称的 72%~85%

M5 Pro 的标称内存带宽是 307 GB/s(Apple 官方 MacBook Pro 规格页,二档证据:检索核实,非我实测)。这个数字不能直接拿去估 decode 速度。我用两种独立方法量它的可达值。

方法一:写个多线程读带宽探针。 一个 4GB 缓冲区分给 N 个线程,每线程用 NEON 向量指令连续累加(保证走 DRAM 而不是缓存),跑 6 轮:

threads= 1  read= 47.7 GB/s
threads= 2  read=121.0 GB/s
threads= 4  read=186.2 GB/s
threads= 6  read=208.7 GB/s
threads= 8  read=216.7 GB/s
threads=18  read=222.4 GB/s

CPU 侧饱和在约 222 GB/s,是标称的 72.4%

方法二:从 decode 速度反推。 decode 每生成一个 token 至少要把全部权重读一遍,所以 模型字节数 × tok/s 是「实际达到的搬运速率」的下界。这条公式本站之前专门推过一遍,这里不重复推导,直接用它做实验——把同一个模型量化成三种大小,看反推值怎么走

模型大小decode tok/s反推带宽占标称
Q2_K2.80 GiB71.51215.0 GB/s70.0%
Q4_K_M4.36 GiB52.06243.7 GB/s79.4%
Q8_07.54 GiB32.25261.1 GB/s85.0%

(测法:llama-quantize --allow-requantize 从同一个 Q4_K_M 文件转出 Q2_K 和 Q8_0,llama-bench -p 512 -n 128 -ngl 99 -r 3必须说清楚:这三个文件只是「同架构、不同字节数」的尺寸探针,不是质量可比的模型——从 Q4 再压到 Q2、或再撑回 Q8,精度是坏的,我这里只借它们的体积做带宽实验。)

两个可以带走的结论:

  1. GPU 路径的可达带宽(261 GB/s)高于我的 CPU 探针(222 GB/s)。 所以「用 CPU 测出来的带宽」不能当作 GPU 推理的上限——它是另一条更矮的天花板。想知道你的机器能吃到多少带宽,最靠谱的探针就是拿一个尽量大的模型去跑 decode。
  2. 模型越大,带宽利用率越高(70% → 85%)。因为每 token 的固定开销(kernel 启动、采样、同步)被摊薄了。这解释了一个反直觉现象:量化带来的加速永远低于文件缩小的比例——Q8 到 Q2 文件小了 2.69 倍,速度只快了 2.22 倍。

参数三:算力只在 prefill 和长上下文上兑现

回到开头那两个数字。同一模型、同一命令,只切换 -ngl

后端prefill (pp512)decode (tg128)
-ngl 0(纯 CPU,6 线程)40.42 tok/s21.37 tok/s
-ngl 99(全上 Metal)1541.65 tok/s53.33 tok/s
提升38.1×2.5×

但真正判决性的证据是上一节那张量化表里我没提的一列——prefill 速度

模型大小prefill tok/s
Q2_K2.80 GiB1613.38
Q4_K_M4.36 GiB1430.28
Q8_07.54 GiB1464.82

模型字节数变了 2.69 倍,prefill 速度只在 12.8% 的范围里晃(而且不单调)。同一组数据里,decode 速度严格反比于字节数。

一个参数变了 2.69 倍,一段速度动了 12.8%,另一段动了 2.22 倍——这就是「两段推理被两个不同的物理量卡住」的直接证据,不需要引用任何论文。prefill 一次算 512 个 token,权重读一遍被 512 个 token 分摊,于是它是算力题;decode 一次算 1 个 token,权重读一遍只服务 1 个 token,于是它是搬运题。(本站讲投机解码那篇的标题就是这个意思:解码慢不是算力问题,是搬运问题。)

所以:如果你的场景是「长提示、短回答」(代码库问答、文档摘要、批量分类),算力是你的闸,GPU 后端和 GPU 核心数值得花钱;如果是「短提示、长回答」(对话、写作、Agent 逐步输出),带宽和模型体积是你的闸,加 GPU 核心几乎白花。

参数四:核数是有最优点的,不是越多越好

sysctl 报出的这台机器的 CPU 拓扑很有意思——两个性能层级叫 Super(6 核)和 Performance(12 核),共 18 核。纯 CPU 路径上扫线程数(-ngl 0 -n 64):

线程246101418
decode tok/s9.9117.1421.5329.9436.1418.32

14 线程是最优点(36.14 tok/s,反推 169.2 GB/s)。打满 18 线程反而掉 49.3%——没给运行时和系统留核,调度开销把收益全吃掉了。

这个反直觉结果有两个实用含义。一是「我的机器有 N 核,就设 N 个线程」是错的,留 2~4 个核给系统通常更快,具体最优点必须扫一次。二是注意 GPU 路径下 llama-bench 默认只用 6 线程就跑出了 53.33 tok/s——比 CPU 拿 14 线程拼出来的 36.14 还高。GPU 路径上 CPU 核数几乎不是杠杆,它只负责喂数据和采样。

隐形参数:上下文长度会同时啃掉两段速度

上下文长度不写在规格表上,但它是唯一一个「你自己每天在调、且同时影响所有闸」的参数。用 llama-bench -d N(先塞 N 个 token 再测):

已有上下文深度0204881921638432768
decode tok/s53.3950.3846.4641.7233.53
prefill tok/s (pp512)1422.78739.29263.90

从空上下文到 32K:decode 掉 37.2%,prefill 掉 81.5%。 注意这不是「变慢了一点」——32K 上下文下再追加 512 token 的提示,处理速度只有空上下文时的 1/5.4。

这解释了本地 Agent 最常见的体验断崖:会话前几轮飞快,跑到几万 token 后每一步都在等。你的「机器够不够快」这个判断,如果是在空上下文下做的,那它对真实工作负载没有参考价值。

顺手测了一个常被推荐的省内存手段——KV cache 量化。同深度(32K)、同开 flash attention 的受控对比:

KV 数据类型decode tok/sKV 内存
f1634.3856.0 KiB/token
q8_027.4529.8 KiB/token

省了 46.8% 的 KV 内存,付出 20.2% 的 decode 速度。 我的判断:KV 量化是容量杠杆,不是速度杠杆——只在你被容量闸卡住(想跑更长上下文或更多并发)时才划算,为了「更快」去开它是反向操作。这一点和「权重量化既省容量又提速」正好相反,别把两件事混成一条经验。

并发:把带宽闸换成算力闸的开关

上面所有 decode 数字都是单流。多流一起跑,权重读一遍能服务多个请求,带宽就被摊薄了。用 llama-batched-bench(256 token 提示 + 128 token 生成,并发数 B):

并发 B124816
decode 聚合 tok/s51.8670.55114.91131.74170.59
prefill 聚合 tok/s1379.041269.301339.041466.841449.11

聚合吞吐 B=1 → B=16 涨 3.29 倍,但单流速度从 51.86 掉到 10.66 tok/s(慢 4.86 倍)。prefill 聚合吞吐全程基本不动(已经是算力饱和的)。

这是本地部署最容易被浪费的一个参数。 单人对话时你的带宽利用率就是上面那 85% 的上限,加机器只能加带宽;但如果你的负载是「批量处理 200 个文件」,把它们并发喂进去,同一台机器的总吞吐能翻 3 倍以上——代价是每一条的首字延迟变长。灰度地说:交互场景优化单流延迟(选小模型、低量化),批处理场景优化聚合吞吐(提并发、别管单流),同一台机器上这两个目标是对立的。

论文怎么说:这套框架不是我编的,但有分歧

上面全是单机实测,学术侧的对应框架是 Roofline。

  • LLM Inference Unveiled: Survey and Roofline Model Insights(arXiv:2402.16363,ID 已回查核实)把 decode 明确刻画为 memory-bound,并用 Roofline 分析量化的三种效果分支。我这次的三点量化实验,就是它那套分析在一台笔记本上的最小复现。
  • RooflineBench(arXiv:2602.11506)是端侧专门做的 Roofline 框架,摘要里两个结论和我的实测方向一致:算术强度受序列长度显著影响;以及模型深度增加时算术强度出现回退。它还提出「硬件异质性导致的效率陷阱」——对应我这里 CPU 探针 222 GB/s 与 GPU 反推 261 GB/s 的分裂。
  • 分歧要说清楚Mind the Memory Gap(arXiv:2503.08311)通过 GPU profiling 反驳了「大 batch 就能进入算力饱和」的常见假设,测到大批量下计算/访存比仍在 0.5~1 ops/byte,依然是 memory-bound。这和我上面「并发把带宽闸换成算力闸」的说法不完全一致——我的数据只能说明并发让聚合吞吐涨了 3.29 倍、prefill 吞吐已经饱和,不能证明 decode 在 B=16 时已经变成算力受限。诚实的表述是:并发把 decode 从「严重带宽受限」推向「不那么受限」,至于有没有跨过 ridge point,我这次没测。

15 分钟自测流程:换任何一台机器都跑这五步

不用记规格表,按顺序跑一遍,你会拿到自己那台机器的四个预算数字。

# 1. 容量闸:加载日志里找 GPU 实际预算(不是内存条容量)
llama-bench -m <model.gguf> -p 1 -n 1 -ngl 99 2>&1 | grep -i "WorkingSetSize\|MTL0"

# 2. 基线:prefill / decode 各自的 tok/s
llama-bench -m <model.gguf> -p 512 -n 128 -ngl 99 -r 3

# 3. 算力闸:GPU 对两段各值多少倍(把 2 的结果和这条比)
llama-bench -m <model.gguf> -p 512 -n 128 -ngl 0  -r 2

# 4. 带宽可达值:模型字节数 × decode tok/s(用你手上最大的模型测最准)
#    再和厂商标称带宽比,得出你的利用率

# 5. 真实负载:在你实际会用到的上下文深度上重测一遍
llama-bench -m <model.gguf> -p 0 -n 64 -ngl 99 -d 0,8192,32768 -r 2

第 5 步是最容易被跳过、也最值钱的一步。空上下文下的跑分和你真实的使用体验之间,我这台差了 37%。

一张总表收尾

你的症状卡住的闸该调的参数别调的参数
加载就 OOM / 掉到 CPU容量换更小量化、降 -c、开 KV 量化线程数、并发
首字慢(长提示)算力开 GPU 后端、降上下文深度、复用前缀缓存换更小量化(几乎无效)
逐字输出慢带宽换更小的模型/量化、投机解码加 GPU 核心、加线程
批量任务总时长长并发未用满提并发数、提 -b/-ub单流延迟优化
会话越久越慢上下文深度截断/摘要历史、前缀缓存换机器

再压成一句话:规格表上的数字都是「上限」,本地部署的全部功夫是找出你当前撞在哪个上限上——而这件事只能测,不能读。

诚实的提醒

  • 标称 307 GB/s 是检索核实的官方规格,我没有独立验证过厂商数字本身;文中所有百分比是「我的实测值 ÷ 这个标称值」。如果标称值的定义与我的测法口径不同(比如含读写合计),这些百分比会整体偏移。
  • 三档量化文件是 requantize 出来的尺寸探针,不是质量可比的模型。 我没有测它们的 perplexity,因此本文对「量化对质量的影响」不做任何断言。
  • KV cache 128K = 7.00 GiB 是手算 + llama-fit-params 的结论一致,不是我从加载日志里直接读到的分配数。 我尝试用 llama-server -c 131072 抓分配日志,两次都在超时前没等到那行输出,没拿到就不写。
  • 并发那组数据只有单机单模型一个尺寸,B>16 我没测,也没测并发下的显存占用曲线。
  • 成本最低的验证实验:上面自测流程的第 2 步和第 3 步,两条命令、五分钟,就能在你自己机器上复现「38 倍 vs 2.5 倍」这个核心分裂。如果你那台机器上这两个倍数接近,说明你的 CPU/GPU 带宽差距很小(常见于统一内存的低配机型),本文关于「算力只在 prefill 兑现」的结论在你那里会更弱。

参考来源

测试环境(本文全部实测数据的来源)

  • 机器:Apple M5 Pro,18 核 CPU(sysctlSuper 6 核 + Performance 12 核)/ 20 核 GPU,24 GB 统一内存,macOS 26.6.2
  • 软件:llama.cpp build 8ed274ef4 (9630),Metal 后端,ggml 0.15.1
  • 模型:Qwen2.5-7B-Instruct GGUF,基线 Q4_K_M(4.36 GiB / 7.62 B 参数,n_layer=28n_embd_k_gqa=512
  • 工具:llama-benchllama-batched-benchllama-quantizellama-fit-params,以及一个自写的多线程 NEON 读带宽探针

厂商规格

arXiv 论文(ID 均已回查核实)

  • arXiv:2402.16363 — LLM Inference Unveiled: Survey and Roofline Model Insights
  • arXiv:2602.11506 — RooflineBench: A Benchmarking Framework for On-Device LLMs via Roofline Analysis
  • arXiv:2503.08311 — Mind the Memory Gap: Unveiling GPU Bottlenecks in Large-Batch LLM Inference
  • arXiv:2607.13068 — The Economics of AI Decoding Chips: Rebalancing Compute, Capacity, and Bandwidth for Efficient LLM Inference

本站相关