Prhub

#55235 [ROCm][Perf] Tune MiniMax-M3 decode top-k for short contexts

原始 PR 作者 Fangzhou-Ai 合并时间 2026-09-14 10:36 文件变更 2 提交数 4 评论 13 代码增减 +90 / -48

执行摘要

gfx950 短上下文 top-k 改用单 CTA,selector 提速约 28%

gfx950 上 MiniMax-M3 decode 阶段的 BF16 top-k selector 在短上下文(生产 8k1k graph 容量仅 74 个稀疏块)下仍使用 B512/C16 的大网格,过度启动大量无用 CTA。PR body 给出的测量显示:query length 4、top-k 16 时,fused selector + sparse table 从 4.138-4.440 us 降至 2.947-3.213 us,边界扫描(容量 65/74/128,query length 1/4/8,index heads 1/2/4)确认 selector 时间下降 32.78-35.32%。PR 同时声明这是已合并 #54682 的窄范围 follow-up,并经过 duplicate-work 检查,与 #53448、#52664、#52059 不冲突。

值得精读,尤其是 _decode_topk_launch_policy 与两个对应测试用例:它们展示了 ROCm 内核性能调优的严谨姿势(把编译期参数纳入 launch policy、128/129 显式边界、CPU 全序 reference 验证 NaN/Inf/有符号零、图回放验证 counter 重置)。对非 ROCm 开发者参考价值有限,但「只有输出精确一致才允许跳过模型评估」的判断标准和「launch geometry 与算术解耦」的设计思路值得借鉴。合并本身无阻塞,维护者已批准。

讨论亮点

该 PR 的技术讨论集中在 PR body 而非 review 评论中:审阅侧只有 zhou9402 的 "LGTM" 和维护者 AndreasKaratzas 的 APPROVED(空 body)。自动审查方面,coderabbitai[bot] 的全量 review 明确表示 "No actionable comments were generated",claude[bot] 因 PR 来自 fork 而禁用自动审查。PR body 中最重要的设计论述是:只改 launch geometry、保持 BF16 precision 契约、不切换到 AITER FP8 路径,并用独立 CPU 全序参考(含 NaN、±Inf、±0.0、并列分数)证明输出精确一致,这也是跳过模型评估的依据。

