banner
约 6,300 字
21 分钟

SGLang 深度优化:Radix 缓存与复杂任务的极致吞吐实践

摘要

本文从 vLLM 与 SGLang 的定位差异出发,先说明 PD 分离架构为什么是复杂 LLM 服务的底座,再拆解 SGLang 语言与运行时优化,包括 RadixAttention 前缀树缓存、缓存感知调度、压缩有限状态机约束解码和 API 推测执行。最后在 RTX 4090 上用 Qwen3-0.6B 完成五个业务场景实验,并同机对比 vLLM 与 SGLang 的延迟和吞吐差异,给出选型边界。

SGLang 深度优化:Radix 缓存与复杂任务的极致吞吐实践

写在前面

vLLM 解决了模型推理本身的速度问题:PagedAttention 消除 KV Cache 碎片,Continuous Batching 消除 GPU 空闲,推测解码加速 Decode 阶段,PD 分离消除 Prefill 与 Decode 的资源争抢。但业务侧的大语言模型应用已经不只是单次问答,多轮对话、批量评分、结构化 JSON 输出、多次模型调用穿插控制流,这类复杂程序的主要开销在于重复计算相同前缀的 KV Cache,以及格式约束导致的无效生成。SGLang 的定位就是解决业务逻辑的吞吐问题,用前端语言简化编程,用后端运行时自动复用缓存并加速约束解码。

本文主线:先对比 vLLM 与 SGLang 的定位,说明 PD 分离架构为什么是复杂服务的底座;再拆解 SGLang 语言的编程方式与运行时四项优化,即 RadixAttention、缓存感知调度、压缩有限状态机(FSM,Finite State Machine,有限状态机)和 API 推测执行;随后给出环境搭建与五个代表性场景;最后用 Qwen3-0.6B 在 RTX 4090 上完成性能验证,并同机对比 vLLM 与 SGLang,量化 RadixAttention 的价值边界。

项目概览

项目

内容

推理引擎

SGLang,对比 vLLM

模型

Qwen3-0.6B(FP16)

硬件

RTX 4090(24 GB 显存)

服务端口

vLLM 8000,SGLang 8001,同机部署

核心机制

RadixAttention、缓存感知调度、压缩 FSM、API 推测执行

验证场景

基础对话、流式输出、多轮对话、批量任务、JSON 约束输出

关键结果

单请求延迟快 5.0 倍,共享前缀快 2.8 倍,8 并发吞吐高 3.5 倍

结果边界

对比受 vLLM 的 --enforce-eager 与 Qwen3 thinking 模式影响,重在验证趋势

背景、目标与约束

从 vLLM 到 SGLang:优化层次的迁移

先把 vLLM 的优化体系按层次拆开,每一层解决一个独立问题:

技术

优化层面

解决的问题

PagedAttention

内存层

KV Cache 碎片与浪费,按块分配、按需扩展

Continuous Batching

调度层

短请求完成后空等长请求,GPU 空闲

推测解码(EAGLE/MEDUSA)

算法层

Decode 阶段算力空闲,减少生成步数

PD 分离

架构层

Prefill 与 Decode 互相干扰,资源无法特化

这些技术叠加使用时遵循固定逻辑:PagedAttention 是基础设施,贯穿 Prefill 与 Decode 两端;Continuous Batching 在两类节点内部各自运行;推测解码只部署在 Decode 集群;PD 分离是最外层的部署策略,决定前三者在哪里运行。

vLLM 解决的是如何让模型跑得快,SGLang 解决的是如何让业务逻辑跑得快。两者的关系可以类比为:vLLM 像通用数据库 MySQL,SGLang 像带脚本能力的 Redis 加 Lua。简单问答用 vLLM 足够;多轮对话、批量任务、严格 JSON 输出这类场景,SGLang 在缓存复用和约束解码上的收益更明显。

Prefill 与 Decode:两种截然不同的计算特性

