【infra之路】03_ZeroOverhead调度器与内存管理
2026/6/10 1:07:54 网站建设 项目流程

SGLang Zero-Overhead 调度器与 GPU 显存管理:CPU/GPU 重叠调度与三大内存池

LLM 推理框架的吞吐量不只取决于 GPU 算力。CPU 端的调度开销和显存的分配策略同样关键。SGLang 在这两个维度上分别做了针对性设计:Zero-Overhead Scheduler 让 CPU 调度延迟对 GPU 执行时间零暴露,三大内存池则把 GPU 显存按职责切分为独立区域,各自可调可控。

串行调度的代价

传统推理框架中,CPU 调度和 GPU 计算是串行执行的。CPU 先完成一轮调度(选择请求、前缀匹配、分配显存、排列 batch),然后把组好的 batch 交给 GPU 执行前向传播。GPU 算完当前 batch 后,又要等 CPU 准备下一个 batch。这段 GPU 空闲等待的时间被称为调度空泡(Bubble)。

在 Decode 阶段,这个问题尤其严重。每次前向传播只处理 1 个 token,GPU 的计算时间很短,但 CPU 的调度时间是固定的。实测中,CPU 调度延迟占 GPU 单次执行时间的比例可达 10-20%。换句话说,GPU 有接近五分之一的算力浪费在等 CPU 上。

OverlapThread:让 CPU 和 GPU 各干各的

SGLang 用一个后台线程 OverlapThread 解决这个问题。核心思路是流水线重叠:GPU 执行当前 batch 的同时,CPU 在后台准备下一个 batch。

GPU: [batch A 计算][batch B 计算][batch C 计算][batch D 计算] CPU: [准备 batch B] [准备 batch C] [准备 batch D] [准备 batch E]

当 GPU 正在跑 batch A 的前向传播时,OverlapThread 并行完成 batch B 的全部调度工作,包括 RadixAttention 前缀匹配、显存分配和请求排序。GPU 算完 A 后直接无缝衔接 B,中间没有空泡。

这个设计的前提假设是 CPU 的调度耗时不超过 GPU 的单 batch 计算时间。在 Decode 阶段这个条件天然成立:GPU 即使只算 1 个 token 的前向传播,也涉及整个模型权重的矩阵运算,耗时远大于 CPU 的调度逻辑。实测中,OverlapThread 带来约 1.1 倍的吞吐提升。

Chunked Prefill 与缓存感知负载均衡

除了重叠调度,SGLang 还有两项配套的调度优化。

Chunked Prefill 处理长 prompt 的问题。一个很长的 prompt 进入 Prefill 阶段时,一次性计算全部 token 的 KV Cache 会长时间独占 GPU,阻塞同一 batch 中的 Decode 请求。分块预填充把长 prompt 切成多个 chunk,每个 chunk 只算一部分 token,让 Decode 请求能穿插执行。分块大小通过--chunked-prefill-size控制,值越小 Decode 延迟越平稳,但 Prefill 的总耗时会增加。

Cache-aware Load Balancing 面向多实例部署场景。多个 SGLang 实例并行服务时,各自的 RadixCache 缓存了不同的前缀。新请求到来后,系统优先把它路由到已缓存对应前缀的实例上,最大化缓存命中率。这项优化在 v0.4 版本引入,带来了 1.9 倍吞吐提升和 3.8 倍缓存命中率提升。

GPU 显存的三池划分

调度解决了时间上的效率,显存管理解决空间上的效率。SGLang 把 GPU 显存划分为三个独立的池。

模型权重池(Model Weights)存储模型参数。大小由模型参数量和量化精度决定,启动后固定不变。Llama-3 70B 的 BF16 权重约 140GB,FP8 量化后约 70GB。

KV 缓存池(KV Cache Pool)存放所有活跃请求和已缓存节点的 KV tensor。这是显存中占比最大也最动态的部分。SGLang 用--mem-fraction-static参数控制它占可用显存的比例,默认 0.9,即 KV 缓存池拿走模型权重池之后剩余显存的 90%。

激活值内存(Activation Memory)存放前向传播过程中的中间计算结果。它的大小是 KV 缓存池分配后的剩余空间。正常情况下不需要手动干预,但如果 KV 缓存池占比过高,留给激活值的空间不足,就会在长 prompt 的 Prefill 阶段触发 OOM。

两个核心 Pool 对象

源码层面,SGLang 用两个 Pool 对象维护显存中的映射关系。

req_to_token_pool维护请求到 token 的映射。给定一个请求 ID,通过它可以查到该请求当前所有的 token ID 列表。token_to_kv_pool维护 token 到 KV tensor 的映射。给定一个 token ID,通过它定位这个 token 在 GPU 显存中的 KV Cache 存储位置,这是 Radix Tree 底层存储的基础。

两个 Pool 串联起来,构成请求、token、KV tensor 三级寻址链路:请求 ID 查 token 列表,token ID 查 KV tensor 的显存地址。RadixAttention 的最长前缀匹配最终就是通过这条链路找到可复用的 KV 数据。

关键调优参数

--mem-fraction-static控制 KV 缓存池占可用显存的比例,默认 0.9。遇到 OOM 时把它降到 0.85,给激活值内存腾出更多空间。这是最常需要调整的参数。

--chunked-prefill-size控制分块预填充每块的 token 数,默认由系统自动计算。长上下文场景下(prompt 超过数千 token),手动设一个较小的值(如 4096)可以防止单次 Prefill 的激活值峰值撑爆显存。

--enable-deterministic-inference开启后保证相同输入产生相同输出,适用于需要结果可复现的场景(如回归测试)。关闭状态下性能略好。

一个可迁移的工程判断方法

当你在部署 SGLang(或任何基于 KV Cache 的推理框架)时遇到 OOM,可以按这个思路定位瓶颈。

先看 OOM 发生在哪个阶段。Prefill 阶段报错,大概率是激活值内存不够,降低--mem-fraction-static让 KV 缓存池让出空间给激活值。Decode 阶段报错,大概率是 KV 缓存池本身容量不足,需要降低并发请求数或减小--mem-fraction-static来压缩缓存总量。如果 OOM 发生在长 prompt 的 Prefill 期间,配合调小--chunked-prefill-size限制单次激活值峰值。

这个方法的本质是把显存看作两个池的博弈:KV 缓存池越大能缓存越多请求,激活值内存越大能支撑越长的单次计算。OOM 是两者边界失衡的信号,调参就是在重新划定这条边界。这个判断框架不局限于 SGLang,对 vLLM、TensorRT-LLM 等同类框架同样适用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询