本文面向:手上有 24GB 级别消费/工作站显卡,准备在本地或小规模生产环境跑大模型的工程师。
涉及框架版本:vLLM 0.6.x、SGLang 0.3.x、Ollama 0.5.x(2026 年 5 月)。
0. 写在前面
"本地部署大模型"这个话题,CSDN 上每个月都有大量新文章。但绝大多数都停留在两类内容:
"安装教程"型:照着官方 README 抄一遍,跑通 demo 就结束。
"性能党"型:上来甩一张图说 A 比 B 快多少倍,没有方法论、没有可复现脚本,读完不知道该选谁。 这篇我想做的是中间那一层——把三个目前最被讨论的框架(vLLM / SGLang / Ollama)放在同一台机器、同一个模型、同一组请求下,给你一套完整的对比方法、部署命令和决策依据。
读完之后你应该能回答两个问题:
-
我自己的业务场景,应该选哪一个?
-
我手上这张卡,能跑多快、能撑多大并发?
1. 测试环境
为了让结果有可比性,本文以下面这套配置为基准。你换成自家硬件,按照同样方法跑一遍就能得到自家数据。
| 项目 | 配置 |
|---|---|
| GPU | NVIDIA RTX 4090(24GB)|
| CPU | Intel i9-14900K |
| 内存 | 64GB DDR5 |
| 操作系统 | Ubuntu 22.04 LTS |
| CUDA | 12.4 |
| 驱动 | 550+ |
| Python | 3.11 |
| 模型 | Qwen3-8B(HF 原版,BF16)|
| 测试集 | ShareGPT V3 抽样 200 条 |
为什么选 Qwen3-8B(BF16):
BF16 单卡刚好能放下(约 16GB 权重 + KV cache),不用量化,避免引入"框架对量化支持差异"这种额外变量;
8B 体量是真实业务里跑得起的"性价比甜点",比 7B 略大但能力更强;
Qwen3 是 2026 年五月最被广泛使用的开源中文模型,三个框架都原生支持。
如果你想跑 14B / 32B,思路完全一样,只是 32B 这一档需要 INT4/INT8 量化才能塞进 24GB。
2. 公平测试的几条铁律(别犯)*
我看过太多"vLLM 比 Ollama 快 10 倍"的文章,仔细一看发现条件根本对不上。这里先把规则定清楚:
-
同一模型权重——不要拿 vLLM 跑 BF16、Ollama 跑 Q4,那不公平。
-
同一 max_tokens——输出长度直接影响吞吐计算,固定为 256 或 512。
-
同一并发模式——单请求顺序 vs 多请求并发,分开测,分开报。
-
预热——前 5 个请求扔掉不计,避免冷启动污染。
-
报告完整指标——只看吞吐量不够,必须配 TTFT(首 token 延迟) 和 显存占用。
下面三个框架的部署章节会给出完全一致的入参,便于横向对比。
3. 三个框架各自的"画像"
在动手之前,先用一段话把每个框架的设计取向讲清楚。这比任何 benchmark 数字都重要——选型从来不是"谁更快",而是"哪一个的取向匹配你的场景" 。
vLLM —— 生产级 Serving 的事实标准
核心创新是 PagedAttention,把 KV cache 当成分页内存管理,解决了显存碎片导致的并发瓶颈。
对高并发批处理的优化最深、最成熟,几百路并发下吞吐稳定。
API 完全对齐 OpenAI 协议,前端代码几乎零成本切换。
缺点:单请求场景下延迟优势不明显;冷启动慢;对小模型/小流量场景"杀鸡用牛刀"。
SGLang —— 后起之秀,强在结构化输出和前缀复用
由 LMSys 团队主导(Vicuna、Chatbot Arena 也是他们做的)。
核心创新是 RadixAttention——一种比 PagedAttention 更激进的前缀缓存方式。
对 多轮对话、Agent 工具调用、Few-shot prompt 这种"共享前缀"场景,性能可以反超 vLLM。
结构化输出(JSON Schema / 正则约束) 是 SGLang 的招牌特性。
缺点:生态不如 vLLM 大,部分模型适配略晚;文档相对零散。
Ollama —— 上手最低、最适合"今天就要用上"
底层是 llama.cpp,主打 GGUF 量化模型 + 单进程跨平台运行。
装好之后一行命令拉模型、一行命令跑:ollama run qwen3:8b。
对 Mac、Windows、Linux 都友好,对没 N 卡的开发者特别友好。
缺点:高并发下吞吐被 vLLM/SGLang 拉开较大差距;调度策略简单;对生产级监控/限流的支持要靠外挂。
一句话总结:
想拼并发吞吐选 vLLM;想拼 Agent/对话场景选 SGLang;想拼上手速度选 Ollama。
三框架设计取向对比
4. 实操一:用 Ollama 跑起来(5 分钟)
最简单的先来。
安装(Linux)
curl -fsSL ollama.com/install.sh | sh
拉模型并启动 daemon
ollama pull qwen3:8b
ollama serve &
测试
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3:8b",
"messages": [{"role":"user","content":"用一句话解释 PagedAttention。"}],
"stream": false
}'
注意 localhost:11434/v1/... 这条路径——Ollama 从 0.4+ 开始已经对外暴露 OpenAI 兼容接口,这是它能进生产的关键一步。
调优建议:
默认只加载一个模型,需要并发可以设 OLLAMA_NUM_PARALLEL=4 和 OLLAMA_MAX_LOADED_MODELS=1;
显存吃满后 Ollama 会自动把部分层卸到 CPU,这时吞吐会断崖式下跌,要么换更小模型、要么用量化版(如 qwen3:8b-q4_K_M) 。
5. 实操二:用 vLLM 跑起来
推荐用 conda/uv 隔离环境
pip install "vllm>=0.6.0"
启动 OpenAI 兼容服务
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-8B \
--dtype bfloat16 \
--max-model-len 8192 \
--gpu-memory-utilization 0.90 \
--port 8000
启动以后同样通过 OpenAI 协议访问:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-8B",
"messages": [{"role":"user","content":"用一句话解释 PagedAttention。"}]
}'
几个关键参数解释:
--gpu-memory-utilization 0.90:告诉 vLLM 可以使用 90% 显存。这是吞吐的关键开关,太低会浪费,太高会 OOM。
--max-model-len 8192:上下文长度。开太长会吃掉 KV cache 预算,并发数随之下降。
--enable-prefix-caching:开启前缀缓存,对系统 prompt 固定的场景有 20%+ 提升,强烈建议加上。
6. 实操三:用 SGLang 跑起来
pip install "sglang[all]>=0.3.0"
启动 OpenAI 兼容服务
python -m sglang.launch_server \
--model-path Qwen/Qwen3-8B \
--dtype bfloat16 \
--context-length 8192 \
--mem-fraction-static 0.85 \
--port 30000
SGLang 的杀手锏在结构化输出,举个例子:
curl http://localhost:30000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-8B",
"messages": [{"role":"user","content":"输出三个城市的天气信息,按下面 JSON 格式:[{name, temp, desc}]"}],
"response_format": {"type": "json_object"}
}' SGLang 对 JSON Schema 的约束是编码层强约束,不是靠 prompt 软提示,所以几乎不会输出非法 JSON。这件事在做 Agent 工具调用时极其重要。
7. 性能压测:可以直接抄走的脚本
下面这个脚本三个框架都能用(因为接口都对齐了 OpenAI 协议),你只要换 BASE_URL 即可。
bench.py
import time, json, asyncio, statistics
import httpx
BASE_URL = "http://localhost:8000/v1" # vLLM=8000 / SGLang=30000 / Ollama=11434
MODEL = "Qwen/Qwen3-8B" # Ollama 用 "qwen3:8b"
CONCURRENCY = 32
N_REQUESTS = 200
MAX_TOKENS = 256
PROMPT = "请写一段 200 字以内的科技公司业务介绍,要求包含三个产品名。"
async def one_request(client, idx):
t0 = time.time()
first_token_time = None
n_tokens = 0
async with client.stream(
"POST", f"{BASE_URL}/chat/completions",
json={
"model": MODEL,
"messages": [{"role": "user", "content": PROMPT}],
"max_tokens": MAX_TOKENS,
"stream": True,
},
timeout=120.0,
) as r:
async for line in r.aiter_lines():
if not line or not line.startswith("data:"):
continue
data = line[5:].strip()
if data == "[DONE]":
break
try:
chunk = json.loads(data)
delta = chunk["choices"][0].get("delta", {})
if delta.get("content"):
if first_token_time is None:
first_token_time = time.time()
n_tokens += 1
except Exception:
pass
t1 = time.time()
return {
"ttft": (first_token_time - t0) if first_token_time else None,
"latency": t1 - t0,
"tokens": n_tokens,
}
async def main():
sem = asyncio.Semaphore(CONCURRENCY)
async with httpx.AsyncClient() as client:
async def bounded(i):
async with sem:
return await one_request(client, i)
t_start = time.time()
results = await asyncio.gather(*[bounded(i) for i in range(N_REQUESTS)])
t_end = time.time()
# 丢弃前 5 个预热请求
results = results[5:]
ttfts = [r["ttft"] for r in results if r["ttft"]]
lats = [r["latency"] for r in results]
total_tokens = sum(r["tokens"] for r in results)
elapsed = t_end - t_start
print(f"并发: {CONCURRENCY} 总请求: {N_REQUESTS}")
print(f"耗时: {elapsed:.1f}s")
print(f"吞吐: {total_tokens / elapsed:.1f} tokens/s")
print(f"TTFT p50/p95: {statistics.median(ttfts):.3f}s / {statistics.quantiles(ttfts, n=20)[-1]:.3f}s")
print(f"端到端 p50/p95: {statistics.median(lats):.2f}s / {statistics.quantiles(lats, n=20)[-1]:.2f}s")
if name == "main":
asyncio.run(main())
跑法:
启动框架后
python bench.py
显存占用单独看:
nvidia-smi --query-gpu=memory.used,memory.free,utilization.gpu --format=csv -l 1
8. 你应当预期看到的结果范围
下面的结论来自社区公开 benchmark 与本人多次测试的归纳,不同硬件/驱动会有偏差,建议你跑完上面脚本以自己的数字为准。**
把同等条件下的三个框架放在一起,你大概率会看到下面这种格局:
| 维度 | Ollama | vLLM | SGLang |
|---|---|---|---|
| 单请求 TTFT | 较快 | 中等 | 较快 |
| 单请求吞吐 | 中等 | 较快 | 较快 |
| 并发吞吐(32 并发) | 明显落后 | 第一梯队 | 第一梯队 |
| 长前缀复用场景 | 一般 | 良好 | 最强 |
| 结构化输出 | 弱 | 中等 | 最强 |
| 显存利用率 | 灵活但低 | 高 | 高 |
| 部署复杂度 | 最低 | 中等 | 中等 |
| 生产监控/限流生态 | 弱 | 最完善 | 中等 |
| 跨平台(Mac/Win/Linux)| 全部 | Linux 优先 | Linux 优先 |
几个值得注意的细节:
在 单请求顺序问答 场景下,Ollama 并不慢,体感差距很小。它真正被拉开的是并发。
prefix caching 开启后,三家差距会被显著拉近——这也是为什么千万别忘了打开。
SGLang 在 Agent 多轮 / Few-shot prompt 场景下,吞吐可以反超 vLLM 20%-40%,这是 RadixAttention 的功劳。
Ollama 的优势在 运维心智成本:写一行 docker run,10 分钟搭好服务这件事,vLLM/SGLang 都做不到。
9. 选型决策表(按业务场景)
不同场景给出推荐组合:
| 业务场景 | 推荐 | 备注 |
|---|---|---|
| 个人/小团队内部工具 | Ollama | 上手快、跨平台、维护成本最低 |
| 高并发 API 网关 | vLLM | 协议齐全、监控生态成熟 |
| Agent / 多工具调用 | SGLang | RadixAttention + JSON Schema 双 buff |
| RAG / 知识库问答 | vLLM | 稳定的高并发是核心 |
| 边缘设备 / 笔记本 | Ollama | 唯一能优雅跑在 Mac/Windows 的 |
| 需要严格 JSON 输出 | SGLang | 编码层强约束,几乎无非法输出 |
| 企业内多模型并行 | vLLM | 多模型路由生态完善 |
我个人在公司里的部署组合是:前端 nginx → vLLM(主流量)+ SGLang(Agent 业务专线)+ Ollama(开发/调试机) ,三家各管一摊,没必要二选一。
10. 踩坑清单
按出现频率排序,都是真实踩过的坑:
1)vLLM 启动报 OOM,但显存看着没满。
原因:--gpu-memory-utilization 默认是 0.9,但 vLLM 启动时会预分配 KV cache,需要"实测的连续空闲显存",被其他进程占了零碎的 1-2GB 就会失败。
解法:跑 nvidia-smi,先 kill 其他 Python 进程,或调低到 0.8。
2)SGLang 启动慢(超过 1 分钟)。
原因:第一次会编译 RadixAttention CUDA kernel。
解法:等。后续启动会用缓存。
3)Ollama 并发请求被串行化。
原因:环境变量 OLLAMA_NUM_PARALLEL 没设置,默认是 1。
解法:export OLLAMA_NUM_PARALLEL=4 后重启 ollama serve。
4)三个框架对同一 prompt 输出不一样。
原因:默认 temperature 和 top_p 不一致。
解法:压测时显式指定 temperature=0,让结果可复现。
5)TTFT 在 vLLM 上很高但吞吐很高。
原因:vLLM 把请求批起来一起跑,单请求会等一会,但整体吞吐上去了。
解法:这是设计取向,要 TTFT 优先选 SGLang 或开 --enable-chunked-prefill。
6)Qwen3 在三家上 tokenizer 报错。
原因:transformers 版本太低,没识别 Qwen3 词表。
解法:pip install -U transformers,至少 4.45+。
11. 结语
如果只能记住三句话,建议是这样:
-
别选"最快的",选"和你场景最匹配的"。 三家都在拼命迭代,半年后绝对数字会变,但设计取向不会变。
-
跑你自己的 benchmark。 别人家的数字仅供参考,硬件、模型、流量模式只要变一项,结论就可能翻车。把上面的 bench.py 改三个参数跑一遍,比读 100 篇文章都管用。
-
多框架共存是常态。 不要把"选型"理解成"非此即彼",生产环境里三家并存非常普遍,各管各的流量切片。
文中所有部署命令、压测脚本均原创整理,已在 RTX 4090 + Ubuntu 22.04 环境跑通。如果你按这套方法实测出与上表预期相差较大的数字,欢迎评论区留硬件配置和具体参数,我会更新到后续文章里。**
**
后续会写:《把 vLLM 推到 100 并发:参数调优全记录》《SGLang RadixAttention 在 Agent 场景下的性能拆解》。感兴趣可以先关注。