LLM 推理分为两个阶段。Prefill(预填充)一次性读入整个 Prompt,计算所有位置的 KV Cache(Key-Value Cache,键值缓存),特征是计算密集型,大量矩阵乘法,GPU 算力是瓶颈,关键指标是 TTFT(Time To First Token,首 Token 延迟)。Decode(解码)基于已生成内容逐个预测下一个 token,特征是内存带宽受限,每一步只算 1 个 token,瓶颈在读取 KV Cache,GPU 计算单元大量空闲,关键指标是 TPOT(Time Per Output Token,每个输出 Token 的耗时)。

两个阶段混在同一 GPU 上交替执行会互相伤害:执行计算密集的 Prefill 时阻塞延迟敏感的 Decode,TPOT 飙升;执行访存密集的 Decode 时计算单元空闲,浪费了 Prefill 需要的算力。PD 分离(Prefill-Decode Decoupling,预填充与解码解耦)把两阶段放到不同 GPU:Prefill 节点作为生产者计算 KV Cache,通过 Connector 异步传输给 Decode 节点,Decode 节点作为消费者边接收边生成。

PD 分离架构工作流程
PD 分离架构工作流程

图 1 是 PD 分离的工作流程示意图:用户请求先进入 Prefill 节点完成 KV Cache 计算,经高速互联传输到 Decode 节点,由 Decode 节点完成自回归生成并返回结果。图中两个节点独立部署,对应正文的核心结论,即两阶段物理隔离后可以分别选择最合适的硬件和 batch 策略。

PD 分离的核心优势有四条:消除干扰,Prefill 不再阻塞 Decode,TPOT 稳定;资源特化,Prefill 用高算力 GPU,Decode 用大显存 GPU;独立扩展,按负载动态调整两类实例比例;Batching 差异化,Prefill 用小 batch,因为其本身已经算力打满,增大 batch 只会增加排队延迟,Decode 用大 batch,把空闲算力利用起来,从内存受限转向算力受限。

vLLM 通过 KV Transfer Connector 实现 PD 分离,Prefill 节点配置 kv_role: kv_producer,Decode 节点配置 kv_role: kv_consumer,传输支持 NVLink、InfiniBand 等高速互联。业界其他实现包括 Mooncake(Kimi 的以 KV Cache 为中心的分离式架构)、NVIDIA Dynamo(声明式 PD 部署)以及 SGLang、llm-d 等。

整体架构

SGLang 由前端语言和后端运行时两部分组成。前端是把 Python 作为宿主语言的领域特定语言,提供生成控制原语(genselect)和并行控制原语(forkjoin);后端是 SGLang Runtime(SRT,SGLang 运行时),负责执行这些原语并应用运行时优化。

SGLang 整体架构与运行时优化(图由AI辅助绘制)
SGLang 整体架构与运行时优化(图由AI辅助绘制)

图 2 是本文的整体架构图:用户请求经 SGLang 语言程序进入解释器或编译器,由 SRT 运行时执行;运行时内部包含 RadixAttention 前缀复用、缓存感知调度、压缩 FSM 约束解码和 API 推测执行四类优化;本地模型与商业 API 模型接入同一套程序语义,输出结果后通过 cache_hit_rate、TTFT、TPOT 等指标反馈调优。这张图对应全文主线,即 SGLang 的价值来自语言层与运行时层的协同设计。

执行模式有两种。解释器模式把提示视为异步流,程序可以在不等待生成完成的情况下继续执行;编译器模式把程序编译成计算图,由图执行器运行,为编译优化留出空间。SGLang 与 LMQL、Guidance 同属低级系统,可以直接操作提示和原语而不改变提示本身,区别在于 SGLang 更专注运行时效率,并配有独立的高性能运行时。

技术选型:SGLang vs vLLM

维度

vLLM

SGLang

选型建议

定位

通用推理引擎,类似 MySQL

复杂 LLM 程序运行时,类似 Redis 加 Lua

简单对话用 vLLM;Agent、多轮、结构化输出用 SGLang

缓存策略

PagedAttention 块级复用

RadixAttention 前缀树复用

多轮对话场景 SGLang 缓存命中率更高

编程方式

OpenAI API 调用

Python 原生嵌入 @function

需要分支、循环、并行控制流时选 SGLang

JSON 生成

靠 Prompt 加后校验

原生约束解码,正则强制

必须输出标准 JSON 的接口用 SGLang 更稳定

兼容性

