SGLang vs vLLM vs llama.cpp:单用户速度之外,你真正该选谁
从单用户与服务器吞吐量的本质差异出发,对比 SGLang / vLLM / llama.cpp 的核心武器:Continuous batching、PagedAttention、RadixAttention、Prefix caching。给出本地 Agent、RAG 客服、多 Agent 编排三类场景的选型清单。
你有没有发现一个很奇怪的现象:同一个大模型,用 llama.cpp 跑一个人聊天,一秒能跑 50 个 token;换成 vLLM 或 SGLang,单用户可能还是 50 个 token,甚至有时候还没 llama.cpp 快。可一旦走进企业服务器、AI Agent、RAG 多人聊天的场景,社区反而清一色倒向 vLLM 和 SGLang。
因为这里真正比的,已经不是一个人能跑多快,而是 100 个人同时来的时候,GPU 还能不能高效率地干活。
一、先把概念摆正:Latency vs Throughput
先想象一个饭店。llama.cpp 很像一个优秀的小炒厨师:你一个人点菜,他专门服务你,炒得又快又好。所以个人电脑、本地模型、单用户 Agent 体验上它常常无可挑剔。
但当突然来了 20 桌客人,你不能让厨师第一桌炒完再炒第二桌——服务器需要的是同时把 20 桌订单组织起来,让厨房流水线一直满载运转。
vLLM 和 SGLang 从一开始就是按这种推理服务器的思路设计的。这里的核心指标也由此切换:
Latency vs Throughput
Latency 是一个用户等多久才能拿到第一个 token;Throughput(吞吐量)是整台机器每秒或每分钟能产出多少 token。多人并发场景下,后者才是真正决定业务的指标。个人聊天时你盯着首字延迟,多人服务时你盯着整张卡的 token/min。
二、第一个关键武器:Continuous Batching
假设有三个用户同时在线:A 正在生成第 100 个 token,B 刚刚输入了一篇 5000 token 的文章等待 prefill,C 正在生成第 20 个 token。最简单的做法是一个一个来,A 没结束,B、C 就排队。但这样 GPU 利用率并不好。
Continuous batching(连续批处理)的做法像公交车:不用等第一批乘客全部到终点、下一批才能上车,而是有人下车,新的乘客立刻补进来。
这时你会发现一个反直觉的现象:多用户一起跑,并不等于把一个人的速度简单除以 N。因为 GPU 非常擅长并行计算,一个用户生成时大量 SM 单元根本没吃满。把多个请求组成 batch,反而能让 GPU 同时计算多个序列。于是单个用户可能慢了一点,但整台服务器每秒产出的 token 总数反而大幅提高。
这就是 vLLM 和 SGLang 运行时调度器最基本的形态——让 GPU 持续保持满载,而不是等某个长请求拖着所有人。
三、高并发的真正瓶颈:不是算力,是 KV Cache
很多人以为并发上不去是 GPU 算力不够,其实限制并发的是显存。具体来说,是 KV Cache。
Transformer 在推理时每一层都要为当前序列缓存 K/V 张量,序列越长、用户越多,KV Cache 占用的显存就线性增长。
一个具体的算账:48GB 显卡
假设你部署 Qwen-32B 这种规模的模型,模型权重就要占掉 30GB 显存,剩下 18GB。一段普通对话可能需要 1GB KV Cache:
1 个用户聊天需要 1GB;10 个用户就是 10GB;20 个用户,18GB 显存很快就要告罄。
四、PagedAttention vs RadixAttention:显存怎么放
vLLM 的核心武器是 PagedAttention。传统做法是给每个用户划一块连续的大显存——A 预留 4GB,B 预留 4GB,C 预留 4GB。但 A 实际只用了 800MB,剩下的位置别人不方便利用,时间一长显存就出现大量碎片和浪费。
PagedAttention 的思路跟操作系统虚拟内存非常像:不要一次给你一整栋楼,而是一页一页分,你用多少我给多少。
KV Cache 按 block 分页管理后,显存利用率大幅提升。同样 48GB,原来只能勉强塞 10 个人,现在能更高效地塞下更多人。所以 PagedAttention 最重要的意义,不是让一个人从 50 token 变成 100 token,而是让显存能容纳更高的并发。
SGLang 的 RadixAttention:跨用户共享 KV
SGLang 在 PagedAttention 之上又多了一招——RadixAttention。它解决的是:不同用户之间有没有重复的 KV Cache 可以直接共享?
案例:企业 RAG 客服的 8000 token system prompt
假设公司部署 RAG 客服系统,100 个客户进来,前面的 system prompt 全都一样——"你是某某科技的 AI 客服"+ 产品说明 + 售后规则 + 知识库摘要,假设一共 8000 tokens。用户 A 问价格,B 问配置,C 问售后,问题不同,但前面 8000 tokens 高度相同。
普通做法是 A prefill 一遍,B 再 prefill 一遍,C 再 prefill 一遍——大量重复计算。RadixAttention 把公共前缀组织成一棵基数树,相同部分只缓存一份 KV,分叉之后再各自走各自的分支。
五、Prefix Caching 与调度器:腾出来的 GPU 才是真省钱
Prefix caching 的价值不只是让单次请求快一点。Prefill 本身非常吃计算资源——假设你有 50 个 Agent,每个都挂着 2 万 token 的公共上下文,每次请求都重新 prefill 的话,GPU 大量时间都在重复阅读同样的东西。
公共 KV Cache 能复用的话,很多请求只需要计算新增的那一小段,GPU 腾出来了,同一张卡自然能服务更多请求。
还有一个容易忽略的核心:调度器
服务器里 30 个请求正在 decode,5 个 4 万 token 的长 prompt 在排队,10 个还在等待——如果调度器很笨,突然塞进一个 4 万 token 的 prefill,正在 decode 的用户会瞬间被卡住。
所以高性能框架会做 chunked prefill、请求调度优先级,甚至把 prefill 和 decode 分离到不同的实例上。这已经不是把模型加载到显卡里跑,而是在做 GPU 资源调度。
六、三大框架的定位与选型
那么 llama.cpp 是不是就不适合多并发?并不是。现在的 llama.cpp server 也有 parallel slots、continuous batching、prompt caching,多个序列同时运行不是不行——只是三者定位不同。
一句话总结:
三大场景的选型清单
┌─────────────────┬──────────────────────┬─────────────────────────────────┐
│ 场景 │ 推荐框架 │ 关键理由 │
├─────────────────┼──────────────────────┼─────────────────────────────────┤
│ 单用户本地 Agent│ llama.cpp │ 模型门槛低、量化成熟、硬件兼容广│
│ │ │ (CPU/NVIDIA/Apple Silicon) │
├─────────────────┼──────────────────────┼─────────────────────────────────┤
│ 多人 RAG 客服 │ vLLM / SGLang │ PagedAttention 撑并发,前缀共享 │
│ │ (SGLang 更优) │ 进一步省 prefill │
├─────────────────┼──────────────────────┼─────────────────────────────────┤
│ 多 Agent 编排 │ SGLang │ RadixAttention 共享 system │
│ │ │ prompt + 智能调度 │
└─────────────────┴──────────────────────┴─────────────────────────────────┘