副标题: 同一台电脑、同一个模型------换掉推理引擎之后,性能差了多少?

一、引子:Ollama 很好,但它是个"黑盒"
三天前,我们用 Ollama 完成了 Qwen3-8B 的本地部署。过程并不轻松------CUDA 版本兼容问题、Vulkan 回退 segfault、显存调优------但最终跑起来了。
Ollama 做了它该做的事:
我:ollama pull qwen3:8b
Ollama:好的,拉取完成,创建模型,启动 HTTP 服务
我:curl localhost:11434/api/generate
Ollama:好的,返回推理结果
一切都很简单。但它同时也藏了很多细节:
- 模型在 GPU 的哪几层跑? ------Ollama 决定,你不知道
- KV Cache 用了多大? ------Ollama 决定,你不知道
- Flash Attention 开了没有? ------Ollama 决定,你不知道
对于"开箱即用"来说,这没问题。但对于"我想知道我到底在用什么东西跑推理"来说,这就有点难受了。
这就是 llama.cpp 的切入点。
二、llama.cpp 是什么
Ollama 和 llama.cpp 的关系:
你写的代码
│
▼
┌──────────────┐
│ Ollama │ ← Go 写的进程管理 + HTTP 路由
│ (约 50MB) │ 帮你拉模型、切模型、保活
├──────────────┤
│ llama.cpp │ ← C++ 写的推理引擎(真正干活的部分)
│ (约 200KB) │ 加载 GGUF、跑 CUDA kernel、输出 token
└──────────────┘
│
▼
GPU
Ollama = llama.cpp + 封装。 封装带来了便利,也带来了额外开销。
llama.cpp 本身是一个 C++ 推理库,直接加载 GGUF 文件、直接调用 CUDA。没有中间层。它提供 llama-cli(命令行推理)和 llama-server(HTTP API 服务)两个入口。
本文的目标: 用同一台电脑(GTX 1660 Ti · 6GB VRAM)、同一个模型(Qwen3-8B Q4_K_M)、在 llama.cpp 上跑一遍,然后跟 Ollama 的结果做对比------看看到底差了多少。
三、编译踩坑:GCC 8 的 filesystem 之痛
如果你想跳过编译部分直接看性能对比,可以跳到第四节。但这个故事我觉得值得记下来。
llama.cpp 的编译本身很简单:
bash
git clone https://github.com/ggerganov/llama.cpp.git
cmake -B build -DGGML_CUDA=ON
cmake --build build -j12
但第一次编译完跑 llama-cli,立刻 segfault。
GDB 看一下:
#0 std::filesystem::path::~path()
#1 ggml_backend_load_best()
问题出在 std::filesystem 上。我的系统 GCC 是 8.4------C++17 的 filesystem 在 GCC 8 里还在"实验性"阶段,需要额外链接 -lstdc++fs。而 llama.cpp 的 CMake 没有处理这个情况。
换 GCC 9 重编,问题解决。
build: 7152 (d414db02) with gcc-9 (Ubuntu 9.4.0) 9.4.0 for x86_64-linux-gnu
小小的教训: 玩 C++ 项目,GCC 版本别太老。llama.cpp 这种紧跟标准的项目,GCC 9+ 是底线。
四、控制变量法:同一模型,两种引擎
实验设计:
| 控制变量 | 取值 |
|---|---|
| 模型 | Qwen3-8B Q4_K_M(4.7GB) |
| 硬件 | GTX 1660 Ti 6GB + 15GB RAM |
| Prompt | "What is machine learning?"(生成 128 tokens) |
| 上下文 | 4096 |
| GPU offload | 全部层(-ngl 36) |
为了保证公平,Ollama 也用同一个本地 GGUF 文件创建模型(而非从 Ollama 仓库拉取)。
# Ollama 侧:
ollama create benchmark-qwen3 -f Modelfile
# Modelfile 内容就一行:
FROM /home/.../qwen3-8b-q4_k_m.gguf
结果
llama.cpp Ollama 差距
──────────── ──────────── ─────────
Prompt eval 速度 144.1 tok/s 56.3 tok/s ×2.6
生成速度 (128 tok) 22.1 tok/s 16.6 tok/s +33%
VRAM 占用 5,336 MiB ~5,000 MiB 接近
Flash Attention 自动开启 默认关闭 ---
Prompt eval 差了 2.6 倍------这是两引擎最本质的区别。Prompt eval 阶段(把用户输入变成模型能理解的内部表示)计算密集但不需要逐 token 输出,这时候 llama.cpp 的零开销架构优势明显。
生成速度差 33%------生成阶段算力瓶颈在 GPU 本身,引擎开销占比小,所以差距缩小了。
VRAM 占用几乎一样------模型本身 4.7GB 占大头,引擎的额外开销可以忽略。
五、实验一:-ngl 分档调优------GPU 给多少,性能长多少?
llama.cpp 最有用的参数之一就是 -ngl(number of GPU layers)。在 Ollama 里你没法控制它,但在 llama.cpp 里你可以精确到"只 offload 9 层到 GPU"。
我们测试了 Qwen3-8B(36 层)在 5 个档位的表现:
-ngl 分档性能曲线(Qwen3-8B Q4_K_M · GTX 1660 Ti)
═══════════════════════════════════════════════════
ngl │ GPU 层数 │ Prompt 512 │ 生成 128 tok │ VRAM 占用
──────┼───────────┼───────────────┼───────────────┼──────────
0 │ 0% │ 116.9 tok/s │ 3.9 tok/s │ ~0
9 │ 25% │ 122.1 tok/s │ 5.0 tok/s │ ~1.3GB
18 │ 50% │ 129.1 tok/s │ 6.4 tok/s │ ~2.6GB
27 │ 75% │ 136.6 tok/s │ 10.1 tok/s │ ~3.9GB
36 │ 100% │ 144.7 tok/s │ 21.5 tok/s │ 5.3GB
这条曲线说明了什么?
Prompt eval(处理用户输入) 随 GPU 层数增加稳步提升:116 → 145,+24%。这是因为 prompt eval 可以并行计算,每多一层在 GPU 上都直接加速。
生成速度 则是一根"拐棍曲线":从 0 到 18 层,只从 3.9 涨到 6.4 tok/s(慢如蜗牛);但到 27 层突然跳到 10.1,再到 36 层的 21.5------翻了 5.5 倍。
为什么会有这个拐点?因为生成是串行的------每生成一个 token,都要经过所有层。当大部分层在 CPU 上时,每步都要等 CPU-GPU 数据传输,这个开销非常大。只有足够多的层在 GPU 上时(大概 75%+),带宽瓶颈才被突破。
实际意义: 如果你的显卡显存不够装下整个模型,用 -ngl 放 75% 的层到 GPU 就能获得接近全 GPU 的体验。比如 Q5_K_M(5.5GB)装不进 6GB 显存,但 -ngl 27 就能跑,速度还有 10 tok/s。
六、实验二:Flash Attention------长上下文的"免费午餐"
Flash Attention 是一种不用存储完整注意力矩阵的算法改进。llama.cpp 支持 --flash-attn 开关。
我们对比了开启和关闭 Flash Attention 时,不同上下文长度下的性能:
Flash Attention on/off 对比(Qwen3-8B · -ngl 36)
═══════════════════════════════════════════════════════
Flash Attn OFF Flash Attn ON 加速比
───────────── ───────────── ─────
pp512 (短输入) 144.1 tok/s 151.5 tok/s +5%
pp4096 (长输入) 112.1 tok/s 131.9 tok/s +18%
tg128 (生成) 21.5 tok/s 21.8 tok/s +1%
有意思的两个发现:
① 短输入时 Flash Attention 几乎没区别(+5%)。 因为 512 token 的注意力矩阵只有 512×512 ≈ 0.25M 个元素,计算量太小,优化的空间有限。
② 长输入时效果显著(+18%)。 4096 token 的注意力矩阵是 4096×4096 ≈ 16M 个元素------64 倍于短输入。Flash Attention 避免了存储这个巨大矩阵,内存带宽压力骤降。
③ 生成速度不变(+1%)。 生成阶段每步只处理 1 个新 token,注意力计算是 O(n) 的,Flash Attention 的优势发挥不出来。
实际意义: 如果你的应用需要长上下文(比如分析整份文档、多轮对话),务必开启 Flash Attention 。它完全免费------不影响输出质量,只改变计算方式。在 llama.cpp 里默认开启,Ollama 则需要设置 OLLAMA_FLASH_ATTENTION=1。
七、升级模型:DeepSeek-R1-Distill-Qwen-7B
既然 llama.cpp 已经跑通了,我们换一个更强一点的模型试试。
DeepSeek-R1-Distill-Qwen-7B是 DeepSeek-R1(671B MoE)蒸馏到 Qwen-7B 架构的版本。参数少但推理能力强。
llama.cpp 上两个模型的对比
Qwen3-8B DeepSeek-R1-Distill-7B
8.19B params 7.62B params
36 layers 28 layers
───────────── ─────────────────────
Prompt 512 144.1 tok/s 160.0 tok/s
Prompt 2048 128.7 tok/s 144.5 tok/s
生成 128 tok 22.1 tok/s 42.7 tok/s ← ×1.9
VRAM 5,336 MiB 4,696 MiB
DeepSeek 的生成速度是 Qwen3 的 1.9 倍。原因简单:层数少(28 vs 36),每步计算量小。而且 VRAM 只用了 4.7GB,比 Qwen3 的 5.3GB 多留出了 600MB 给 KV cache。
说明一件事: 72B 的模型"质感"可以通过蒸馏压缩到 7B 的"速度"里。虽然是 7B 级的参数,推理能力确实比同体量的通用模型强一截。
八、API 延迟对比:不只是快,是可控
除了命令行,llama.cpp 也提供 HTTP API 服务(llama-server),暴露 OpenAI 兼容的 /v1/chat/completions 接口。
llama-server 启动:
./build/bin/llama-server \
-m qwen3-8b-q4_k_m.gguf \
-ngl 36 \
--port 8080 \
--flash-attn 1
访问:
curl http://localhost:8080/v1/chat/completions \
-d '{"messages":[{"role":"user","content":"你好"}]}'
API 延迟对比(同一 prompt,生成 128 tok):
┌──────────────────────┬──────────────┬──────────────┬──────┐
│ │ llama-server │ Ollama │ 差距 │
├──────────────────────┼──────────────┼──────────────┼──────┤
│ Generation 速度 │ 20.5 tok/s │ 16.6 tok/s │+23% │
│ 总耗时 │ 6.50s │ 8.07s │+24% │
│ API 协议 │ OpenAI 兼容 │ 自定义 │ │
└──────────────────────┴──────────────┴──────────────┘──────┘
多出 23% 的吞吐。但我觉得 llama-server 更大的优势不是快,而是可控:
- 可以用
-ngl N精确控制多少层在 GPU - 可以用
-c 32768设置上下文长度(Ollama 默认 2048) - 可以用
--flash-attn 1开关 Flash Attention - 可以实时看每层的显存占用、KV cache 大小
九、什么时候该用哪个?
在跑完这组测试后,我的个人结论是:
选 Ollama:
- 你需要多模型切换 (
ollama run model1然后ollama run model2) - 你不想碰编译
- 你只需要一个快速的 API 服务
选 llama.cpp:
- 你想压榨性能(多 23% 的生成速度,2.6 倍的 prompt eval)
- 你需要精细控制(每层 offload、KV cache 大小、量化策略)
- 你想做调优实验(不同 -ngl 的对比、Flash Attention 开关的影响)
- 你是开发者,想把推理引擎嵌入自己的应用
选哪个模型:
- 日常聊天、工具调用 → Qwen3-8B(生态成熟、Chat Template 完整)
- 推理、编程、数学 → DeepSeek-R1-Distill-Qwen-7B(R1 蒸馏增益明显)
十、总结
从 Ollama "降一层"到 llama.cpp,我们得到了:
- 性能提升 20-30%------对于 6GB 显存的入门级显卡,这相当于"刚好能用"和"比较流畅"的区别
- 全栈可控------每一层在哪跑、KV cache 多大、Flash Attention 开不开,自己说了算
- 对引擎的理解------拆掉 Ollama 的封装后,原来推理引擎本身不复杂,复杂的是工程化封装
Ollama 和 llama.cpp 不是"谁替代谁"的关系。它们本质上是一个东西的不同抽象层级:
硬件 → llama.cpp → Ollama → 用户
裸引擎 开箱即用
选哪个,取决于你想站在这条链的哪一端。
附:本文所有数据均在同一硬件上实测获得
| 硬件 | 参数 |
|---|---|
| GPU | NVIDIA GeForce GTX 1660 Ti 6GB |
| 驱动 | 570.133.07 / CUDA 12.8 |
| CPU | 12 核 |
| 内存 | 15GB |
| llama.cpp | commit d414db02, build 7152 |
| Ollama | v0.30.11 |
测试命令:
bash
# llama-bench(自动跑多次取平均)
./build/bin/llama-bench \
-m /models/qwen3-8b-q4_k_m.gguf \
-m /models/DeepSeek-R1-Distill-Qwen-7B-Q4_K_M.gguf \
-ngl 0,36 -p 512,2048 -n 128 -r 2
# Ollama API 测试
curl -X POST http://localhost:11434/api/generate \
-d '{"model":"benchmark-qwen3","prompt":"...","stream":false,...
系列全篇:
- 从零到一:用 AI Agent 辅助在 6GB 显卡上本地部署大模型实战--- 部署全流程
- 只有 B 级模型,怎么干出 A 级的活? --- 任务拆解方法论
- Agent 不是更聪明的模型,而是长了手脚的模型 --- Agent 能力框架