Prhub

#23973 [Fix] FP8 Qwen3-Next quant error by removing fallback fused shards

原始 PR 作者 Jiminator 合并时间 2026-04-30 05:33 文件变更 2 提交数 1 评论 3 代码增减 +52 / -2

执行摘要

修复 Qwen3-Next FP8 量化加载错误

修复由 #23467 引入的回归问题,该问题导致 Qwen3-Next FP8 在 B200 4-GPU 上服务器初始化时失败,报错:"ValueError: Weight output_partition_size = 8 is not divisible by weight quantization block_n = 128"。PR body 详细描述了根因:in_proj_bain_proj_qkvz 是 Qwen3-Next FP8 checkpoint 中真实的统一张量(明确列在 modules_to_not_convert 中),但 #23467 将它们加入 _FALLBACK_FUSED_SHARDS 后,会被解构为不存在的子张量名(如 in_proj_bin_proj_a),导致 is_layer_skipped 错误地返回 False,最终触发 Fp8LinearMethod.validate_block_quant_shapes 的校验失败。

该 PR 值得精读,因为它展示了 FP8 量化配置中 modules_to_not_convert 与融合层映射的微妙交互,以及回归问题的诊断过程。测试文件的设计清晰且具有代表性,可作为类似场景的测试模板。

讨论亮点

唯一的 review 评论来自合并者 b8zhong,他批准了 PR 并询问:"Do you mind adding a note somewhere in the code, since I think this is the difference of Q3N weight format vs Q3.5 weight format?" 这暗示了 Qwen3-Next 与 Qwen3.5 权重格式的差异应在代码中明确注释,以避免未来再次误操作。但该建议未在本次 PR 中实施。

实现拆解

实现分为两部分:

  1. 源码修复python/sglang/srt/layers/quantization/utils.py):从 _FALLBACK_FUSED_SHARDS 字典中删除 "in_proj_ba": ["in_proj_b", "in_proj_a"]"in_proj_qkvz": ["in_proj_qkv", "in_proj_z"] 两个条目。这个字典作为 packed_modules_mapping 缺失时的回退映射,用于将融合线性层名映射到其分片名。删除后,in_proj_bain_proj_qkvz 不再被视为虚拟融合名,因此 is_layer_skipped 会直接使用 modules_to_not_convert 中的原始名进行路径匹配,正确跳过这些层。
  2. 新增回归测试test/registered/quant/test_is_layer_skipped.py):新增 CPU 回归测试文件(约 5s,stage-a-test-cpu),包含三个测试用例:
    • test_qwen3_next_in_proj_ba_is_skipped:验证 in_proj_bamodules_to_not_convert 中时正确被跳过。
    • test_qwen3_next_in_proj_qkvz_is_skipped:验证 in_proj_qkvzmodules_to_not_convert 中时正确被跳过。
    • test_mlp_gate_does_not_match_gate_up_proj:验证 mlp.gate 不会错误匹配 mlp.gate_up_proj,确保 #23467 的原始动机仍被保留。
文件 模块 状态 重要度
python/sglang/srt/layers/quantization/utils.py 量化层 modified 5.69
test/registered/quant/test_is_layer_skipped.py 测试 added 7.22

关键符号

is_layer_skipped _module_path_match

关键源码片段

python/sglang/srt/layers/quantization/utils.py core-logic

核心修复文件:删除 _FALLBACK_FUSED_SHARDS 中错误的两行,修复回归问题。

# python/sglang/srt/layers/quantization/utils.py
# 关键片段:_FALLBACK_FUSED_SHARDS 的定义(修复后)# Known fused-linear -> shard names. Used as a fallback when the quant
# config doesn't ship packed_modules_mapping (typical for HF FP8 configs).
# 注意:in_proj_ba 和 in_proj_qkvz 已被移除,因为它们是 Qwen3-Next FP8
# 中真实的统一张量(非虚拟融合名),不应被解构。
_FALLBACK_FUSED_SHARDS: Mapping[str, List[str]] = {
    "qkv_proj": ["q_proj", "k_proj", "v_proj"],
    "gate_up_proj": ["gate_proj", "up_proj"],
}
test/registered/quant/test_is_layer_skipped.py test-coverage

新增回归测试文件,覆盖修复场景并确保 #23467 的原始动机仍被保留。

