vLLM vs SGLang:两个推理引擎的架构哲学

18 分钟阅读

vLLM vs SGLang:两个推理引擎的架构哲学

前情说明:《现代 LLM 推理引擎剖析》讲了 vLLM 的完整实现。这一篇讲同一个赛道另一个明星选手 SGLang——它的技术路线跟 vLLM 有本质差异,理解这两个引擎的哲学分歧,能让你看清整个 LLM Serving 领域的取舍地图。 素材主要来自两处:vLLM Blog 《Anatomy of vLLM》(2025-09-05)和 LMSYS Blog《Fast and Expressive LLM Inference with RadixAttention》(2024-01-17)+《SGLang v0.4》(2024-12-04)。


起手:两个引擎的定位差异

如果 2024 年初你要选一个 LLM 推理引擎:

  • vLLM(UC Berkeley,2023 年 6 月发布)——核心贡献是 PagedAttention,用操作系统级的分页思想管理 KV cache
  • SGLang(同 UC Berkeley 团队,2024 年 1 月发布)——核心贡献是 RadixAttention + 前端 DSL,用编译器思想优化整个 LLM 程序

有意思的是——两个项目是同一个学校甚至有部分重叠人员(Ion Stoica 是两边共同的 mentor)。为什么会出两个不同的引擎?因为它们要解决的问题不一样:

维度 vLLM 想解决 SGLang 想解决
核心痛点 单一 request 的 KV 内存碎片 多 request 之间的重复计算
抽象层 request 内的 token 序列 request 之间的公共前缀
前端 无(就是个 serving 库) 有 DSL(可以写 LLM 程序)
哲学 内存管理器(OS 视角) 编译器 + 缓存(编译器视角)

一句话概括:vLLM 是"给 LLM 做一个操作系统",SGLang 是"给 LLM 做一个编译器 + 缓存"。


一、PagedAttention vs RadixAttention:两种数据结构的哲学

1.1 PagedAttention:把 KV cache 当虚拟内存

vLLM 的 PagedAttention 借鉴 OS 虚拟内存的思想:

  • 把 KV cache 切成固定大小 block(默认 16 token)
  • 每个 request 有自己的 block table(token → block 映射)
  • 全局有个 free block pool(空闲块的双向链表)
Request 1: token_ids = [t0, t1, ..., t31]  (32 tokens)
Block Table: [block_5, block_12]  (2 blocks × 16 tokens)

Free Block Pool (在物理 HBM 上的具体分布):
[block_0, block_3, block_7, block_9, ...]

关键洞察:block 之间不需要物理连续——就像 OS 里一个进程的内存页可以分散在任何物理帧里。

这解决了什么?内存碎片。传统实现(比如 HuggingFace 早期)会给每个 request 预留 max_seq_len 的连续空间——实际用不了那么多就浪费。PagedAttention 让内存利用率从 60% 提到 95%+。

1.2 RadixAttention:把 KV cache 当共享前缀树

SGLang 的思考完全不同:同一个 batch 里的多个 request,很可能有共同的前缀——

场景一:System prompt 共享——1000 个 chat 请求都以同样的 system prompt 开头 场景二:Few-shot examples——所有查询都带同样的 5 个示例 场景三:Tree-of-Thought——同一个问题采样 10 条思路,前缀相同 场景四:多轮对话——第 5 轮请求的前缀是前 4 轮的完整历史

如果每个 request 都各自算一遍前缀的 KV,巨额浪费。

SGLang 的方案是 RadixAttention:

  • 用 radix tree(压缩前缀树)存所有 request 的 KV cache 索引
  • 树的每条边可以是一段 token 序列(不像标准 trie 一条边一个字符)
  • 全局共享——一个 request 存进去的 KV,别的 request 立刻能复用
  • 用 LRU 策略淘汰不常用的分支

来看关键的数据结构层面对比:

PagedAttention:
    request_1  →  [block_5, block_12, block_3]     ┐
    request_2  →  [block_7, block_9, block_15]     ├─ 各自独立
    request_3  →  [block_1, block_4, block_8]      ┘

