4、性能指标与监控:tokens/s、内存占用、GPU利用率
跑起多 GPU 推理只是第一步,真正头疼的是——我怎么知道它跑得好不好?
我记得刚开始调 llama.cpp 多卡配置时,看着终端刷刷刷输出,心里根本没底。到底快不快?显存够不够?哪张卡在偷懒?靠猜肯定不行。今天我把平时盯的那几个指标和工具摊开讲,都是我实际踩过的坑。
4.1 核心指标:先抓住这三个
我个人习惯把监控指标分成三组,少一个都不踏实。
| 指标 | 含义 | 关注点 |
|---|---|---|
| tokens/s | 每秒生成的 token 数量 | 吞吐量,直接影响用户体验 |
| 显存占用 | 每张 GPU 上分配的显存大小(MB/GB) | 显存溢出会直接崩,要留余量 |
| GPU 利用率 | GPU 计算核心的忙碌百分比 | 利用率低说明通信瓶颈或负载不均 |
这三个指标是铁三角。只看 tokens/s 不够——万一显存爆了,速度再快也是废的。只看利用率也不行,我遇见过利用率 98% 但 tokens/s 很低的诡异情况,后来发现是显存带宽瓶颈。
4.2 tokens/s:最直白的性能标尺
llama.cpp 默认会在日志末尾打印 token 吞吐,格式类似:
llama_print_timings: eval time = 1234.56 ms / 128 tokens ( 9.64 ms per token, 103.69 tokens per second)
llama_print_timings: total time = 5678.90 ms / 128 tokens ( 44.37 ms per token, 22.54 tokens per second)
这里有两个值要分清:
- eval time tokens/s:纯解码阶段的吞吐。我一般主要看这个,它反映模型推理的真实速度。
- total time tokens/s:包含了 prompt 处理(prefill)的时间。如果你做长上下文对话,这个指标更有参考价值。
我曾经犯过一个错:只看 total tokens/s,觉得太慢,一通调参结果更糟。后来才发现是 prompt 太长导致 prefill 占了大头。嗯,这里要注意——测试时尽量用相同长度的 prompt 做对比,否则数据不可比。
小技巧:想单独测解码速度?用 --temp 0 -n 256 固定生成长度,只看 eval time 段的 tokens/s 。
4.3 显存占用:别等 OOM 才后悔
多 GPU 推理时显存分配和单卡完全不同。模型层被拆到多张卡上,加上 KV cache 也在各卡之间分布。我最怕的是遇到 CUDA OOM,程序直接崩掉,连日志都不完整。
显存占用关注两个点:
- 模型权重:单卡上实际存放了多上层的权重。用
nvidia-smi能看到每张卡的显存使用。 - KV cache:推理时动态增长。上下文越长,cache 越大,显存压力越集中在第一张卡(通常是 rank 0)。
我习惯在运行前用 nvidia-smi --query-gpu=memory.total,memory.free --format=csv -l 1 盯住剩余显存。一旦某张卡剩余显存低于 1GB,立刻调小 -c(context size)或者降低 batch size。
注意:llama.cpp 的分片策略默认是把模型均匀分配到各卡,但 KV cache 是按需分配。如果你看到某张卡显存暴涨,多半是 cache 分配策略的问题,试试 --tensor-split 手动调整。
4.4 GPU 利用率:发现负载不均的照妖镜
利用率这个指标容易被忽略。我见过一位朋友,tokens/s 只有预期的一半,查了很久才发现其中一张卡利用率一直在 20%,其他三张 80% 以上。这就是典型的负载不均。
用 nvidia-smi 的 utilization.gpu 字段可以看:
nvidia-smi --query-gpu=index,name,utilization.gpu,memory.used,memory.total --format=csv -l 5
输出例子:
index, name, utilization.gpu [%], memory.used [MiB], memory.total [MiB]
0, Tesla V100-SXM2, 89%, 14530 MiB, 32768 MiB
1, Tesla V100-SXM2, 45%, 8002 MiB, 32768 MiB
2, Tesla V100-SXM2, 88%, 14010 MiB, 32768 MiB
3, Tesla V100-SXM2, 90%, 14320 MiB, 32768 MiB
你看,卡1利用率只有45%,明显拖后腿。这种情况我遇到过两次:一次是 NVLink 线缆松动,一次是 PCIe 插槽速率不对(跑在 x4 上)。
利用率最好持续观察 30 秒以上,不要只看某一瞬间。用 -l 5 每 5 秒刷新一次,观察波动趋势。
4.5 工具推荐:nvidia-smi 和 nvtop
两个工具我都会用,但场景不同。
| 工具 | 优点 | 缺点 | 我什么时候用 |
|---|---|---|---|
| nvidia-smi | 系统自带,命令灵活,可脚本化 | 界面丑陋,单次信息过多 | 写自动化监控脚本时 |
| nvtop | 可视化 htop 风格,进程级占用 | 需安装,某些容器内无法用 | 调试时开着终端看实时波动 |
nvidia-smi 常用命令合集:
# 实时刷新,显示所有卡的基本信息
watch -n 1 nvidia-smi
# 只打印利用率+显存,适合抓日志
nvidia-smi --query-gpu=index,utilization.gpu,memory.used --format=csv -l 5
# 打印进程级占用,看是哪条 python 进程在吃显存
nvidia-smi pmon -c 1
# 带时间戳输出,方便后期分析
nvidia-smi --query-gpu=timestamp,index,utilization.gpu,memory.used --format=csv -l 10 > gpu_log.csv
nvtop 我更喜欢的原因:它把每张卡的历史曲线都画出来了,你能一眼看出利用率波动是否有规律。装起来也简单:
sudo apt install nvtop # Ubuntu
brew install nvtop # Mac(但 Mac 下意义不大)
nvtop 里按 p 可以切换进程视图,能看到是哪个进程的哪个 CUDA 上下文在跑。我曾经用这个抓到过同事的测试代码跟我的任务抢显存,哈哈。
个人习惯:我会同时跑两个终端:一个用 watch -n 2 nvidia-smi 简单看总览,另一个开 nvtop 盯着利用率曲线。定位问题后,再用脚本抓详细日志。
4.6 监控最佳实践:一个脚本搞定
下面这个脚本我放在每个实验项目里,运行推理时同时启动:
#!/bin/bash
# monitor_gpu.sh —— 记录 tokens/s 之外的硬件情况
INTERVAL=5
OUTPUT="gpu_$(date +%Y%m%d_%H%M%S).csv"
echo "timestamp,index,util_gpu,mem_used(MiB),mem_total(MiB)" > $OUTPUT
while true; do
nvidia-smi --query-gpu=timestamp,index,utilization.gpu,memory.used,memory.total \
--format=csv,noheader,nounits >> $OUTPUT
sleep $INTERVAL
done
推理结束后,用 Python 画个图,一眼就能看出哪张卡有问题。我曾经画过一个项目图,发现 rank 0 的显存使用线斜率高得离谱,调大 --tensor-split 后立刻改善了。
4.7 避坑指南:监控中的常见陷阱
- 只看 tokens/s 不看波动:tokens/s 平稳才是好,忽高忽低可能是有 ECC 纠错反复执行。我遇到过一块显存坏道的卡,跑几秒就掉速,换了就正常了。
- mclk 和 pclk 锁频问题:如果 GPU 利用率低,先用
nvidia-smi -q -d CLOCK看看时钟频率是否跑满。我踩过散热不足导致降频的坑,利用率上不去,速度血崩。 - 多卡通信瓶颈不在显存:利用率低 + tokens/s 低,但显存没满,很可能是卡间通信带宽不够。用
nvlink-status或nvidia-smi topo -m检查拓扑。
这张流程图概括了我这章的思路:从推理任务出发,盯住三个指标,用合适工具抓取,最后分析调整。你后期遇到性能问题,也可以沿着这个路径排查。