Prhub

#34645 [AMD][CI] Add GPT-OSS perf benchmarks to the ROCm 7.2 nightly

原始 PR 作者 michaelzhang-ai 合并时间 2026-08-17 07:35 文件变更 4 提交数 2 评论 5 代码增减 +283 / -0

执行摘要

AMD nightly 补 GPT-OSS 性能基准,覆盖 MI30x/MI35x

GPT-OSS 是 ROCm 7.2 + Triton 3.7 性能回退报告中的受影响模型之一,但在 AMD 上没有任何吞吐量覆盖,此前只有 DeepSeek-V4 有。PR body 明确说明:'GPT-OSS ... has no throughput coverage on AMD — DeepSeek-V4 is the only affected model that does.' 同时 benchmark 暂无 nightly 历史,无法立即设置可辩护的吞吐量阈值,所以本 PR 先建立数据源,为后续回归门禁(如 #34640 的 DeepSeek-V4 gating)铺路。

建议精读两个新增测试文件与 generate_simple_markdown_report:一是看如何通过复用 accuracy 的 SERVER_ARGS 保证吞吐量归因正确,二是看 warmup 首行的约定如何与报告函数联动,三是记住多步骤 job 必须清理 GITHUB_STEP_SUMMARY 的教训。设计上最有价值的决策是“先建数据源、后设阈值”——不要在没有历史基线时硬编阈值。后续关注点:积累几周 nightly 数据后为两个 suite 补充吞吐量门禁。

讨论亮点

review 的核心交锋集中在 PR body 的 Speed Test 表格上。HaiShaw 两次追问性能差异:第一次问两套架构表为何差距明显,michaelzhang-ai 答为独立基线(MI35x gfx950 + MXFP4 vs MI30x gfx942 + bf16,精度分裂由硬件能力决定),并用权重足迹佐证 120b 在 MI30x 上流量多 2.38x 而吞吐比 2.41x 吻合;第二次追问同一表内两组数据差异,michaelzhang-ai 承认先前答错,真实原因是 20b 与 120b 两个模型的独立扫描被表格展示成一组,同时暴露了一个真 bug——新增 perf step 未清理 github_summary.md,导致 accuracy 块被重复追加到 GITHUB_STEP_SUMMARY,已在 commit e18b280 修复并复验。

实现拆解

  1. 新增 MI35x 基准测试test/registered/amd/perf/mi35x/test_gpt_oss_perf_mi35x.py):通过 register_amd_ci(est_time=3600, suite="nightly-perf-8-gpu-mi35x-gpt-oss", nightly=True) 注册到 AMD nightly。setUpClassbatch_sizes = [1, 1, 8, 16, 64],首元素重复的 1 是 warmup,因为该 benchmark 自己拉起 server,需先支付 JIT 与 autotuning 成本。ENV_VARS 设置 SGLANG_USE_AITER=1SGLANG_USE_AITER_MOE_GU_ITLV=1,原因是 AITER 的 MXFP4 fused MoE 对 gpt-oss 采用分离的 gate/up tile 布局,其他 AITER 调用方默认 interleave,需要显式 opt out。

  2. 新增 MI30x 基准测试test/registered/amd/perf/mi30x/test_gpt_oss_perf_amd.py):suite 为 nightly-perf-8-gpu-gpt-oss,模型路径换成 lmsys/gpt-oss-*-bf16ENV_VARS 仅保留 SGLANG_USE_AITER=1(bf16 走 AITER 默认路径,无需 tile 布局开关)。其余结构与 MI35x 版本一致,同样复用 MI30x accuracy 测试的 serving recipe。

  3. 提炼共享报告函数python/sglang/test/nightly_bench_utils.py):新增 generate_simple_markdown_report(results, default_gpu_config=""),把之前散落在 AMD perf sweep 里的 markdown 渲染逻辑收拢:去掉 H100 定价 cost 列;GPU_CONFIG 缺失时用参数兜底;按“前两条 batch_size 相同”丢弃 warmup 首行;ITL 由 output_throughput 反推(无直接测量值)。两个新测试都复用该函数,保证输出 schema 与现有 AMD perf 报告一致。

  4. workflow 追加性能步骤.github/workflows/nightly-test-amd-rocm720.yml):在 nightly-accuracy-8-gpu-rocm720nightly-accuracy-8-gpu-mi35x-rocm720 两个 job 内各追加一个 Performance Test step,if: ${{ !cancelled() }},timeout 120 min,执行 run_suite.py --hw amd --suite nightly-perf-8-gpu-{...} --nightly --timeout-per-file 5400。选择复用 accuracy job 而非新建 job:MI30x 独立 job 拉镜像 + 装依赖约需 49 min,共享后实测成本仅 MI30x 6.5 min / MI35x 3.9 min(约 1.4 GPU-h/night),而独立 job 约 3.4 GPU-h/night。两个 step 均新增 > github_summary.md 清理语句,避免 perf step 把 accuracy 块二次追加到 GITHUB_STEP_SUMMARY(commit e18b280 修复)。

  5. 配套验证:性能数据验证 run 31850998638 四步全过;summary 清理修复后 run 31936282742 复验通过。静态校验包含 registry 单文件解析、workflow YAML、重复 job 名、Ruff/Black/isort/codespell。未新增任何精度断言,也无吞吐量阈值。