OpenAI API 兼容

OpenAI API 兼容加 SGLang 原生语法

迁移成本低,可逐步切换

选型的本质是看业务场景:单次问答的缓存价值不大,vLLM 生态更成熟;多轮对话、批量任务、严格格式输出、并行子任务组合时,SGLang 的缓存与约束能力直接转化为成本和延迟收益。

核心实现

SGLang 语言:用 Python 写 LLM 程序

SGLang 提供一组原语控制语言模型的提示状态:function 定义函数,systemuserassistant 表示对话角色,image 处理图像输入,select 从选项中选择最高概率项,fork 创建并行分支,gen 生成文本,run 执行函数。这些原语与 Python 语法完全兼容,可以直接使用 ifforreturn 写控制流。

资料中的多维论文评分程序是理解 SGLang 语言的最佳案例,它使用分支-求解-合并技术,把一个大任务拆成三个维度并行评估再合并。根据资料对代码的逐行说明,核心流程可以整理为如下伪代码:

Python
from sglang import function, system, user, assistant, select, fork, gen

dimensions = ["Clarity", "Originality", "Evidence"]

@function
def multi_dimensional_judge(s, path, essay):
    # 1. 添加系统指令与用户请求,先判断文章与图片是否相关
    s += system("你是一个严谨的论文评审员")
    s += user("判断文章与图片是否相关", image=path)
    s += assistant(select(["相关", "不相关"]))
    if s["related"] == "不相关":
        return

    # 2. fork 创建并行分支,每个维度独立评分
    branches = []
    for d in dimensions:
        with fork() as f:
            f += user(f"请从维度 {d} 评估文章:{essay}")
            f += assistant(gen(f"score_{d}"))
        branches.append(f)

    # 3. join 合并各分支结果
    for f in branches:
        s += f

    # 4. 按正则约束生成 JSON 总结与总分
    s += assistant(gen("summary", regex=SCHEMA))
    return s

SGLang 多维论文评分程序示例
SGLang 多维论文评分程序示例

图 3 是资料中该程序的原始代码示例,红色标注了 SGLang 提供的原语。对比手写 Prompt,这段程序的关键收益有三个:一是 if s["related"] == "不相关": return 让模型判断结果直接决定分支走向;二是 fork 表面上只是 for 循环,运行时却让三个维度并行生成,并通过 RadixAttention 复用共同上下文;三是 regex=SCHEMA 在解码层面强制 JSON 格式,比提示词约束可靠得多。

RadixAttention:前缀树级的 KV Cache 复用

自回归解码中每一步输出都依赖前一步,同一系统提示、同一段对话历史会在多次调用中被反复处理。RadixAttention 用基数树(Radix Tree)管理 token 序列与 KV Cache 张量的映射,允许快速的前缀搜索、重用、插入和逐出。

基数树与传统前缀树的差别在于边的标记方式:传统 Trie 的每条边只标一个字符,查找“北京市朝阳区”要经过六个节点;基数树的边可以标记一段连续序列,例如直接把“北京市”作为一条边,下面再分叉“朝阳区”和“海淀区”,查找效率大幅提升。KV Cache 张量以非连续、分页的布局存储,每页对应一个 token,索引关系由树结构维护。

配合 LRU(Least Recently Used,最近最少使用)逐出策略:显存不足时优先逐出最久未使用的叶子节点,公共祖先保留,因为其他请求仍可能复用。这相当于先抽掉最具体的档案,保留“北京市朝阳区”这个公共文件夹。

AI 客服中心场景中 RadixAttention 的动态演变
AI 客服中心场景中 RadixAttention 的动态演变

图 4 是 AI 客服中心场景下 RadixAttention 在 9 个时间点的动态演变:绿色节点表示新建的缓存,蓝色节点表示被复用的缓存,红色节点表示被 LRU 驱逐的缓存。核心观察有三点:同一客户第二轮对话直接复用第一轮前缀;新客户加入时节点拆分,多个客户共享系统提示;显存不足时驱逐最久未使用的叶子,保留公共祖先。这张图对应一个结论,即 RadixAttention 的收益来自前缀共享和分层逐出。

