Prhub

#32568 [AMD] Add Kimi-K3 8-GPU MI35x nightly accuracy CI

原始 PR 作者 michaelzhang-ai 合并时间 2026-08-18 05:48 文件变更 3 提交数 6 评论 2 代码增减 +185 / -10

执行摘要

MI35x 新增 Kimi-K3 精度 nightly,替换 K2.6

PR body 明确指出:Kimi-K3 cookbook 虽然发布了 MI350X/MI355X 1×8 部署配方,AMD Day-0 跟踪 issue #32548 也报告了吞吐,但两者都没有提供自动化端到端精度信号——cookbook 该 cell 仍标记为 verified: false,#32548 只测速度。测试文件 docstring 进一步说明:"That issue reports throughput on MI355 TP8 but no accuracy, and the cookbook cell for this topology is still published as verified: false. So the recipe is known to run at speed; what is missing, and what this test supplies, is evidence that it produces correct tokens." 即配方能跑多快已知,缺的是"跑得对不对"的证据。

值得精读,尤其是 6 个 commit 的演进过程。这个 PR 虽无源码逻辑,但展示了高质量的 CI 工程判断:精度先于性能、用硬件 ISA 约束校验"替换"决策、不 pin 废弃镜像、用 completion harness 规避 chat 模板陷阱。对想了解 sglang AMD nightly 编排(register_amd_ci、run_suite.py、job_select 联动)的读者是很好的范本。

讨论亮点

Review 层面没有实质交锋:HaiShaw 直接 APPROVED(body 为空)。真正的设计争论体现在 6 个 commit 的演进消息里,作者在提交说明中给出了清晰的工程论证:

  • perf 与 accuracy 的先后关系(commit 4d4d1a8):"Throughput on a checkpoint that spends most of an hour loading is not worth an 8-GPU MI35x slot until the kernels are known to emit correct tokens, and that runner is contended enough that a wasted slot costs real time."——在确认内核输出正确 token 之前,不值得为性能占一个稀缺的 8 卡槽位。
  • K2.6 不应被整体退役(commit dfc6954):"K3's native MXFP4 weights need gfx95x and mxfp4 does not register on gfx942 ... dropping those jobs would have left MI30x with no Kimi accuracy signal rather than moving it to a new one."——这是对"新模型取代旧模型"直觉的纠偏,硬件 ISA 约束使覆盖无法迁移。
  • 不 pin 镜像的决定(PR body):"Pinning the abandoned rocm720-mi35x-k3-* image line would make a nightly rot over time."——作者选择信任标准 unpinned 镜像,避免废弃镜像线导致 nightly 逐渐腐烂。

实现拆解