文件 模块 状态 重要度
test/registered/amd/perf/mi35x/test_gpt_oss_perf_mi35x.py 性能基准 added 6.96
test/registered/amd/perf/mi30x/test_gpt_oss_perf_amd.py 性能基准 added 6.95
python/sglang/test/nightly_bench_utils.py 基准工具 modified 5.12
.github/workflows/nightly-test-amd-rocm720.yml CI 工作流 modified 4.33

关键符号

generate_simple_markdown_report TestNightlyGptOssPerformanceMI35x.setUpClass TestNightlyGptOssPerformanceMI35x.test_bench_one_batch TestNightlyGptOssPerformanceAMD.setUpClass TestNightlyGptOssPerformanceAMD.test_bench_one_batch

关键源码片段

test/registered/amd/perf/mi35x/test_gpt_oss_perf_mi35x.py test-coverage

MI35x(gfx950)GPT-OSS MXFP4 吞吐量基准的核心新增文件,定义了 registers、SERVER_ARGS、AITER tile 布局开关与测试主流程,是本 PR 在 gfx950 上的主要数据源。

"""MI35x nightly performance benchmark for GPT-OSS (8-GPU)."""import os
import unittestfrom sglang.test.ci.ci_register import register_amd_ci
from sglang.test.nightly_bench_utils import generate_simple_markdown_report
from sglang.test.nightly_utils import NightlyBenchmarkRunner
from sglang.test.test_utils import DEFAULT_URL_FOR_TEST, _parse_int_list_envregister_amd_ci(
    est_time=3600,
    suite="nightly-perf-8-gpu-mi35x-gpt-oss",
    nightly=True,
)RESULT_DIR = "performance_results_gpt_oss_mi35x"# MI35x 直接服务 MXFP4 原始 checkpoint;MI30x 只能走 bf16 转换版本。
GPT_OSS_20B_MODEL_PATH = os.environ.get("GPT_OSS_20B_MODEL_PATH", "openai/gpt-oss-20b")
GPT_OSS_120B_MODEL_PATH = os.environ.get("GPT_OSS_120B_MODEL_PATH", "openai/gpt-oss-120b")# 与 MI35x accuracy 测试完全一致的 serving 参数:吞吐量变化必须归因于
# serving stack,而不是 benchmark recipe 差异。
SERVER_ARGS = [
    "--trust-remote-code",
    "--tp", "8",
    "--attention-backend", "triton",
    "--chunked-prefill-size", "130172",
    "--max-running-requests", "128",
    "--mem-fraction-static", "0.85",
]# AITER 的 MXFP4 fused MoE 对 gpt-oss 使用分离的 gate/up tile 布局;
# 其他 AITER MXFP4 调用方默认是 interleave,这里显式 opt out。
ENV_VARS = {
    "SGLANG_USE_AITER": "1",
    "SGLANG_USE_AITER_MOE_GU_ITLV": "1",
}
​
​
class TestNightlyGptOssPerformanceMI35x(unittest.TestCase):
    """MI35x nightly performance benchmark for the MXFP4 GPT-OSS models."""
​
    @classmethod
    def setUpClass(cls):
        cls.base_url = DEFAULT_URL_FOR_TEST
        # 首元素重复的 1 是 warmup:该 benchmark 自己拉起 server,
        # 需要先支付 JIT 编译与 autotuning 成本,避免污染首个数据点。
        cls.batch_sizes = [1, 1, 8, 16, 64]
        cls.input_lens = tuple(_parse_int_list_env("NIGHTLY_INPUT_LENS", "4096"))
        cls.output_lens = tuple(_parse_int_list_env("NIGHTLY_OUTPUT_LENS", "512"))
        cls.models = [GPT_OSS_20B_MODEL_PATH, GPT_OSS_120B_MODEL_PATH]
        cls.runner = NightlyBenchmarkRunner(RESULT_DIR, cls.__name__, cls.base_url)
        cls.runner.setup_result_directory()
        cls.runner.full_report = f"## {cls.__name__}\n"
