一次意外的跑分,让我重新审视这两个 AI 推理引擎
一、缘起
事情要从我买了Macbook Neo说起。
一直想等mac mini M5,但是越等mac mini m4越贵,越等mac mini m4越没货,mac mini m5又没有上市,只好乘着东哥那边还有优惠下手了Macbook Neo。前几天一直在手机上部署本地大模型,我就想试试Macbook Neo 8G RAM是个什么水平。我熟练地从源码编译了最新版 llama.cpp,下载了 Qwen2.5-0.5B-Instruct Q2_K GGUF 模型(396MB),跑 llama-bench——51 tok/s。嗯,正常水平,A18 Pro 芯片就该是这个表现。
因为最近看其它Up主的视频,都是在使用Ollama,所以我就想着用Ollama也测一下。「之前手机为什么不使用Ollama?」
因为我用 Ollama 的历史体验并不好。几个月前在手机上跑,一个字一个字往外蹦,响应巨慢。后来换成 llama.cpp 直接调用同一个模型,速度明显提升。从那以后,我心里就烙下了一个根深蒂固的印象:
"llama.cpp 才是快的,Ollama 只是方便但慢。"
二、跑分
抱着"走个过场试试"的心态,我装上了 Ollama,把同一个 GGUF 文件导入进去,跑了三轮 benchmark。
然后我愣住了。
Ollama 快了 40%。
我不信。又跑了一遍,还是 72。再跑,71.6。我甚至重启了电脑再测,结果依然稳定。
那一刻,我感觉过去几个月的认知被掀翻了。
下面是完整的跑分数据,每一个数字都是实测得来:
| 指标 | llama-cli (每次重载) | llama-cpp-python | llama-server (GPU常驻) | Ollama |
|---|---|---|---|---|
| Token 生成速度 | 51 tok/s | 56 tok/s | ~52 tok/s | 72 tok/s 🏆 |
| Prompt 处理速度 | 94 tok/s | — | — | 1,000+ tok/s |
| 模型加载耗时 | 195 ms 🏆 | ~500ms | 仅首次加载 | 1,180 ms (首次) |
| 128 tokens 总耗时 | ~2.5s | ~2.3s | ~2.6s | ~1.6s 🏆 |
| KV Cache 精度 | fp16 (16bit) | fp16 | fp16 | q8_0 (8bit) 🏆 |
| Flash Attention | 需手动开启 | 需手动开启 | 需手动开启 | 默认开启 🏆 |
| GPU 常驻 | ❌ 每次重载 | ✅ 进程内常驻 | ✅ 服务常驻 | ✅ 服务常驻 |
注意:llama-cli 的加载时间只有 195ms,在三者中最快。但因为每次运行都重新加载,在短对话场景下产生了额外开销。而 Ollama 虽然首次加载需要 1.18s,但后续推理完全跳过这一步。不过加载时间并不是速度差距的主因——真正拉开差距的,是下面这些优化。
所以我一开始错误的以为Ollama快是因为常驻GPU(因为我一开始只是使用llama-cli进行测试,它加载模型的速度只有Ollama零头的时间,如果让llama.cpp常驻GPU是不是就比Ollama快了?上面已经有了答案:并没有!)
llama-server 就是干这个的——它以后台 HTTP 服务运行,模型加载一次后常驻 GPU 内存:
llama-server -m model.gguf -ngl 99 --flash-attn on --port 8080
curl http://127.0.0.1:8080/v1/chat/completions \
-d '{"messages":[{"role":"user","content":"你好"}]}'
三、发生了什么?
冷静下来,我分析了一下差距的来源。
第一,Flash Attention。
Ollama 启动时我加了环境变量 OLLAMA_FLASH_ATTENTION="1"。Flash Attention 是 2022 年提出的一种高效注意力机制实现,能显著降低显存带宽需求,尤其在长序列推理时优势巨大。我编译 llama.cpp 时虽然也启用了 Metal,但没有专门去开 Flash Attention 的优化选项。
第二,KV Cache 量化。
OLLAMA_KV_CACHE_TYPE="q8_0" 也是关键。KV Cache 是自回归模型中存储历史 Key-Value 向量的缓存,随着生成长度增加会线性膨胀。把它量化为 8-bit,内存带宽消耗直接减半,这对推理速度的提升立竿见影。
第三,KV Cache 量化才是隐藏的王牌。
我原本以为 GPU 常驻是主要差距,于是用 llama-server 启动了常驻服务来验证——结果即使 GPU 常驻 + Flash Attention 全开,速度也只有 ~52 tok/s,远不及 Ollama 的 72 tok/s。
这说明真正的差距不在"常驻",而在 KV Cache 量化。
Ollama 默认将 KV Cache 量化为 8bit(q8_0),而 llama.cpp 默认是 fp16。KV Cache 在生成时会随着 token 数量线性增长,很快成为显存带宽的瓶颈。量化为 8bit 后,每次读写的数据量减半,带宽消耗直接减半——这对生成速度的提升是实打实的 20-40%。
但 llama-server 默认不使用 KV Cache 量化(除非手动改源码或传参),而且 Ollama 在 Metal 后端上有更多隐式的调度优化。
所以结论是:Ollama 快,不是因为它做了 llama.cpp 做不到的事,而是因为它把 llama.cpp 能做的事默认开好了。
四、那之前的经历是怎么回事?
我又想起了手机上那个"一个字一个字蹦"的 Ollama。
当时的 Ollama 大概率是 CPU 模式运行的。
Ollama 在 macOS 上自动选择 GPU(Metal),但早期的 Ollama 版本或者在没有 GPU 的环境中,默认会退回到 CPU。CPU 推理大模型,尤其是没有量化的小模型,速度就是会惨不忍睹——几秒钟一个字的情况我见过太多。
所以不是 Ollama 慢,是我当时没有让它用上 GPU。
而 llama.cpp 需要手动编译 Metal 支持,我那时花了功夫去配置,自然就觉得"llama.cpp 更快"。
五、四个工具的定位
现在我可以重新给这四个工具做个客观的定位了:
| 工具 | 速度 | GPU 常驻 | 适用场景 |
|---|---|---|---|
| Ollama | 最快 🏆 | ✅ 服务常驻 | 日常对话、交互式使用、API 服务 |
| llama-server | 快 | ✅ 服务常驻 | 自建 API、需常驻的批量任务 |
| llama-cpp-python | 快 | ✅ 进程内常驻 | Python 二次开发、自定义推理逻辑 |
| llama-cli | 快 | ❌ 每次重载 | 一次性脚本、调试、CI 集成 |
Ollama 适合"用"模型——安装即用,开箱全加速,体验最好。
llama.cpp(含 server/cli)适合"调"模型——高度可控,适合深度定制和调试。如果想让它常驻,用 llama-server 即可。
两者并不冲突,关键是要用对姿势——以及 把配置调对。
六、后记
这次测试给我上了一课:
-
永远不要靠"以前的体验"给工具下定论。 环境变了,配置变了,结论可能完全相反。
-
跑分之前先检查配置。 确认 GPU 已启用,确认优化已打开,否则你测的不是工具的上限,而是你配置的下限。
-
默认设置不等于最佳设置。 Ollama 不开启 Flash Attention 也能用,但开与不开差距巨大。花五分钟看文档,往往能带来翻倍的提升。
-
核心差距在 KV Cache 量化,不在 GPU 常驻。 用
llama-server可以让 llama.cpp 也 GPU 常驻,但速度差距依然存在——因为 Ollama 默认开了 KV Cache 8bit 量化,而 llama.cpp 的默认配置没开。 -
苹果芯片 + Metal 是真的强。 A18 Pro 跑小模型能到 70+ tok/s,响应几乎无延迟。Apple Silicon 的统一内存架构对推理的加成,远比纸面参数看起来的大。
最后,附上我这次测试的环境配置,供参考:
设备: MacBook Neo (A18 Pro, 8GB)
系统: macOS 26.5
模型: Qwen2.5-0.5B-Instruct Q2_K (396MB)
Ollama: v0.31.1 + Metal + Flash Attention + q8_0 KV Cache
llama.cpp: master branch (自编译) + Metal GPU + Flash Attention
llama-server: 同上,常驻 HTTP 服务模式
如果你也在跑本地大模型,不妨重新审视一下你的工具链配置——也许你手里的工具,远比你想象的要强。