RadixAttention:
    Root
     ├── "You are a helpful assistant" (共享 system prompt)
     │    ├── "User: hello. Assistant: hi" (chat session 1)
     │    │    └── "User: how are you"
     │    └── "User: what is AI" (chat session 2)
     │         └── "Assistant: AI is..."
     └── "Solve this math problem" (共享 few-shot prefix)
          ├── "Q: 2+2? A: 4"
          └── "Q: 3+5? A: 8"

每一个共享路径 = 一次 KV cache 复用。RadixAttention 是自动发现共享的——用户不需要显式配置什么是 system prompt、什么是 few-shot。

1.3 一图对比:两种数据结构解决什么问题

问题 PagedAttention 解 RadixAttention 解
KV 内存碎片 ✅ 分页 ❌ 不直接解决
单请求内存利用率 ✅ 高 中等
多请求共享前缀 ⚠️ 需要 prefix caching 补丁 ✅ 原生支持
System prompt 复用 ✅(通过 prefix caching) ✅ 原生
多轮对话历史复用 ✅(v0.5+) ✅ 原生
树状生成(Tree-of-Thought / Beam Search) ⚠️ 需要特殊支持 ✅ 天然契合
Few-shot batch 学习 ⚠️ 需要手动配置 ✅ 自动

vLLM 后来也在 PagedAttention 基础上加了 prefix caching(就是给 block hash 建 hash map),功能覆盖也差不多了。但架构哲学的原始差异依然存在——vLLM 是"给 request 分页 + 加个 prefix hash 表",SGLang 是"用树天然表达共享关系"。


二、调度哲学:Continuous Batching vs Zero-Overhead Scheduler

2.1 vLLM V1 的做法

vLLM V1 的 scheduler 做的事情(参考前一篇文章):

  1. 每一步 step():schedule → forward → postprocess
  2. Scheduler 同步阻塞——决定完这一步跑什么,才能发 forward
  3. CPU 和 GPU 之间顺序执行

这是最直觉的模式,但有个隐性开销——CPU 上 scheduling 逻辑跑的时候,GPU 是空转的。

2.2 SGLang v0.4 的 Zero-Overhead Scheduler

SGLang v0.4 引入了一个特别聪明的优化——Zero-overhead batch scheduler:

我们把 CPU scheduling 和 GPU 计算重叠起来。Scheduler 提前一批调度,把下一批的所有 metadata 都准备好。这样 GPU 永远不停。

原理示意:

时间轴:
CPU: [调度批 1] [调度批 2] [调度批 3] [调度批 4] [调度批 5]
GPU:           [跑批 1  ] [跑批 2  ] [跑批 3  ] [跑批 4  ] [跑批 5]
                          ↑ GPU 从批 2 开始永远不停

灵感来自论文 NanoFlow。实现细节:

  • 提前一步计算调度
  • 通过 future tokens 解决依赖问题
  • 精心编排 CUDA event 和 stream 同步

收益:SGLang v0.4 用 Nsight profiler 验证了 5 个连续 decode 批的 GPU 没有一次空闲。相比 v0.3 提速 1.1x,相比其他 SOTA 提速 1.3x。

为什么 vLLM V1 当时没做? 因为 vLLM 的抽象层复杂,同步模型简单可靠。SGLang 从设计之初就走"极致榨干 GPU"路线,愿意为一点点性能吃复杂度。

2.3 调度理念的深层差异

如果把两个引擎的 scheduler 放一起对比:

层面 vLLM V1 SGLang v0.4
同步模型 单批同步执行 一批调度、一批 GPU 计算重叠
调度开销 明显(一半时间可能耗在 CPU) 接近零
实现复杂度 中等 高(future tokens、CUDA event)
稳定性 高 中(需要精细同步)
收益 一批批稳定跑 GPU 永远不空闲

架构哲学:vLLM 走"OS 稳态"路线,SGLang 走"编译器极致优化"路线。


三、分布式负载均衡:DP Coordinator vs Cache-Aware Router

场景:你部署了 8 个 DP 副本,请求进来时打给哪一个?

3.1 vLLM 的策略:负载均衡得分

vLLM 用的是简单的 加权评分:

score = len(waiting) * 4 + len(running)
# 选 score 最低的副本

意思是尽量让队列最空的副本接下一个请求。这是纯"看忙不忙"的策略。

3.2 SGLang 的策略:Cache-Aware Router

