跑一个大模型到底有几种跑法?从 llama.cpp、Ollama 到 vLLM 的三条路

本文回答一个看似简单的问题:「我想在本地跑个大模型」——然后呢?这三个 GitHub 上最火的推理项目,其实回答的是三个完全不同的问题。

周末折腾本地大模型的人,大概率经历过这个场景:搜「本地跑 LLM」,GitHub 推荐给你 llama.cpp、Ollama、vLLM 三个项目,Star 数加起来四十多万。打开 README 都是「高性能推理」,看起来互为竞品。

但我研究完这三个项目的架构后,结论是:**它们根本不是竞品,是三条不同的路,解决三个不同的问题。**选错工具不是性能差一点的问题,是方向错了。

三个项目,三个问题

先把结论摆出来:

项目回答的问题服务对象优化目标
llama.cpp「我的笔记本/手机能跑吗?」单个极客单请求延迟、硬件可及性
Ollama「我只想跑,不想折腾」所有人上手门槛(降到一条命令)
vLLM「一块 GPU 怎么服务上千用户?」生产 API 服务多请求吞吐

注意看第三列——llama.cpp 和 vLLM 的优化目标甚至是对立的。一个恨不得为单个请求榨干每一毫秒延迟,一个恨不得把一千个请求塞进一个 batch 里摊薄成本。这两个目标天然冲突,所以它们不可能是同一个工具的两种实现。

llama.cpp:把 LLM 从数据中心拽到你的笔记本

llama.cpp 的故事从一个意外开始:2023 年初 LLaMA 权重泄露,作者 Georgi Gergashev 四天内拿出了原型。这个「时机比技术重要」的案例后来被反复引用,但真正让这个项目活下来的是架构决策。

它做了一个关键分层:底层是 ggml 张量库(算子、内存管理、多后端抽象),上层是 llama 推理引擎(模型定义、KV Cache、采样、API)。最大的亮点是多后端抽象——同一套 ggml API,底下可以是 CPU(AVX2/AVX512/NEON)、CUDA、Metal、Vulkan、SYCL、HIP,还支持 CPU+GPU 混合推理(-ngl 指定丢给 GPU 的层数)。你 MacBook 上跑 Metal、Linux 服务器上跑 CUDA、安卓手机上跑 NEON,代码是同一套。

几个值得工程师琢磨的设计选择:

1. 静态计算图 + 内存预分配,推理循环零 malloc。 训练框架(PyTorch)用动态图换灵活性,llama.cpp 反其道而行:推理场景输入形状可预知,那就把灵活性扔掉,换确定性的内存布局和零分配开销。这是「推理性能优先于灵活性」的明确表态。

2. 量化优于剪枝/蒸馏。 剪枝和蒸馏要重训练,量化不要——4-bit 量化把 7B 模型从 14GB 压到 4GB,任何模型拿来就能压。配合自包含的 GGUF 格式,直接催生了「量化模型分发」这个生态位,HuggingFace 上满屏的 GGUF 文件就是证明。

3. 零依赖,make 就能编。 不是炫技,是增长策略——make && run 两分钟上手,把「编译环境」这个劝退环节压缩到接近零。

核心权衡一句话:用户体验 > 开发效率。 复杂性内化(多后端、量化、KV Cache 管理全是苦活),简单性暴露给用户。

Ollama:它不是 llama.cpp 的对手,是它的前端

这是最容易被误解的一对。很多人把 Ollama 和 llama.cpp 放一起比性能,其实 Ollama 底层就是 llama.cpp——它是用 Go 写的包装层,干的事是:

  • ollama pull 自动下载模型(llama.cpp 得自己找 GGUF 文件)
  • 声明式 Modelfile 配置(不用背命令行参数)
  • 多模型生命周期自动管理
  • 开箱即用的 REST API

本质上 Ollama 把「开发者工具」包装成了「消费级产品」,使用门槛从「编译 + 配置参数」降到「一条命令」。它 177K Star 证明了一件事:包装与分层同样创造巨大价值——不发明新技术、只降低门槛就够了。

这就是经典的「引擎 + 包装」分层:llama.cpp 负责性能与灵活性,Ollama 负责体验与生态。类似的还有 LM Studio、GPT4All,全建在 llama.cpp 上。引擎催生包装,包装带来用户,用户反哺生态——飞轮转起来之后,格式即护城河(GGUF 成了事实标准)。

代价也很明确:包装层的功能更新永远滞后于引擎,需要自定义采样参数、语法约束的极客还是得回到 llama.cpp。合理的默认值覆盖了 90% 用户,剩下的 10% 自己承担。

