Prhub

#26389 【NPU】【bugfix】fix server error when mtp unquant

原始 PR 作者 cen121212 合并时间 2026-05-30 20:01 文件变更 5 提交数 6 评论 33 代码增减 +148 / -98

执行摘要

修复 NPU MTP 草稿模型未量化时的服务器崩溃

在 NPU 上,主模型和草稿模型可能使用不同的量化方案。随着 DeepEP 的重构,MTP 量化的环境变量错误地影响了主模型,导致服务器启动或运行时崩溃 (参考 PR body)。用户遇到类似错误:if self.quant_config.for_quantization: AttributeError: 'NoneType' object has no attribute 'for_quantization'

该 PR 修复了 NPU 上 MTP 未量化场景的关键崩溃,值得合并。但需要关注标准 DeepEP 量化路径的手动环境变量问题,建议跟踪 #26408 中的讨论以确定最终方案。

讨论亮点

关键讨论点:

  • gemini-code-assist[bot] 指出:直接使用 os.environ 设置环境变量不一致,应使用 envs 模块,并且要确保异常安全,添加 try...finally 块。这些建议最终被采纳。
  • OrangeRedeng 评论:移除 deepep.py 中的 _update_int8_quant_env 会破坏 Ascend NPU 上调度器输出类型的自动检测。作者回应称会手动设置 DEEP_NORMAL_MODE_USE_INT8_QUANT=1 环境变量。后续讨论还涉及标准 DeepEP 量化可能被破坏的问题 (#26408 提出了替代方案)。
  • 依赖重构:OrangeRedeng 承认是他的 DeepEP 调度器重构 (#22822) 导致了这个回归,并愿意帮助修复。

实现拆解

实现拆解如下:

  1. 移除 DeepEP 调度器中的自动环境变量设置:在 deepep.py_update_int8_quant_env 方法中,直接操作 os.environ 的代码被替换为 pass,并添加 TODO 注释说明未来需要适配主模型与草稿模型的不同量化方案。

  2. 在三个 MTP 草案模型 forward 方法中临时覆盖环境变量:在 deepseek_nextn.pyqwen3_5_mtp.pyqwen3_next_mtp.pyforward 方法开头,判断条件为 is_npu() and self.quant_config is None and get_global_server_args().quantization is not None 时,使用 ExitStack 进入上下文,通过 envs.SGLANG_DEEPEP_BF16_DISPATCH.override(True)envs.DEEP_NORMAL_MODE_USE_INT8_QUANT.override(False) 临时覆盖环境变量。整个 forward 逻辑被包裹在 try...finally 块中,确保异常发生时环境变量也能正确恢复。

  3. 调整测试环境变量:在 test_npu_deepep.py 中显式设置 DEEP_NORMAL_MODE_USE_INT8_QUANT=1,以保持测试与新的行为一致。

文件 模块 状态 重要度
python/sglang/srt/models/deepseek_nextn.py 草案模型 modified 7.7
python/sglang/srt/models/qwen3_5_mtp.py 草案模型 modified 7.21
python/sglang/srt/models/qwen3_next_mtp.py 草案模型 modified 7.11
python/sglang/srt/layers/moe/token_dispatcher/deepep.py 调度器 modified 5.61
test/registered/ascend/basic_function/parallel_strategy/expert_parallelism/test_npu_deepep.py 测试 modified 3.68

关键符号

DeepseekNextNDecoderLayer.forward Qwen3_5ForCausalLMMTP.forward Qwen3NextForCausalLMMTP.forward _DeepEPDispatcherImplBase._update_int8_quant_env

关键源码片段

python/sglang/srt/models/deepseek_nextn.py core-logic

DeepSeek NextN 草案模型 forward 方法中加入了环境变量覆盖逻辑,是核心修复之一。

@torch.no_grad()
def forward(
    self,
    input_ids: torch.Tensor,
    positions: torch.Tensor,
    forward_batch: ForwardBatch,
    input_embeds: torch.Tensor = None,
) -> torch.Tensor:
    # 使用 ExitStack 管理环境变量覆盖
    exit_stack = ExitStack()
    if (
        _is_npu
        and self.quant_config is None
        and get_global_server_args().quantization is not None
    ):
        # ascend mtp unquant: 强制关闭 INT8 量化,使用 BF16 调度
        exit_stack.enter_context(envs.SGLANG_DEEPEP_BF16_DISPATCH.override(True))
        exit_stack.enter_context(
            envs.DEEP_NORMAL_MODE_USE_INT8_QUANT.override(False)
        )
​
    try:
        # 原有的 forward 逻辑保持不变
        if input_embeds is None:
            hidden_states = self.embed_tokens(input_ids)
        else:
            hidden_states = input_embeds
        if hidden_states.shape[0] > 0:
            eh_input = torch.cat(
                (self.enorm(hidden_states),
                 self.hnorm(forward_batch.spec_info.hidden_states
                            if self.rot_weight is None
                            else torch.matmul(forward_batch.spec_info.hidden_states, self.rot_weight))),
                dim=-1,
            )
            hidden_states = self.eh_proj(eh_input) if isinstance(self.eh_proj, ReplicatedLinear) else self.eh_proj(eh_input)
        # ... 剩余的前向传播
        return self.logits_processor(input_ids, hidden_states, self.lm_head, forward_batch)
    finally:
        # 确保环境变量恢复,防止泄漏
        exit_stack.close()

评论区精华

使用 os.environ 与 envs 模块的安全性 正确性

gemini-code-assist[bot] 指出直接使用 os.environ 设置环境变量风险高,应使用 envs 模块并添加 try...finally 保证异常安全。作者最初在 deepseek_nextn.py 中使用了 os.environ,但在后续 commit 中改为 ExitStack 与 envs。

结论:最终实现使用了 envs 模块的 override 方法和 try...finally 块,确保了异常安全。 · 已解决

移除自动环境变量后标准 DeepEP 量化的影响 设计

OrangeRedeng 指出移除 deepep.py 中的 _update_int8_quant_env 会破坏 Ascend NPU 上调度器输出类型的自动检测。作者回应将手动设置 DEEP_NORMAL_MODE_USE_INT8_QUANT=1。后续讨论显示标准 DeepEP 量化可能被破坏,并提出了替代 PR #26408。

结论:当前 PR 移除了自动设置,要求用户或调用方手动设置环境变量。尚未完全解决标准 DeepEP 量化路径的兼容性问题,需跟踪 #26408。 · unresolved

Qwen MTP 模型未包裹 try-finally 的风险 正确性

gemini-code-assist[bot] 在 qwen3_5_mtp.py 和 qwen3_next_mtp.py 上评论指出,如果 forward 过程中抛出异常,exit_stack.close() 不会执行,导致环境变量泄漏。建议添加 try...finally。作者确认并完成了修改。

结论:已添加 try...finally 块,风险解除。 · 已解决

风险与影响

技术风险:

  1. 回归风险:移除 deepep.py 中的自动环境变量设置后,标准 DeepEP 量化路径 (非 MTP 场景) 将无法自动设置 DEEP_NORMAL_MODE_USE_INT8_QUANT。作者声称会手动导出,但若用户未手动设置,相关功能可能异常(已在 #26408 中讨论)。
  2. 条件覆盖不完整:修复仅针对 MTP 草案模型。若其他场景(如非 MTP 的推测解码)也涉及主模型与草稿模型量化不一致,仍可能崩溃。
  3. 环境变量泄漏:若 ExitStack 未能正确恢复(例如在极端异常下),环境变量可能泄漏,影响后续请求。当前实现使用了 try...finally,降低了该风险。

影响分析:

  • 直接用户:在 NPU 上使用 DeepSeek NextN 或 Qwen 系列模型且启用推测解码 (--speculative-algorithm NEXTN) 并指定草稿模型未量化 (--speculative-draft-model-quantization unquant) 的用户,将不再遇到服务器崩溃。
  • 其他用户:未使用 MTP 草案模型或不在 NPU 上的用户不受影响。但对于标准 DeepEP 量化,用户可能需要手动设置 DEEP_NORMAL_MODE_USE_INT8_QUANT=1 环境变量。
  • 团队维护:代码量变化不大 (148 行添加, 98 行删除),主要是逻辑重组,易于理解。
标准 DeepEP 量化需手动设置环境变量 环境变量泄漏风险 ( 已通过 try-finally 缓解 ) 回归风险:非 MTP 场景的量化自动检测被移除

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论