4. CPU vs GPU负载分配

说实话,很多人刚用 llama.cpp 时,第一个困惑就是:到底把多少层扔给 GPU?全给?全给 CPU?还是中间找个数?

这背后其实是 macOS 统一内存架构的一个核心特性——CPU 和 GPU 共享同一块物理内存。听起来很美好对吧?但共享也意味着争抢。你如果一股脑把模型所有层都塞给 GPU,显存(或者说 Unified Memory 里给 GPU 划的那部分)会瞬间撑爆,然后系统开始疯狂 swap,推理速度反而比纯 CPU 还慢。

我最初在 M1 Max 上调一个 13B 模型时就踩过这个坑。当时想着 GPU 快,直接 --n-gpu-layers 40,结果一跑,延迟从 20ms/ 飙到 200ms/。看了活动监视器才发现,内存压力全红了,内核在不停换页。从那以后,我每次调参数都会先试个中位数,再上下微调。

理解 --n-gpu-layers 的工作原理

Transformer 模型的层是逐层堆叠的。你把前 N 层放到 GPU 上计算,剩下的层让 CPU 处理。这个参数本质上是做了一次**计算拆分**:GPU 负责前 L 层的矩阵运算,CPU 处理后续的 MLP 和 Attention。

为什么不是全给 GPU 更快?因为 GPU 虽然算力强,但它对内存带宽的消耗也大。当显存不够时,系统必须把 GPU 不需要的层换出到主存,这一来一回的拷贝成本,直接抵消了 GPU 的加速优势。

我自己习惯先把模型总层数查出来。比如 LLaMA 7B 有 32 层,13B 有 40 层。然后从一半开始试。

小提示:查看模型总层数的命令:./llama-cli -m model.gguf --perf 或者直接看 GGUf 元数据。也可以跑一次 --n-gpu-layers 999,如果报错说超出层数,就能知道最大值。

找到平衡点的实验方法

不要凭感觉猜。我建议用下面这套流程,简单有效:

  1. 先跑纯 CPU 基线:--n-gpu-layers 0,记录 tokens/second 和内存占用。
  2. 从 50% 层数起步:比如模型 40 层,先试 20。
  3. 逐步递增:每次加 5 层,记录延迟和内存峰值。
  4. 观察拐点:当延迟下降突然变慢甚至反弹,说明 GPU 已经过载。
  5. 往回退 5~10 层:那个点就是最佳平衡区。

我曾经用一个 13B 模型做过完整测试,结果很有意思。当 GPU 层数从 0 增加到 20 时,速度线性提升;到 25 层时达到峰值;再往上加到 30 层,速度反而掉了 15%。说明芯片的内存带宽已经饱和了。

GPU 层数 延迟 (ms/) 内存占用 (GB) 备注
0 (纯 CPU) 85 6.2 基线
10 52 7.8 明显加速
20 33 9.5 性能最佳
25 31 10.6 接近瓶颈
30 36 12.1 开始衰退
40 (全 GPU) 58 15.8 已过载

关键发现:对于 M 系列芯片,通常最佳 GPU 层数在总层数的 50% ~ 70% 之间。具体数值取决于模型大小和芯片内存带宽。M1 Ultra 或 M2 Max 可以压到 70%,M1 基础款建议控制在 50% 以内。

避坑指南

用这个参数时,有几个坑我一开始都掉进去过:

  • 不要用 --n-gpu-layers 999 当默认值。虽然 llama.cpp 会自动截断到最大层数,但全量 GPU 往往不是最优解,尤其大模型。
  • 小心内存压力指示。只看物理内存占用有时会骗人。macOS 的 '内存压力' 图表变成红色时,哪怕空闲内存还有,也说明交换已经在发生了。我习惯用 top -l 1 -s 0 | grep "PhysMem" 看 wired memory。
  • Q4_K_M 和 Q8_0 模型的最佳层数不同。量化级别越低,每层参数越小,GPU 可以多塞几层。换量化后要重新测。

曾经我犯过一个错:在 M2 上跑 Q4_K_M 的 34B 模型,直接设 --n-gpu-layers 60(总层 60),结果系统卡死。后来发现 --mlock 和 GPU 层数同时开启会导致内存锁定冲突。如果你碰到类似情况,先用 --no-mmap 试试。

一个实用的调参决策流程

下面这张图总结了整个决策逻辑,你可以照着它一步步走,基本不会偏。

GPU 层数调优决策流程 设置初始层数(0 或 50% 总层) 运行推理测试 评估延迟(ms/)和内存压力 观察是否出现 swap 延迟最优且无 swap? 确定最佳 --n-gpu-layers 调整层数 +/- 5 层 调参提醒 • 每次只改一个变量 • 记录每次结果 • 结合上下文长度调整 M系列经验值 • M1: 40%~50% 总层 • M2 Pro/Max: 50%~65% • M3 Max: 60%~75% • 7B/13B 通常 20~25 层最优

这个流程的核心是**迭代收敛**。你不需要一次找到最佳值,只要每次调整后有改进方向就行。我通常做 3~4 轮测试就能锁定最优层数。

给不同配置的推荐值

最后,我根据自己的项目经验,整理了一张快速参考表。你可以把它贴在终端旁边:

芯片型号 统一内存 推荐 GPU 层数(13B Q4_K_M) 推荐 GPU 层数(34B Q4_K_M)
M1 (8GB) 8 GB 8~12 不推荐运行
M1 Pro (16GB) 16 GB 16~20 12~16
M2 Max (32GB) 32 GB 22~26 20~24
M3 Max (48GB) 48 GB 28~32 26~32
M2 Ultra (96GB) 96 GB 35~40 34~40

注意,这个表格只是起点。如果你用的量化级别更高(比如 Q8_0),每层消耗的内存变大,推荐层数要适当减少。相反,如果你用 Q2_K,可以多塞几层。

我的习惯:拿到一个新模型,第一件事就是用 llama-cli --n-gpu-layers 0 --perf 测一次纯 CPU 速度。然后设置 --n-gpu-layers = 总层数 * 0.5 测第二次。接着往上加 5 层,直到出现性能拐点。整个过程十分钟不到,结果却非常可靠。

嗯,总结一下就是:别贪心,别盲目全给 GPU。找到那个“再多一层就完蛋”的临界点,然后往回退几层,那个点就是你当前芯片和模型的最优解。