vLLM:瓶颈不是算力,是内存管理

如果说前两个是「个人工具」,vLLM 是彻底的「服务器思维」。它源自 UC Berkeley Sky Computing Lab,核心论文发在系统顶会 SOSP 2023,核心论断只有一句:

LLM 推理的瓶颈不是计算,而是内存管理。

为什么?传统推理服务给每个请求按最大上下文长度预分配 KV Cache 内存。一个请求进来,不管它实际生成 50 个 token 还是 500 个,显存先按 4096 的上限占住。结果 GPU 显存利用率惨不忍睹——浪费超过 90%,GPU 利用率不到 30%。不是 GPU 算不过来,是显存被预留满了,塞不进更多请求。

vLLM 的解法漂亮在「跨领域借鉴」:把操作系统的虚拟内存搬进 GPU 显存。

graph TD A[KV Cache 切成固定块<br/>每块 16 token] --> B[块表映射<br/>逻辑块 → 物理块] B --> C[按需分配<br/>零碎片] B --> D[引用计数<br/>前缀共享 CoW] C --> E[Continuous Batching<br/>调度器每个 step 动态进出] E --> F[GPU 利用率 30% → 90%+]
  • PagedAttention:KV Cache 切成固定大小的物理块(通常 16 个 token),块表(就是页表)把逻辑块映射到物理块,按需分配,彻底消灭碎片。
  • 引用计数 + Copy-on-Write:多个请求共享同一段前缀(同一个 system prompt,一千个用户),物理块只存一份。虚拟内存的共享内存概念,原样照搬。
  • Continuous Batching:调度器每个 step 决定哪些请求继续、哪些新请求插队进来,显存不够就抢占低优先级请求。传统 static batching 是「凑齐一批一起跑,最慢的拖死所有人」;continuous batching 让 GPU 永远满载。

效果不是「略有提升」:内存利用率提升 2-4 倍,GPU 利用率从不足 30% 拉到 90% 以上。Anyscale、Roblox、LinkedIn、Airbnb 都在生产环境用,它就是 LLM API 服务的事实标准。

怎么选?一张表说完

你的场景选谁为什么
想在笔记本/手机上跑,甚至纯 CPUllama.cpp(或套个 Ollama)全平台 + CPU 可跑,只有它做得到
不想折腾,开箱即用Ollama一条命令,REST API 现成
需要自定义采样/语法约束llama.cppOllama 的功能更新永远滞后
公司 API 服务,高并发vLLMcontinuous batching + 分页显存,吞吐就是它的命
单机开发测试 OpenAI 兼容接口Ollama / vLLM 都行两者都内置 OpenAI 兼容 API

一句话记忆:**llama.cpp 是单用户工具,vLLM 是多租户服务器,Ollama 是 llama.cpp 的易用外壳。**前者互补而非竞争,后者是分层而非竞品。

坑与调试笔记

  • Ollama 跑起来但慢:多半是模型没落到 GPU 上。看日志里的 offload 层数,显存不够时它静默退回 CPU,速度差一个数量级。换小一号的量化版本(Q4_K_M → Q3_K_M)通常立刻缓解。
  • llama.cpp 的 -ngl 参数:GPU 层数不是越大越好,层数 × 层显存超了会 OOM 或者更糟——静默掉回 CPU。从小往大试,盯着显存占用调。
  • vLLM 显存占用「虚高」是特性不是 bug:它默认预分配大部分显存给 KV Cache 池(gpu_memory_utilization 默认 0.9)。跟别的进程抢卡时记得调低,否则直接 OOM。
  • GGUF 量化版本的选择:Q4_K_M 是质量/体积的甜点位;Q8 接近无损但体积翻倍;Q2 系列省空间但质量损失明显,别在正式用途上用。

写在最后

这三个项目放在一起看,其实是一堂「架构服务于场景」的公开课:

  1. 优化目标决定架构。 延迟优先(llama.cpp 静态图零 malloc)和吞吐优先(vLLM 动态分页)是两套完全不同的设计,不存在「全能方案」。
  2. 包装层的价值被长期低估。 Ollama 一行推理代码没发明,177K Star。把门槛从「编译」降到「一条命令」,就是产品级贡献。
  3. 最聪明的优化往往是跨界搬运。 vLLM 没发明新硬件、新算法,只是把 1960 年代操作系统的虚拟内存思想搬进了 GPU 显存。你手上的老问题,别的领域可能二十年前就解过了。

下次有人问「llama.cpp 和 vLLM 哪个更好」,你可以反问一句:你是要自己跑,还是要服务一千个人?