SGLang v0.4 引入了 Cache-Aware Load Balancer:

  • Router 维护一个近似的全局 radix tree——记录每个 worker 上缓存了哪些前缀
  • 请求进来时,先查这个近似树,选前缀命中率最高的 worker
  • 用惰性更新,几乎零同步开销

对比:

Round-robin (naïve) Cache-Aware Router
Throughput 82,665 tok/s 158,596 tok/s
Cache hit rate 20% 75%

1.9x throughput, 3.8x cache hit rate——差距惊人。

为什么效果这么大? 因为在共享前缀多的场景里(多轮对话、agent workload),把请求路由到"已经有缓存"的副本,能省下整段 prefill——收益是 prefix 长度的线性倍数。

Router 本身是用 Rust 实现的独立进程,跟 SGLang worker 通信,比 Python 版快 2x。可以横向扩展到多节点。

3.3 哲学差异

vLLM 假设 request 是均匀无状态的,所以负载均衡看"哪个副本最闲"就行。

SGLang 假设 request 是"程序",有共享结构——所以要看"哪个副本已经有相关缓存"。

这个差异体现了两个引擎的世界观:

  • vLLM:LLM = 无状态的 token generator
  • SGLang:LLM = 有状态的、有共享结构的程序

四、DP Attention For DeepSeek:MoE 的特殊优化

SGLang v0.4 还有个专门针对 MLA (Multi-head Latent Attention) 的优化——DP Attention。

背景:DeepSeek 模型用 MLA,只有一个 KV head。如果按标准 TP=8 切分:

  • 8 个 GPU 都保留完整的 KV cache → 8x 冗余 → 显存浪费严重 → batch size 上不去

SGLang 的做法:attention 层走 DP,MoE 层走 TP/EP:

Attention 层 (DP=8):
    每个 GPU 处理自己的 batch,各自的 KV cache 独立
    → KV cache 显存需求 / 8

    ↓ All-Gather

MoE 层 (TP/EP):
    合并所有 batch,跑专家计算

    ↓ Split back

后续 layer...

收益:SGLang v0.4 在 8×H100 上跑 DeepSeek,decoding throughput 提升 1.9x——因为省下的显存全部转成更大 batch。

这个优化在 vLLM 里最近也在跟进,但 SGLang 早了半年。再次印证 SGLang "为特定模型架构做定制优化" 的路线——它更愿意为 SOTA 模型做深度手术。


五、前端 DSL:SGLang 独有的杀手锏

到目前为止讨论的都是 backend。SGLang 有个 vLLM 完全没有的东西——前端 DSL。

一个例子(多维度作文评审):

@sgl.function
def multi_dim_judge(s, essay):
    forks = s.fork(4)  # 并行创建 4 个副本
    forks[0] += "Judge grammar: " + essay
    forks[0] += sgl.gen("grammar_score", choices=["1", "2", "3", "4", "5"])
    forks[1] += "Judge creativity: " + essay
    forks[1] += sgl.gen("creativity_score", choices=["1", "2", "3", "4", "5"])
    # ...
    s += "Merge: " + forks[0]["grammar_score"] + " " + forks[1]["creativity_score"]
    s += sgl.gen("final_grade")

这是在 Python 里嵌入的 DSL——可以:

  • fork 并行分支
  • gen 非阻塞异步生成
  • choices 约束采样空间
  • 自动做共享前缀优化(同一 essay 只算一次 KV)

执行模式两种:

  1. Interpreter 模式:像 Python 一样解释执行
  2. Compiler 模式:先跟踪成 dataflow graph,再由 graph executor 跑——可以做代码移动、指令选择、auto-tuning

为什么这个前端重要? 因为很多 LLM 应用本质上是"程序"——Agent flow、Tree-of-Thought、self-consistency、DSPy pipeline——这些用普通 API 表达起来啰嗦、性能差,用 SGLang DSL 能:

  • 自动利用 RadixAttention 共享前缀
  • 并发多个 gen 调用(不用手写 asyncio)
  • 编译期优化调度顺序

这才是 SGLang 名字的由来——Structured Generation Language。RadixAttention 只是 runtime 支撑,DSL 才是它跟 vLLM 真正拉开差距的地方。

