执行摘要
- 一句话:移除已失效的 inplace MoE 融合专家机制
- 推荐动作:建议阅读此 PR,以了解如何在大型代码库中安全、系统地删除被版本约束废弃的死代码。特别值得关注的是 torch custom op 输出别名输入的约束如何驱动架构决策,以及如何通过测试和 CI 验证大规模接口变更的正确性。
功能与动机
在 torch 2.9+ 中,torch custom op 无法安全表示输出别名输入,导致 inplace_fused_experts 被 disable_inplace() 强制禁用(参见 issue #26378)。自 vLLM 将 torch 最低版本提升至 2.11.0 后,inplace 路径始终为死代码。PR 执行已在 fused_moe.py:1585 遗留的 TODO:“Can get rid of inplace/outplace torch ops.”,彻底清理该技术债务。
实现拆解
- 删除 inplace_fused_experts 及 inplace_fused_experts_fake 的 custom op 注册:在
vllm/model_executor/layers/fused_moe/fused_moe.py 中移除两个函数及 direct_register_custom_op 调用。
- 重命名 outplace_fused_experts 为 fused_experts_op:在
fused_moe.py 中将原 outplace 函数更名为 fused_experts_op,其 fake 实现更名为 fused_experts_op_fake,并更新 dispatch_fused_experts_func 选择器为直接调用 torch.ops.vllm.fused_experts。
- 移除所有调用站点的 inplace 参数:从
fused_experts、fused_experts_impl、fused_marlin_moe、batched_fused_marlin_moe、FusedMoEKernel、FusedMoEKernelModularImpl 等函数/方法的签名中删除 inplace 形参,并移除对应的 if self.inplace: 分支逻辑。
- 删除 disable_inplace 配置:移除
vllm/model_executor/layers/fused_moe/utils.py 中的 disable_inplace() 函数,移除 vllm/model_executor/layers/fused_moe/config.py 中 FusedMoEConfig.disable_inplace 字段,并清理所有引用该配置的导入和条件语句。
- 更新所有调用者和测试文件:涵盖所有 oracle 后端(fp8, mxfp4, nvfp4 等)、量化集成(quark, bitsandbytes, compressed-tensors)、模型特定代码(arctic, minicpm)以及 15 个 MoE 测试文件。
关键文件:
vllm/model_executor/layers/fused_moe/fused_moe.py(模块 MoE内核;类别 source;类型 core-logic;符号 inplace_fused_experts, inplace_fused_experts_fake, outplace_fused_experts, fused_experts_op): 核心文件:删除了 inplace_fused_experts 及其 fake 实现,重命名 outplace_fused_experts 为 fused_experts_op,移除 inplace 参数传递。
vllm/model_executor/layers/fused_moe/modular_kernel.py(模块 MoE内核;类别 source;类型 core-logic;符号 inplace): 删除 FusedMoEKernelModularImpl 中的 inplace 属性和相关分支,简化 apply 逻辑。
vllm/model_executor/layers/fused_moe/utils.py(模块 工具函数;类别 source;类型 data-contract;符号 disable_inplace): 删除 disable_inplace() 函数和 functools, is_torch_equal_or_newer 导入。
vllm/model_executor/layers/fused_moe/config.py(模块 配置层;类别 source;类型 configuration;符号 disable_inplace): 删除 FusedMoEConfig.disable_inplace 字段,简化配置。
vllm/model_executor/layers/fused_moe/experts/marlin_moe.py(模块 MarLin专家;类别 source;类型 data-contract): 移除 fused_marlin_moe 和 batched_fused_marlin_moe 的 inplace 参数及相关断言。
vllm/model_executor/layers/fused_moe/layer.py(模块 MoE层;类别 source;类型 data-contract): 删除对 disable_inplace 的引用,移除 inplace 参数传递给 ModularKernel。
关键符号:inplace_fused_experts, inplace_fused_experts_fake, outplace_fused_experts, fused_experts_op, disable_inplace, FusedMoEKernelModularImpl.init, FusedMoEKernelModularImpl.apply, FusedMoEKernel.inplace, fused_marlin_moe, batched_fused_marlin_moe, fused_experts
关键源码片段
vllm/model_executor/layers/fused_moe/fused_moe.py
核心文件:删除了 inplace_fused_experts 及其 fake 实现,重命名 outplace_fused_experts 为 fused_experts_op,移除 inplace 参数传递。
# fused_experts_op —— 替代原 inplace_fused_experts 和 outplace_fused_experts
# 移除了 inplace 参数,所有调用统一走 outplace 路径
def fused_experts_op(
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
activation: str = "silu",
apply_router_weight_on_input: bool = False,
use_fp8_w8a8: bool = False,
use_int8_w8a8: bool = False,
use_int8_w8a16: bool = False,
use_int4_w4a16: bool = False,
ocp_mx_scheme: str | None = None,
per_channel_quant: bool = False,
global_num_experts: int = -1,
expert_map: torch.Tensor | None = None,
w1_scale: torch.Tensor | None = None,
w2_scale: torch.Tensor | None = None,
w1_zp: torch.Tensor | None = None,
w2_zp: torch.Tensor | None = None,
a1_scale: torch.Tensor | None = None,
a2_scale: torch.Tensor | None = None,
block_shape: list[int] | None = None,
w1_bias: torch.Tensor | None = None,
w2_bias: torch.Tensor | None = None,
) -> torch.Tensor:
# 原 inplace=True 分支已删除,inplace 参数不复存在
# 直接调用 fused_experts_impl,内部 inplace 参数固定为 False
return fused_experts_impl(
hidden_states,
w1,
w2,
topk_weights,
topk_ids,
activation,
apply_router_weight_on_input,
use_fp8_w8a8,
use_int8_w8a8,
use_int8_w8a16,
use_int4_w4a16,
ocp_mx_scheme,
per_channel_quant,
global_num_experts,
expert_map,
w1_scale,
w2_scale,
w1_zp,
w2_zp,
a1_scale,
a2_scale,
block_shape,
w1_bias,
w2_bias,
)
vllm/model_executor/layers/fused_moe/modular_kernel.py
删除 FusedMoEKernelModularImpl 中的 inplace 属性和相关分支,简化 apply 逻辑。
@final
class FusedMoEKernelModularImpl:
def __init__(
self,
prepare_finalize: FusedMoEPrepareAndFinalizeModular,
fused_experts: FusedMoEExpertsModular,
# inplace: bool = False, # 已删除:不再支持 inplace
):
self.prepare_finalize = prepare_finalize
self.fused_experts = fused_experts
# self.inplace = inplace # 已删除
moe_parallel_config = fused_experts.moe_config.moe_parallel_config
self.moe_parallel_config = moe_parallel_config
self.is_dp_ep = (
moe_parallel_config is not None
and moe_parallel_config.dp_size > 1
and moe_parallel_config.use_ep
)
# 在 apply 方法中,原先 if self.inplace: output = hidden_states 分支被移除,
# 直接统一 output = torch.empty_like(hidden_states)
def apply(self, ...) -> torch.Tensor:
# if self.inplace: # 已删除
# assert shared_experts is None
# assert not disable_inplace()
# output = hidden_states
# else:
output = torch.empty_like(hidden_states)
# ... 后续统一处理
评论区精华
在 code review 中,bnellnm 首先建议将 dispatch_fused_experts_func 分支直接替换为 torch.ops.vllm.fused_experts 调用,并将残留的 TODO 注释清理干净;提交者 zyongye 确认已更新。此外,PR 合并后 fxmarty-amd 在 issue 评论中指出该 PR 破坏了 benchmarks/kernels/benchmark_moe.py(因 disable_inplace 被删除),随后在 #44041 中修复。
- 删除剩余 TODO (design): 提交者 zyongye 移除该 TODO。
- 重命名 outplace_fused_experts 为 fused_experts (design): 提交者 zyongye 采纳并执行重命名,最终 op 名为
fused_experts。
- 破坏 benchmarks/kernels/benchmark_moe.py (correctness): 该问题在 #44041 中修复,通过适配 benchmark 代码。
风险与影响
- 风险:主要风险来自
disable_inplace() 函数的删除:任何直接调用该函数的外部代码(如 benchmarks/kernels/benchmark_moe.py)都会引入 NameError,该问题已在 #44041 修复。此外,若外部项目直接通过 inplace 参数调用 fused_experts 等公开 API,也会因签名变更而报错,但 vLLM 内部所有调用点已全部更新。鉴于测试套件通过且改动均为机械性删除,回归风险较低。
- 影响:对最终用户无功能或性能影响(inplace 路径从未生效)。对开发者而言,MoE 内核的调用接口简化,减少了因
inplace 参数带来的认知负担和维护成本。系统层面,减少了注册的 custom op 数量,降低因 Torch 升级导致的潜在兼容性问题。
- 风险标记:向后兼容风险, 核心路径变更, 大量文件修改, 工具链影响
关联脉络
- PR #26378 torch custom op output aliasing input issue: 原始 issue,描述 torch custom op 无法安全处理输出别名输入,导致 inplace 机制被禁用。
- PR #26497 Add disable_inplace() guard for torch>=2.9: 引入 disable_inplace() 函数作为临时工作区,本 PR 最终移除了该防护。
- PR #44041 Fix benchmark_moe.py after disable_inplace removal: 修复本 PR 导致的 benchmark 脚本破坏。
参与讨论