​
    def test_bench_one_batch(self):
        """依次 benchmark 两个模型尺寸,任一失败则整体 fail。"""
        failures = []
        env = os.environ.copy()
        env.update(ENV_VARS)
        try:
            for model_path in self.models:
                with self.subTest(model=model_path):
                    results, success, _ = self.runner.run_benchmark_for_model(
                        model_path=model_path,
                        batch_sizes=self.batch_sizes,
                        input_lens=self.input_lens,
                        output_lens=self.output_lens,
                        other_args=SERVER_ARGS,
                        extra_bench_args=["--trust-remote-code"],
                        env=env,
                    )
                    if results:
                        # 与 AMD 既有 perf sweep 相同的输出 schema,直接拼接进报告。
                        self.runner.full_report += (
                            generate_simple_markdown_report(results, "MI35x") + "\n"
                        )
                    if not success:
                        failures.append(f"benchmark failed for {model_path}")
        finally:
            self.runner.write_final_report()
        if failures:
            self.fail("\n".join(failures))
test/registered/amd/perf/mi30x/test_gpt_oss_perf_amd.py test-coverage

MI30x(gfx942)GPT-OSS bf16 吞吐量基准的新增文件,展示与 MI35x 分裂的 checkpoint 与 AITER 配置差异,是 gfx942 上的主要数据源。

"""MI30x nightly performance benchmark for GPT-OSS (8-GPU)."""import os
import unittestfrom sglang.test.ci.ci_register import register_amd_ci
from sglang.test.nightly_bench_utils import generate_simple_markdown_report
from sglang.test.nightly_utils import NightlyBenchmarkRunner
from sglang.test.test_utils import DEFAULT_URL_FOR_TEST, _parse_int_list_envregister_amd_ci(
    est_time=3600,
    suite="nightly-perf-8-gpu-gpt-oss",
    nightly=True,
)RESULT_DIR = "performance_results_gpt_oss_mi30x"# MI30x 无法原生服务 MXFP4 checkpoint,只能用社区 bf16 转换版本;
# 模型分裂与既有 accuracy 测试保持一致,保证两个架构各自门禁。
GPT_OSS_20B_MODEL_PATH = os.environ.get(
    "GPT_OSS_20B_BF16_MODEL_PATH", "lmsys/gpt-oss-20b-bf16"
)
GPT_OSS_120B_MODEL_PATH = os.environ.get(
    "GPT_OSS_120B_BF16_MODEL_PATH", "lmsys/gpt-oss-120b-bf16"
)# 与 MI35x 不同:bf16 走 AITER 默认路径,无需 gate/up tile 布局开关。
ENV_VARS = {"SGLANG_USE_AITER": "1"}
​
​
class TestNightlyGptOssPerformanceAMD(unittest.TestCase):
    """MI30x nightly performance benchmark for the bf16 GPT-OSS models."""
​
    @classmethod
    def setUpClass(cls):
        cls.base_url = DEFAULT_URL_FOR_TEST
        # 首元素重复的 1 是 warmup:自己拉起 server,避免 JIT/autotuning
        # 成本污染首个数据点,与 MI35x 版本行为一致。
        cls.batch_sizes = [1, 1, 8, 16, 64]
        cls.input_lens = tuple(_parse_int_list_env("NIGHTLY_INPUT_LENS", "4096"))
        cls.output_lens = tuple(_parse_int_list_env("NIGHTLY_OUTPUT_LENS", "512"))
        cls.models = [GPT_OSS_20B_MODEL_PATH, GPT_OSS_120B_MODEL_PATH]
        cls.runner = NightlyBenchmarkRunner(RESULT_DIR, cls.__name__, cls.base_url)
        cls.runner.setup_result_directory()
        cls.runner.full_report = f"## {cls.__name__}\n"
python/sglang/test/nightly_bench_utils.py test-coverage

新增共享报告函数 generate_simple_markdown_report,统一 AMD perf sweep 的 markdown 输出 schema,并实现 warmup 首行丢弃与 ITL 反推逻辑。

def generate_simple_markdown_report(
    results: List[BenchmarkResult], default_gpu_config: str = ""
) -> str:
    """生成不带 H100 定价 cost 列的 markdown 报告,并丢弃 warmup 首行。    warmup 由调用方通过重复第一个 batch size 表达:当前两条结果
    batch_size 相同且结果多于一行时,去掉第一条(JIT/autotuning 预热)。
    """
    model_header = results[0].model_path
    if results[0].run_name and results[0].run_name != "default":
        model_header += f" ({results[0].run_name})"
