框架对比:vLLM 与 SGLang、LMCache 与 Mooncake
Serving 与性能 · 组件选型 · 推理过程动态图解 · LMCache + Mooncake 联合链路
这一页做两组对比:vLLM 和 SGLang 是同一层的推理引擎,比较它们怎样调度、怎样管理 GPU 上的 KV、怎样执行;LMCache 和 Mooncake 不在同一层,比较它们在 KV 的管理、存储和传输上各负责什么、怎样组合。对比基于资料使用的源码版本(vLLM f3871c3、SGLang 30dc2f9、LMCache ff9185d、Mooncake dd1217b),两边都在快速演进,新版本要重新核对。
先说结论:vLLM 和 SGLang 的核心思路越来越接近(连续批处理、分页 KV、前缀缓存、重叠调度都有),差别主要在前缀索引的数据结构、调度时是否考虑缓存、以及生态和扩展方式。LMCache 和 Mooncake 不是二选一:LMCache 懂“KV 是什么”,Mooncake 擅长“把字节可靠高速地存到集群里”,常见做法是把 Mooncake Store 作为 LMCache 的后端。
1. vLLM 与 SGLang:调度与执行
| 维度 | vLLM(V1 引擎) | SGLang | 怎样理解差别 |
|---|---|---|---|
| 进程结构 | API 服务进程 + EngineCore 进程(调度和执行),多卡时再有 worker 进程 | TokenizerManager → Scheduler → 模型 worker → DetokenizerManager,多个进程通过 IPC 串起来 | 都把 CPU 上的分词、调度和 GPU 执行拆开,减少互相阻塞 |
| 每轮选谁 | 统一 token 预算:先满足正在运行的请求,剩余预算给等待队列;长 Prefill 自动切块 | 可选调度策略:考虑缓存的(如最长前缀匹配 lpm)和不考虑缓存的(如 fcfs);同样支持 chunked prefill | SGLang 可以优先调度能命中更长前缀的请求,提高复用;代价是可能牺牲先来先服务的公平性 |
| 显存不够时 | 抢占:释放被抢占请求的 KV 块,之后重算 | 同样有撤回(retract)请求和按缓存情况决定的准入 | 都靠“重算”换回显存,抢占越多浪费越多 |
| CPU 与 GPU 重叠 | 可开启异步调度,让下一轮的调度和本轮 GPU 计算重叠 | 重叠调度器:CPU 准备下一轮时 GPU 在算本轮(有关闭开关) | Decode 每轮很短时,CPU 调度开销可能占到不小比例,重叠能明显提高 GPU 利用率 |
| 图优化 | torch.compile + CUDA Graph(分段和完整两种) | CUDA Graph,也支持 torch.compile | 都是为减少小 kernel 的启动开销,主要帮 Decode |
| 结构化输出 / 前端 | 支持结构化输出后端 | 起源于“结构化生成语言”,前端 DSL 和受约束解码是传统强项 | 多轮、分支、受约束生成多的场景,SGLang 的前缀复用更容易发挥 |
| 分布式与 PD | TP/PP/DP/EP;PD 分离通过 KV Connector(NIXL、LMCache、Mooncake 等) | TP/PP/DP/EP、DP attention;PD 分离内置,传输后端可选 Mooncake、NIXL 等 | vLLM 走统一的 Connector 接口扩展;SGLang 把更多能力做成内置选项 |
2. vLLM 与 SGLang:GPU 上的 KV 管理
两者都用分页显存,区别在于“怎样找到可复用的前缀”:vLLM 给每个装满的块算一条 hash 链,用 hash 表查找;SGLang 用一棵基数树(radix tree),从根往下按 token 匹配。
| 维度 | vLLM:块 hash(APC) | SGLang:RadixCache |
|---|---|---|
| 索引结构 | hash 表:块 hash → 物理块;hash 包含父块 hash | 基数树:每个节点存一段 token 及其 KV 位置,根到节点的路径就是前缀 |
| 匹配粒度 | 按块(默认 16 个 token),只匹配装满的块 | 按 token 或按页(页大小可配置),可以匹配到更细的位置 |
| 部分重叠的前缀 | 天然支持:前几个块 hash 相同就共享 | 在分界处把节点拆成两段(split) |
| 保护正在用的数据 | 块的引用计数 ref_cnt | 节点的 lock_ref,从被引用的节点一直锁到根 |
| 淘汰 | 引用为 0 的块按空闲队列顺序(近似 LRU)被重新分配时淘汰 | 从 lock_ref = 0 的叶子节点里按最久未访问淘汰 |
| 分层缓存 | 通过 KV Connector 接外部缓存(LMCache、Mooncake 等) | HiCache:GPU、主机内存、外部存储三层,可接 Mooncake 等后端 |
基数树的工作过程(vLLM 的块 hash 过程见块管理器深入):
- SGLang 用一棵基数树(radix tree)管理所有缓存的 KV。每个节点保存一段 token 和对应的 KV 位置;从根到某个节点的路径就是一段完整前缀。根节点永远锁定。
- 请求 1 没有命中任何前缀,算完后把“S + Q1”整段作为一个节点插入。运行期间节点的 lock_ref = 1,不能被淘汰。
- 请求 1 结束,lock_ref 归零,但节点和 KV 仍留在树里,等待以后复用。
- 请求 2 的前缀 S 和已有节点部分相同:在 S 和 Q1 的分界处把节点拆成两段(_split_node),S 被两个请求共享,只需要为 Q2 计算新的 KV。正在使用的路径都会被加锁。
- 请求 3 的前缀完全不同,从根节点开出新分支。树可以同时保存很多不同的前缀。
- 需要空间时,从“lock_ref = 0 的叶子节点”里按最后访问时间淘汰(用堆找最旧的)。叶子被删后,如果父节点也变成没被锁的叶子,下一轮也可能被淘汰。正在使用的路径永远不会被删。
面试怎么答“哪个好”:没有绝对的好坏。基数树对大量共享前缀、多轮分支对话的匹配更细、更直观;块 hash 实现简单、天然适合按块管理和跨进程传输(hash 可以直接作为外部缓存的 key)。真正的性能差异要在同样模型、同样负载、同样硬件下测,而且要分开看前缀命中、调度和 kernel 的贡献。
3. LMCache 与 Mooncake:各管哪一层
| 维度 | LMCache | Mooncake |
|---|---|---|
| 定位 | 推理引擎和存储之间的 KV 管理层 | 以 KV 为中心的分布式存储与传输系统(Store + Transfer Engine) |
| 懂不懂 KV 语义 | 懂:按 chunk(常用 256 token)切分、算前缀 hash、区分 rank 和组、处理布局 | 基本不懂:Store 看到的是 key 和字节,TE 看到的是地址和长度 |
| 和引擎的接口 | vLLM KV Connector、SGLang 集成;负责 lookup、retrieve、store 与设备拷贝 | 被上层调用:LMCache 后端、SGLang HiCache 后端、vLLM Connector、PD 交接通道 |
| 元数据 | 每个 LMCache 服务管理自己的 L1;多实例可选 Coordinator | 中心化 Master 管对象位置、副本、租约;可部署高可用 |
| 存储介质 | L1 主机内存(自己管);L2 交给适配器:本地磁盘、Redis、Mooncake、NIXL、对象存储等 | 各节点贡献的内存段为主,也可下沉到本地盘、DFS 等 |
| 写入可见性 | L1 先 staging,填完再发布 | PutStart 分配 → 写数据 → PutEnd 发布 |
| 读保护 | L1 读写保留;L2 适配器层的锁约定 | 读租约(默认 10 秒)、soft/hard pin |
| 淘汰 | L1 按策略(如 LRU)淘汰;不淘汰正在读写的对象 | 用量超过高水位(默认 90%)后淘汰约 5%,跳过有租约、有 pin 的对象 |
| 传输 | GPU ↔ L1 的设备拷贝;L2 传输交给后端 | Transfer Engine:RDMA 多网卡、按拓扑选路、slice 切分、失败重试 |
| 容错 | 主要依赖后端;失败时回退重算 | 多副本、Master 高可用;副本全丢时上层回退重算 |
| 额外能力 | CacheBlend(非前缀复用)、CacheGen(压缩)、逐层加载、可观测性 | PD 交接、Conductor(全局前缀索引)、EP 通信、权重传输等 |
3.1 怎样选、怎样组合
| 场景 | 推荐 | 原因 |
|---|---|---|
| 单机多轮对话,主机内存够用 | LMCache(L1 主机内存)或引擎自带的分层缓存 | 不需要分布式存储,少一层复杂度 |
| 多台机器共享长文档、长系统提示 | LMCache + Mooncake Store 作为 L2 | LMCache 处理语义和设备拷贝,Mooncake 提供跨节点内存池和 RDMA |
| SGLang 部署 | HiCache + Mooncake 后端 | SGLang 自己管理分层,直接用 Mooncake 做外部层 |
| PD 分离,只需要把本次请求的 KV 交给 Decode | Mooncake Transfer Engine 或 NIXL | 这是一次性的点对点传输,不需要对象存储 |
| 非前缀片段复用(RAG 文档顺序变化) | LMCache CacheBlend | 需要选择性重算,属于 KV 语义层的能力,要评估质量 |
完整的保存与恢复链路、三层保护的区别,见 LMCache + Mooncake 联合链路。
4. 更多常见对比
面试和选型中还经常遇到下面几组“同类方案怎么选”的问题。
4.1 四种并行方式(动态演示见进阶动态图解)
| 方式 | 切什么 | 通信 | 适合 | 主要问题 |
|---|---|---|---|---|
| TP 张量并行 | 每层的权重矩阵 | 每层 all-reduce,频繁且在关键路径上 | 单卡放不下、需要降低单请求延迟;机内 NVLink | 跨机做 TP 通信代价很高 |
| PP 流水线并行 | 按层分段 | 段与段之间传激活,量小 | 模型特别大、跨机部署 | 流水线气泡;单请求延迟不降 |
| DP 数据并行 | 不切模型,复制多份 | 推理时几乎没有 | 提高总吞吐 | 每份都要完整权重;前缀缓存分散在各副本 |
| EP 专家并行 | MoE 的专家 | 每个 MoE 层两次 all-to-all | 大 MoE 模型 | 负载不均、网络敏感、故障影响面大 |
4.2 PD 分离与混合部署
| 维度 | 混合部署 + chunked prefill | PD 分离 |
|---|---|---|
| Decode 受 Prefill 干扰 | 有,但被切块后变小 | 几乎没有 |
| 额外开销 | 无 | 每个请求传一次 KV,需要高速网络 |
| 扩缩容 | 整体扩缩 | Prefill 池、Decode 池分别扩缩,可用不同硬件 |
| 复杂度 | 低 | 高:路由、传输、故障处理都更复杂 |
| 适合 | 中小规模、输入不太长 | 大规模、长输入、对 ITL 的 P99 要求严格 |
4.3 SGLang HiCache 与 LMCache
| 维度 | HiCache | LMCache |
|---|---|---|
| 位置 | SGLang 内置,和 RadixCache 一体 | 独立的缓存层,可服务 vLLM、SGLang |
| 层级命名 | GPU 为 L1、主机内存为 L2、外部存储为 L3 | 主机内存为 L1、外部后端为 L2 |
| 和调度的结合 | 直接参与 SGLang 的前缀匹配和调度 | 通过 Connector 接口告诉引擎能复用多少 |
| 部署 | 随 SGLang 进程 | 可作为独立进程(MP 模式),引擎重启后缓存仍在 |
| 外部存储 | Mooncake 等后端 | 多种 L2 适配器,含 Mooncake |
4.4 引擎内前缀缓存与外部 KV 缓存
| 维度 | 引擎内(APC、RadixCache) | 外部(LMCache、Mooncake 等) |
|---|---|---|
| 容量 | 受 GPU 显存限制 | 主机内存、远端内存、磁盘,可大很多 |
| 命中后的成本 | 几乎为零,直接用 | 要加载:查询 + 网络 + H2D |
| 跨实例共享 | 不能 | 可以 |
| 进程重启 | 缓存丢失 | 可以保留 |
| 结论 | 两者互补:引擎内缓存负责最热的部分,外部缓存负责“放不下、跨实例、要保留”的部分;实验时要分开统计两者的命中 | |