Prhub

#42434 Revert "[Core] Replace routing replay with device cache and async D2H pipeline" (#39917)

原始 PR 作者 aoshen02 合并时间 2026-05-14 14:49 文件变更 15 提交数 6 评论 5 代码增减 +614 / -1377

执行摘要

回退 MoE 路由捕获机制到共享内存方案

根据 PR body ,原作者(aoshen02)来自 nemo RL 有紧急发布需求,无法很快支持 #39917 方案中缺失的 prefix caching 等特性。经双方沟通,同意暂时还原 #39917,回退到之前的共享内存设计,并准备使用 #39568 作为后续更完善的方案。

建议密切关注被回退的 device cache 方案与后续 #39568 的演进关系。核心设计决策(共享内存 vs. device pipeline)值得深入阅读 routed_experts_capturer.py 中的注释和实现差异。对于直接使用 routed_experts API 的客户,需评估移除字段的影响。

讨论亮点

自动代码审查工具 gemini-code-assist[bot] 指出了两个潜在问题:

  • 还原移除了 moe_layer_id 赋值,可能引发 AttributeError。作者回复验证 FusedMoE.layer_id 已通过 @propertylayer_name 解析,无错误。
  • 绑定阶段使用 module.layer_id 可能超出 buffer 边界。作者回复 buffer 维度为 num_hidden_layers(全局层数),与全局层索引一致,不会越界。
    两个问题均被作者详细回复并标记为误报,未遗留未解决担忧。

实现拆解

  1. 核心模块重写vllm/model_executor/layers/fused_moe/routed_experts_capturer.py):从自定义 CUDA op + _RoutedExpertsDeviceCache / _RoutedExpertsHostCache 完全切换到 SharedMemory 方案,引入 _file_lock_create_or_attach_shared_memory 等辅助函数,RoutedExpertsCapturerRoutedExpertsReader 作为全局单例管理设备缓冲区和跨进程共享内存。
  2. 模型运行器解耦vllm/v1/worker/gpu_model_runner.py):移除异步 D2H 拷贝触发和路由数据提取调用(issue_routing_d2h_copyextract_routed_experts_for_current_batch),改用 RoutedExpertsCapturer.get_instance().clear_buffer() 替换 finalize_pending_copy;导入简化,仅依赖 RoutedExpertsCapturer
  3. 调度器直连共享内存vllm/v1/core/sched/scheduler.py):Scheduler.__init__ 中创建 RoutedExpertsReader 单例并 attach_buffer_get_routed_experts 直接通过共享内存的 numpy 视图获取路由数据,不再依赖 ModelRunnerOutput.routed_experts_dict
  4. API 响应精简vllm/entrypoints/openai/chat_completion/serving.py 等):移除 prompt_routed_experts 和 completion 级别的专家路由信息输出,output_processor.py 不再调用 split_routed_experts
  5. 配置与文档清理vllm/config/vllm.pydocs/training/routed_experts_replay.md):删除对 enable_return_routed_experts 的并行限制校验;整个文档文件随方案删除。
文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/routed_experts_capturer.py 路由捕获 modified 9.15
vllm/v1/worker/gpu_model_runner.py 模型运行器 modified 8.24
tests/model_executor/test_routed_experts_capture.py 测试 modified 8.02
vllm/v1/core/sched/scheduler.py 调度器 modified 7.78
vllm/config/vllm.py 配置校验 modified 7.13
vllm/v1/engine/output_processor.py 输出处理 modified 6.64
docs/training/routed_experts_replay.md 文档 removed 5.2

关键符号

RoutedExpertsCapturer.create RoutedExpertsCapturer.init_buffer RoutedExpertsCapturer.capture RoutedExpertsCapturer.clear_buffer RoutedExpertsReader.create RoutedExpertsReader.attach_buffer RoutedExpertsReader.get_routed_experts _get_num_experts_per_tok _file_lock _create_or_attach_shared_memory GPUModelRunner._bind_routed_experts_capturer GPUModelRunner.init_routed_experts_capturer Scheduler._get_routed_experts