根据 LMSYS 的研究,在 A10G GPU 上使用 Llama-7B 与 Mixtral-8x7B 模型测试,与 Guidance 和 vLLM 等现有系统相比,采用 RadixAttention 的 SGLang 实现了最高 5 倍的吞吐量提升;论文端到端测试中,Llama-7B 上吞吐量最高提升 6.4 倍,延迟最高降低 3.7 倍。

缓存感知调度

缓存命中率定义为缓存命中的 token 数除以提示 token 总数。当等待队列中有大量请求时,执行顺序显著影响命中率:如果频繁在不同请求之间切换,刚加载的缓存马上被换出,产生缓存抖动。SGLang 的缓存感知调度按匹配前缀长度对请求排序,优先处理前缀匹配更长的请求,而不是先来先服务(FCFS,First Come First Served)。类比机场安检,让去同一目的地的乘客连续过安检,设备状态不用反复切换。

调度的核心步骤为:取出等待队列请求,用基数树匹配每个请求输入 token 的前缀,按匹配长度排序,结合可逐出缓存与可用内存选择下一批请求,执行后更新基数树引用计数,并把完成的请求插入树中供后续复用。

约束解码:压缩有限状态机

LLM 应用经常要求输出符合 JSON 等特定格式。传统做法把正则表达式转换成 FSM,解码时维护当前状态、检索允许的 token、把非法 token 的概率置零,逐个 token 解码。问题在于常量序列(例如 {"summary": )本来只有一个有效后续,逐 token 解码仍然要多次前向传播,FSM 与模型运行器之间缺乏集成,无法合并解码步骤。

SGLang 的压缩 FSM 把 FSM 中相邻的单次转换边压缩成一条边,识别出可以一次解码多个 token 的片段,在单次前向传播中同时解码,并且适用于所有正则表达式。

正常有限状态机与压缩有限状态机的解码过程
正常有限状态机与压缩有限状态机的解码过程

图 5 对比了正常 FSM 与压缩 FSM 的解码过程:常规方式下常量序列跨多个解码阶段,压缩后相邻转换边合并,多个 token 在一次前向中完成。这张图对应约束解码章节的核心结论,即压缩边减少了前向传播次数,长序列和复杂约束场景收益最大。

约束解码与 Prompt 工程的对比:

对比维度

Prompt 工程(传统方式)

约束解码(SGLang)

格式正确率

约 90%-95%,总有意外

100%,物理上不可能输出非法格式

错误处理

需要 try-except 加重试逻辑

不需要,一次生成就是合法的

生产稳定性

可能因格式错误报警

输出结构恒定

API 推测执行

商业模型 API(如 GPT、Claude)只能黑盒访问,每次 gen 对应一次 API 调用,既耗时又计费。API 推测执行在第一次调用时忽略停止条件,多生成一些 token 并缓存,后续原语直接从缓存中匹配复用,减少 API 调用次数。适用条件很明确:程序结构规律(如固定提取 name、job、age 三个字段),能接受 10%-20% 的 token 浪费换取延迟和成本下降;程序有复杂分支或使用本地开源模型时不应使用,本地模型直接用 RadixAttention 更划算。

环境与部署

环境准备与安装

SGLang 与 vLLM 存在依赖冲突(sgl-kerneltriton 等),必须使用独立虚拟环境,并且选择 CUDA 12.4 镜像。CUDA 11.8 环境下 FlashInfer 和 sgl-kernel 都会报错,不要试图就地修复,直接换镜像最快。

安装顺序很重要,资料中的顺序为:升级 pip;无依赖模式安装 sgl-kernelpip install sgl-kernel --force-reinstall --no-deps);安装 sglang[all] 并指定 FlashInfer 的 cu124/torch2.5 版本 wheel;最后固定安装 transformers==4.57.1,因为 transformers 5.x 会报错。

启动服务与推荐参数

启动 Qwen3-0.6B 服务前先设置 CUDA 库路径解决 libnvrtc.so.12 错误,然后启动服务:

bash
export LD_LIBRARY_PATH=/root/autodl-tmp/sglang_env/lib/python3.10/site-packages/nvidia/cuda_nvrtc/lib:$LD_LIBRARY_PATH