​
    # 优先取环境变量 GPU_CONFIG,缺失时退回调用方传入的架构名。
    gpu_config = os.getenv("GPU_CONFIG", default_gpu_config)
    if gpu_config:
        model_header += f" [{gpu_config}]"
​
    summary = f"### {model_header}\n"
    summary += (
        "| batch size | input len | latency (s) | "
        "input throughput (tok/s) | output throughput (tok/s) | ITL (ms) |\n"
    )
    summary += (
        "| ---------- | --------- | ----------- | "
        "------------------------ | ------------------------- | -------- |\n"
    )
​
    report_results = (
        results[1:]
        if len(results) > 1 and results[0].batch_size == results[1].batch_size
        else results
    )
​
    for result in report_results:
        # ITL 没有直接测量值,由 output_throughput 反推:
        # 1 / ( 吞吐 / batch_size) * 1000。
        itl = (
            1 / (result.output_throughput / result.batch_size) * 1000
            if result.output_throughput > 0
            else 0
        )
        summary += (
            f"| {result.batch_size} | {result.input_len} | {result.latency:.2f} | "
            f"{result.input_throughput:.2f} | {result.output_throughput:.2f} | "
            f"{itl:.2f} |\n"
        )
    return summary

评论区精华

两套架构性能数据差异的解释 question

HaiShaw 在 PR body 的 Speed Test 部分提问 'why two runs show notable different performance?';michaelzhang-ai 解释为两个独立基线:MI35x 是 gfx950 + MXFP4,MI30x 是 gfx942 + bf16,精度分裂由硬件能力决定(MI30x 不能原生服务 MXFP4),并给出权重足迹数据:120b 在 MI30x 上每 GPU 权重流量多 2.38x,而 MI35x/MI30x 的 BS 64 吞吐比恰好 2.41x,符合带宽受限 decode 预期。

结论:确认为独立基线而非同工作负载重复;跨架构吞吐比与权重流量比吻合,数据可信。 · 已解决

同一表格内两组数据差异与 GITHUB_STEP_SUMMARY 重复渲染 正确性

HaiShaw 追问 'why 2 set of runs within same table (same HW, same model) show very diff perf.';michaelzhang-ai 承认先前答错:真实原因是 gpt-oss-20b 与 gpt-oss-120b 两个模型的独立扫描,PR 表格只在每个块首行打印模型名导致误读。同时暴露真 bug:新增 perf step 未清理 github_summary.md,导致 accuracy 块被重复 append 到 GITHUB_STEP_SUMMARY。

结论:展示层是 PR 描述 bug,测量本身正确;CI 渲染 bug 在 commit e18b280 修复(两个 step 均加 `> github_summary.md`),复验 run 31936282742 通过。 · 已解决

风险与影响

技术风险集中在 CI 基础设施层,不涉及运行时 serving 代码。(1)nightly 稳定性:性能步骤失败会让所在 job 变红,if: !cancelled() 避免 accuracy 失败后空跑,但 benchmark 错误(如 Hugging Face 拉取失败、超时)仍可能污染 nightly 红绿状态。(2)无自动回归门禁:当前只报告不下结论,ROCm 7.2 slowdown 的自动捕获需要后续基于这批基线设置阈值。(3)warmup 检测是隐含契约:generate_simple_markdown_report 以“前两条 batch_size 相同”判断 warmup,后续新调用方若不遵守“重复首元素”约定,行为可能不符合预期(函数 docstring 已说明)。(4)外部 checkpoint 依赖:lmsys/gpt-oss-*-bf16 为社区转换版本,上游变动会悄然改变数据前提;MI35x 的 MXFP4 路径依赖 AITER tile 布局开关,AITER 升级可能改变 SGLANG_USE_AITER_MOE_GU_ITLV 语义。

影响范围集中在 AMD ROCm 7.2 nightly 测试基础设施:MI30x/MI35x 各自新增 20b/120b 两组吞吐量扫描,每次 4 个 batch size,新增成本约 1.4 GPU-h/night(复用 accuracy job 后比独立 job 节省约 2 GPU-h/night)。不触碰任何运行时 serving 代码,对用户请求路径零影响。对团队而言,这为 AMD 性能回归守门提供了第一批可量化的基线数据,与 #34640 的 DeepSeek-V4 gating 一起构成 ROCm 7.2 slowdown 的追踪闭环。

CI 稳定性影响:perf 步骤失败会使 job 变红 无吞吐量阈值,暂不能自动捕获性能回归 外部 checkpoint 依赖(Hugging Face 拉取) warmup 丢弃逻辑依赖调用方约定

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论