4、CPU vs GPU负载分配:如何通过--n-gpu-layers参数控制层数,找到最佳平衡点
说实话,很多人刚用 llama.cpp 时,第一个困惑就是:到底把多少层扔给 GPU?全给?全给 CPU?还是中间找个数?
这背后其实是 macOS 统一内存架构的一个核心特性——CPU 和 GPU 共享同一块物理内存。听起来很美好对吧?但共享也意味着争抢。你如果一股脑把模型所有层都塞给 GPU,显存(或者说 Unified Memory 里给 GPU 划的那部分)会瞬间撑爆,然后系统开始疯狂 swap,推理速度反而比纯 CPU 还慢。
我最初在 M1 Max 上调一个 13B 模型时就踩过这个坑。当时想着 GPU 快,直接 --n-gpu-layers 40,结果一跑,延迟从 20ms/token 飙到 200ms/token。看了活动监视器才发现,内存压力全红了,内核在不停换页。从那以后,我每次调参数都会先试个中位数,再上下微调。
理解 --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,如果报错说超出层数,就能知道最大值。
找到平衡点的实验方法
不要凭感觉猜。我建议用下面这套流程,简单有效:
- 先跑纯 CPU 基线:
--n-gpu-layers 0,记录 tokens/second 和内存占用。 - 从 50% 层数起步:比如模型 40 层,先试 20。
- 逐步递增:每次加 5 层,记录延迟和内存峰值。
- 观察拐点:当延迟下降突然变慢甚至反弹,说明 GPU 已经过载。
- 往回退 5~10 层:那个点就是最佳平衡区。
我曾经用一个 13B 模型做过完整测试,结果很有意思。当 GPU 层数从 0 增加到 20 时,速度线性提升;到 25 层时达到峰值;再往上加到 30 层,速度反而掉了 15%。说明芯片的内存带宽已经饱和了。
| GPU 层数 | 延迟 (ms/token) | 内存占用 (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 试试。
一个实用的调参决策流程
下面这张图总结了整个决策逻辑,你可以照着它一步步走,基本不会偏。
这个流程的核心是**迭代收敛**。你不需要一次找到最佳值,只要每次调整后有改进方向就行。我通常做 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。找到那个“再多一层就完蛋”的临界点,然后往回退几层,那个点就是你当前芯片和模型的最优解。