python -m sglang.launch_server \
    --model-path /root/autodl-tmp/models/qwen/Qwen3-0.6B \
    --port 8001 \
    --host 0.0.0.0 \
    --mem-fraction-static 0.6

RTX 4090 上运行 Qwen3-8B 的推荐配置给出了更完整的参数组合:

bash
python -m sglang.launch_server \
    --model-path /root/autodl-tmp/models/qwen/Qwen3-8B \
    --port 8001 \
    --host 0.0.0.0 \
    --mem-fraction-static 0.85 \
    --max-running-requests 16 \
    --context-length 4096 \
    --chunked-prefill-size 2048 \
    --enable-metrics

常见启动参数及其作用:

参数

说明

--port 8001

API 服务端口号

--host 0.0.0.0

允许外部访问

--mem-fraction-static 0.6

GPU 显存静态占用比例,小模型低一些,大模型可到 0.85

--tp-size 2

张量并行,多 GPU 时使用

--context-length 4096

最大上下文长度,越短越省显存

--chunked-prefill-size 2048

分块预填充大小,降低首 Token 延迟

--max-running-requests 16

最大并发请求数

--enable-metrics

开启 Prometheus 格式监控指标

--chat-template qwen3

强制指定对话模板,报错时手动指定

启动成功后会输出 Running on http://0.0.0.0:8001,可用 curl http://localhost:8001/health 做健康检查,用 curl http://localhost:8001/v1/models 查看模型信息。

常见问题排查

问题排查集中在四类:CUDA 版本不对,用 nvcc --versionnvidia-smi 检查,11.8 直接换 12.4 镜像;PyTorch 的 CUDA 版本与系统不匹配,手动安装 cu124 的 PyTorch wheel,对应 torch==2.5.1torchvision==0.20.1torchaudio==2.5.1;依赖冲突,删除旧环境重新创建纯净环境;显存不足,降低 --mem-fraction-static 到 0.5-0.6,减小 --context-length,换更小模型,或使用 --quantization awq 加载量化模型。

代表性场景

场景一:基础对话

基础对话与 OpenAI API 完全一致,已有代码只需改 base_url 即可迁移:

Python
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8001/v1",
    api_key="not-needed"
)

response = client.chat.completions.create(
    model="Qwen3-0.6B",
    messages=[
        {"role": "system", "content": "你是一个有帮助的AI助手。"},
        {"role": "user", "content": "用一句话解释什么是大语言模型。"}
    ],
    temperature=0.7,
    max_tokens=200
)
print(response.choices[0].message.content)

场景二:流式输出

流式输出设置 stream=True,逐块接收并打印,适合聊天机器人和实时翻译:

Python
stream = client.chat.completions.create(
    model="Qwen3-0.6B",
    messages=[{"role": "user", "content": "写一首关于春天的五言绝句。"}],
    stream=True,
    max_tokens=200
)
for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

场景三:多轮对话

多轮对话是 SGLang 的优势场景,RadixAttention 自动缓存之前轮次的 KV Cache:

Python
messages = [
    {"role": "system", "content": "你是一个电商客服,帮助用户查询订单和处理退款。"}
]
user_inputs = [
    "我的订单 2024001 到哪了?",
    "那这个订单可以退款吗?",   # 第二轮复用第一轮的 KV Cache
    "退款需要多久到账?",        # 第三轮复用前两轮的 KV Cache
]
for user_input in user_inputs:
    messages.append({"role": "user", "content": user_input})
    response = client.chat.completions.create(
        model="Qwen3-0.6B",
        messages=messages,
        max_tokens=300,
        temperature=0.7
    )
    reply = response.choices[0].message.content
    messages.append({"role": "assistant", "content": reply})

第一轮对话时系统 Prompt 与用户消息的 KV Cache 被计算并存入基数树,第二轮不变的前缀直接命中缓存,只需计算新增的用户消息;轮次越多缓存复用越多,资料中给出的结论是首 Token 延迟可降低 50% 以上。

场景四:批量任务处理

批量任务共享同一个 Prompt 模板,是 RadixAttention 收益最大的场景。以文章批量评分为例,系统提示词是共享前缀,只计算一次:

Python
SYSTEM_PROMPT = """你是一个专业的文章评审员。请从以下三个维度评估文章:
1. 清晰度(1-10分):逻辑是否清晰,表达是否流畅
2. 深度(1-10分):是否有深入分析,不是泛泛而谈
3. 实用性(1-10分):对读者是否有实际参考价值
请用 JSON 格式返回评分结果。"""

def score_article(article):
    response = client.chat.completions.create(
        model="Qwen3-0.6B",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"请评估以下文章:\n\n{article}"}
        ],
        max_tokens=1000,
        temperature=0.3
    )
    return response.choices[0].message.content

with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
    results = list(executor.map(score_article, articles))

资料给出的量化对比:用 vLLM 处理 100 篇文章,系统 Prompt(约 100 tokens)被重复计算 100 次;SGLang 通过 RadixAttention 只计算一次,相当于省了约 9900 tokens 的 Prefill 计算量,文章越多节省越大。

场景五:JSON 约束输出

对接下游系统时,用 SGLang 原生 API 的 regex 参数做约束解码,输出一定是合法 JSON:

Python
response = requests.post(
    "http://localhost:8001/generate",
    json={
        "text": "Extract the person's information from the following text.\n\n"
                "Text: My name is Zhang Wei, I am 28 years old, I work as a "
                "software engineer at ByteDance.\n\nOutput:",
        "sampling_params": {
            "max_new_tokens": 200,
            "temperature": 0,
            "regex": r'\{"name": "[^"]+", "age": \d+, "company": "[^"]+", "role": "[^"]+"\}'
        }
    }
)
result = json.loads(response.json()["text"])

输出结果为:

JSON
{
  "name": "Zhang Wei",
  "age": 28,
  "company": "ByteDance",
  "role": "software engineer"
}

性能验证与监控

性能验证脚本包含三组测试:冷启动(第一次请求,无缓存)、缓存命中(重复相同 Prompt 十次)、共享前缀(20 个请求共享同一前缀,10 并发)。结果如下:

测试

结果

冷启动测试

平均延迟 298 ms

缓存命中测试

平均 208 ms,最小 206 ms,最大 213 ms

共享前缀测试

20 个请求总耗时 381 ms,平均每请求约 19 ms

缓存命中率是 SGLang 的核心监控指标,启动时加 --enable-metrics 后通过 Prometheus 格式暴露:

纯文本
curl http://localhost:8001/metrics

sglang:num_running_requests    # 当前正在运行的请求数
sglang:num_waiting_requests    # 等待队列中的请求数
sglang:token_usage             # Token 使用统计
sglang:cache_hit_rate          # 缓存命中率(RadixAttention 核心指标)

不同场景的缓存命中率参考值:多轮对话通常 60%-80%,批量任务(共享模板)通常 80%-95%,完全随机的单轮问答接近 0%。命中率越低,SGLang 相对 vLLM 的优势越不明显。

对比结果与评估

实验设置

对比实验在 RTX 4090(24 GB)上运行,模型为 Qwen3-0.6B,vLLM 与 SGLang 同机部署,分别占用 8000 和 8001 端口。为保证互不抢占资源,两个引擎各分配 40% 显存:vLLM 使用 --gpu-memory-utilization 0.4,SGLang 使用 --mem-fraction-static 0.4

先单独测试 vLLM 基线,覆盖冷启动、缓存命中、共享前缀、多轮对话、并发吞吐五个维度:

测试项

结果

说明

冷启动延迟

1482 ms

首次请求,无 KV Cache

缓存命中延迟

1061 ms(10 次平均)

加速比 1.40 倍,Prefix Caching 生效

共享前缀(20 请求)

8722 ms

平均每请求 436 ms

多轮对话

2183 / 1895 / 1907 ms

后续轮次有缓存加速但幅度有限

并发吞吐(8 并发)

QPS 6.41,吞吐 641.3 tok/s

20 个请求,总耗时 3.12 s

同一台机器上使用相同测试逻辑测试 SGLang:

测试项

结果

说明

冷启动延迟

315 ms

首次请求即很快

缓存命中延迟

222 ms(10 次平均)

最低 206 ms,RadixAttention 高效复用

