Prhub

#31870 [lora] Fix WAR race: never write MoE runner output into hidden_states in place

原始 PR 作者 sshleifer 合并时间 2026-07-24 15:58 文件变更 2 提交数 4 评论 3 代码增减 +121 / -0

执行摘要

修复 LoRA MoE 写后读竞争导致生成乱码

在 LoRA 长 decode 评测中会出现随机乱码,根源在于 MoE runner 的 inplace 写与 dual-stream 共享专家读构成 CUDA graph 内的写后读竞争。PR body 详细描述了根因和验证。

值得精读,展示了如何诊断 CUDA graph 中的写后读竞争、如何用最小复现定位问题,以及一个配置项即可修复的案例。

讨论亮点

作者在 issue 评论中提供了确定性最小复现方法(单 GPU、无 serving 栈),复现 forward_normal_dual_stream 的 stream 边缘并与 eager 参考对比,证实 inplace=True 时满足 analytic 写后读。review 仅由 yushengsu-thu 审批通过,无额外讨论。

实现拆解

  1. 根因定位:作者通过分析 forward_normal_dual_stream 的 stream 边界和 LoRA runner 的 inplace 行为,确认是 hidden_states 的写后读竞争。
  2. 核心修复:在 python/sglang/srt/lora/layers.pyFusedMoEWithLoRA.__init__ 中,从 base_layer 拿到 moe_runner_config 后立即设置 self.moe_runner_config.inplace = False,覆盖可能来自 base 的 inplace=True
  3. 回归测试:新增 test/registered/unit/lora/test_lora_moe_inplace_unit.py,构造明确 inplace=True 的配置,调用真实构造函数,断言 shared config 的 inplace 被强制为 False,并覆盖 triton fallback 和 marlin 两种 runner core。
文件 模块 状态 重要度
python/sglang/srt/lora/layers.py LoRA 层 modified 5.53
test/registered/unit/lora/test_lora_moe_inplace_unit.py 单元测试 added 7.79

关键符号

FusedMoEWithLoRA.__init__ _make_base_layer test_constructor_forces_inplace_off test_marlin_lora_runner_core_sees_non_inplace_config

关键源码片段

python/sglang/srt/lora/layers.py core-logic

核心修复文件:在 FusedMoEWithLoRA 构造时强制设置 inplace=False,一行代码消除写后读竞争。

# python/sglang/srt/lora/layers.py
class FusedMoEWithLoRA(BaseLayerWithLoRA):
    def __init__(self, base_layer: FusedMoE, lora_backend: BaseLoRABackend):
        super().__init__(base_layer, lora_backend)
        # ... 省略其他初始化 ...
        self.moe_runner_config = base_layer.moe_runner_config
        # 强制 inplace=False:避免 dual-stream 前向中 hidden_states 的写后读竞争
        self.moe_runner_config.inplace = False
        # 后续初始化逻辑保持不变
        self.dispatcher = base_layer.dispatcher
        # ...
test/registered/unit/lora/test_lora_moe_inplace_unit.py test-coverage

新增回归测试,覆盖 triton fallback 和 marlin 两种 runner core,验证构造函数强制 inplace=False。

# test/registered/unit/lora/test_lora_moe_inplace_unit.py
class FusedMoEWithLoRAInplaceTest(unittest.TestCase):
    def _construct(self, quant_method=None):
        from sglang.srt.lora.layers import FusedMoEWithLoRA
        base_layer = _make_base_layer(quant_method) # inplace=True
        lora_backend = types.SimpleNamespace()
        self.assertTrue(base_layer.moe_runner_config.inplace)
        layer = FusedMoEWithLoRA(base_layer, lora_backend)
        return layer, base_layer
​
    def test_constructor_forces_inplace_off(self):
        layer, base_layer = self._construct()
        self.assertIs(layer.moe_runner_config, base_layer.moe_runner_config)
        self.assertFalse(layer.moe_runner_config.inplace)
​
    def test_marlin_lora_runner_core_sees_non_inplace_config(self):
        # 模拟 marlin runner 后端
        with mock.patch("sglang.srt.layers.moe.utils.get_moe_runner_backend",
                        return_value=MoeRunnerBackend.MARLIN):
            layer, base_layer = self._construct(
                quant_method=mock.MagicMock(spec=CompressedTensorsFusedMoEMethod))
        core = getattr(layer._lora_runner, "runner_core", None)
        self.assertIsNotNone(core)
        core_config = getattr(core, "config", None) or getattr(core, "runner_config", None)
        self.assertIs(core_config, base_layer.moe_runner_config)
        self.assertFalse(core_config.inplace)

评论区精华

根因分析与确定性复现 正确性

作者在 issue 评论中提供了单 GPU、无 serving 栈的确定性复现脚本,通过复制 stream 边缘和 eager 对比确认竞争。

结论:确认 `inplace=True` 导致写后读竞争,修复方式为强制 `inplace=False`。 · 已解决

风险与影响

影响范围窄,仅 LoRA 场景;通过强制 inplace=False(分配新 buffer)而非复用 hidden_states,可能带来轻微额外显存开销,但 PR body 中验证了 decode 吞吐和 tpot 不变。非 LoRA 路径已有独立分配,不受影响。测试覆盖两种 runner core,回归风险低。

对使用 LoRA 且模型包含 dual-stream MoE(如 DeepseekV2)的用户意义重大,消除随机乱码;非 LoRA 用户无影响。修复本身一行代码,易于审查。

CUDA graph 竞争 LoRA 专用修复

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论