MoE 路由、expert dispatch 与执行侧证据#

这章解决什么问题#

执行模型前面的章节已经把主生成循环、采样、输出尾部、hidden states 和 routed experts 的旁路返回语义讲了出来,但如果系统底层是 MoE 模型,仍然还有一层很关键的执行现实没有被单独说透:

  • token 到底怎样被路由到专家
  • 专家选择结果怎样在执行层继续传播
  • request 级 return_routed_experts 和 runtime 级 expert distribution record 为什么是两条不同但相连的链

如果没有这一章,读者虽然知道“可以返回 routed experts”,却仍然很难回答:

  • 这些专家 id 从哪里来
  • 它们是在执行链哪个位置被捕获
  • 为什么同一条执行链上既会有 request-level 旁路返回,又会有全局 recorder

为什么这层值得单独成章#

因为它正好站在执行模型一个很有价值的位置:

  • 不是单纯 API surface 的字段
  • 也不是单纯维护端点

它真正属于:

  • 执行层自己在做路由和 dispatch
  • 然后把部分证据向外暴露

这非常像优秀系统书应该强调的一类结构:某个能力先在 runtime 内部自然存在,后来才被做成可返回、可记录、可运维控制的外部能力。

一张图:MoE 路由证据在执行链里如何分叉#

这张图解决的理解障碍是:很多读者会把专家路由看成“模型内部黑盒”,或者反过来只把它当成一个输出字段。源码其实已经把它分成了两条并行证据链。

flowchart LR
    Router["moe/topk.py\nselect experts"] --> Dispatch["token dispatcher / moe runner"]
    Dispatch --> Main["main forward result"]
    Router --> Capture["routed_experts_capturer"]
    Router --> Record["expert_distribution recorder"]
    Capture --> Proc["scheduler_output_processor_mixin"]
    Proc --> Ret["request-level returned experts"]
    Record --> Dump["runtime-level record / dump"]

图比纯文字多解释的一点是:同一份专家选择结果,在执行层里会自然分出两条下游:

  • 面向单请求返回
  • 面向全局运行时记录

topk.py 为什么是这条线的第一阅读入口#

如果你想读 MoE 执行语义,最稳的起点不是一头扎进某个 token dispatcher,而是先读:

  • python/sglang/srt/layers/moe/topk.py

因为这里会把:

  • topk_weights
  • topk_ids

真正产出来,而且在 _post_process_topk_ids(...) 之后,会显式调用:

  • get_global_expert_distribution_recorder().on_select_experts(topk_ids=topk_ids)

这说明专家选择并不是纯局部中间值,而是已经被系统正式当成可观察执行证据。

为什么专家选择一产出就会进入 recorder#

这是一个很重要的设计判断:

  • 如果你只在后面某个更晚阶段再去“猜”专家分布,信息已经不再完整
  • 在 top-k 选择刚产出时记录,才最接近执行事实

因此这条 recorder 链的本质不是调试附加项,而是执行侧最原生的观测点。

从技术书角度看,这很值得强调,因为它说明上游把专家选择当成正式运行时事实,而不是可有可无的 debug 材料。

routed_experts_capturer.py 在解决什么问题#

如果 recorder 是 runtime-level 的全局记录器,那么:

  • routed_experts_capturer.py

更像 request-level 的局部证据容器。

它的核心动作是:

  • 在 device buffer 上按 layer 捕获 topk_ids
  • 在 forward 结束后同步到 host buffer
  • 再通过 req_to_token_poolreq_pool_idx 把某个请求对应的 routed experts 切出来

这非常关键,因为它说明 request-level routed experts 返回并不是“在 API 层重新拼”,而是执行层先主动为后续请求级索引保留了一份缓冲。

为什么 req_to_token_pool 会再次出现#

这恰好回扣了前面调度与内存部分的核心观点:只要某个执行侧证据需要最后回到“单请求视角”,系统往往还是要借:

  • req_to_token_pool
  • req_pool_idx

去把 batch 级或 token 级状态重新切回请求级视图。

因此 routed experts 这条线再次证明:

  • ReqToTokenPool 不只是 cache 索引账本
  • 也是很多执行侧附加证据回到单请求视角时的桥梁

