Prhub

#41161 Fix static actorder handling for compressed-tensors WNA16 MoE

原始 PR 作者 ZewenShen-Cohere 合并时间 2026-06-23 06:46 文件变更 3 提交数 8 评论 5 代码增减 +92 / -9

执行摘要

修复 static actorder 下 WNA16 MoE w2 scales sharding

当使用 actorder=static(等效于 weight-order,无运行时 g_idx)的 compressed-tensors WNA16 MoE checkpoint 时,tp=2, group_size=32 的 Qwen3-A3B W4A16 模型在加载时因 Marlin kernel 推断出无效 group size 而崩溃。根本原因是原有逻辑将任何 truthy actorder 值视为 grouped actorder,导致 w2 scales 未按 TP 分区 shard 且 is_k_full 为 False。

值得精读的设计决策:将 sharding 规则提取为纯静态方法,方便单元测试和后续扩展;早失败原则(对不可整除 size 抛出异常)优于运行时 CUDA 崩溃。建议未来关注枚举替换字符串的后续 PR。

讨论亮点

枚举比较 vs 字符串比较:gemini-code-assist[bot] 建议在 _w2_scale_sharding 中使用 ActivationOrdering.GROUP 枚举成员替代硬编码字符串 "group",以增强健壮性和可维护性。作者 ZewenShen-Cohere 回复同意该建议,但认为应在独立 PR 中统一修改,避免本 PR 范围过大。该讨论未在本 PR 中修改,但已记录为待改进项。

实现拆解

  1. 提取 sharding 决策逻辑:在 compressed_tensors_moe_wna16_marlin.py 中新增静态方法 _w2_scale_sharding,根据 actorder 是否为 "group" 决定是否加载完整 w2 scales(load_full_w2)以及 is_k_full 的值。
  2. 改进 create_weights 调用:在 create_weights 中调用 _w2_scale_sharding 替代原有内联逻辑,并移除旧注释。
  3. 添加整除性验证:当 load_full_w2=False 时,检查 intermediate_size_per_partition % group_size == 0,否则抛出清晰的 ValueError。
  4. 同步到非 Marlin 后端:在 compressed_tensors_moe_wna16.py 中添加相同的整除性验证,确保 Flashinfer 等后端也获得早期错误反馈。
  5. 单元测试覆盖:新增参数化测试 test_wna16_marlin_moe_w2_scale_sharding,覆盖 "group""static""weight"None 及 channel-wise 场景,验证 _w2_scale_sharding 返回值符合预期。
文件 模块 状态 重要度
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16_marlin.py 量化层 modified 7.37
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16.py 量化层 modified 6.25
tests/quantization/test_compressed_tensors.py 测试 modified 5.43

关键符号

CompressedTensorsWNA16MarlinMoEMethod._w2_scale_sharding CompressedTensorsWNA16MarlinMoEMethod.create_weights CompressedTensorsWNA16MoEMethod.create_weights

关键源码片段

vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16_marlin.py core-logic

核心变更文件,新增 `_w2_scale_sharding` 方法和整除性验证,修复 static actorder 下的 w2 scale sharding 逻辑。

@staticmethod
def _w2_scale_sharding(
    actorder,
    group_size: int,
    intermediate_size_per_partition: int,
    intermediate_size_full: int,
) -> tuple[bool, int, bool]:
    """Decide how to shard w2 group scales across TP for WNA16 Marlin MoE.    Only ``actorder="group"`` permutes activations by ``g_idx`` at runtime
    and therefore needs the full-K (unsharded) w2 scales plus ``is_k_full``.
    ``actorder="weight"``/``"static"`` (and ``None``) reorder weights at
    quantization time, so scales shard normally per TP rank.
    """
    # 仅当 actorder == "group" 且 group_size != -1 时才需要完整加载 w2 scales
    load_full_w2 = (actorder == "group") and group_size != -1
    w2_scales_size = (
        intermediate_size_full if load_full_w2 else intermediate_size_per_partition
    )
    # is_k_full 在非 group 或分区等于完整大小时为 True
    is_k_full = (actorder != "group") or (
        intermediate_size_per_partition == intermediate_size_full
    )
    return load_full_w2, w2_scales_size, is_k_full
tests/quantization/test_compressed_tensors.py test-coverage

新增参数化单元测试,全面覆盖各种 actorder/group_size/TP 分区场景,确保修复正确性并防止回归。

@pytest.mark.parametrize(
    "actorder,group_size,part,full,expected",
    [
        # actorder="group" with real grouping: must load full-K w2 scales and,
        # when sharded (part != full), report is_k_full=False.
        (ActivationOrdering.GROUP, 32, 64, 128, (True, 128, False)),
        # actorder="group" but unsharded (part == full): full scales, k_full.
        (ActivationOrdering.GROUP, 32, 128, 128, (True, 128, True)),
        # actorder="group" with channel-wise (group_size == -1): no full load.
        (ActivationOrdering.GROUP, -1, 64, 128, (False, 64, False)),
        # "static"/"weight" reorder at quant time -> shard normally + k_full.
        # Regression: static actorder under TP must keep is_k_full=True so the
        # Marlin kernel never gets the invalid (group_size=16, is_k_full=0).
        ("static", 32, 64, 128, (False, 64, True)),
        ("weight", 32, 64, 128, (False, 64, True)),
        (None, 32, 64, 128, (False, 64, True)),
    ],
)
def test_wna16_marlin_moe_w2_scale_sharding(actorder, group_size, part, full, expected):
    from vllm.model_executor.layers.quantization.compressed_tensors.\
        compressed_tensors_moe.compressed_tensors_moe_wna16_marlin import \
        CompressedTensorsWNA16MarlinMoEMethod
    result = CompressedTensorsWNA16MarlinMoEMethod._w2_scale_sharding(
        actorder, group_size, part, full
    )
    assert result == expected

评论区精华

使用 ActivationOrdering 枚举替代字符串比较 设计

gemini-code-assist[bot] 建议在 `_w2_scale_sharding` 中使用 `ActivationOrdering.GROUP` 而非硬编码字符串 `"group"`,以避免未来库实现变更导致的兼容性问题。

结论:作者认为该建议合理,但字符串比较在代码中多处使用,决定在独立 PR 中统一修改,不在本 PR 中调整。 · deferred

风险与影响

类型假设风险_w2_scale_sharding 中直接比较 actorder == "group",依赖 compressed-tensors 库将 ActivationOrdering 实现为 StrEnum。若未来库改变实现或用户传入其他类型,可能无法正确匹配。当前风险可控,但建议逐步迁移到枚举比较。兼容性风险:新增的整除性验证可能阻止一些原本能勉强运行但 scale 跨边界的 TP 配置,但这种“失败”比后期 CUDA kernel 崩溃更可取,属于安全增强。

用户影响:使用 compressed-tensors WNA16 MoE 且 actorder=static/weight 的用户现在可以正确加载模型并使用 TP>1。修复后 gsm8k 评测 TP=1 与 TP=2 分数接近,精度无损。系统影响:仅影响 compressed_tensors 量化中 MoE 权重 sharding 路径,不影响其他量化或线性层。团队影响:明确了不同 actorder 类型的语义,提升了代码可读性和可测试性,降低了未来维护成本。

核心路径变更 枚举比较风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论