
每个请求都带同一段系统提示词,GPU 就把它重算一遍。
摘要
SGLang 用基数树缓存 KV,让相同前缀只算一次,天然适合 Agent、多轮对话和结构化输出。本文讲清它是什么、现状如何、和 vLLM 怎么选,并给出从单机启动到前端语言的上手路径。
SGLang 是什么
SGLang 是一个开源的大模型推理框架,Apache-2.0 协议,由 LMSYS 社区发起(论文作者包括 Ion Stoica、Joseph Gonzalez 等)。官方 README 现在的定位是:面向 Agent 工作负载、大规模服务和强化学习 rollout 的推理框架。
它由两部分组成:
- 前端语言:用 Python 写"LLM 程序",里面可以有多轮调用、分支、并行和结构化输出。
- 后端运行时:真正跑模型的推理引擎,对外提供 OpenAI 兼容接口。
绝大多数人只会用到后端:启动一个服务,用 OpenAI 的客户端去调。前端语言是进阶选项,后面再讲。
核心:RadixAttention
做过多轮对话或 Agent 的人都知道,请求之间有大量重复内容:同一段系统提示词、同一份文档、同一段对话历史。每次都重新计算这些 token 的 KV,是纯粹的浪费。
RadixAttention 的做法是把已经算过的 KV 缓存放进一棵基数树里,新请求进来,先沿着树找最长的公共前缀,命中的部分直接复用,只计算后面新增的 token。缓存满了,按 LRU 淘汰叶子节点。
另一个特色是压缩有限状态机:约束输出格式(比如必须是合法 JSON)时,它能一次跳过确定的多个 token,而不是逐个解码。
论文摘要里给出的数字是:在 Agent 控制、推理、多轮对话等任务上,吞吐最高可达当时先进推理系统的 6.4 倍。注意是"最高",而且对比的是 2023 年底的系统,别把它当成今天的日常加速比。
现在发展到哪了
截至 2026 年 10 月中旬,我查到的公开信息:
- GitHub 约 3.7 万 star、9.6 千 fork。
- 版本在 0.5.x 系列上快速迭代,官方安装文档的源码示例已经用到 v0.5.21。
- 新版要求 CUDA 13,CUDA 12 的镜像和 wheel 不再发布,最后一个 CUDA 12 标签是 v0.5.19-cu129。老机器要留意。
- 硬件覆盖很广:NVIDIA 从 A100 到 B200/GB300,AMD Instinct MI300 系列到 MI355X,Google TPU,Intel GPU 和 CPU,Apple Silicon,还有华为昇腾和摩尔线程。
- 模型方面,项目标签里列出的有 DeepSeek、GLM、GPT-OSS、Llama、MiniMax、Qwen 等,完整兼容列表见官方 Cookbook。
- 除了语言模型,还内置了图像/视频生成引擎 SGLang Diffusion。
可以看出,它已经不只是"前缀缓存"那个点子,而是一个覆盖多硬件、多模型的完整推理栈。
和 vLLM、TensorRT-LLM 怎么选
先说明:下面的对比来自公开的第三方文章,不是我自己的实测,具体数字强烈建议在你自己的负载上复测。
| SGLang | vLLM | TensorRT-LLM | |
|---|---|---|---|
| 前缀缓存 | 基数树,token 级 | 块级哈希 | 本文未对比 |
| 擅长 | Agent、结构化输出、重度提示词复用 | 高并发通用服务、混合硬件 | 固定 NVIDIA 集群、延迟优先 |
| 代价 | 迭代快,新版要求 CUDA 13 | 前缀重复多时缓存粒度不如基数树细 | 硬件绑定,灵活性差 |
| 上手难度 | 低 | 低 | 中(仅限 NVIDIA,调优项多) |
几点判断:
- 前缀重复多(长系统提示词、RAG 固定文档、多轮对话、Agent 反复调用):优先试 SGLang。
- 纯通用高并发、前缀几乎不重复:两者差距不大,选团队更熟的。有第三方测试显示,Llama 3.3 70B 的 FP8 场景下两者只差几个百分点;而在 8B 小模型上,SGLang 的吞吐领先约三成。这些数字的版本和负载都没写清楚,只能当参考。
- NVIDIA 卡固定、追求极致延迟、愿意花时间调优:TensorRT-LLM。
vLLM 的原理可以看前面写过的 PagedAttention 那篇,它们解决的是不同层面的问题:PagedAttention 管显存怎么分页,RadixAttention 管算过的结果怎么复用。
由浅入深的使用教程
第一步:安装
推荐用 uv,要求 Python 3.11+ 和 CUDA 13 环境:
bash
pip install --upgrade pip
pip install uv
uv venv --python 3.12
source .venv/bin/activate
uv pip install --prerelease=allow sglang
--prerelease=allow 建议保留。官方文档提醒:uv 版本低于 0.12.0 时不加它,可能静默装到旧版本;0.12.0 及以上加了也无副作用。
不想折腾环境,直接用 Docker:
bash
docker pull lmsysorg/sglang:latest
docker run -it --gpus all \
--shm-size 32g \
-p 30000:30000 \
-v "$HOME/.cache/huggingface:/root/.cache/huggingface" \
--ipc=host \
lmsysorg/sglang:latest bash
第二步:启动服务
在环境里执行:
bash
sglang serve Qwen/Qwen2.5-0.5B-Instruct --host 0.0.0.0 --port 30000
不指定 --port 时默认也是 30000。官方快速上手文档里仍然能看到旧写法 python3 -m sglang.launch_server --model-path ...,旧写法仍可用,安装文档现在用的是 sglang serve。先用小模型跑通,再换大的。
第三步:像调用 OpenAI 一样调用
bash
curl -s http://localhost:30000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "Qwen/Qwen2.5-0.5B-Instruct",
"messages": [{"role": "user", "content": "法国的首都是哪里?"}]}'
用 Python 客户端也一样,只要改 base_url:
python
import openai
client = openai.Client(base_url="http://127.0.0.1:30000/v1", api_key="None")
response = client.chat.completions.create(
model="Qwen/Qwen2.5-0.5B-Instruct",
messages=[{"role": "user", "content": "列出三个国家和它们的首都"}],
temperature=0,
max_tokens=64,
)
print(response.choices[0].message.content)
到这里,你已经可以把原来调用 OpenAI 的代码无缝切到自己的服务上。
第四步:让前缀缓存真正起作用
RadixAttention 默认就是开着的,但它只复用逐 token 完全一致的前缀 ,从第一个不同的 token 开始,后面都要重算。写提示词时记住一条:固定的内容放前面,变化的内容放后面。
python
SYSTEM = "你是一名严谨的代码审查员......(一大段固定规则)"
for diff in diffs:
client.chat.completions.create(
model="Qwen/Qwen2.5-0.5B-Instruct",
messages=[
{"role": "system", "content": SYSTEM}, # 固定前缀,只算一次
{"role": "user", "content": diff}, # 每次不同
],
)
反过来,把当前时间或请求编号放在系统提示词开头,会让每个请求的前缀都不同,缓存全部失效。这是最常见的踩坑。
第五步:结构化输出
要让模型稳定输出 JSON,可以用 OpenAI 风格的 response_format 传入 JSON Schema:
python
schema = {
"type": "object",
"properties": {
"city": {"type": "string"},
"population": {"type": "integer"},
},
"required": ["city", "population"],
}
resp = client.chat.completions.create(
model="Qwen/Qwen2.5-0.5B-Instruct",
messages=[{"role": "user", "content": "给出巴黎的名称和大致人口"}],
response_format={
"type": "json_schema",
"json_schema": {"name": "city_info", "schema": schema},
},
)
print(resp.choices[0].message.content)
解码过程会被约束在这个 Schema 内(默认用 XGrammar 后端),格式错误基本消失;但如果 max_tokens 太小导致输出被截断,JSON 仍可能不完整,兜底校验别全删。
第六步:多卡与显存调优
模型一张卡放不下时,用张量并行:
bash
sglang serve <模型> --tp 2 --port 30000
如果启动时报显存不足,可以调低静态显存占比:
bash
sglang serve <模型> --mem-fraction-static 0.8
这两个参数的具体含义和默认值,随版本会有变化,以 sglang serve --help 的输出为准。
第七步:前端语言(进阶)
当你的逻辑不是"一问一答",而是多步骤、带分支和并行时,前端语言会比手写循环更清晰:
python
import sglang as sgl
sgl.set_default_backend(sgl.RuntimeEndpoint("http://localhost:30000"))
@sgl.function
def tip_suggestion(s, topic):
s += sgl.user(f"给我三条关于{topic}的建议")
s += sgl.assistant(sgl.gen("tips", max_tokens=128))
forks = s.fork(2)
for i, f in enumerate(forks):
f += sgl.user(f"把第 {i + 1} 条建议展开成一段话")
f += sgl.assistant(sgl.gen("detail", max_tokens=128))
# 分支结果不会自动合并,要按下标取回,拼进主线
s += sgl.user(
"第 1 条展开:" + forks[0]["detail"] + "\n"
+ "第 2 条展开:" + forks[1]["detail"] + "\n"
+ "总结以上内容"
)
s += sgl.assistant(sgl.gen("summary", max_tokens=64))
state = tip_suggestion.run(topic="学习 Python")
print(state["summary"])
fork 出来的分支共享同一个前缀,RadixAttention 会让这部分只算一次;分支各自生成的结果要像上面这样用 forks[i]["detail"] 取回,主线才能看到。这就是前端语言和后端运行时联合设计的价值。
适合谁
- 在做 Agent、RAG、多轮对话,提示词里有大段固定内容的开发者。
- 需要稳定结构化输出的业务(抽取、分类、工具调用)。
- 要在非 NVIDIA 硬件(AMD、昇腾等)上做推理的团队。
不太适合:只想要最省事的单模型 demo,Ollama 之类的本地工具更轻;也不适合 CUDA 12 环境又不想升级的机器。
速查清单
- 环境:Python 3.11+、CUDA 13,
uv pip install --prerelease=allow sglang。 - 启动:
sglang serve <模型> --port 30000,接口是 OpenAI 兼容的/v1。 - 提速:固定内容放前面,变化内容放后面,别在开头塞时间戳。
- 结构化:用
response_format传 JSON Schema。 - 多卡:
--tp N;显存紧张调--mem-fraction-static。 - 选型:前缀重复多选 SGLang,其余在自己的负载上跑一遍再定。
你现在的服务里,每个请求开头有多少 token 是完全一样的?