实现拆解

  1. 变更入口与 policy 扩展:修改 vllm/models/minimax_m3/amd/ops/index_topk.py 中的 _decode_topk_launch_policy,新增常量 DECODE_TOPK_SHORT_BLOCKS_PER_CHUNK = 128,并把返回值从 3 元组扩展为 6 元组 (num_topk_chunks, topk_block_size, selector_num_warps, selector_num_stages, single_tile_guaranteed, adaptive_final_merge),使 Triton kernel 的编译期特化参数(BLOCK_SIZE_K、num_warps、num_stages)由 launch policy 统一输出,消除调用点的隐式推导。

  2. 核心分支逻辑:在 is_gfx950 and topk == 16 and 0 < max_block <= 16 * 512 的快速路径内再分两档:max_block <= 128 时返回 (1, 128, 2, 1, True, True)(单 CTA、128 块、2 warps、1 stage);否则保持 16-chunk 快速路径 (16, 512, 4, 2, True, True)。范围外(非 gfx950、topk 非 16 或超长上下文)维持原有 target-grid 推导,统一返回 (1 << (target.bit_length() - 1), 512, 8, 2, False, False),保证 128/129 边界有显式分支锁定。

  3. 调用点改造:minimax_m3_index_decode 解包新的 6 元组,将 topk_block_size、selector_num_warps、selector_num_stages 分别传给 _decode_topk_fused_kernel 的 BLOCK_SIZE_K、num_warps、num_stages,替代原先由 adaptive_final_merge 推断 num_warps 的逻辑。

  4. 测试配套:tests/kernels/attention/test_minimax_m3.py 中 test_amd_decode_topk_launch_policy 的期望值扩展为 6 元组,并新增 (74, ...)、(128, ...)、(129, ...) 三组用例分别覆盖生产容量、边界内最后一档和越界切换;test_amd_decode_fused_topk_total_order_and_replay 增加 block_size_k、num_warps、num_stages 参数化并新增 (1, True, True, 74, 128, 2, 1, 16, 1) 用例,block_counts 按 BLOCK_SIZE_K 动态生成 15/16/17/63/64/65/127/128/129 等边界长度,分数注入点改为 block_size_k // 2 与 block_size_k,并用 repeat 对齐短序列长度列表。

  5. 验证与部署:测试全量 121 passed、13 skipped;pre-commit 全部通过;由于输出精确一致,未重新跑模型评估。CI 共触发 4 轮 Buildkite(#87142、#87276、#87371、#88455),最终由维护者批准合并。

文件 模块 状态 重要度
vllm/models/minimax_m3/amd/ops/index_topk.py 选择器内核 modified 5.38
tests/kernels/attention/test_minimax_m3.py 内核测试 modified 5.62

关键符号

_decode_topk_launch_policy minimax_m3_index_decode test_amd_decode_topk_launch_policy test_amd_decode_fused_topk_total_order_and_replay

关键源码片段

vllm/models/minimax_m3/amd/ops/index_topk.py core-logic

核心实现文件:扩展 `_decode_topk_launch_policy` 返回值,新增短上下文单 CTA 分支,并让调用点 `minimax_m3_index_decode` 使用 policy 输出的 `BLOCK_SIZE_K`、`num_warps`、`num_stages` 编译特化参数。

# 短上下文特化阈值:gfx950 生产 8k1k graph 容量仅 74 个稀疏块,
# 远小于默认 chunk 的 512 块,继续用大 CTA 会造成明显浪费。
DECODE_TOPK_SHORT_BLOCKS_PER_CHUNK = 128
DECODE_TOPK_BLOCKS_PER_CHUNK = 512
MAX_DECODE_TOPK_FAST_CHUNKS = 16
​
​
def _decode_topk_launch_policy(
    max_block: int,
    total_q: int,
    num_idx_heads: int,
    topk: int,
    *,
    is_gfx950: bool,
) -> tuple[int, int, int, int, bool, bool]:
    """选择 selector 的 CTA 网格与编译期 launch 配置。
​
    返回值依次为:num_topk_chunks、block_size_k、num_warps、num_stages、
    single_tile_guaranteed、adaptive_final_merge,直接决定 Triton kernel
    的编译特化,因此 128/129 边界必须用显式分支锁定。
    """
    if (
        is_gfx950
        and topk == 16
        and 0 < max_block <= MAX_DECODE_TOPK_FAST_CHUNKS * DECODE_TOPK_BLOCKS_PER_CHUNK
    ):
        # 短上下文特化:单 CTA、128 块、2 warps、1 stage,
        # 覆盖 74 块这种生产容量,避免启动 16 个 512 块的大 CTA。
        if max_block <= DECODE_TOPK_SHORT_BLOCKS_PER_CHUNK:
            return 1, DECODE_TOPK_SHORT_BLOCKS_PER_CHUNK, 2, 1, True, True
        # 中等容量仍走 16-chunk 快速路径,但 warps 收敛为 4。
        return (
            MAX_DECODE_TOPK_FAST_CHUNKS,
            DECODE_TOPK_BLOCKS_PER_CHUNK,
            4,
            2,
            True,
            True,
        )
    # 长上下文 / 非 gfx950:按目标网格反推 chunk 数,保持原有语义。
    target = max(
        1,
        1 << (DECODE_TOPK_TARGET_GRID // max(1, total_q * num_idx_heads)).bit_length() - 1,
    )
    return (
        1 << (target.bit_length() - 1),
        DECODE_TOPK_BLOCKS_PER_CHUNK,
        8,
        2,
        False,
        False,
    )
# 调用点不再自行推导编译参数,统一由 launch policy 决定。
(
    num_topk_chunks,
    topk_block_size, # 短上下文分支为 128,其余为 512
    selector_num_warps, # 短上下文分支为 2
    selector_num_stages, # 短上下文分支为 1
    single_tile_guaranteed,
    adaptive_final_merge,
) = _decode_topk_launch_policy(
    max_block=max_block,
    total_q=total_q,
    num_idx_heads=num_idx_heads,
    topk=topk,
    is_gfx950=is_gfx950,
)
​
_decode_topk_fused_kernel[(batch, num_idx_heads, num_topk_chunks)](
    ...,
    BLOCK_SIZE_K=topk_block_size,
    BLOCK_SIZE_T=block_size_t,
    EMIT_SPARSE_TABLE=emit_sparse_table,
    SINGLE_TILE_GUARANTEED=single_tile_guaranteed,
    ADAPTIVE_FINAL_MERGE=adaptive_final_merge,
    num_warps=selector_num_warps,
    num_stages=selector_num_stages,
)
tests/kernels/attention/test_minimax_m3.py test-coverage

测试配套:将 launch policy 断言扩展为 6 元组,新增 74/128/129 边界用例;fused kernel 测试新增短上下文特化参数组合,并把边界序列改为按 BLOCK_SIZE_K 动态生成,覆盖图回放与全序一致性。

@pytest.mark.parametrize(
    ("max_block", "total_q", "num_idx_heads", "topk", "is_gfx950", "expected"),
    [
        # 空容量回退到通用网格。
        (0, 24, 1, 16, True, (2, 512, 8, 2, False, False)),
        # 短上下文特化:单 CTA、128 块、2 warps、1 stage。
        (1, 1, 1, 16, True, (1, 128, 2, 1, True, True)),
        # 生产 8k1k graph 的实际容量 74 块,必须命中特化分支。
        (74, 4, 1, 16, True, (1, 128, 2, 1, True, True)),
        # 边界内最后一档 128 块。
        (128, 4, 1, 16, True, (1, 128, 2, 1, True, True)),
        # 129 块越过边界,立即切回 16-chunk 快路径。
        (129, 4, 1, 16, True, (16, 512, 4, 2, True, True)),
        # 非 gfx950 或 topk != 16 保持原有回退策略。
        (513, 16, 4, 16, False, (1, 512, 8, 2, False, False)),
    ],
)
def test_amd_decode_topk_launch_policy(
    max_block: int,
    total_q: int,
    num_idx_heads: int,
    topk: int,
    is_gfx950: bool,
    expected: tuple[int, int, int, int, bool, bool],
):
    """短上下文特化有严格的 dispatch 边界。"""
    from vllm.models.minimax_m3.amd.ops.index_topk import (
        _decode_topk_launch_policy,
    )
​
    assert (
        _decode_topk_launch_policy(
            max_block,
            total_q,
            num_idx_heads,
            topk,
            is_gfx950=is_gfx950,
        )
        == expected
    )

评论区精华

自动审查:fork 限制与 CodeRabbit 无意见 other

claude[bot] 指出 PR 来自 fork,自动 review 被禁用;coderabbitai[bot] 的全量 review 明确表示 No actionable comments were generated。

结论:无自动审查阻塞,依赖维护者手动审查。 · 已解决

人类审阅签收 other

zhou9402 回复 LGTM,作者随后询问是否可以合并;维护者 AndreasKaratzas 以空 body APPROVED。

结论:维护者批准合并,无实质技术争议。 · 已解决

精度契约与 FP8 路径边界 设计

PR body 强调只改 launch geometry,保持 BF16 precision 契约,不切换到 AITER FP8 路径;边界扫描 65/74/128 与 128/129 dispatch 边界由测试参数化锁定。

结论:作为设计约束被测试固化,无反对意见。 · 已解决

风险与影响

风险面较窄:变更只命中 gfx950 + topk==16 + max_block<=128 的 decode 分支,非 ROCm 平台或无此条件的路径完全不受影响。但该分支直接决定 decode 主路径的 kernel 编译特化,若 128/129 边界判断错误会启动错误 CTA,因此测试专门锁定了 74、128、129 三个关键点。另一个潜在风险是编译特化参数现已散落在 policy 返回值中,未来新增 specialization 时必须同步更新调用点和测试参数化,否则容易遗漏。性能上,129-512 块区间仍走 B512/C16(warps 4、stages 2)的快速路径,未提供中间容量过渡配置,可能仍有优化空间。CI 上测试标记为 skipif not is_rocm,回归验证依赖 AMD CI(本 PR 跑了 4 轮 Buildkite)。数值风险很低,因为仅改变 launch geometry 且测试覆盖全序排序、有符号零、counter 重置与图回放。

对用户:gfx950 ROCm 上 MiniMax-M3 短上下文(≤128 稀疏块,约 8k context)decode 的 selector 阶段提速约 33-35%,selector + sparse table 总耗时下降约 26-29%,属于直接可见的 decode 延迟改善;其他架构、容量和 top-k 取值行为完全不变。对系统:无 API、配置、调度器或精度契约变化,输出与之前逐位一致,不需要模型重新评估。对团队:确立了一个可复用的内核调优模式——launch policy 直接返回编译期特化参数(BLOCK_SIZE_K、num_warps、num_stages),并用边界用例和 CPU 全序参考把 dispatch 行为锁死;代价是测试变得更复杂,后续维护需要同步更新。

ROCm 专用路径 128 块 dispatch 边界 launch-policy 编译特化参数化 测试仅 ROCm 生效

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论