SGLang 是由 UC Berkeley LMSYS 团队(就是开发 Vicuna、FastChat 的那个组)于 2024 年推出的高性能 LLM 推理框架。名字来自 "Structured Generation Language"。
它不是从零搭建的推理系统,而是在 Python + Triton GPU kernel 基础上,做了两件核心事情:
① 系统层:RadixAttention
自动 KV Cache 前缀复用。同一个 system prompt 被多个请求复用时,KV Cache 只算一次,自动共享。不需要用户感知。
② 语言层:SGLang 原语
提供 Python DSL,把多轮对话、并行采样、约束生成(JSON/Regex)封装成声明式接口,运行时自动调度并行执行。
与主流框架对比
| 框架 | 核心机制 | KV 前缀复用 | 编程模型 | 适合场景 |
|---|---|---|---|---|
| vLLM | PagedAttention | ❌ 无自动复用 | OpenAI 兼容 API | 通用 serving,最广泛 |
| SGLang | RadixAttention | ✅ 自动前缀共享 | Python DSL + REST | 有公共前缀的场景(RAG/Agent) |
| TensorRT-LLM | 静态编译优化 | ✅(手动) | C++ / Python | NVIDIA 硬件极致优化 |
| LMDeploy | PagedAttention + 量化 | ❌ | Python API | 轻量部署,国产模型 |
| Ollama | llama.cpp | ❌ | 简单 REST | 本地单机推理 |
要理解 SGLang 的核心优化,必须先搞清楚 LLM 推理时 KV Cache 是什么、占多大内存、为什么贵。
1.1 Attention 计算与 KV Cache
Transformer 每个 Attention 层在计算第 $t$ 个 token 时,需要访问前面所有 token 的 Key 和 Value 向量。为了避免重复计算,推理时会把它们缓存起来——这就是 KV Cache:
1.2 前缀重用问题:传统 PagedAttention 的盲点
vLLM 的 PagedAttention 把 KV Cache 分成固定大小的 Page(block),解决了显存碎片问题。但它有一个盲点:多个请求共用同一前缀时,KV Cache 仍然各自计算、各自存储,形成大量冗余。
- RAG:检索到的相同文档片段 + 格式 prompt
- Agent:固定的系统角色定义和工具描述
- Few-shot:每个请求开头的固定示例
- 多轮对话:前几轮的历史对话
- 代码补全:相同的文件上下文
RadixAttention 是 SGLang 的核心创新。它把所有请求的 KV Cache 组织成一棵 Radix Tree(压缩前缀树),自动发现并复用公共前缀,完全对用户透明。
2.1 Radix Tree 数据结构
2.2 淘汰策略:引用计数 + LRU
2.3 性能收益
- 每次请求都包含:System Prompt(200 tokens)+ 检索到的文档块(1000 tokens)+ 用户问题(50 tokens)
- 如果 1000 个用户问的是同一篇文档,前 1200 tokens KV Cache 只算一次
- 第 2 个请求开始,TTFT(首 token 延迟)从 ~500ms 降到 ~20ms(跳过 Prefill)
- 整体吞吐量提升 5~6×
KV Cache 管理只是一方面。要榨干 GPU 的吞吐量,还需要精巧的调度策略。SGLang 在这里借鉴并改进了 Continuous Batching,并引入了 Chunked Prefill。
3.1 Continuous Batching:请求粒度的动态调度
传统推理框架(TGI 早期版本等)使用静态 Batching:等一批请求都到齐了再一起推理,某个请求先生成完也要等其他人。这会造成严重的 GPU 空转。
3.2 Chunked Prefill:平衡 Prefill 与 Decode 的延迟
Continuous Batching 遇到一个新问题:Prefill 阶段(算整个 prompt)计算量大,会抢占 Decode 请求的 GPU 时间,导致在线请求卡顿(TTFT 增大)。
3.3 SGLang 调度器设计
Prefill-first 策略
新请求优先 Prefill(否则 TTFT 无限延长),Prefill 完成后加入 Decode batch。Chunked Prefill 控制单步 Prefill 计算量不超过预算。
内存感知调度
调度器实时追踪 KV Cache 占用。如显存不足,会先触发 RadixAttention 淘汰,再必要时 preempt(换出)低优先级请求(swap 到 CPU 或直接 recompute)。
TTFT
Time To First Token
首 token 延迟。受 Prefill 速度影响,RadixAttention 命中时极低(毫秒级)
TPOT
Time Per Output Token
每个生成 token 的延迟。受 batch size 和 KV Cache 访问速度影响
Throughput
output tokens / second
系统整体吞吐量。Continuous Batching + RadixAttention 双重加速
SGLang 提供两套接口:底层 HTTP/Python Runtime API(兼容 OpenAI,用于接入现有工具链),以及SGLang 语言原语(高级 DSL,用于复杂多步推理编程)。
4.1 Runtime API:OpenAI 兼容接口
启动服务器后,SGLang 自动提供 OpenAI 兼容的 HTTP 端点,直接替换 openai.baseUrl 即可:
# 启动 SGLang 服务
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-8B-Instruct \
--host 0.0.0.0 --port 30000 \
--tp 1 # tensor_parallel_size
# 使用 OpenAI 客户端
from openai import OpenAI
client = OpenAI(base_url="http://localhost:30000/v1", api_key="EMPTY")
response = client.chat.completions.create(
model="meta-llama/Llama-3-8B-Instruct",
messages=[{"role": "user", "content": "你好"}],
max_tokens=100,
)
也支持原生 Python Runtime API,可以更细粒度地控制缓存行为:
import sglang as sgl
# 初始化 Runtime
runtime = sgl.Runtime(model_path="meta-llama/Llama-3-8B-Instruct")
sgl.set_default_backend(runtime)
# 简单生成
@sgl.function
def simple_qa(s, question):
s += sgl.system("你是一个有用的助手")
s += sgl.user(question)
s += sgl.assistant(sgl.gen("answer", max_new_tokens=200))
# 单个调用
state = simple_qa.run(question="什么是深度学习?")
print(state["answer"])
runtime.shutdown()
4.2 SGLang 原语:高级并行控制
SGLang 最强大的地方在于其声明式编程原语,可以轻松实现并行采样、多步推理、Fork-Join 等复杂模式:
import sglang as sgl
@sgl.function
def best_of_n_qa(s, question, n=3):
s += sgl.system("你是一个专业的问答助手,请尽量准确回答。")
s += sgl.user(question)
# fork: 并行生成 n 个候选答案(共享上方 KV Cache)
forks = s.fork(n)
for f in forks:
f += sgl.assistant(sgl.gen("answer", max_new_tokens=200, temperature=0.8))
# join: 汇聚所有结果
s.join(forks)
# 让模型从 n 个答案中选最好的
s += sgl.user("以上" + str(n) + "个答案,哪个最准确?请指出序号。")
s += sgl.assistant(sgl.gen("best", max_new_tokens=50, temperature=0.0))
state = best_of_n_qa.run(question="深度学习和机器学习的区别是什么?")
# 访问结果
for i in range(3):
print(f"答案 {i}:", state[f"answer_{i}"])
print("最优答案:", state["best"])
4.3 约束生成(Structured Output)
SGLang 内置约束解码:强制模型输出严格符合 JSON Schema 或正则表达式的文本,无需后处理。
import sglang as sgl
@sgl.function
def extract_info(s, article):
s += sgl.user(f"从以下文章中提取信息:{article}")
s += sgl.assistant(
sgl.gen(
"json_output",
# 强制输出符合 JSON Schema
json_schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"author": {"type": "string"},
"keywords": {"type": "array", "items": {"type": "string"}},
"summary": {"type": "string", "maxLength": 200}
},
"required": ["title", "author", "keywords", "summary"]
}
)
)
# 或使用 regex 约束(如提取日期)
@sgl.function
def extract_date(s, text):
s += sgl.user(f"提取文本中的日期:{text}")
s += sgl.assistant(
sgl.gen("date", regex=r"\d{4}-\d{2}-\d{2}") # 强制 YYYY-MM-DD 格式
)
outlines 库实现,支持 JSON Schema / Regex / CFG 多种约束形式。
5.1 多 GPU 部署(Tensor Parallel)
SGLang 通过 --tp 参数一键开启 Tensor Parallel,自动把模型权重切分到多张卡:
# 单机多卡(8 卡 A100,TP=8)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-70B-Instruct \
--tp 8 \
--port 30000
# 多机部署(2 节点,每节点 8 卡,TP=16)
# 节点 0(主节点)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-70B-Instruct \
--tp 16 \
--dist-url "tcp://主节点IP:29500" \
--nnodes 2 --node-rank 0
# 节点 1
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-70B-Instruct \
--tp 16 \
--dist-url "tcp://主节点IP:29500" \
--nnodes 2 --node-rank 1
5.2 量化(INT4 / FP8)
量化在保持推理质量的同时大幅减少显存占用和提升吞吐量。SGLang 支持多种量化格式:
# AWQ 量化(INT4,推荐)
python -m sglang.launch_server \
--model-path casperhansen/llama-3-8b-instruct-awq \
--quantization awq \
--port 30000
# GPTQ 量化
python -m sglang.launch_server \
--model-path TheBloke/Llama-2-7B-Chat-GPTQ \
--quantization gptq
# FP8 量化(H100 原生支持,精度最高)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-8B-Instruct \
--quantization fp8 # 需要 H100 / 4090
| 量化格式 | 显存占用 | 精度损失 | 推理速度 | 推荐场景 |
|---|---|---|---|---|
| BF16(无量化) | 2 字节/参数 | baseline | baseline | 质量优先 |
| FP8 | 1 字节/参数 | 极小 | 1.5~2× | H100 生产环境首选 |
| AWQ (INT4) | 0.5 字节/参数 | 小 | 2~3× | 消费级 GPU / 资源受限 |
| GPTQ (INT4) | 0.5 字节/参数 | 小~中 | 1.5~2× | 与 AWQ 类似,模型生态更丰富 |
5.3 投机解码(Speculative Decoding)
投机解码用小模型(draft model)快速生成多个候选 token,再用大模型(target model)一次性并行 verify,大幅降低 TTFT 和 TPOT:
# 启动投机解码(使用 draft model)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-70B-Instruct \
--speculative-draft-model-path meta-llama/Llama-3-8B-Instruct \
--speculative-num-draft-tokens 4 \
--tp 4 --port 30000
# Eagle-2 投机解码(SGLang 内置,更高接受率)
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-70B-Instruct \
--speculative-algorithm EAGLE2 \
--tp 4 --port 30000
SGLang vs vLLM 性能基准(官方 Benchmark)
选型决策表
| 场景 | 推荐框架 | 理由 |
|---|---|---|
| RAG / 检索增强系统 | SGLang ✅ | 文档前缀高度重复,RadixAttention 效果最佳 |
| 多轮对话 Chatbot | SGLang ✅ | 历史对话作为前缀可复用,且支持 fork 并行采样 |
| Agent / 工具调用 | SGLang ✅ | 固定 system prompt 共享 + 约束生成 JSON 输出 |
| 结构化数据提取 | SGLang ✅ | 内置 JSON Schema / Regex 约束解码 |
| 通用 OpenAI 兼容 API | vLLM 或 SGLang | 两者都支持,SGLang 在有前缀时更优 |
| 独立单次推理(无前缀) | vLLM / TRT-LLM | 无复用场景下 SGLang 无优势;TRT-LLM 编译优化更好 |
| NVIDIA 硬件极致优化 | TensorRT-LLM | 静态图编译,A100/H100 性能天花板 |
| 本地单机轻量部署 | Ollama / LMDeploy | 部署简单,无需 Python 环境 |
常见问题 FAQ
- Q:SGLang 和 vLLM 能混用吗?
A:不能直接混用,但都兼容 OpenAI API 格式,可以通过负载均衡在两者之间路由。 - Q:RadixAttention 会影响输出质量吗?
A:不会。它只是复用已计算的 KV Cache,与模型参数无关,输出分布完全等价。 - Q:SGLang 支持 LoRA 吗?
A:支持,--lora-paths参数可加载多个 LoRA adapter,运行时动态切换。 - Q:显存不够时 SGLang 会怎么处理?
A:RadixAttention 淘汰旧缓存 → preempt 低优先级请求(recompute 策略)→ 拒绝新请求(可配置)。 - Q:SGLang 的 fork() 安全吗?会影响其他请求?
A:安全。fork 后各分支的 KV Cache 是 Copy-on-Write 的,共享前缀只读,独有部分独立存储。
一句话总结
SGLang 的核心洞察是:LLM 推理中大量请求共享相同的前缀,这些重复计算是系统层面的浪费。RadixAttention 用一棵前缀树把这个浪费彻底消灭——共享的前缀只算一次,只存一份。有了这个基础,再叠加 Continuous Batching、Chunked Prefill 和高级编程原语,才有了 SGLang「有公共前缀场景比 vLLM 快 2~5×」的竞争力。
选型原则只有一句话:如果你的推理工作有大量公共前缀(RAG / Agent / 多轮),选 SGLang;如果基本没有公共前缀,vLLM 足够了。