Rust in Everything

调度

主线位置:「一个请求的一生」里分词之后、前向之前的那一段:排队、进批、每一步拿多少 token。两个引擎的调度器都是 Python,跑在引擎核心 / 调度器进程里;SGLang 的调度器在按前缀排序时会调用前缀树,那棵树的核心在 Linux 上默认是 Rust(见 树核心)。

版本:vLLM v0.32.0、SGLang v0.5.22

两个引擎都做连续批处理:每一步重新决定这一批里有谁,请求随到随进、做完就走。区别在「一步」的形状:

  • vLLM v1:没有预填充阶段和解码阶段之分。每个请求只有「已算 token 数」和「总 token 数」,调度器在一个 token 预算内让它们往前追。解码(每个请求 1 个 token)和预填充块在同一次前向里。
  • SGLang:每一步先试着组一个预填充批,组得成就只跑预填充;组不成才让运行中的请求解码一步。--enable-mixed-chunk 打开后,解码请求才会搭在预填充批里一起跑,默认关。

这个差别决定了长 prompt 混进来时短请求的解码会不会停。本部分三页:

  1. 连续批处理:vLLM schedule() 先排 running、再排 waiting 的两段循环;token 预算 max_num_batched_tokens 与并发上限 max_num_seqs 的默认值(按显存和入口分档,24 GB 卡上的 API 服务是 2048 和 256);SGLang 的预填充批与解码批、PrefillAdder、调度策略(默认 fcfs,lpm 等按前缀树排序)。
  2. 分块预填充:长 prompt 切块后每步的预算怎么分;两边的开关与默认值;一个按源码规则写的离散步模拟:vLLM 分块时短请求每步都拿到解码 token,SGLang 默认配置下长 prompt 的每个块都让解码停一步,打开混合块后不停。效果的毫秒数待实测(C3-4)。
  3. 抢占:KV 块不够时,vLLM v1 从 running 尾部踢请求、清零重算、放回 waiting 队首;SGLang 在解码前检查显存,按「输出最少、输入最长」先 retract,释放的 KV 不进前缀树,放回队尾,并把新 token 估计比调高。两边单机都没有换出到 CPU(SGLang 只在 PD 分离的解码端备份 KV)。
flowchart LR
  wait["等待队列"] --> sched{"调度器一步"}
  run["运行中"] --> sched
  sched -->|"vLLM:一个预算统一分"| mix["解码 + 预填充块 同一次前向"]
  sched -->|"SGLang:有预填充就先跑"| pre["预填充批"]
  sched -->|"SGLang:没有预填充"| dec["解码批"]
  sched -->|"KV 不够"| out["抢占 / retract 回等待队列"]
  out --> wait

💬 关联讨论

发起讨论
加载中...