但这也是双刃剑:SGLang 前端有学习成本,OpenAI-compatible API 生态没那么友好。vLLM 走的是"OpenAI API 完全兼容"路线,Drop-in 替换成本极低。


六、当代场景对比:什么时候用谁

到 2026 年,两个引擎都发展得很成熟,vLLM 补齐了 prefix caching,SGLang 补齐了 disaggregated P/D 和 speculative decoding。功能层面差异在缩小。真正的选型逻辑长这样:

6.1 选 vLLM 的场景

  • 通用推理:无特定模型架构偏好,Llama / Qwen / Mistral 等主流模型
  • OpenAI-compatible API 兼容性:需要 drop-in 替换现有 OpenAI 后端
  • 生产稳定性优先:需要成熟的 CI/benchmark 体系(vLLM 有专门的产品质量团队和 nightly benchmark)
  • 异构硬件:需要跑在 AMD / Ascend / ARM CPU 等非 NVIDIA 硬件(vLLM 生态更广)
  • 多样化 workload:既有 chat 又有 batch 生成、code、multimodal 都要

6.2 选 SGLang 的场景

  • DeepSeek / MoE 模型:DP Attention 优化是 SGLang 独门武器(vLLM 后来跟上但 SGLang 早半年)
  • 高共享前缀 workload:agentic workload、tree-of-thought、self-consistency、DSPy pipeline
  • Structured Generation 是刚需:JSON schema decoding、复杂 grammar 约束(SGLang 有 compressed FSM 和 xgrammar 深度集成)
  • 需要 DSL 表达复杂 LLM 程序:想在服务器端写 fork/branch/gen 而不是靠 client 自己 orchestrate
  • 需要 Rust 级性能的 router:cache-aware routing 生产上跑 8+ 副本时收益极大

6.3 一个可能被忽视的事实

很多生产团队最后是"两个都装"——用 SGLang 跑 DeepSeek 主力,vLLM 跑其他模型。因为两个引擎的核心贡献是互补而不是替代:

  • 一个 request 内的内存管理:vLLM 的 PagedAttention 是标杆
  • 多个 request 间的缓存复用:SGLang 的 RadixAttention 是标杆

这两个东西在设计层面本来就该并存。事实也是——vLLM 从 v0.5 开始加了 prefix caching(基本上就是 hash-based 版本的 RadixAttention),SGLang 也在用 paged 布局管理 KV。两者在互相收敛。


七、承前启后:更大的图景

站远一点看,vLLM 和 SGLang 的分歧反映了 LLM 推理领域的一个根本张力:

LLM 是"独立请求的黑盒 API" 还是 "结构化的程序"?

  • 前者视角(vLLM):LLM 是无状态的 token generator,我们优化的是 stateless 请求的吞吐
  • 后者视角(SGLang):LLM 是有共享结构的程序,我们优化的是程序间的结构复用

2026 年,随着 agentic workload 崛起(fin 讲 CPU 完全体那篇的核心论点之一),"程序视角"越来越占上风:

  • Agent 是长时间运行的 stateful 进程
  • 每个任务的 sandbox 需要复制大量共享上下文
  • Tree-of-Thought、self-consistency 天然是并行分支
  • Multi-turn chat 就是共享前缀的最典型 case

RadixAttention 这个设计在 2024 年看起来是"针对特定 workload 的优化",但到 2026 年回看,它其实提前 2 年预判了 agent 时代的到来。这也是为什么 SGLang 在 DeepSeek 上飞、在 agent 上飞——它的核心数据结构就是为这类 workload 生的。

而 vLLM 的胜利在于普世性——它的 PagedAttention 是 KV 内存管理的 canonical 方案,几乎所有后来的引擎都在借鉴。到 v1 版本,vLLM 已经把 prefix caching / disaggregated P/D / speculative decoding 都集成进来,成了事实上的参考实现。

两个引擎,两条路线,共同定义了 2024-2026 的 LLM Serving 状态艺术。


附:素材来源

主线

论文

姊妹篇


整理于 2026-07-31。原文技术细节归属 vLLM 和 SGLang/LMSYS 团队。中文表达可能有解读偏差,请以原文为准。

Zoe

Written by

Zoe

AI Infra Engineer · LLM Serving · GPU/RDMA · 造工具的偏执狂

评论