笔记日期: 2026-06-28 笔记作者: Zhongzhu Zhou 论文标题: Moebius: Serving Mixture-of-Expert Models with Seamless Runtime Parallelism Switch 作者: Shaoyu Wang, Yizhuo Liang, Jaeyong Song, Chong Li, Seo Jin Park arXiv: 2606.26607 状态 / 来源: 预印本(2026)
一句话总结
Moebius 把专家并行(EP)和张量并行(TP)看作同一组模型权重的两种”数据布局视图”,并在推理服务过程中动态切换,切换窗口仅 215–434 ms、内存开销仅 2.4%,在 RL rollout 场景下比最优静态布局快 1.16–1.25 倍。
前置知识
在深入正文之前,我们先把几个核心背景知识梳理清楚。读者不需要是分布式系统专家,只要对 Transformer 推理有基本了解即可。
MoE(混合专家)架构回顾
混合专家模型(MoE)将 Transformer 中的部分(或全部)前馈网络(FFN)层替换为 个”专家”子网络的集合。推理时,一个轻量级路由器为每个输入 token 选择其中的 个专家(通常 到 ),只计算这 个专家的输出。
设输入 token 嵌入为 ,MoE 层的输出为:
其中 是路由器输出(专家权重向量), 是门控权重, 是第 个专家网络。每个专家有两组权重矩阵:
其中 是隐藏维度, 是专家中间维度。以 Qwen3-235B-A22B 为例: 个专家,每个 token 激活 个,专家权重占模型总内存的 70–80%。
张量并行(TP)基础
张量并行将权重矩阵本身切分到 个 GPU 上。MoE 场景下的 TP 布局:
- 每个 GPU 持有全部 个专家,但每个专家的中间维度被切分:
- 每个专家的计算跨所有 个 GPU 按列/行方式拆分,专家层后需要 All-Reduce 聚合
- KV Cache 也按注意力头切分:每个 rank 持有 个 KV 头
TP 适合什么场景? 低并发(小 batch)时,专家 GEMM 受内存带宽限制。TP 将专家权重分散到多块 GPU,每块 GPU 的工作集更小,能更快喂饱计算单元。
专家并行(EP)基础
专家并行将专家本身分布到 个 GPU 上,每个 GPU 持有 个完整专家:
token 路由需要 All-to-All 通信:token 发往持有其目标专家的 GPU 计算,结果再发回。注意力层以数据并行方式运行——每个 GPU 独立处理自己分配到的请求及其完整 KV Cache。
EP 适合什么场景? 高并发(大 batch)时,专家 GEMM 变为计算瓶颈。EP 让每个专家的完整权重集中在一块 GPU 上,最大化每个专家的 batch size,从而获得高计算利用率。
TP/EP 的交叉点问题
存在一个交叉 batch 大小 ,在此两种策略的优劣互换:
对 Qwen3-235B-A22B 在 8×H200 上,–。实际生产负载持续跨越 :突发请求浪涌让 短时间内增长 2–3 个数量级;RL rollout 步骤开始时有大量活跃序列(利于 EP),随后序列逐渐完成,剩下少量长上下文”尾巴序列”(利于 TP)。任何静态选择都会在某段时间内性能次优。
CUDA Graph 机制
CUDA Graph 将一系列 GPU 操作(kernel 启动、内存拷贝)记录成可复用的图对象,用单次 cudaGraphLaunch 调用重放。这消除了每步的 CPU 调度开销(大模型每步 1–10 ms),对低 batch 场景尤为重要。
关键约束: CUDA Graph 重放要求所有张量的设备内存地址在 capture 到 replay 期间保持不变。这意味着权重张量、KV Cache、注意力状态必须一直驻留在同一个地址——这是 Moebius 设计必须绕过的核心约束。
分页注意力与 KV Cache
现代推理系统(vLLM、SGLang)使用分页注意力(Paged Attention):每条序列的 KV Cache 以”页”(物理 GPU 内存块)形式存储,通过逻辑-物理页表管理。EP 下每个 GPU 持有其分配序列的完整 KV(全部注意力头);TP 下 KV 按注意力头切分到各 GPU。因此,切换并行策略时,必须同时重新分配 KV Cache——这是 Moebius 中最昂贵的操作之一。
一、问题背景:生产级 MoE 服务的 TP/EP 困境
MoE 大模型——Mixtral-8x7B、DeepSeek-V3、Qwen3-235B-A22B——已成为生产级大模型服务的主流。稀疏激活(每个 token 只触发少量专家)在保持强大能力的同时降低了单 token 计算量,但庞大的参数总量带来了新的并行化挑战。
图 1. 8×GPU 部署下 TP 与 EP 布局对比示意
┌─────────────────────────────────────────────────────────────────────┐
│ 张量并行(TP) │
│ GPU 0 GPU 1 GPU 2 GPU 3 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │全部 E 个 │ │全部 E 个 │ │全部 E 个 │ │全部 E 个 │ │
│ │专家 │ │专家 │ │专家 │ │专家 │ │
│ │I 维 [0-¼]│ │I 维[¼-½] │ │I 维[½-¾] │ │I 维[¾-1] │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ←──────── 每个专家层后 All-Reduce 同步 ────────────────────────→ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 专家并行(EP) │
│ GPU 0 GPU 1 GPU 2 GPU 3 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │专家 0..31│ │专家32..63│ │专家64..95│ │专家96..127│ │
│ │完整 I 维 │ │完整 I 维 │ │完整 I 维 │ │完整 I 维 │ │
│ │自身请求 │ │自身请求 │ │自身请求 │ │自身请求 │ │
│ │完整 KV │ │完整 KV │ │完整 KV │ │完整 KV │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ←──── 每个专家层前 All-to-All 分发 token ──────────────────────→ │
└─────────────────────────────────────────────────────────────────────┘
配置为 EP 的系统在低并发时浪费吞吐(TPOT 52 ms vs. TP 的 37 ms);配置为 TP 的系统在突发高并发时导致 TTFT 崩溃(p99 TTFT 达 90 s)。没有任何一种静态选择普遍最优。
为什么之前的切换方案不适用于 MoE?
- 权重数据量大: 专家权重占 MoE 模型内存 70–80%。针对稠密模型设计的切换方案传输的权重量很小,MoE 需要在 NVLink 上传输数十 GB 数据。
- 不能清空在途请求: 生产环境不能在切换前等待所有请求完成——长上下文请求的 KV 清空需要数分钟,期间用户会收到超时报错。
- CUDA Graph 失效: 引擎重启会打破 CUDA Graph 的张量地址绑定,必须重新 capture(耗时数分钟),再启动服务。
Moebius 同时解决了这三个问题。
二、核心洞见:EP 与 TP 是同一模型的两种数据视图
Moebius 最关键的智识贡献是重新定义了 EP 和 TP 的关系:它们不是两套不同的系统,而是同一份张量数据的两种布局。
当 (P 能整除 E)时,EP 和 TP 每个 GPU 持有的数据总量完全相同:
这种体积不变性(volume invariance)使原地重新分片(in-place resharding)成为可能:无需申请额外内存,变换是保体积的,只需在 GPU 之间重新分配数据。
EP 布局(rank 持有专家 ):
TP 布局(rank 持有所有专家的第 个 I 维切片):
两种布局下每个 GPU 的元素总数相同。从一种变成另一种,需要沿不同轴重新分配——本质上是一次分布式 All-to-All 交换。
三、权重重新分片:EP↔TP 变换算法
3.1 EP→TP 重新分片
EP 下,rank 持有专家 的完整中间维度。TP 下,rank 需要每个专家的第 个 I 维切片。变换步骤如下:
算法 1:EP→TP 权重重新分片(gate/up 权重)
输入:W_EP[r] ∈ ℝ^{(E/P, 2I, H)},在 rank r 上(本 rank 持有的 E/P 个专家,完整 I 维)
输出:W_TP[r] ∈ ℝ^{(E, 2I/P, H)},在 rank r 上(全部 E 个专家,I/P 切片)
第 1 步 — 本地打包(按目标 rank 分组):
对每个 peer rank p ∈ [0, P):
chunk[p] = W_EP[r][:, p*(2I/P) : (p+1)*(2I/P), :]
# 形状:(E/P, 2I/P, H),这是我们的专家中 rank p 需要的那段 I 切片
第 2 步 — 拼接为 AllToAll 发送缓冲区:
send_buf = concat(chunk[0], chunk[1], ..., chunk[P-1], axis=0)
# 形状:(E, 2I/P, H)
第 3 步 — AllToAll 交换(通过 NVLink 直传 kernel):
recv_buf = AllToAll(send_buf)
# recv_buf[p*(E/P) : (p+1)*(E/P), :, :]
# ← rank p 发来的 chunk[r](rank p 的专家,我们的 I 切片)
第 4 步 — 直接放置(recv_buf 已是正确的 TP 布局):
W_TP[r] = recv_buf # 形状 (E, 2I/P, H),所有 E 个专家的 I/P 切片 ✓
图 2. EP→TP 数据流示意(P=4 GPU,E=8 专家,简化)
EP 布局(切换前): TP 布局(切换后):
GPU 0: [专家0,1] 完整 I GPU 0: [专家0..7] I[0:I/4]
GPU 1: [专家2,3] 完整 I ─→ A2A GPU 1: [专家0..7] I[I/4:I/2]
GPU 2: [专家4,5] 完整 I GPU 2: [专家0..7] I[I/2:3I/4]
GPU 3: [专家6,7] 完整 I GPU 3: [专家0..7] I[3I/4:I]
每个 GPU 向 P 个 peer 各发一个 chunk,从 P 个 peer 各收一个 chunk。
每 GPU 传输量 = (P-1)/P × 本 GPU 专家权重大小(一次 NVLink 传输)。
3.2 TP→EP 重新分片
逆向变换。TP 下每个 rank 持有所有专家的 切片,EP 下需要 个专家的完整 维度。
算法 2:TP→EP 权重重新分片(gate/up 权重)
输入:W_TP[r] ∈ ℝ^{(E, 2I/P, H)},在 rank r 上(所有 E 个专家的 I/P 切片)
输出:W_EP[r] ∈ ℝ^{(E/P, 2I, H)},在 rank r 上(本 rank 的 E/P 个专家,完整 I 维)
第 1 步 — 按专家所有者分割:
对每个 peer rank p ∈ [0, P):
chunk[p] = W_TP[r][p*(E/P) : (p+1)*(E/P), :, :]
# 形状:(E/P, 2I/P, H),rank p 的专家,我们的 I 切片
第 2 步 — 拼接为 AllToAll 发送缓冲区:
send_buf = concat(chunk[0], ..., chunk[P-1]) # 形状 (E, 2I/P, H)
第 3 步 — AllToAll 交换:
recv_buf = AllToAll(send_buf)
# recv_buf[p*(E/P) : (p+1)*(E/P), :, :]
# ← rank p 发来的 chunk[r](我们的专家,rank p 的 I 切片)
第 4 步 — 本地交错拼接(将各 rank 的 I 切片组装成完整专家):
对每个本地专家 e ∈ [0, E/P):
对每个 peer rank p ∈ [0, P):
W_EP[r][e, p*(2I/P) : (p+1)*(2I/P), :] = recv_buf[p*(E/P)+e, :, :]
# 结果:W_EP[r] 形状 (E/P, 2I, H),中间维度完整 ✓
为什么 TP→EP 比 EP→TP 代价更高? EP 下每个 rank 持有其请求序列的完整 KV(全部注意力头);TP 下 KV 头按 rank 切分。TP→EP 切换时,需要从所有 TP rank 汇集 KV 头,合并后写入新的 EP owner——这相当于将 KV 传输量翻倍,是切换时间差异(215 vs. 434 ms)的主要原因。
3.3 传输代价分析
每个 GPU 每次重新分片的传输数据量为:
对 Qwen3-235B-A22B(E=128,H=7168,I=2048,P=8,94 层,BF16):
有效 AllToAll 带宽约为 NVLink 峰值的 70%(约 280 GB/s per GPU):
Moebius 实测无 KV 情况下权重重新分片约 152 ms,与理论估算基本吻合。生产场景完整切换(215–434 ms)还包含 KV Cache 重新分配,后者随 Cache 占用量线性增长。
四、请求重新分配:KV Cache 迁移
权重重新分片只是第一步——在途请求携带的 KV Cache 状态也必须随并行策略切换而迁移。
4.1 EP→TP:按注意力头重新分区 KV
EP 下 rank 持有其分配序列的完整 KV(全部 个头)。TP 下每个 rank 持有全部请求的 个头。
算法 3:EP→TP KV Cache 迁移
输入:rank r 对请求集合 {R_r} 持有完整 KV(全部 H_kv 头,以页为单位)
输出:rank r 对所有请求持有 H_kv/P 个头的 KV
第 1 步 — 元数据 All-Gather:
广播所有 rank 的页表元数据
GlobalOrder = 合并所有 {R_0}, {R_1}, ..., {R_{P-1}},按 seq_id 排序
第 2 步 — 确定头所有权:
rank r 负责头 [r * H_kv/P : (r+1) * H_kv/P]
第 3 步 — 逐请求、逐层迁移 KV 页:
对 GlobalOrder 中每个 seq_id:
source_rank = EP 下持有该序列的 rank
对每层 l ∈ [0, N_layers):
对属于 (seq_id, 层 l) 的每个 KV 页 p:
直传:source_rank 将 KV[头=r*H_kv/P:(r+1)*H_kv/P, 页 p]
写入 rank r 的目标页地址(页表原地更新)
关键优化:利用页表直接定位物理 KV 页,无需先拷贝到连续缓冲区,省去一次 HBM 额外读写。
4.2 TP→EP:序列重新分配
TP 下所有 rank 共同处理全部请求;EP 下每个 rank 处理不相交的请求子集,需要决定新的序列归属并迁移 KV。
算法 4:TP→EP 序列分配与 KV 迁移
第 1 步 — 贪心最长优先序列分配:
将所有在途请求按当前 KV 页数降序排列
初始化 load[r] = 0(各 rank 的负载计数)
对每个序列 seq(从长到短):
r* = argmin_r(load[r]) # 分配到负载最小的 rank
assign(seq → r*)
load[r*] += pages(seq)
第 2 步 — KV 迁移到新 owner:
对分配到 rank r 的每个序列 seq:
对每层 l:
对该序列第 l 层的每个 KV 页:
从所有 P 个 TP rank 汇集头切片
将完整 KV 写入 rank r 的目标页地址
更新 rank r 的页表条目
为什么用最长优先贪心? 最长优先贪心是经典的 First-Fit Decreasing(FFD)装箱启发式算法,能达到最优装箱的 近似比。大序列(大 KV 占用)最难适配,优先处理避免后期无法插入。用当前 KV 页数代替未来生成长度(后者未知),对快完成序列可能高估负载——这是本文的一个局限,在批判性分析节讨论。
五、统一内存管理器(UMM):让 CUDA Graph 在切换后依然有效
5.1 固定地址约束
CUDA Graph 在 capture 时记录所有 kernel 的字面设备指针。replay 时要求这些指针仍然有效。如果 Moebius 在切换时动态重新分配权重,所有 CUDA Graph 都需要重新 capture——耗时数分钟,彻底抵消切换带来的收益。
5.2 UMM 设计:一块大缓冲区,两套别名视图
每个 GPU 在启动时分配一块足够容纳任意一种布局工作张量的单一连续缓冲区,并通过指针算术为两种模式建立别名视图:
统一缓冲区(每 GPU),形状:(N_layers + 1, max_expert_slot_bytes)
TP 模式别名: 第 i 层权重 → buffer[i] (slot i)
EP 模式别名: 第 i 层权重 → buffer[i + 1] (slot i+1,偏移 1)
暂存空间: buffer[0] → 始终可用(EP 的偏移让 slot 0 在 TP 第 0 层时空闲)
图 3. UMM 缓冲区布局与 EP→TP 原地重新分片的兼容性
Buffer slots: [ slot 0 ] [ slot 1 ] [ slot 2 ] [ ... ] [ slot N_L ]
暂存 ↑ ↑
EP 第 0 层 EP 第 N_L-1 层
TP 第 1 层 TP 第 N_L 层
EP→TP 切换第 0 层的过程:
1. 读取 buffer[1](EP 对第 0 层的别名)中的权重
2. 在 AllToAll 过程中以 buffer[0] 作为暂存区
3. 将 TP 布局权重写入 buffer[0](此时 TP 第 0 层的别名)
→ 因为 TP 第 0 层 = buffer[0],EP 第 0 层 = buffer[1],源和目标不重叠
CUDA Graph 被 capture 时,TP 第 0 层的地址是 buffer[0]。
切换后,TP 权重正好写在 buffer[0]——CUDA Graph 地址依然有效,无需重新 capture。
偏移一位的技巧: EP 第 层在 slot ,TP 第 层在 slot 。因此 slot 0 在任何状态下都是空闲的暂存区。切换完成后,TP 权重落在 CUDA Graph 期望的地址——无缝衔接。
5.3 内存开销分析
UMM 比单模式多一个额外 slot(暂存区):
加上双模式注意力分片(TP 注意力元数据 + EP 注意力元数据同时驻留内存),总开销为 2.4%。Moebius 以放弃约 2.8 GB KV 容量余量来覆盖这一成本——对 141 GB H200 而言代价极小。
六、融合直传 Kernel:绕过 NCCL 暂存缓冲区
标准 NCCL 集合通信使用 CPU 侧固定内存(pinned memory)作为暂存缓冲区,需要多次 HBM 读写。Moebius 实现了融合直传 kernel,通过 NVLink peer access 将源 GPU 的 HBM 数据直接写入目标 GPU 的 HBM:
表 1. NCCL 方案 vs. Moebius 直传 kernel 的 HBM 读写次数对比
| 传输内容 | 方法 | HBM 次数(发+收) | NVLink 次数 |
|---|---|---|---|
| 专家权重 | NCCL(朴素) | 2 + 1 = 3 | 1 |
| 专家权重 | Moebius 直传 | 1 + 0 = 1 | 1 |
| KV Cache | NCCL(朴素) | 3 + 2 = 5 | 1 |
| KV Cache | Moebius 直传 | 1 + 0 = 1 | 1 |
图 4. NCCL 暂存路径 vs. Moebius 直传路径
NCCL 路径(权重重新分片):
GPU_src HBM → CPU 固定内存(暂存) → NVLink → CPU 固定内存(暂存) → GPU_dst HBM
(共 3 次 HBM 读写,1 次 NVLink)
Moebius 直传路径:
GPU_src HBM → NVLink → GPU_dst HBM
(1 次 HBM 读(源端),0 次额外读写,1 次 NVLink)
实测结果:
- 专家权重:比 NCCL 快 1.49×
- KV Cache:比 NCCL 快 2×+
- NVLink 利用率:在两个传输方向均超过 70% 峰值带宽
七、切换策略:非对称迟滞控制
7.1 策略形式化
Moebius 使用带非对称迟滞的阈值策略,避免在负载边界附近频繁振荡切换。
参数(交互式服务模式):
- :当前在途请求数超过 时,触发 TP→EP 切换
- :过去 步的均值低于 时,触发 EP→TP 切换
- :两次连续切换之间的冷却时间
- 容量检查:切换前确认目标模式有足够 KV 空间容纳所有在途请求
切换策略伪代码:
每个推理步骤执行:
count ← 当前活跃请求数
history.append(count)
mean_count ← mean(history 最近 W 步)
if 距上次切换时间 < C:
跳过(冷却期)
if mode == TP and count ≥ T_h:
if KV_容量(EP) ≥ 在途请求需求:
触发切换(TP → EP)
重置冷却计时器
elif mode == EP and mean_count < T_ℓ:
if KV_容量(TP) ≥ 在途请求需求:
触发切换(EP → TP)
重置冷却计时器
7.2 非对称性的原因
图 5. 迟滞区间下的切换状态机
请求数 B
│
256 ┼──────────────────────────── T_h(快速响应:立即切到 EP)
│ ↑ 切换 ↑ 切换
│ ┌──────┘ EP 区间 ┌─────────────┘
│ │ │
205 ┼──────────────────────────── T_ℓ(缓慢响应:均值持续低才切回 TP)
│ TP ↓ 切换 EP→TP ↓ 切换
└─────────────────────────────────────────────────────→ 时间
TP EP TP EP TP
↑ ↑
5s 冷却期 5s 冷却期
- 快速进入 EP(用 ,立即触发): 突发请求瞬间拉高 TTFT,必须快速响应。
- 缓慢退出 EP(用 ,均值触发): 短暂的请求数低谷不应触发代价高昂的切换;均值窗口过滤瞬时抖动。
- 迟滞区间(): 防止在边界附近振荡——如果两个阈值相同,负载在 230 附近波动时每隔几步就会触发一次切换。
7.3 RL Rollout 特化策略
RL rollout 每步内请求数单调递减(序列完成后不再有新请求加入),因此策略简化为:
请求数一旦降至 以下立即切换到 TP,无需迟滞(不存在振荡风险——batch size 只减不增)。
八、系统架构与工程实现
8.1 整体架构
Moebius 以最小侵入性叠加在 SGLang v0.5.5 之上,仅修改 SGLang 原有代码 200 行:
图 6. Moebius 系统架构,分层叠加于 SGLang
┌────────────────────────────────────────────────────────────────────┐
│ SGLang 前端 │
│ 请求调度器 → Token 采样 → 流式响应输出 │
└──────────────────────────────┬─────────────────────────────────────┘
│ 请求 / 解码步骤
┌──────────────────────────────▼─────────────────────────────────────┐
│ Moebius 切换协调器(新增) │
│ ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 切换策略 │ │ 容量监控器 │ │ 运行时选择器 │ │
│ │ (迟滞阈值、 │ │ (切换前检查 │ │ (TP/EP CUDA │ │
│ │ 冷却计时器) │ │ KV 空间是否足够)│ │ Graph 指针切换)│ │
│ └─────────────────┘ └──────────────────┘ └──────────────────┘ │
└──────────────────────────────┬─────────────────────────────────────┘
│ 触发重新分片
┌──────────────────────────────▼─────────────────────────────────────┐
│ 统一内存管理器 + 重新分片引擎(新增) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 统一缓冲区 [0..N_layers]:EP 别名 / TP 别名 / 暂存区 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────┐ ┌──────────────────────────┐ │
│ │ 直传 Kernel │ │ 页表管理器 │ │
│ │ (NVLink GPU→GPU 直写, │ │ (KV 页迁移,序列重分配) │ │
│ │ 跳过 NCCL 暂存缓冲区) │ └──────────────────────────┘ │
│ └─────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
8.2 双运行时常驻
TP 和 EP 两套 CUDA Graph(每套 36 个,涵盖 per-rank batch 1 到 256)在启动时即 capture,并同时常驻内存。一次切换的完整流程:
- 正常完成当前解码步骤
- 执行权重重新分片 kernel(直传 AllToAll,算法 1 或 2)
- 执行 KV Cache 迁移(算法 3 或 4)
- 原子地将运行时指针从一套 CUDA Graph 切换到另一套
- 继续推理——无需重新 capture,无需热身,无需重启引擎
8.3 实现规模
- Moebius 核心代码: 7,400 行
- 修改 SGLang 原有代码: 200 行
- 常驻内存: TP + EP 两套 CUDA Graph,通信组,注意力元数据——全部同时在 HBM 中
200 行的修改量充分说明 Moebius 的设计具有良好的可组合性——它能干净地嵌入 SGLang 而不需要大规模重构。
九、实验结果详解
9.1 实验配置
- 硬件: 8× NVIDIA H200(每块 141 GB HBM,NVLink 全连接)
- 模型: Qwen3-235B-A22B,94 层,64 个 Query 头 / 4 个 KV 头,BF16 精度
- 配置: 最大 2,048 个请求,内存占比 0.85,启用 CUDA Graph
- 基线: 静态 TP(低并发最优)和静态 EP(高并发最优)
9.2 突发在线服务场景
负载设计: 3,107 个真实 ChatBot Arena 请求在 375 秒内回放,包含两个请求洪峰(80 req/s 和 120 req/s)和 300 秒的低负载静默期(~5 req/s)。
| 指标 | 静态 TP | 静态 EP | Moebius |
|---|---|---|---|
| 洪峰期均值 TTFT | 9.9 s | — | 3.1 s(提升 3.2×) |
| 洪峰期 p99 TTFT | 90 s | — | 6.0 s(提升 15×) |
| 静默期均值 TPOT | ~37 ms | 52 ms | ~37 ms(接近 TP) |
| 切换次数 | — | — | 4 次 |
静态 TP 在洪峰期遭遇队列雪崩,p99 TTFT 高达 90 秒;静态 EP 在静默期的 TPOT 比 TP 差 40%。Moebius 在整段轨迹中仅切换 4 次,始终贴近最优静态策略。
图 7. 突发服务场景下 TTFT 与 TPOT 对比(简化示意)
TTFT(秒)随时间变化:
90 s ┤ 静态 TP:████████████████ (洪峰 p99 TTFT = 90 s,灾难性)
│
6 s ┤ Moebius:████ (洪峰 p99 TTFT = 6 s)
│
0 ┼────────────────────────────────────────────────────────→ 时间(375s)
│←洪峰1→│←──────── 静默 300s(TP 模式) ──────────→│←洪峰2→│
│ [EP] │ │ [EP] │
9.3 RL Rollout 场景
负载设计: DeepMath 基准测试,9 个 rollout 步骤,每步 2,048 个 prompt,最大 32,768 token。
每个 rollout 步骤呈现特征性高→低 batch 轨迹:步骤开始时 2,048 条序列同时生成(利于 EP),随序列完成逐渐减少,最后剩少量长上下文”尾巴”(利于 TP)。
| 对比对象 | Moebius 加速比 |
|---|---|
| vs. 更差的静态策略 | 最高 1.31× |
| vs. 更好的静态策略 | 1.16–1.25×(均值 1.22×) |
| vs. 先知(每步最优布局) | 超越先知 |
| 端到端训练速度(Amdahl 投影) | 1.10–1.20× |
Moebius 超越先知——反直觉但合理:先知只在步骤间切换(基于上一步统计信息),Moebius 可在步骤内部切换(基于步内 batch 衰减实时响应)。在每步的尾巴阶段及时切到 TP,捕获步内收益是先知无法做到的。
9.4 切换延迟分解
图 8. 各方法切换延迟对比(EP→TP,清空 KV 基准)
方法 延迟
──────────────────────────────────────────────────────────────────────
引擎重启(朴素基线) 93 – 133 s ████████████████████████
从主机内存重载 13 – 20 s ██████
Moebius 仅权重(无 KV) ~152 ms █
Moebius 生产(低 KV 占用) 215 ms █
Moebius 生产(高 KV 占用) 434 ms █
──────────────────────────────────────────────────────────────────────
215–434 ms 的区间是负载相关的:权重重新分片约 152 ms 是固定底线;KV Cache 迁移随缓存占用量增加(在途请求越多→ KV 页越多→迁移越慢)。
传输速度对比(直传 kernel vs. NCCL):
- 专家权重:1.49× 更快
- KV Cache:2×+ 更快
- NVLink 利用率:两个传输方向均 >70% 峰值带宽
9.5 内存占用分析
图 9. 每 GPU 内存构成对比
每 GPU 内存(GB):
静态 TP 静态 EP Moebius
模型权重 ████████ ████████ ████████ (三者相同)
TP 注意力 KV ████ · ▓ (TP 按头切分,需复制)
EP 注意力 KV · ████ ▓ (EP 数据并行,各自持有)
UMM 双模开销 · · █ (2.8 GB 双模式缓冲区)
──────────────────────────────────────────────────────────────────────
vs. 静态 TP 基准 −3.9 GB −3.7 GB
vs. 静态 EP +3.9 GB 基准 +0.2 GB
Moebius 内存比静态 EP 多 0.2 GB,比静态 TP 少 3.7 GB。2.8 GB 的双模式缓冲区通过让出小量 KV 容量余量来覆盖,在 141 GB H200 上几乎可忽略。
CUDA Graph 常驻代价: 两套各 36 个图,同时常驻;选择运行时(决定使用哪套图)的开销不足 1 ms——远优于首次切换时重新 capture 需要的数分钟。
十、设计选择分析:为什么这样做 / 替代方案 / 边界条件
选择 1:统一内存管理器(UMM)
为什么这样做: CUDA Graph 需要固定地址。若切换时重新分配权重地址,所有图都需要重新 capture(每次切换需数分钟,不可接受)。若保留两套完整权重分配(EP 全量 + TP 全量),内存翻倍——对 235B 模型每块 GPU 新增 30–40 GB,几乎没有 KV 空间。
替代方案: 双独立分配(HBM 同时存两套完整权重)。消除 UMM 的偏移别名复杂性,但内存翻倍,在 Qwen3-235B-A22B 规模下不可行。
边界条件: UMM 依赖 EP 和 TP 每 GPU 数据总量相同(公式 6)。若目标布局需要更多每 GPU 数据(如全数据并行复制),UMM 缓冲区需要扩大,部分抵消内存优势。
选择 2:原地 AllToAll 重新分片 vs. 清空后重载
为什么这样做: 切换前清空所有在途请求会导致用户侧可见的延迟峰值或超时——长上下文请求 KV 清空本身可能需要数分钟。从主机内存重载需要 13–20 s,即便对批处理服务也太慢。
替代方案: 清空队列、暂停服务、从 SSD/主机内存重载权重、恢复服务。避免了原地重新分片的工程复杂性,但引入 13–133 s 服务中断——对交互式服务不可接受,对 RL 训练流水线也代价高昂。
边界条件: 原地重新分片依赖 NVLink 的 GPU→GPU 直写能力。在 InfiniBand 连接的集群(云端 A100/H100 部署的常见配置)上,直传 kernel 退化,切换延迟可能增长 3–5 倍,使 Moebius 的收益变得不明显。
选择 3:最长优先贪心序列分配(TP→EP)
为什么这样做: 最长优先贪心是经典 FFD 装箱启发式,复杂度 ,可同步完成,不需要昂贵的求解器,实测近似最优(4/3 近似比)。
替代方案: 随机分配或轮询。更简单,但负载方差大,某些 GPU 成为瓶颈,EP 吞吐量在不平衡场景下下降 10–20%。
边界条件: 用当前 KV 页数代替未来生成长度。接近完成的长序列 KV 页数多,但对未来负载贡献少——启发式可能过度分配,造成轻微不平衡。
选择 4:双运行时 CUDA Graph 常驻
为什么这样做: 惰性 capture(首次切换时再 capture)会在第一次切换时引发 1–5 分钟的停服——对交互式服务触发一次服务等级违规,彻底否定 Moebius 的意义。
替代方案: 惰性 capture。首次切换耗时长,后续切换快速。对切换极罕见(每天 < 1 次)且首次切换延迟可忍受(离线批处理)的场景可减少基线内存占用。
边界条件: 双套 CUDA Graph 常驻要求两套都能同时放入 HBM。H200 的 141 GB 完全够用;24–48 GB 的 A10 或早期 GPU 上,图常驻开销可能侵占 KV Cache 空间到不可接受的程度。
选择 5:200 行 SGLang 集成
为什么这样做: 最小化侵入,使 Moebius 受益于 SGLang 的持续优化(RadixAttention、分块 prefill、投机解码)而无需维护分叉版本。
替代方案: 从零构建以双模式为一等公民的定制推理引擎。设计自由度最大,但需要重新实现 SGLang 的全部优化——多年工程投入,且有持续维护负担。
边界条件: 依赖 SGLang 内部 API 稳定性。SGLang 大规模重构时可能需要 Moebius 的非平凡修复。
十一、批判性分析:不足与可改进之处
W1:固定阈值策略依赖人工调参
不足之处: 切换策略参数(、、、)没有推导过程,仅列出了 Qwen3-235B-A22B 在 8×H200 上的经验值 、。不同模型(更小的 MoE、不同 或 )、不同硬件(4×H100、8×A100)、不同并行度( 或 )的交叉点 都不同,需要重新调参。
作者淡化的问题: 交叉点 是 NVLink 带宽、内存带宽、专家数量、隐藏维度、GPU 数量的多变量函数,没有闭合解析式。论文未提供任何 profiling 工具或自动标定方法。运维人员在新硬件上部署 Moebius 必须自行跑基准测试和手动设置参数,增加了使用摩擦。
可以改进的地方: 开发在线 profiling 模块,在启动时以几个不同 batch 大小分别测量 EP 和 TP 的步骤延迟,拟合线性交叉模型,自动标定 和 ,使 Moebius 能在任意硬件/模型组合下自配置。
W2:实验仅覆盖一种模型和一种硬件配置
不足之处: 所有定量结果均来自 Qwen3-235B-A22B 在 8×H200 NVLink 全连接集群上的测试,没有:
- 更小的 MoE 模型(Mixtral-8x7B 46.7B、DeepSeek-V2 236B)
- 不同并行度( 或 )
- 非 NVLink 硬件(InfiniBand 连接的 A100 集群,云端最常见的配置)
作者淡化的问题: InfiniBand 带宽通常为 25–100 GB/s,远低于 NVLink 的 400–900 GB/s。直传 kernel 相对于 NCCL 的优势(1.49–2×+)在 InfiniBand 上可能大幅缩水,使切换延迟从 215–434 ms 增长到数秒,彻底抹平交互式服务的收益。
可以改进的地方: 提供以 、模型规模和网络互连带宽为参数的解析代价模型,并在至少两种硬件配置(如 A100+InfiniBand 和 H100+NVLink)上验证,明确 Moebius 在哪些参数范围内有收益、在哪些范围内不值得部署。
W3:KV Cache 迁移正确性未涵盖边界情况
不足之处: 算法 3 和算法 4 以页为粒度操作 KV,论文断言正确性但未讨论:
- 部分页: 每条序列最后一页通常未满,迁移时需处理非整页数据,否则可能污染邻近数据。
- 共享前缀缓存: SGLang 的 RadixAttention 在具有公共前缀的请求间共享 KV 页。将共享页迁移到某个 EP rank 后,如果另一 rank 仍引用该页,可能破坏前缀缓存一致性。
- 分块 prefill 中的序列: 切换发生时正在 prefill 的序列 KV 状态是部分计算的,迁移时必须保持一致性。
可以改进的地方: 针对上述边界情况提供形式化正确性证明或详细的情况分析,并添加覆盖部分页、RadixAttention 共享和分块 prefill 中途迁移的单元测试和集成测试。
W4:内存开销可能被低估
不足之处: 论文报告的 2.4% 开销仅计入了 UMM 备用 slot 和双模式注意力分片,未明细:
- 双套通信缓冲区(TP 的 AllReduce 通信组 + EP 的 AllToAll 通信组同时常驻)
- CUDA Graph 元数据表(72 个图各有一套元数据)
- 切换协调器的状态(页表快照、历史窗口缓冲区、模式元数据)
在显存紧张的硬件上,这些额外开销累积可能达到数 GB,压缩 KV 容量优势。
可以改进的地方: 提供针对 Qwen3-235B-A22B 8×H200 配置的完整内存预算表,用 GB 绝对值列出每个组件(权重、KV 池、CUDA Graph、通信组、UMM 开销、切换协调器状态)。
W5:缺乏故障恢复协议
不足之处: 在重新分片过程中发生 GPU 故障或 NVLink 中断,模型状态会处于不一致状态(部分层已切换到新布局,其余层仍是旧布局)。论文未讨论故障检测、回滚到上一个布局或优雅降级。在数千 GPU 的集群上,215–434 ms 窗口内多 GPU 故障的概率虽小但非零。
可以改进的地方: 定义故障原子性语义(故障时回滚到切换前状态?清空后重启?),实现在恢复推理前验证布局一致性的提交协议,添加切换一致性检查失败时的告警钩子。
L1:作者低估了 InfiniBand 部署的挑战
论文简要提及 NVLink 连接是直传 kernel 实现 70%+ 带宽利用率的前提,但没有充分提示 InfiniBand 是未验证的情形。绝大多数云规模 GPU 集群(AWS、Azure、GCP 的 A100/H100 部署)在节点间使用 InfiniBand 而非 NVLink。直传 kernel 相对于 NCCL 的 1.49–2×+ 加速在 InfiniBand 上可能大幅缩小(NCCL 本身对 InfiniBand 已高度优化)。论文应包含对 InfiniBand 部署的性能预期讨论和估算。
L2:负载泛化性不明确
服务实验使用 ChatBot Arena(对话类),rollout 实验使用 DeepMath(数学推理)。代码生成、RAG、多模态、结构化输出等工作负载的请求长度分布和到达模式截然不同,影响越过 的频率和每次切换的 KV 迁移代价。服务轨迹中的 4 次切换和相应时序能否在其他负载类型下复现,尚不清楚。
L3:与投机解码的交互未探讨
JetSpec、LayerSkip 等投机解码系统每个请求维护多份 KV Cache(草稿模型 KV + 验证器 KV)。Moebius 切换期间需要同时迁移两套 KV,可能将 KV 迁移代价翻倍。此外,投机解码的步骤粒度与标准解码不同,可能使交叉点 发生偏移。这一交互在论文中完全未提及。
I1:学习型切换策略(RL 控制器)
用轻量级 RL 控制器替代手调阈值策略:输入为{当前 batch 大小、TTFT 趋势、TPOT 趋势、KV 占用率},输出切换决策,优化目标为 SLO 达成率。表格 Q-learning 代理额外开销极小,且能自动适应新负载分布,无需重新调参。
I2:跨节点拓扑感知切换
将 Moebius 扩展到单 NVLink 域之外:实现拓扑感知 KV 重分配,优先进行节点内迁移(NVLink),再进行节点间迁移(InfiniBand)。分阶段方法摊薄 InfiniBand 代价,将 Moebius 的适用范围扩展到大规模跨节点部署。
I3:混合部分层切换
对底部 层(注意力主导)使用 EP,对顶部 层(中等 batch 下专家 GEMM 主导)使用 TP。这种混合布局可能在 时优于纯 EP 或纯 TP,代价是需要实现部分重新分片协议。
I4:惰性 KV 迁移
切换后不立即迁移全部 KV Cache,而是在下一个解码步骤中按需拉取缺失的 KV 头。这将切换窗口从 215–434 ms 压缩到约 152 ms(仅权重迁移),以分散的按需传输代价换取更短的集中式停顿。
I5:与前缀缓存集成
与 SGLang 的 RadixAttention 集成:共享的前缀 KV 页只迁移一次,然后更新所有引用该页的页表条目,而非对每个引用的请求独立迁移。对具有高前缀复用率的工作负载(如共享系统提示),可将 KV 迁移代价降低 50–80%。
十二、总结
Moebius 证明了 MoE 推理服务的并行策略应该是动态的而非静态的。核心洞见——EP 和 TP 是同一份模型权重的两种数据视图——既简洁又具有可操作性。工程实现(固定地址的 UMM、融合 NVLink 直传 kernel、双运行时常驻、非对称迟滞切换策略)设计严谨,取得了令人信服的成果:215–434 ms 切换窗口、2.4% 内存开销,以及在 RL rollout 场景下相比最优静态策略实现 1.16–1.25× 的加速,甚至超越步间先知。
局限性是真实存在的但可解决:单一硬件配置评估留下泛化性疑问,阈值策略需要人工调参,InfiniBand 路径未验证,也没有故障恢复协议。随着 MoE 模型成为主流和基于 RL 的后训练逐渐普及,Moebius 所代表的动态适应范式将成为日益重要的基础设施。
从业者决策参考:
你的工作负载会跨越 TP/EP 交叉点 B* 吗?
├── 否(稳定高并发或低并发)→ 使用静态 EP 或 TP,Moebius 的额外复杂性不必要。
└── 是(突发服务或 RL rollout):
你的集群是 NVLink 连接(单节点或 NVLink 桥接)吗?
├── 是 → Moebius 是强有力的候选,预计 rollout 加速 1.16–1.25×。
└── 否(InfiniBand)→ 等待 IB 验证结果,切换代价可能抵消收益。
KV Cache 容量有余量覆盖 2.4% 开销吗?
├── 是 → 在阈值自动标定工具可用后部署。
└── 否(显存极度紧张)→ 考虑惰性 KV 迁移变体(改进方向 I4)。
附录:交叉点分析与相关工作定位
A1. 交叉点 B* 的 Roofline 推导
为了从理论上理解 TP/EP 交叉点 ,考虑单个专家 GEMM 的计算量与内存带宽需求。
TP 模式(P 个 GPU 共享每个专家)下,算术强度(AI)为:
EP 模式下,本地有效 batch 大小为 ( 为路由 top-k):
此外,EP 的 All-to-All 通信增加了额外开销:
理论交叉条件:
其中 是 Roofline 模型的”山脊点”。H200 的峰值算力约 1,979 TFLOPS(BF16),HBM 带宽约 3.35 TB/s,因此 FLOPs/byte。代入 ,,:
这远高于实验观测到的 –,说明实际交叉点主要由 All-to-All 通信开销和 CUDA kernel 启动开销决定,而非纯粹的计算/带宽比值。这也是为什么必须通过实际 profiling 来标定阈值 ,而非依赖解析公式。
A2. 相关工作定位
稠密模型的运行时并行切换:
- Amoeba(OSDI 2024):稠密 Transformer 的运行时并行度切换,假设权重可以快速从主机内存传输(对 7–13B 模型可行,对 235B MoE 不可行)。
- Flying Serving 和 Shift Parallelism:类似方法,专注于异构硬件下的负载再平衡,均假设切换前引擎完全清空请求队列。
MoE 专用推理服务:
- HAP(MLSys 2024):离线混合并行策略选择,部署后静态固定。
- DeepSpeed-MoE 和 DeepSpeed-Inference:固定并行策略,无运行时切换能力。
- Lina:在固定 EP 布局内进行专家负载均衡,与 Moebius 正交(可组合)。
RL 训练基础设施:
- HybridFlow、OpenRLHF、DAPO:解耦 rollout + 训练流水线,关注同步/异步数据流,而非步骤内并行策略自适应。
- StreamRL 和 AReaL:异步 rollout 生成,Moebius 可作为互补组件(应用于其 rollout workers 内部)。
Moebius 与上述所有工作正交——它复用它们的 kernel 和调度算法,在其上叠加动态布局切换层。
A3. CUDA Graph Capture 代价说明
Moebius 每种布局 capture 36 个 CUDA Graph(TP + EP = 共 72 个)。每个图的 capture 过程包括:
- 热身(1–3 次迭代): 以 eager 模式执行模型,填充 kernel 参数记录。
- 图 capture(1 次迭代): 通过
cudaGraphBeginCapture重放,记录所有 kernel 启动和内存操作。 - 图实例化: 将图 JIT 编译为可执行对象。
对 Qwen3-235B-A22B(94 层,per-rank batch 1–256),每套图 capture 约需 2–5 分钟。两套图在启动时一次性 capture,此后整个部署周期内无需重复。Moebius 将这笔一次性开销摊薄为零——惰性 capture 方案则会将它打到首次切换时,引发一次服务等级违规,正是 Moebius 明确要避免的。