本地跑大模型这件事,从2023年到现在,工具链已经换了好几茬。我自己的机器上同时装着 vLLM、SGLang、Ollama 和 llama.cpp,不是因为选择困难,而是这四个东西压根解决的就不是同一类问题。经常有人问我"到底该用哪个",这个问题本身就不太对——就像问"螺丝刀和电钻该买哪个",得先看你是要拧眼镜腿还是要在承重墙上打孔。这篇就把这四个推理引擎的底层机制、适用边界、部署实操和踩坑经验一次讲透,看完你应该能根据自己的硬件、并发量和延迟要求,直接锁定该用哪个,而不是每个都装一遍再删。
1. 先搞清楚推理引擎到底在解决什么问题
1.1 大模型推理的两个阶段:Prefill 和 Decode
要理解这四个引擎的差异,得先知道大模型推理在计算层面分两个截然不同的阶段。
Prefill(预填充)阶段处理的是你输入的整段 prompt。这一步是高度并行的矩阵乘法,GPU 利用率能拉满,属于计算密集型(compute-bound)。你输入1000个token,这1000个token是一次性并行喂进模型的。
Decode(解码)阶段是逐token生成的。每生成一个token,都要把整个模型的权重读一遍(或者读KV Cache),但计算量很小,属于内存带宽密集型(memory-bound)。这就是为什么生成速度往往比预填充慢得多——瓶颈不在算力,在显存带宽。
这两个阶段的特性差异,直接决定了不同引擎的优化方向。vLLM 和 SGLang 的核心创新几乎都围绕"如何让 Decode 阶段更高效地利用显存带宽"和"如何让多个请求的 Prefill/Decode 更好地并行"展开。而 llama.cpp 和 Ollama 更多是在"单请求、低资源"场景下做量化压缩和内存管理。
1.2 四个引擎的定位差异,一张表说清
| 引擎 | 核心定位 | 典型硬件 | 并发能力 | 量化支持 | 上手难度 |
|---|---|---|---|---|---|
| vLLM | 高吞吐生产级服务 | 数据中心GPU | 极强(PagedAttention) | GPTQ/AWQ/FP8 | 中等 |
| SGLang | 高吞吐+复杂编排 | 数据中心GPU | 极强(RadixAttention) | GPTQ/AWQ/FP8 | 中等偏上 |
| Ollama | 本地一键运行 | 消费级GPU/CPU | 弱(默认串行) | GGUF | 极低 |
| llama.cpp | 极致轻量可移植 | CPU/边缘设备/手机 | 弱 | GGUF | 中等 |
这张表是结论,但背后的"为什么"才是关键。vLLM 的 PagedAttention 解决的是 KV Cache 显存碎片问题;SGLang 的 RadixAttention 解决的是多请求共享前缀的重复计算问题;Ollama 本质上是 llama.cpp 的封装加上模型管理和 API 层;llama.cpp 则是把量化做到极致、能在各种奇怪硬件上跑起来的 C++ 实现。
1.3 选型的第一性原理:先问自己三个问题
我在帮别人选型时,通常先问三个问题:
- 并发量多大?如果同时只有1-2个用户,Ollama 或 llama.cpp 足够;如果要支撑几十上百并发,vLLM 或 SGLang 是唯一选择。
- 硬件是什么?有 A100/H100 这类数据中心卡,闭眼选 vLLM/SGLang;只有消费级显卡或纯 CPU,llama.cpp 系更实际。
- 是否需要复杂编排?如果只是简单对话,vLLM 够用;如果涉及多轮结构化输出、分支推理、前缀共享,SGLang 的编程模型更顺手。
这三个问题的答案组合起来,基本就能锁定方向。下面逐个拆解。
2. vLLM:PagedAttention 到底解决了什么痛点
2.1 KV Cache 的显存浪费问题
vLLM 最核心的贡献是PagedAttention。要理解它,先得理解传统推理的 KV Cache 是怎么浪费显存的。
在 Transformer 推理中,每个已生成的 token 都需要缓存其 Key 和 Value 向量,这就是 KV Cache。传统实现里,KV Cache 是连续分配的——系统会预分配一块足够大的连续显存来存放最大可能长度的 KV Cache。问题是,你根本不知道用户会生成多长,所以要么按最大长度预分配(浪费巨大),要么动态扩展(产生显存碎片)。
实测数据很能说明问题:传统方案下,KV Cache 的显存利用率往往只有20%-40%,剩下全是碎片和预留。这意味着你花大价钱买的显存,一大半在空转。
PagedAttention 的思路借鉴了操作系统的虚拟内存分页机制:把 KV Cache 切成固定大小的 block(比如16个token一块),这些 block 在物理显存上不需要连续,通过一张 block table 来映射逻辑位置到物理位置。这样一来,显存利用率能拉到90%以上,同样的卡能塞下更多并发请求。
2.2 连续批处理(Continuous Batching)的威力
vLLM 另一个杀手锏是连续批处理。传统批处理是静态的:攒够一批请求,一起跑完,再处理下一批。问题是不同请求生成长度差异巨大,短请求早就结束了,还得等长请求跑完,GPU 大量空转。
连续批处理在每个 decode step都重新组批:某个请求生成完了,立刻把它踢出去,腾出的位置马上塞进新来的请求。这样 GPU 几乎不会因为等待而空转。在真实负载下,连续批处理能把吞吐量提升数倍到十几倍,具体取决于请求长度的分布。
2.3 vLLM 部署实操:从镜像到跑通
vLLM 官方推荐用 Docker 部署,最省心。以加载 Qwen 系列模型为例:
docker run --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ -p 8000:8000 \ --ipc=host \ vllm/vllm-openai:latest \ --model Qwen/Qwen2.5-7B-Instruct \ --max-model-len 8192 \ --gpu-memory-utilization 0.9几个参数值得展开说:
--gpu-memory-utilization 0.9:告诉 vLLM 可以用90%的显存。这个值不是越高越好,留一点给 CUDA context 和其他开销,0.85-0.92 是比较稳的区间。设成0.95以上有时会 OOM。--max-model-len:最大上下文长度。这个值直接决定 KV Cache 的预分配规模,设太大浪费显存,设太小长文本会报错。要根据实际业务的最长输入来定。--ipc=host:共享主机 IPC 命名空间,多卡通信时需要,单卡也建议加上避免一些共享内存问题。
启动后就是一个 OpenAI 兼容的 API 服务,直接调/v1/chat/completions即可。
2.4 vLLM 的坑:版本、显存和长上下文
第一个坑是版本匹配。vLLM 迭代非常快,不同版本对模型架构的支持差异很大。比如某些新出的模型架构,老版本 vLLM 直接加载失败。我一般建议锁定一个稳定版本,别盲目追 latest。如果遇到"model architecture not supported",先查该版本的支持列表。
第二个坑是显存估算。很多人按"模型参数量 × 2字节"来估算,这是错的。7B 模型 FP16 权重大约14GB,但加上 KV Cache、激活值、CUDA context,实际需要18-20GB。所以24GB的卡跑7B模型,max-model-len别设太大,否则并发一上来就 OOM。
第三个坑是长上下文下的性能断崖。上下文超过一定长度后,attention 的计算量是平方级增长的,吞吐会明显下降。如果业务是长文档处理,要么用支持 FlashAttention 的版本,要么考虑分块处理。
3. SGLang:RadixAttention 与前缀共享的工程价值
3.1 从 PagedAttention 到 RadixAttention 的演进
SGLang 的RadixAttention可以理解为 PagedAttention 的进阶版。它用一棵基数树(Radix Tree)来管理 KV Cache,核心目标是解决多请求共享前缀的重复计算问题。
举个典型场景:多轮对话。第二轮对话的输入是"系统提示 + 第一轮问答 + 第二轮问题",其中"系统提示 + 第一轮问答"这部分和之前完全一样。传统引擎会把这部分重新算一遍 Prefill,纯属浪费。RadixAttention 通过基数树识别出共享前缀,直接复用已有的 KV Cache,只计算新增部分。
在系统提示很长(比如几千token的角色设定)或者 few-shot 示例固定的场景下,这个优化能省下大量 Prefill 计算。实测中,前缀共享率高的负载,SGLang 的吞吐能比 vLLM 高出不少。
3.2 SGLang 的编程模型:适合什么场景
SGLang 不只是个推理引擎,它还有一套前端 DSL(领域特定语言),用来编排复杂的生成流程。比如:
- 分支推理:同一个问题生成多个候选,再选最优
- 结构化输出:强制模型按 JSON schema 输出
- 多轮工具调用:模型调用工具、拿结果、继续推理的循环
如果你的业务只是"用户问、模型答"的简单对话,SGLang 的编程模型优势用不上,vLLM 更简单直接。但如果涉及 Agent、RAG 里的复杂编排,SGLang 能省掉大量胶水代码。
3.3 SGLang 部署与调优要点
SGLang 的部署和 vLLM 类似:
python -m sglang.launch_server \ --model-path Qwen/Qwen2.5-7B-Instruct \ --port 30000 \ --mem-fraction-static 0.85--mem-fraction-static对应 vLLM 的gpu-memory-utilization,控制静态分配的显存比例。SGLang 还有个--schedule-policy参数,可以选择调度策略,在延迟优先和吞吐优先之间权衡。
实操心得:SGLang 对前缀共享的优化在冷启动时体现不出来,需要请求积累到一定量、基数树建起来之后才明显。所以压测时别只发几个请求就下结论,要模拟真实的多轮对话模式。
4. Ollama:把 llama.cpp 包装成"开箱即用"
4.1 Ollama 的本质:llama.cpp + 模型管理 + API
很多人以为 Ollama 是独立引擎,其实它的推理内核就是llama.cpp。Ollama 在 llama.cpp 之上加了三层东西:
- 模型管理:类似 Docker 的
pull/run体验,自动下载 GGUF 量化模型 - API 服务:默认暴露 OpenAI 兼容接口
- Modelfile:可以自定义系统提示、参数、模板
所以 Ollama 的性能上限就是 llama.cpp 的性能上限,它的价值在于把复杂的编译、量化选择、参数配置全部隐藏了。
4.2 Ollama 的安装与国内下载加速
Ollama 安装本身很简单,官网下载对应平台安装包即可。但国内用户最大的痛点是模型下载慢。默认从官方源拉取,速度经常只有几十KB/s。
解决办法是配置镜像源。Ollama 支持通过环境变量指定模型仓库地址:
export OLLAMA_HOST=0.0.0.0:11434 export OLLAMA_MODELS=/data/ollama/models把OLLAMA_MODELS指到大容量磁盘,避免默认装在系统盘把 C 盘撑爆——这是很多人踩过的坑,模型动辄几个GB,C 盘很快就红了。
至于下载加速,可以手动下载 GGUF 文件放到模型目录,或者使用国内的模型托管平台。手动放置时注意目录结构,Ollama 对文件命名和 manifest 有要求,放错位置会识别不到。
4.3 Ollama 的并发瓶颈与适用边界
Ollama 默认是串行处理请求的。也就是说,两个请求同时进来,第二个要等第一个跑完。虽然可以通过OLLAMA_NUM_PARALLEL环境变量开启并行,但受限于 llama.cpp 的架构,并发能力远不如 vLLM。
所以 Ollama 的适用边界很清晰:个人开发、本地测试、单用户场景。如果你要做一个给团队用的服务,Ollama 会很快成为瓶颈。我见过有人用 Ollama 搭内部服务,几个人同时用就开始排队,体验很差。
4.4 Ollama 常见报错排查
热词里出现的500 internal server error: llama-server process是 Ollama 的典型报错。这个错误通常意味着底层的 llama.cpp 进程崩了,原因可能是:
- 显存不足:模型太大,GPU 装不下,尝试用更小的量化版本(Q4 代替 Q8)
- 模型文件损坏:下载中断导致 GGUF 文件不完整,删掉重新拉
- 上下文长度超限:请求的上下文超过了模型配置,调小
num_ctx
排查时先看 Ollama 的日志(ollama serve前台运行能看到详细输出),日志里会明确告诉你底层进程为什么退出。
5. llama.cpp:量化与可移植性的极致
5.1 GGUF 格式与量化等级的选择
llama.cpp 的核心资产是GGUF 格式和一套成熟的量化方案。量化就是把模型权重从 FP16 压缩到更低精度,牺牲一点精度换取大幅的显存/内存节省。
常见的量化等级:
| 量化等级 | 每权重位数 | 7B模型大小 | 质量损失 | 适用场景 |
|---|---|---|---|---|
| Q8_0 | 8 bit | ~7.5GB | 极小 | 显存充足,追求质量 |
| Q6_K | 6 bit | ~5.8GB | 很小 | 平衡之选 |
| Q5_K_M | 5 bit | ~5.0GB | 小 | 推荐日常使用 |
| Q4_K_M | 4 bit | ~4.1GB | 可接受 | 显存紧张首选 |
| Q3_K_M | 3 bit | ~3.3GB | 明显 | 极限压缩 |
| Q2_K | 2 bit | ~2.7GB | 严重 | 不推荐 |
我的经验是:Q4_K_M 是性价比拐点。再往下压,质量损失开始明显,模型会变"笨";往上到 Q6/Q8,收益递减但体积涨得快。除非有特殊需求,Q4_K_M 或 Q5_K_M 是大多数人的最优解。
5.2 llama.cpp 在边缘设备上的玩法
llama.cpp 最迷人的地方是可移植性。它是纯 C++ 实现,几乎能在任何有编译器的平台上跑。热词里提到的 Android 版就是典型——通过 Termux 或者专门的 App,能在手机上跑小模型。
在 Android 上跑 llama.cpp 的关键是:
- 用 ARM 优化的编译选项(NEON 指令集)
- 选择小模型(1B-3B)和低量化(Q4 以下)
- 控制上下文长度,手机内存有限
实测下来,骁龙8系芯片跑 1B-3B 的 Q4 模型,生成速度能到每秒几个token,做简单的本地助手够用,但别指望流畅对话。
5.3 用 llama.cpp 搭本地编程助手
llama.cpp 自带一个llama-server,提供 OpenAI 兼容 API,可以接各种编程助手插件。配置思路:
./llama-server \ -m models/qwen2.5-coder-7b-q4_k_m.gguf \ -c 8192 \ -ngl 99 \ --port 8080-ngl 99表示把所有层都放到 GPU 上(99是"全部"的惯用写法),-c是上下文长度。如果显存不够,减少-ngl的值,让部分层留在 CPU 上,速度会慢但能跑起来。
踩坑提醒:编程助手对上下文长度要求高,代码文件动辄几千行。-c设太小会导致代码被截断,模型"看不到"完整上下文,补全质量直线下降。建议至少 8192,有条件上 16384。
6. 四个引擎的横向对比与选型决策树
6.1 吞吐、延迟、资源占用的实测对比
在相同硬件(单张24GB消费级卡)和相同模型(7B Q4)下,我做过一轮粗略对比:
| 指标 | vLLM | SGLang | Ollama | llama.cpp |
|---|---|---|---|---|
| 单请求首token延迟 | 中 | 中 | 低 | 低 |
| 单请求生成速度 | 快 | 快 | 中 | 中 |
| 10并发总吞吐 | 极高 | 极高 | 低 | 低 |
| 显存占用 | 高 | 高 | 中 | 低 |
| 冷启动时间 | 长 | 长 | 短 | 短 |
注意这里的对比是同模型同量化下的相对值。vLLM/SGLang 在低并发时优势不明显,甚至因为调度开销略慢;但并发一上来,差距是数量级的。
6.2 按场景选型:五类典型需求
场景一:个人本地玩模型。选 Ollama。一键安装,ollama run就能用,不用折腾编译和参数。
场景二:本地编程助手。选 llama.cpp。可控性强,能精细调参,配合编辑器插件体验好。
场景三:团队内部 API 服务。选 vLLM。OpenAI 兼容,并发强,部署标准化。
场景四:Agent/RAG 复杂编排。选 SGLang。前缀共享和编程模型能省大量工程。
场景五:边缘设备/手机。选 llama.cpp。可移植性是唯一解。
6.3 混合部署:为什么我机器上四个都装着
实际工作中,这四个不是互斥的。我的典型配置是:
- vLLM跑生产 API 服务,处理高并发请求
- SGLang跑需要复杂编排的实验性任务
- Ollama用来快速试新模型,
pull下来就能跑 - llama.cpp做量化和边缘部署的验证
它们共享同一批 GGUF 或 HuggingFace 模型文件,通过软链接复用,省磁盘空间。这种混合模式让我在不同任务间切换时不用重新配置环境。
7. 部署中那些文档不会告诉你的坑
7.1 显存估算:别信"参数量×2"
前面提过,但值得再强调。显存占用 = 权重 + KV Cache + 激活值 + CUDA context + 碎片。经验公式:
- 权重:参数量 × 精度字节数(FP16是2,INT4约0.5)
- KV Cache:2 × 层数 × 头数 × head_dim × 序列长度 × 精度字节数
- 其他开销:预留 2-4GB
7B FP16 模型,8192上下文,实际需要约 20GB。所以 24GB 卡跑 7B 是舒服的,跑 13B 就紧张了。
7.2 模型格式转换的坑
HuggingFace 格式和 GGUF 格式不通用。vLLM/SGLang 用 HF 格式,Ollama/llama.cpp 用 GGUF。转换需要专门的脚本(llama.cpp 仓库里的convert_hf_to_gguf.py)。转换时注意:
- 分词器配置要一起转,否则 tokenize 结果不一致
- 量化在转换后进行,先转 FP16 GGUF 再量化
- 转换耗时长,大模型可能要几十分钟
7.3 网络与镜像:国内部署的现实问题
国内拉取 HuggingFace 模型经常超时。解决方案:
- 用
hf_transfer加速下载 - 配置镜像站
- 手动下载后放到缓存目录
Docker 镜像同理,vLLM 的镜像有好几个GB,拉取慢的话可以配置国内镜像加速。
7.4 版本锁定:别做"最新版受害者"
这四个工具都在快速迭代,新版本可能引入不兼容变更。生产环境务必锁定版本,用具体的 tag 而不是 latest。我吃过亏:某次自动更新后,vLLM 的 API 参数变了,服务直接起不来。现在所有部署都用固定版本号,升级前先在测试环境验证。
8. 我个人的选型心得
折腾这四个引擎两年多,最大的体会是:没有最好的引擎,只有最合适的场景。新手最容易犯的错是"用 Ollama 搭生产服务"或者"用 vLLM 跑单机测试",前者并发崩,后者大炮打蚊子。
如果只能记一句话:并发高、有数据中心卡,选 vLLM 或 SGLang;单机、消费级硬件、图省事,选 Ollama;要极致控制或跑在奇怪设备上,选 llama.cpp。
另外分享一个实用技巧:不确定选哪个时,先用 Ollama 快速验证模型效果,确认模型可用后,再根据并发需求迁移到 vLLM/SGLang。这样能避免在环境配置上浪费大量时间——毕竟模型选错了,引擎再优化也没用。
最后提一句量化:很多人为了省显存无脑上 Q2/Q3,结果模型变傻还找不到原因。量化等级的选择要结合任务难度,简单分类任务 Q4 够用,复杂推理任务尽量 Q6 以上。这个权衡没有标准答案,得自己实测。