共享前缀(20 请求)

432 ms

平均每请求仅 21.6 ms

同脚本直接对比

最后用同一个脚本同时连接两个引擎,保证请求、参数和测试条件完全一致:

指标

vLLM

SGLang

对比

单请求延迟(ms)

1051.8

209.7

SGLang 快 5.0 倍

共享前缀耗时(ms,20 请求并发)

1071.2

378.9

SGLang 快 2.8 倍

QPS(8 并发)

6.47

22.43

SGLang 高 3.5 倍

吞吐(tok/s)

647.2

2242.5

SGLang 高 3.5 倍

差异来源需要拆开看。单请求延迟差距较大的一部分原因是配置不对称:vLLM 使用 --enforce-eager 禁用了 torch.compile,且 Qwen3 默认开启 thinking 模式,vLLM 端生成 token 数远多于未触发 thinking 的 SGLang 端,延迟差异被放大。共享前缀场景的差距则来自缓存机制本身:SGLang 的 RadixAttention 通过基数树自动识别 20 个请求的共同前缀,KV Cache 只计算一次;vLLM 的 Prefix Caching 是块级匹配,粒度不如基数树灵活。并发吞吐的差距叠加了缓存感知调度,SGLang 优先处理命中率高的请求,减少重复计算。

公平性说明:两个引擎使用相同模型、相同硬件、相同 40% 显存占比;但 vLLM 使用 --enforce-eager,SGLang 使用默认配置,且 Qwen3 在 vLLM 中默认触发 thinking 模式,生成更多 token。实际生产的差距会随场景和配置变化,本次对比重在验证 RadixAttention 的价值趋势,不把具体倍数当作绝对结论。

问题、限制与改进

第一,对比实验存在配置不对称。--enforce-eager 和 thinking 模式差异会放大单请求延迟差距,要得到严格公平的基准,需要统一关闭 thinking、统一 eager 与编译模式,再分别测量。

第二,SGLang 的优势集中在缓存可复用的场景。完全随机的单轮问答缓存命中率接近 0%,此时 RadixAttention 没有发挥空间,SGLang 相比 vLLM 的优势不明显,简单问答选择 vLLM 更合适。

第三,API 推测执行依赖预测准确性。程序分支复杂或下一步难以预测时,多生成的 token 无法被复用,反而增加调用成本;商业 API 场景下官方 Prompt Caching 是更成熟的选择。

第四,环境依赖严苛。SGLang 要求 CUDA 12.4、特定版本的 sgl-kernel、FlashInfer 与 transformers==4.57.1,与 vLLM 环境不能共存,多引擎部署需要独立环境和显存预算分配。

工程启发

选型先看业务场景。vLLM 适合简单单轮问答、需要 EAGLE/MEDUSA 推测解码、与现有 OpenAI API 代码无缝迁移、依赖社区生态的场景;SGLang 适合多轮对话(客服、面试)、批量任务共享前缀、结构化输出(JSON 约束解码)、复杂 LLM 程序(fork/join 并行)的场景。

监控层面,把 cache_hit_rate 作为 SGLang 服务的首要指标,它直接反映 RadixAttention 的复用程度;配合 num_running_requestsnum_waiting_requests、TTFT、TPOT 判断瓶颈在缓存、调度还是解码。多轮和批量场景若命中率低,优先检查提示结构是否稳定,避免每次请求都改变前缀。

调度层面,缓存感知调度给了一个通用启发:同类请求聚合处理,优先复用已有计算状态,而不是严格按到达顺序执行。这个思想同样适用于 RAG 查询、批处理任务和缓存系统设计。

小结

SGLang 的优化体系可以归纳为三层:架构层用 PD 分离把 Prefill 与 Decode 解耦,让两类节点各自特化;缓存层用 RadixAttention 和缓存感知调度,在前缀树粒度复用 KV Cache;解码层用压缩 FSM 保证结构化输出一次生成合法。实测数据表明,在多轮、批量、结构化输出这类业务逻辑复杂的场景中,SGLang 的延迟和吞吐优势显著,但优势边界与业务结构强相关,选型应基于场景判断,而不是引擎能力对比的单一数字。

END