3. 单GPU推理基础:加载模型、运行推理、理解性能瓶颈

有了环境,模型也下载好了,接下来就是真刀真枪地干活了。这一章,我带你走一遍单GPU推理的全流程。

说白了,这是后面多卡并行的基础。你连一张卡都玩不转,多卡肯定更头疼。我个人习惯,先跑通最小的闭环,再考虑优化。

3.1 模型加载:选对方式,省一半功夫

llama.cpp加载模型,核心就一个函数:llm_load_session。但参数怎么配,差别很大。

我的建议:第一次跑,用默认参数就够了。别一上来就调什么--n-gpu-layers、--threads,容易把自己搞晕。

先看看最基本的加载命令:

# 最简单的CPU推理(测模型是否正常)
./main -m qwen2.5-7b-q4_k_m.gguf -p "你好" -n 64

# 启用GPU加速(把部分层卸载到GPU)
./main -m qwen2.5-7b-q4_k_m.gguf --n-gpu-layers 99 -p "你好" -n 64

--n-gpu-layers 这个参数,我刚开始搞的时候踩过坑。你以为设得越大越好?不一定。

来看这个表,就知道分层卸载是怎么回事:

n-gpu-layers 值 计算位置 显存占用 适用场景
0 全部CPU 极低 首次验证,测模型是否损坏
全部层数(如99) 全部GPU 最高 追求极致速度,显存足够
部分层(如20) CPU+GPU混合 中等 显存不够时,兼顾速度
注意:显存不够时不要硬上全部GPU。我曾经把一张8G卡的--n-gpu-layers设到100%,结果模型加载到一半直接OOM(Out of Memory)。最后只能退回到CPU+GPU混合模式。

3.2 运行推理:跑通第一句话

模型加载成功后,运行推理其实就一行命令。但这里面有几个关键参数,我得给你说明白。

./main -m qwen2.5-7b-q4_k_m.gguf \
       --n-gpu-layers 99 \
       --ctx-size 4096 \
       --threads 8 \
       --batch-size 512 \
       -p "解释一下什么是量子纠缠" \
       -n 256

几个关键点:

  • --ctx-size:上下文窗口大小。你想想看,你问的问题越长,需要的上下文越大。但别设太大,显存会爆。
  • --threads:CPU线程数。这个主要影响Prompt处理阶段(prefill),推理阶段(decode)影响不大。
  • --batch-size:批处理大小。我建议别超过512,大了反而容易出奇怪的问题。

3.3 理解性能瓶颈:别只看Token/s

很多人跑完推理,看一眼Token/s就完了。太浪费了。这里面能挖的信息很多。

举个例子,推理过程其实分两个阶段:

  1. Prefill(预填充):处理你的输入Prompt。这一阶段计算量大,速度快不起来。
  2. Decode(逐token生成):一个字一个字往外蹦。这一阶段延迟高,是用户感知的“卡顿”来源。

你打开--verbose选项,就能看到这两个阶段的耗时。

./main -m qwen2.5-7b-q4_k_m.gguf --n-gpu-layers 99 --verbose -p "你好" -n 32

输出里会看到类似这样的行:

llm_load_tensors: offloaded 27/27 layers to GPU
llama_prompt_eval: time =  123.45 ms / 10 tokens (12.35 ms per token)
llama_kv_cache: re-creating cache for this model
llama_decode: time = 1234.56 ms / 32 tokens (38.58 ms per token)

注意看:llama_prompt_eval是prefill阶段,llama_decode是decode阶段。如果prefill太慢,说明你的Prompt太长或者模型太大。如果decode太慢,说明单卡算力不够了。

3.4 真正常见的瓶颈在哪?

我整理了一个清单,你跑完推理后,按这个顺序排查:

瓶颈类型 现象 最可能的解法
显存不足 加载模型直接报OOM 降低--n-gpu-layers,或换小模型
Prefill太慢 输入很长时,首token延迟高 增大--batch-size,或升级CPU
Decode太慢 生成时一个字半天才出来 这是单卡上限,多卡并行能解决
内存带宽瓶颈 CPU-GPU频繁数据传输 尽量把层全卸载到GPU
核心结论:单卡推理的瓶颈,90%出在decode阶段。而多GPU并行,正好是解决decode速度慢的终极手段。这就是为什么我们要学这个课程。

3.5 一张图看懂:单GPU推理全过程

下面这张SVG图,把整个流程和瓶颈点串起来了。你想想看,从加载模型到输出第一个字,中间经历了什么?

单GPU推理流程与性能瓶颈 加载模型 GGUF / 分层卸载 分配显存 KV Cache / 参数 Prefill Prompt计算 Decode 逐Token生成 瓶颈1:显存不足 • OOM -> 降低n-gpu-layers • 换小模型 • 用更低的量化 瓶颈2:Prefill慢 • 增大batch-size • 升级CPU/内存带宽 • 缩短Prompt 瓶颈3:Decode慢 • 单卡算力上限 • 多GPU并行主力解决 • 模型量化 多GPU并行 专门解决Decode瓶颈

3.6 跑一个完整示例:从加载到输出性能报告

好了,理论知识差不多了。我们动手跑一个完整的测试命令:

# 1. 先跑CPU基准(n-gpu-layers=0)
./main -m qwen2.5-7b-q4_k_m.gguf \
       --n-gpu-layers 0 \
       --ctx-size 2048 \
       --threads 8 \
       --batch-size 512 \
       --verbose \
       --prompt "写一首关于春天的诗" \
       --n-predict 128 \
       2>&1 | tee cpu_benchmark.log

# 2. 再跑GPU加速(调用全部层)
./main -m qwen2.5-7b-q4_k_m.gguf \
       --n-gpu-layers 99 \
       --ctx-size 2048 \
       --threads 8 \
       --batch-size 512 \
       --verbose \
       --prompt "写一首关于春天的诗" \
       --n-predict 128 \
       2>&1 | tee gpu_benchmark.log

跑完之后,你对比两个日志文件里的llama_prompt_eval和llama_decode耗时。你会发现:

  • Prefill阶段,CPU和GPU差距不大(因为计算量受内存带宽限制)
  • Decode阶段,GPU快很多(这才是GPU真正发挥作用的地方)
实用技巧:我一般会保存每次跑出来的性能日志,格式化成表格。这样不同配置、不同模型之间的差异一目了然。

嗯,这一章就到这里。单卡推理是基本功,你把上面的流程跑一遍,理解每个阶段的时间开销,后面学多卡并行的时候,就知道每一分性能提升是从哪里来的了。