关键源码片段

vllm/model_executor/layers/fused_moe/routed_experts_capturer.py data-contract

核心实现文件,从 device cache + custom op 重写为共享内存 + 文件锁,是整个 revert 的主要载体。

# vllm/model_executor/layers/fused_moe/routed_experts_capturer.pyimport fcntl
import os
import tempfile
from contextlib import contextmanager
from multiprocessing import shared_memory
from typing import Generator_TMP_DIR = tempfile.gettempdir()
_LOCK_FILE_PREFIX = os.path.join(_TMP_DIR, "vllm_routed_experts")
_BUFFER_PREFIX = "vllm_routed_experts_buffer"
​
​
@contextmanager
def _file_lock(lock_file: str, mode: str = "wb+") -> Generator[None, None, None]:
    """跨进程文件锁,确保共享内存创建和附加的原子性。"""
    with open(lock_file, mode) as fp:
        fcntl.flock(fp, fcntl.LOCK_EX)
        try:
            yield
        finally:
            fcntl.flock(fp, fcntl.LOCK_UN)
​
​
def _create_or_attach_shared_memory(
    name: str, size: int, lock_file: str
) -> shared_memory.SharedMemory:
    """创建或附加到已存在的共享内存块,通过文件锁处理竞态。"""
    # 确保锁文件存在
    with open(lock_file, "wb"):
        pass
​
    with _file_lock(lock_file):
        try:
            shm = shared_memory.SharedMemory(name=name, create=True, size=size)
        except FileExistsError:
            shm = shared_memory.SharedMemory(name=name, create=False, size=size)
​
        # 如果 size 不匹配,重建共享内存
        if shm.size != size:
            shm.close()
            shm.unlink()
            try:
                shm = shared_memory.SharedMemory(name=name, create=True, size=size)
            except FileExistsError:
                shm = shared_memory.SharedMemory(name=name, create=False, size=size)
​
    return shm

评论区精华

移除 moe_layer_id 可能导致 AttributeError 正确性

gemini-code-assist[bot] 指出还原移除了 moe_layer_id 赋值,但绑定时期待 layer_id 属性,可能 AttributeError。

结论:aoshen02 回复验证 FusedMoE.layer_id 已通过 @property 从 layer_name 解析,不存在错误。 · 已解决

使用全局 layer_id 索引可能超出 buffer 边界 正确性

gemini-code-assist[bot] 指出绑定时使用 module.layer_id 可能超出 device_buffer 的 MoE 层数范围。

结论:aoshen02 回复 buffer 维度为 (num_hidden_layers, ...),与全局层索引一致,不会越界。 · 已解决

风险与影响

  1. 性能回归:从自定义 CUDA op + 预分配 device buffer 切回共享内存 + 文件锁,可能增加跨进程同步开销,但典型批量场景影响有限。
  2. 兼容性风险:OpenAI API 响应中移除了 routed_experts 字段,依赖该字段的客户端会失效,属于破坏性变更。
  3. 配置安全性VllmConfig 中对 enable_return_routed_experts 的并行校验被整段删除,若用户在共享内存方案下启用未支持的并行(如 PP > 1),可能因未验证路径抛出意外错误。
  4. 并发风险:共享内存使用文件锁,高并发场景下可能成为瓶颈。

对用户:所有使用 --enable-return-routed-experts 的推理请求将不再在 API 响应中获得 routed_experts 信息。对系统:路由专家数据不再通过 device->host 异步 pipeline 传输,转而通过操作系统共享内存,可能轻微增加延迟但减小 GPU 内存占用。对团队:该 revert 为后续基于 #39568 的重构铺平了道路,但短期内需要维护两个版本的 capturer 实现。

核心路径变更 API 响应字段移除 共享内存并发瓶颈 配置校验移除

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论