Scheduler event loop 矩阵与运行模式切换#
这章解决什么问题#
运行时架构前面已经把分层、装配、消息织网和 data parallel controller 讲出来了,但还有一个非常容易让读者困惑的点没有单独展开:Scheduler 并不是只有一个 event loop。根据 overlap、pipeline parallel、pdmux 以及 disaggregation mode 的组合,系统会进入一组不同的 event loop 变体。
这一章的目标,就是把这组“模式矩阵”讲清楚。
为什么这层值得单独成章#
很多读者第一次读 scheduler.py 时,会天然把 event_loop_normal() 当成唯一主循环,然后在后面遇到 event_loop_overlap()、event_loop_*_disagg_prefill()、event_loop_*_disagg_decode() 时逐渐迷失。实际上,源码已经把这件事抽象得很明确:dispatch_event_loop(scheduler) 会根据一组模式开关选择不同主循环。
一本更像大部头技术书的作品,不应该让读者在这里靠自己“猜矩阵”,而应该把矩阵写出来。
一张图:Scheduler 主循环不是单条,而是一组模式选择#
这张图解决的理解障碍是:读者容易把不同 event loop 看成互不相干的特殊实现,而不是同一调度器在不同模式下的主循环矩阵。
flowchart TD
Start["dispatch_event_loop(scheduler)"] --> Mode["disaggregation_mode"]
Mode --> Null["NULL"]
Mode --> Prefill["PREFILL"]
Mode --> Decode["DECODE"]
Null --> N1["pdmux / pp / overlap / normal"]
Prefill --> P1["pp_disagg_prefill / overlap_disagg_prefill / normal_disagg_prefill"]
Decode --> D1["pp_disagg_decode / overlap_disagg_decode / normal_disagg_decode"]图比纯文字多解释的一点是:Scheduler 的主循环选择,是一棵正式的模式树,而不是零散的 if/else 补丁。
run_event_loop() 的意义:先建调度 stream,再选主循环#
scheduler.py 的 run_event_loop() 很短,但很关键:
- 先创建
schedule_stream - CPU 情况下再做一个 no-op synchronize 修正
- 最后进入
dispatch_event_loop(self)
这说明 event loop 选择并不是在 scheduler 外部偷偷决定的,而是 scheduler 自己运行入口的一部分。
dispatch_event_loop(...) 已经把模式树写得很清楚#
源码里这棵树大致是:
DisaggregationMode.NULLenable_pdmux->event_loop_pdmux()pp_size > 1->event_loop_pp()enable_overlap->event_loop_overlap()- else ->
event_loop_normal()
DisaggregationMode.PREFILLpp_size > 1->event_loop_pp_disagg_prefill()enable_overlap->event_loop_overlap_disagg_prefill()- else ->
event_loop_normal_disagg_prefill()
DisaggregationMode.DECODEpp_size > 1->event_loop_pp_disagg_decode()enable_overlap->event_loop_overlap_disagg_decode()- else ->
event_loop_normal_disagg_decode()
从技术书角度看,这段代码非常值得被直接翻译成模式矩阵,因为它能显著降低读者对 scheduler 模式爆炸的心理负担。
为什么这层不是“只是几个实现变体”#
因为它会直接改变:
- 结果队列是否存在
- CPU/GPU overlap 是否发生
- prefill/decode 是否分离
- pipeline parallel 的推进方式
- 哪些结果在什么时候被
process_batch_result(...)吸收
这意味着 event loop 的选择不是局部优化,而是调度器整体行为边界的一部分。
event_loop_normal() 与 event_loop_overlap() 的根本差异#
event_loop_normal()#
它比较直观:
- 收请求
get_next_batch_to_run()run_batch(batch)process_batch_result(batch, result)- 更新
last_batch
event_loop_overlap()#
它则显式维护 result_queue,把:
- 当前 batch 的 GPU 运行
- 上一个 batch 的 CPU 结果处理
重叠起来。这说明 overlap 模式并不是把普通循环“稍微并行一下”,而是从主循环结构上引入了新的中间状态。
这也解释了为什么前面的 execution / scheduling 章节经常要专门讨论 overlap 对 grammar、result queue 和 last_batch 的影响。
为什么 disaggregation 还要再乘一层矩阵#
因为一旦进入 PREFILL 或 DECODE disaggregation mode,排队结构和 batch 结果处理方式都变了。也就是说:
- overlap 与 normal 是一层变化
- disaggregation 又是另一层变化
这正是 scheduler 模式矩阵看起来复杂的根本原因:它不是单轴变化,而是多轴组合。
这层和前面几章怎样回扣#
这章和整本书很多地方会自然回扣:
- 与运行时架构:解释为什么一个 scheduler 会有多种运行人格
- 与调度与内存:解释不同 event loop 会怎样改变 waiting/batch/update 的形状
- 与 execution model:解释 overlap 为什么会影响结果处理和 grammar 同步
- 与扩展与调试:解释排障时为什么先确认当前运行模式非常重要
这也是为什么它值得单独成章,而不是只在各章边角料里零散出现。
这一层最容易出现的误判#
1. 把 event_loop_normal() 当成唯一主循环#
这会让你在 overlap / disaggregation 场景下误读很多后续逻辑。
2. 以为 overlap 只是“性能优化开关”#
它其实直接改变主循环结构。
3. 以为 disaggregation 只改数据传输,不改调度主循环#
源码明确说明它也改 event loop 选择。
如果你要排查 scheduler 行为异常,先怎么走#
建议先问这三个问题:
- 当前
disaggregation_mode是什么? - 是否启用了 overlap?
- 是否启用了
pp_size > 1或enable_pdmux?
这三个问题一旦答不清,后面看任何调度异常都容易走偏。
小结#
这一章真正想补齐的,是运行时架构里对调度主循环的“模式树解释”:
Scheduler不是一个 event loop- 它是一组按模式矩阵切换的主循环
- 只有把这层矩阵看清,后面调度、执行和排障章节里的很多分支才会真正连成一套系统
到这里,运行时架构对 scheduler 的解释就不只停在“有个主循环”,而开始解释“为什么主循环本身会有多种人格”。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。