mini-infer系统实战-23-Control Plane:最难的不是统计出来,而是先量对
mini-infer系统实战-23-Control Plane:最难的不是统计出来,而是先量对
很多人第一次看 MoE 推理里的 Expert Parallelism,会觉得 control plane 只是一个“顺手补充说明”的东西:
packed path 已经比 padded 快了,exact bytes 也已经对了,那再补一个
control_plane_ms不就结束了吗?
如果只是做概念讲解,这句话没什么问题。
但真正把它做成一轮可以落盘、可以复盘、可以拿去面试讲清楚的工程实现时,你会发现这件事远比“多打一行指标”复杂。
在 mini-infer 的 Phase 20,我做的是一版 EP Control Plane 收敛。最终正式 benchmark 平均结果是:
- dense:
22610.50 tok/s ep_padded:43392.45 tok/sep_packed:52229.29 tok/sEP packed / dense = 2.310xEP packed / EP padded = 1.204xep_packed_control_plane = 0.0239 ms/runep_packed_control_plane_share = 0.019443,约1.94%max_abs_diff_packed = 0.000000
这些数字都来自该阶段的正式 benchmark,不是估算值。
这篇文章想讲的不是“control plane 是什么”,而是更实际的问题:
为什么 Phase 20 真正难的不是把 control-plane 指标打印出来,而是先确保你量到的真的是 control plane。
背景:Phase 19 已经证明 packed 更快,但还不能回答“快在哪里”
Phase 19 已经把 synthetic MoE 上的 dense / ep_padded / ep_packed 通信闭环跑通了。正式 benchmark 两次复跑平均结果是:
- dense:
22552.86 tok/s ep_padded:43046.93 tok/sep_packed:52400.26 tok/sEP packed / dense = 2.323xEP packed / EP padded = 1.217xep_packed_bytes_per_layer = ep_ideal_bytes_per_layer = 262144max_abs_diff_packed = 0.000000
这已经说明两件事:
- packed path 的 hidden-state payload bytes 已经收回到 ideal
- packed path 在当前 synthetic workload 下确实比 padded 更快
但 Phase 19 还留着一个很明确的 production gap:
- packed path 仍依赖 host-side split-size Python list
- benchmark 只能说明“packed 更快”
- 却不能说明“packed 现在还慢在哪里、control plane 还占多少”
这就是我把 Phase 20 定成 EP Control Plane 收敛 的原因。
问题定义:你不是要“给系统多加一个指标”,你是在重新定义 benchmark 的解释力
Phase 20 的核心目标不是再造一条新数据通路,而是把 packed 路径里原本混在一起的几类成本分开:
- source-rank router
- dispatch layout 构建
- token packing
- split-size control plane
- 真正的 all-to-all payload
如果这些东西混在一个吞吐数字里,你当然还是能说:
ep_packed更快ep_packed_bytes_per_layer == ep_ideal_bytes_per_layer
但你没法回答更像大厂推理组会问的问题:
- packed path 现在还慢在哪里?
- control plane 到底是不是主要瓶颈?
- 当前是不是已经值得去做 grouped GEMM / overlap?
所以 Phase 20 的真正交付,不是把 benchmark 表从 8 列变成 10 列,而是让 benchmark 本身变得更有解释力。
方案:把 packed control plane 从“默认存在的杂项”收敛成显式 contract
我这轮改的核心文件是三个:
总体思路是:
EPMoELayer继续保留comm_mode="padded"和comm_mode="packed"两条路径- packed 的 split-size 组装不回到 per-layer hot path
- worker 侧显式构造
PackedControlPlane - benchmark 正式输出:
control_plane_mscontrol_plane_sharecontrol_plane_note
这意味着 Phase 20 的目标不是去掉所有 Python control plane,而是先把它收拢、命名、计量、解释。
实现细节:真正新增的不是一个字段,而是一层职责边界
1. PackedControlPlane
在 moe_layer.py 里,我新增了 PackedControlPlane 和 build_packed_control_plane()。
它的职责很明确:把 packed 路径的 split-size 相关信息收敛成单独的控制面对象,而不是散在 distributed forward 里临时组装。
这个对象包含的不是 payload,而是 worker 在 all_to_all_single 前后真正需要的 split 信息:
sender_splitsreceiver_splitsreturn_sender_splitsreturn_receiver_splitsrecv_countrecv_back_count
这个动作的意义不是“代码更优雅”,而是让你能非常明确地说:
哪些是 data plane,哪些是 control plane。
2. _prepare_packed_control_plane()
在 ep_engine.py 里,worker 侧新增了 _prepare_packed_control_plane():
1 | def _prepare_packed_control_plane(...): |
这里有两个关键点:
- packed 的 split-size control plane 现在在 worker 侧统一构造
- 它的计时边界被单独定义出来了
你可以把这个 helper 理解成“Phase 20 真正的主角”。因为这轮不是在改 MoE 数学,而是在改 benchmark 解释力。
3. benchmark contract 扩展
在 benchmark_moe.py 里,Phase 20 不再只输出:
- dense / padded / packed throughput
- ideal / padded / packed bytes
而是额外输出:
control_plane_mscontrol_plane_sharecontrol_plane_note
并且这些 note 是显式写进结果里的,不靠“默认理解”:
ep_packed_impl_noteep_packed_control_plane_note
这件事很像真实 infra 项目里的一个常见原则:
如果一个重要指标需要靠口头解释才能成立,那它还不算真正稳定的系统指标。
这轮真正踩到的坑:第一次我量出来的根本不是 control plane
Phase 20 最有价值的部分,不是实现本身,而是中途踩到的 benchmark 口径坑。
错误版本发生了什么
第一版 control_plane_ms/share 看起来是“有值”的。
worker 里大致是这么做的:
- source rank 先做
prepare_packed_source_context() - 再
broadcast(send_counts) - 然后开始计时
- 再做:
packed_send_counts.cpu().tolist()build_packed_control_plane()
表面上看,这好像已经是“只测 control plane”了。
但问题在于:
- 前面刚在同一 CUDA stream 上做完 source-rank router / dispatch
- 你一旦执行
packed_send_counts.cpu().tolist() - 这个 D2H copy 会把前面尚未完成的 GPU 工作一起等完
也就是说,你以为自己在测:
- send-count 读回
- Python split-size helper
实际上你测到的还包含:
- source-rank router
- dispatch layout
- token packing 的 GPU 完成等待
错误信号有多明显
最小 2-GPU compare 一度得到:
ep_packed_control_plane_share = 0.481655
这意味着 packed path 里将近一半时间都在 control plane 上。
如果这是真的,Phase 19 的 ep_packed 根本不可能在官方 workload 下稳定比 ep_padded 快。
所以这个值本身就在告诉你:
不是系统坏了,而是指标口径坏了。
这就是 Phase 20 最关键的工程经验:
先怀疑你量的东西是不是你以为的那个东西,再去解释结果。
修正方式:先把前序 GPU 工作收口,再开始 control-plane 计时
最终修正方式非常直接,但它的意义比代码量大得多。
我在 _prepare_packed_control_plane() 开始前显式加了:
1 | torch.cuda.synchronize(device) |
这个同步的作用不是为了“保守一点”,而是为了明确切开两个时间段:
- source-rank router / dispatch / broadcast 已经结束
- 现在开始计量:
packed_send_counts的 GPU -> CPU 读回PackedControlPlanehelper 本身
修完后,最小 compare 的结果变成:
ep_packed_control_plane_ms = 0.1108ep_packed_control_plane_share = 0.000257
这两个数字才第一次像“单独的 helper 成本”。
到了官方 workload,最终稳定在:
ep_packed_control_plane = 0.0239 ms/runep_packed_control_plane_share = 0.019443,约1.94%
这组数字是可信的,因为它已经不再混着前序 GPU 工作。
正式实验:Phase 20 到底交付了什么
正式 workload 是:
batch_size=4seq_len=16hidden_size=512intermediate_size=1024num_experts=8top_k=2dtype=float16warmup=2runs=5src_rank=1
同配置跑了 2 次,平均结果是:
| 指标 | 结果 |
|---|---|
| Dense throughput | 22610.50 tok/s |
| EP padded throughput | 43392.45 tok/s |
| EP packed throughput | 52229.29 tok/s |
EP packed / dense |
2.310x |
EP packed / EP padded |
1.204x |
ep_packed_control_plane |
0.0239 ms/run |
ep_packed_control_plane_share |
0.019443(约 1.94%) |
max_abs_diff_padded |
0.000000 |
max_abs_diff_packed |
0.000000 |
shard_ratio |
0.5002 |
ep_packed_bytes_per_layer |
262144 |
ep_ideal_bytes_per_layer |
262144 |
这些数据说明了三件事:
-
Phase 19 的主线结果没有回退
packed 仍然保持 exact bytes、数值等价和更高吞吐。 -
control plane 已经被量化,而且占比不高
它现在不是 packed path 的主要瓶颈。 -
Phase 20 的核心交付不是“更快了多少”,而是 benchmark 终于能解释“为什么现在不是它在拖后腿”。
真正的结论:Phase 20 解决的不是 Python list,而是 benchmark 的解释权
如果只从功能角度看,Phase 20 好像只是:
- 新增一个 helper
- 多了两个 benchmark 字段
- 写了一些 note
但这恰好会误导你低估这轮工作的价值。
Phase 20 真正解决的是:
- 让
control_plane_ms/share变成可信指标 - 让 packed path 的剩余性能问题不再停留在猜测
- 让下一阶段的优化方向变得明确
现在我可以非常明确地说:
- packed split-size helper 仍然存在
- 它仍然依赖 Python list
- 但在当前官方 workload 下,它只占
1.94%
这句话对后续阶段的价值远高于一句“packed 更快”。
因为它直接把下一步范围缩小了:
- 不是继续盲改 control plane
- 而是该看
grouped GEMM / overlap - 或者把 synthetic EP 路径接到更完整的 serving / 生成链路
总结
Phase 20 给我的一个很强的经验是:
在推理系统里,很多时候最难的不是把东西做出来,而是确保你测到的真的是你以为自己在测的东西。
Phase 19 证明了 packed communication 是更好的数据通路。
Phase 20 则进一步证明了:
- control plane 可以被显式量化
- 当前 packed path 的 control plane 已经不是主要瓶颈
- benchmark 的解释力本身也是系统工程的一部分
如果只做功能实现,你会得到一个“能跑”的 EP 原型。
如果把 benchmark 口径、失败点和计时边界一起收住,你得到的才是一个可以拿去讲清楚、拿去对比、拿去继续规划下一阶段的推理系统阶段成果。