request-level 返回与 runtime-level recorder 的边界#

这两条线虽然都围绕 experts,但职责并不相同。

request-level 路由证据#

它主要对应:

  • return_routed_experts
  • req.routed_experts
  • scheduler_output_processor_mixin.py
  • 最终经 TokenizerManager 被 base64 编进 meta_info

这条线更适合回答:

  • 这个请求在这次运行里到底选了哪些专家

runtime-level expert distribution record#

它主要对应:

  • ExpertDistributionRecorder
  • with_forward_pass(...)
  • with_current_layer(...)
  • start_record() / stop_record() / dump_record()

这条线更适合回答:

  • 在一段时间窗口里,系统整体的专家分布和 dispatch 是否平衡

优秀技术书在这里必须主动把两者拆开。否则读者会把它们都当成“专家路由可视化”,却不知道一个是单请求证据,一个是全局运行时统计。

ExpertDistributionRecorder 为什么不像普通 logger#

eplb/expert_distribution.py 里的 recorder 很值得认真看,因为它不是简单地写日志。它有明确的生命周期动作:

  • start_record()
  • stop_record()
  • dump_record()

还有明确的上下文入口:

  • with_current_layer(...)
  • with_forward_pass(...)
  • disable_this_region()

这说明它更像一个:

  • 运行中启停的全局采集器

而不是一个永远打开的轻量 logger。

这也正是为什么相关 HTTP / Engine 控制端点必须存在:因为这条 recorder 链本来就被设计成可以在运行中打开和关闭。

token dispatcher 与 expert distribution 为什么天然绑定#

deepep.py 等 token dispatcher 路径可以看到,某些更深的 expert dispatch 行为还会继续调:

  • on_deepep_dispatch_normal(...)
  • on_deepep_dispatch_low_latency(...)

这说明 expert distribution record 不只想知道“选了谁”,还想知道:

  • dispatch 到各 rank / expert 的真实分布怎样

这比 request-level routed_experts 又更进一步,说明它关注的是更全局的 dispatch 健康度,而不只是单请求解释性。

为什么这章和 5.16 不重复#

5.16 的重点是:

  • hidden states / routed experts 作为旁路返回语义怎样附着进输出对象

这一章的重点是:

  • routed experts 这份证据在执行层是怎样被产生、捕获和双向分流的

换句话说:

  • 5.16 讲“怎么返回”
  • 5.17 讲“为什么有这些返回,以及它们在执行侧从哪里来”

这一层最容易出现的误判#

1. 以为 return_routed_experts 就等于 expert distribution record#

一个偏 request-level,一个偏 runtime-level。

2. 以为 routed experts 是输出阶段后处理时临时算出来的#

真正的捕获点在执行层的 expert 选择与 dispatch 周围。

3. 以为这条链只对调试有意义#

对 MoE 运行时来说,它同样直接关系到 dispatch 平衡、分析与维护。

如果你要顺着源码读这条 MoE 证据链,推荐顺序是什么#

建议按下面顺序:

  1. layers/moe/topk.py
  2. layers/moe/routed_experts_capturer.py
  3. eplb/expert_distribution.py
  4. scheduler_output_processor_mixin.py
  5. HTTP / Engine 上的 expert distribution 控制端点

这样读,你先看到“证据从哪里产生”,再看到“证据怎样被捕获”,最后才看到“证据怎样被对外暴露或导出”。

工程收益与代价#

收益:

  • 单请求和全局窗口都能得到专家路由证据
  • 执行层不需要为每个消费场景重算专家选择
  • MoE 运行时的解释性和可运维性都更强

代价:

  • 缓冲和序列化成本会上升
  • recorder 生命周期要被控制好,否则开销可能很大
  • 用户很容易把 request-level 和 runtime-level 两套证据混为一谈

小结#

这一章真正要补齐的,是执行模型里一条此前还散在多处的 MoE 主线:

  • top-k 专家选择先在执行层自然发生
  • 然后分出 request-level routed experts 返回链
  • 以及 runtime-level expert distribution record 链

到这里,执行模型就不只会讲“生成怎样推进”,也开始更完整地覆盖“MoE 模型的专家路由怎样成为执行证据和运行时控制面的一部分”。