实现分四步完成,核心是新增一个端到端精度测试并接入 AMD nightly 编排:

  1. 新增 MI35x 精度测试文件 test/registered/amd/accuracy/mi35x/test_kimi_k3_eval_mi35x.py(+163 行)。文件顶部调用 register_amd_ci(est_time=9000, suite="nightly-amd-accuracy-8-gpu-mi35x-kimi-k3", nightly=True) 完成 suite 注册;TestKimiK3EvalMI35x.setUpClass 通过环境变量 GSM8K_NUM_QUESTIONSGSM8K_MAX_NEW_TOKENS 提供可调入口;test_kimi_k3_gsm8k_accuracypopen_launch_server 拉起 TP8 服务,参数完全对齐 #32548 的非投机配方(Triton attention、bfloat16、--mem-fraction-static 0.85--disable-radix-cache),并设置 --reasoning-parser kimi_k3--tool-call-parser kimi_k3。关键设计是选用 few-shot completion 评估而不是 chat 路径:K3 的 thinking 永久开启,答案走 reasoning_content,chat 路径的 message.content 为空会得 0 分;completion 路径直接对原始输出打分,并与既有 Kimi 兄弟测试(K2/K2.5/K2.6)保持一致。环境方面设置了 4 个 AITER 相关变量(SGLANG_USE_AITERSGLANG_AITER_K3_OPTAITER_FLYDSL_FORCEAITER_SITUV2_A8W4),其中前两个在 sglang 内被读取,后两个由 AITER 自身消费。评估完成后按 ACCURACY_THRESHOLD = 0.92 断言(与 AMD Kimi 精度家族一致),CI 下将结果写入 GitHub step summary,finallykill_process_tree 确保 server 进程被回收。

  2. 工作流替换 .github/workflows/nightly-test-amd-rocm720.yml(+21/-10)。将 nightly-8-gpu-mi35x-kimi-k26-rocm720 替换为 nightly-8-gpu-mi35x-kimi-k3-rocm720,同步更新 job_selectcheck-all-jobs 两处条目;MI30x 的 nightly-8-gpu-kimi-k26-rocm720 保留,注释从 "MI30x + MI35x" 改为 "MI30x"。由于 2.8T 权重的 MXFP4 checkpoint 约 1.56 TB,加载耗时占大头,job 超时从 180 分钟提升到 300 分钟,--timeout-per-file 从 7200 升到 14400。

  3. suite 注册 test/run_suite.py(+1/-0)。在 NIGHTLY_SUITES[HWBackend.AMD] 列表中插入 nightly-amd-accuracy-8-gpu-mi35x-kimi-k3,使 run_suite.py --hw amd --suite ... 能发现该测试。

  4. 提交演进中的范围收敛(来自 6 个 commit)。初始版本同时包含 accuracy 与 perf 两个 job,且 perf 通过 needs: 依赖 accuracy 通过后才运行;但 perf job 在唯一一次分派中 23 秒即死于 setUpClass(基于 #33832 之前的 NightlyBenchmarkRunner API 编写),因此被拆出推迟到 #34985。随后恢复 MI300 的 K2.6 任务、保留 ROCm 7.0 工作流不动,最终只替换 7.2 lane 的 MI35x 槽位。

文件 模块 状态 重要度
test/registered/amd/accuracy/mi35x/test_kimi_k3_eval_mi35x.py 测试用例 added 7.39
.github/workflows/nightly-test-amd-rocm720.yml CI 工作流 modified 4.74
test/run_suite.py 测试编排 modified 3.11

关键符号

TestKimiK3EvalMI35x.setUpClass TestKimiK3EvalMI35x.test_kimi_k3_gsm8k_accuracy

关键源码片段

test/registered/amd/accuracy/mi35x/test_kimi_k3_eval_mi35x.py test-coverage

核心新增文件,定义了 Kimi-K3 在 MI35x 8 卡上的 GSM8K 精度测试:服务器参数、AITER 环境变量、评估断言与 CI summary 输出全部集中于此。

class TestKimiK3EvalMI35x(CustomTestCase):
    """Kimi-K3 GSM8K 精度评估测试,运行在 AMD MI35x 8 卡(TP8)上。"""
​
    @classmethod
    def setUpClass(cls):
        cls.base_url = DEFAULT_URL_FOR_TEST
        # 允许通过环境变量覆盖题目数与生成长度,便于本地快速调试
        cls.num_questions = int(os.environ.get("GSM8K_NUM_QUESTIONS", "1319"))
        cls.max_new_tokens = int(os.environ.get("GSM8K_MAX_NEW_TOKENS", "512"))
​
    def test_kimi_k3_gsm8k_accuracy(self):
        # 服务器参数对齐 #32548 非投机配方:TP8 + Triton attention +
        # bfloat16 + 静态内存占比 0.85,并关闭 radix cache
        other_args = [
            "--tp", str(TP_SIZE),
            "--attention-backend", "triton",
            "--dtype", "bfloat16",
            "--mem-fraction-static", "0.85",
            "--disable-radix-cache",
            # 并发上限与 decode graph 捕获上限保持一致:1319 题一次性
            # 全量提交,必须显式限流,避免突破权重之后仅剩的约 53 GB/ 卡
            # 显存预算;捕获超过并发上限的 decode graph 没有收益
            "--cuda-graph-max-bs-decode", str(MAX_RUNNING_REQUESTS),
            "--max-running-requests", str(MAX_RUNNING_REQUESTS),
            # K3 的 thinking 永远开启,需用专用 parser 解析输出
            "--reasoning-parser", "kimi_k3",
            "--tool-call-parser", "kimi_k3",
            "--trust-remote-code",
            '--model-loader-extra-config', '{"enable_multithread_load": true}',
            "--watchdog-timeout", "1200",
        ]
​
        env = os.environ.copy()
        # K3 的 MoE 在 ROCm 上由 AITER 提供,四个环境变量缺一不可:
        # SGLANG_USE_AITER 是总开关;SGLANG_AITER_K3_OPT 在 mxfp4.py 与
        # models/kimi_k3.py 中被读取;AITER_SITUV2_A8W4 选择 W4A8 SiTU
        # expert 内核;AITER_FLYDSL_FORCE 由 AITER 自身消费,仓库内不 grep
        env["SGLANG_USE_AITER"] = "1"
        env["SGLANG_AITER_K3_OPT"] = "1"
        env["AITER_FLYDSL_FORCE"] = "1"
        env["AITER_SITUV2_A8W4"] = "1"
​
        process = popen_launch_server(
            KIMI_K3_MODEL_PATH, self.base_url,
            timeout=SERVER_LAUNCH_TIMEOUT, other_args=other_args, env=env,
        )
​
        try:
            requests.get(self.base_url + "/flush_cache")
            args = SimpleNamespace(
                num_shots=8, data_path=None,
                num_questions=self.num_questions,
                parallel=self.num_questions,
                max_new_tokens=self.max_new_tokens,
                host="http://127.0.0.1",
                port=int(self.base_url.split(":")[-1]),
            )
            metrics = run_eval_few_shot_gsm8k(args)
            acc = metrics["accuracy"]
            passed = acc >= ACCURACY_THRESHOLD
            status = "✅ PASS" if passed else "❌ FAIL"
            print(f"  accuracy={acc:.3f} threshold={ACCURACY_THRESHOLD} {status}")
​
            if is_in_ci():
                # 在 GitHub Actions 中把结果写入 step summary 表格
                summary = "### Kimi-K3 Model (MI35x)\n\n"
                summary += "| Model | TP | Accuracy | Threshold | Status |\n"
                summary += "| ----- | -- | -------- | --------- | ------ |\n"
                summary += f"| {KIMI_K3_MODEL_PATH} | {TP_SIZE} | {acc:.3f} | {ACCURACY_THRESHOLD} | {status} |\n"
                write_github_step_summary(summary)
​
            self.assertGreaterEqual(
                acc, ACCURACY_THRESHOLD,
                f"Kimi-K3 accuracy {acc:.3f} below threshold {ACCURACY_THRESHOLD}",
            )
        finally:
            # 无论成败都确保销毁 server 进程,避免污染后续测试
            kill_process_tree(process.pid)

评论区精华

perf nightly 是否应与 accuracy 一起落地 设计

初始提交同时包含 accuracy 与 perf 两个 job,perf 通过 needs 依赖 accuracy 通过后才运行;但 perf job 在唯一一次分派中 23 秒即死于 setUpClass,因为测试基于 #33832 之前的 NightlyBenchmarkRunner API 编写。

结论:将 perf 部分拆出推迟到 #34985 跟踪,本 PR 只落 accuracy,避免在精度未经证实前占用稀缺的 8 卡 MI35x 槽位。 · 已解决

MI300 的 Kimi-K2.6 nightly 是否应退役 设计

K3 原生 MXFP4 权重需要 gfx95x,mxfp4 不会在 gfx942(MI300/MI325)上注册,因此 K3 无法接管 MI30x 的覆盖;若同时退役 K2.6,MI30x 将失去全部 Kimi 精度信号。

结论:恢复 nightly-8-gpu-kimi-k26 与 nightly-8-gpu-kimi-k26-rocm720,仅替换 MI35x 槽位。 · 已解决

ROCm 7.0 lane 是否要改动 设计

7.0 lane 上的 MI35x Kimi-K2.6 job 若被删则覆盖直接消失而非迁移,因为 K3 无法在 7.0 上运行。

结论:7.0 workflow 原样保留,本 PR 只改 7.2 工作流。 · 已解决

codespell 单字命中导致全 CI 中止 bugfix

docstring 中 unparsable 拼写被 codespell 标记,而 check-pr-test-health 会在 lint 变红时快速失败全部 21 个 base-b-* job。

结论:改为树内一致拼写 unparsable,而非向 .codespellrc 加入 ignore-words-list。 · 已解决

风险与影响

本 PR 无源码主路径改动,但作为硬件精度门禁仍有以下风险:

  • 外部资源依赖:测试默认从 HuggingFace 拉取 moonshotai/Kimi-K3,MXFP4 权重约 1.5 TB,下载失败或 HF 波动会直接导致 nightly 失败,且浪费 8 卡 MI35x 槽位(job 超时已调至 300 分钟)。
  • AITER 内核耦合:测试依赖 SGLANG_AITER_K3_OPTAITER_SITUV2_A8W4 等 AITER 行为,AITER 升级或改名会导致测试静默失效或误报;AITER_FLYDSL_FORCE 由 AITER 自身消费,仓库内无法 grep 验证,存在黑盒依赖。
  • CI 门禁单点敏感:commit bb70e1f 显示一个 codespell 单词(unparsable)就能让 check-pr-test-health 快速失败全部 21 个 base-b-* job,说明门禁链路对微小问题极度敏感。
  • 阈值样本量小:0.92 阈值基于一次 0.956 的 green run(0.2% unparsable),统计样本有限,后续硬件或内核抖动可能造成偶发失败。
  • 覆盖盲区:MI30x(gfx942)没有 K3 精度覆盖,这是硬件能力限制而非测试遗漏,但值得在文档中持续标注。

影响范围集中在 AMD CI 基础设施,不涉及运行时行为:

  • 对 AMD 团队:首次获得 Kimi-K3 在 MI355X 上自动化端到端精度信号,避免依赖手动验证;GSM8K 0.92 阈值与既有 Kimi 精度家族对齐,口径统一。
  • 对 nightly 资源:ROCm 7.2 lane 的 MI35x 8 卡槽位从 K2.6 迁移到 K3,单 job 时长从约 74 分钟(精度跑完)拉长到包含约 1.5 TB 权重加载的全流程,占用时间显著增加。
  • 对覆盖矩阵:MI30x 的 K2.6 精度覆盖保留,ROCm 7.0 lane 不变,总体覆盖不降反升(MI35x 精度从 K2.6 换成新模型 K3)。
  • 后续演进:perf 覆盖拆到 #34985 单独跟踪,未来 K3 性能 nightly 会以本 PR 的精度门禁为前提合入。
外部权重依赖(约 1.5 TB 下载) 长耗时 job(超时 300 分钟) CI 门禁对 lint 单词级敏感 仅 MI35x 覆盖(gfx95x 限制) AITER 内核耦合

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论