# test/registered/quant/test_is_layer_skipped.py
# 新增的回归测试文件from sglang.srt.layers.quantization.utils import is_layer_skipped
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCaseregister_cpu_ci(est_time=5, suite="stage-a-test-cpu")# Qwen3-Next FP8 的 packed_modules_mapping 等价物(来自 qwen3_next.py:908-911)
# 注意:in_proj_ba 和 in_proj_qkvz 被故意省略,因为它们是真实统一张量
QWEN3_NEXT_FUSED_MAPPING = {
    "qkv_proj": ["q_proj", "k_proj", "v_proj"],
    "gate_up_proj": ["gate_proj", "up_proj"],
}
​
​
def _qwen3_next_ignored_layers(layer_idx: int, name: str) -> list:
    """
    模拟 Fp8Config.from_config 中的规范化:每个条目同时保留
    "model.<...>" 和 bare "<...>" 两种形式。
    """
    base = f"layers.{layer_idx}.linear_attn.{name}"
    return [base, f"model.{base}"]
​
​
class TestIsLayerSkipped(CustomTestCase):
    def test_qwen3_next_in_proj_ba_is_skipped(self):
        # 回归测试 for #23467:in_proj_ba 是 FP8 checkpoint 中的统一张量,
        # modules_to_not_convert 明确列出它,因此必须跳过 FP8 量化。
        # 否则在 tp=4 时 validate_block_quant_shapes 会因
        # output_partition_size=8 与 block_n=128 不兼容而报错。
        prefix = "model.layers.0.linear_attn.in_proj_ba"
        ignored = _qwen3_next_ignored_layers(0, "in_proj_ba")
        self.assertTrue(is_layer_skipped(prefix, ignored, QWEN3_NEXT_FUSED_MAPPING))
​
    def test_qwen3_next_in_proj_qkvz_is_skipped(self):
        prefix = "model.layers.5.linear_attn.in_proj_qkvz"
        ignored = _qwen3_next_ignored_layers(5, "in_proj_qkvz")
        self.assertTrue(is_layer_skipped(prefix, ignored, QWEN3_NEXT_FUSED_MAPPING))
​
    def test_mlp_gate_does_not_match_gate_up_proj(self):
        # 验证 #23467 的原始动机:modules_to_not_convert 中的 "mlp.gate"
        # 不能错误地跳过 "mlp.gate_up_proj"。
        ignored = ["mlp.gate"]
        self.assertFalse(
            is_layer_skipped("model.layers.0.mlp.gate_up_proj", ignored, {})
        )
        self.assertTrue(is_layer_skipped("model.layers.0.mlp.gate", ignored, {}))

评论区精华

代码注释建议:Qwen3-Next 与 Qwen3.5 权重格式差异 other

合并者 b8zhong 建议在代码中添加注释说明 Qwen3-Next 与 Qwen3.5 权重格式的差异,因为这可能是导致误将 `in_proj_ba` 等加入回退表的原因。

结论:未在本次 PR 中实施,但建议在后续 PR 中补充注释。 · unresolved

风险与影响

回归风险:修复移除两个条目后,若其他模型(如 Qwen3.5)的 FP8 配置确实依赖于这两个条目进行正确的分片映射,则可能导致类似加载失败。但 PR 说明指出,这些模型会通过自身的 packed_modules_mapping 声明融合关系,is_layer_skipped 优先使用该映射而非回退表,因此风险较低。测试覆盖:新增的回归测试覆盖了关键场景,但未测试 Qwen3.5 等模型是否仍能正常工作。建议在 CI 中补充运行相关模型的 FP8 加载测试。

直接影响:修复了 Qwen3-Next FP8 在 B200 4-GPU 上无法启动的问题。影响范围:仅限于使用 _FALLBACK_FUSED_SHARDS 回退映射的量化配置;对于自带 packed_modules_mapping 的模型配置无影响。对用户:Qwen3-Next FP8 用户可正常加载模型。对系统:变更极小,仅删除两行代码,性能无影响。

缺少注释说明权重格式差异 仅覆盖 Qwen3-Next 场景,未测试 Qwen3.5

关联 Issue

#23467 fix: dot-boundary match in is_layer_skipped for FP8 modules_to_not_convert

完整报告

参与讨论