Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions .github/workflows/nightly-test-amd-rocm720.yml
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

# 8-GPU Qwen 3.5 (Accuracy) ROCm 7.2
# 8-GPU Qwen 3.5 (Accuracy + Performance combined) ROCm 7.2
nightly-8-gpu-qwen35-rocm720:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-qwen35-rocm720,'))
runs-on: linux-mi325-8gpu-sglang
Expand Down Expand Up @@ -653,6 +653,18 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

- name: Performance Test ROCm 7.2 (8-GPU Qwen 3.5 FP8)
timeout-minutes: 120
continue-on-error: true
run: |
> github_summary.md # Clear summary file
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
-e SGLANG_USE_AITER=1 \
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
python3 run_suite.py --hw amd --suite nightly-perf-8-gpu-qwen35-fp8 --nightly --timeout-per-file 5400 ${{ inputs.continue_on_error && '--continue-on-error' || '' }} || TEST_EXIT_CODE=$?
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

# 8-GPU GLM-5 (Accuracy) ROCm 7.2
nightly-8-gpu-glm5-rocm720:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-glm5-rocm720,'))
Expand Down Expand Up @@ -1207,7 +1219,7 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

# MI35x 8-GPU Qwen 3.5 (Accuracy) ROCm 7.2
# MI35x 8-GPU Qwen 3.5 (Accuracy + Performance combined) ROCm 7.2
nightly-8-gpu-mi35x-qwen35-rocm720:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-mi35x-qwen35-rocm720,'))
runs-on: linux-mi35x-gpu-8
Expand Down Expand Up @@ -1240,6 +1252,18 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

- name: Performance Test MI35x ROCm 7.2 (8-GPU Qwen 3.5 FP8)
timeout-minutes: 120
continue-on-error: true
run: |
> github_summary.md # Clear summary file
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
-e SGLANG_USE_AITER=1 \
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
python3 run_suite.py --hw amd --suite nightly-perf-8-gpu-mi35x-qwen35-fp8 --nightly --timeout-per-file 5400 ${{ inputs.continue_on_error && '--continue-on-error' || '' }} || TEST_EXIT_CODE=$?
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

nightly-8-gpu-mi35x-glm5-rocm720:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-mi35x-glm5-rocm720,'))
runs-on: linux-mi35x-gpu-8
Expand Down
28 changes: 26 additions & 2 deletions .github/workflows/nightly-test-amd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

# 8-GPU Qwen 3.5 (Accuracy)
# 8-GPU Qwen 3.5 (Accuracy + Performance combined)
nightly-8-gpu-qwen35:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-qwen35,'))
runs-on: linux-mi325-8gpu-sglang
Expand Down Expand Up @@ -656,6 +656,18 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

- name: Performance Test (8-GPU Qwen 3.5 FP8)
timeout-minutes: 120
continue-on-error: true
run: |
> github_summary.md # Clear summary file
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
-e SGLANG_USE_AITER=1 \
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
python3 run_suite.py --hw amd --suite nightly-perf-8-gpu-qwen35-fp8 --nightly --timeout-per-file 5400 ${{ inputs.continue_on_error && '--continue-on-error' || '' }} || TEST_EXIT_CODE=$?
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

nightly-8-gpu-glm5:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-glm5,'))
runs-on: linux-mi325-8gpu-sglang
Expand Down Expand Up @@ -1212,7 +1224,7 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

# MI35x 8-GPU Qwen 3.5 (Accuracy)
# MI35x 8-GPU Qwen 3.5 (Accuracy + Performance combined)
nightly-8-gpu-mi35x-qwen35:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-mi35x-qwen35,'))
runs-on: linux-mi35x-gpu-8
Expand Down Expand Up @@ -1245,6 +1257,18 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

- name: Performance Test MI35x (8-GPU Qwen 3.5 FP8)
timeout-minutes: 120
continue-on-error: true
run: |
> github_summary.md # Clear summary file
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
-e SGLANG_USE_AITER=1 \
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
python3 run_suite.py --hw amd --suite nightly-perf-8-gpu-mi35x-qwen35-fp8 --nightly --timeout-per-file 5400 ${{ inputs.continue_on_error && '--continue-on-error' || '' }} || TEST_EXIT_CODE=$?
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

nightly-8-gpu-mi35x-glm5:
if: (github.repository == 'sgl-project/sglang' || github.event_name == 'pull_request') && (!(inputs.job_filter || inputs.job_select) || (inputs.job_filter || inputs.job_select) == 'all' || contains(format(',{0},', inputs.job_filter || inputs.job_select), ',nightly-8-gpu-mi35x-glm5,'))
runs-on: linux-mi35x-gpu-8
Expand Down
139 changes: 139 additions & 0 deletions test/registered/amd/perf/mi30x/test_qwen35_fp8_perf_amd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Nightly performance benchmark for Qwen3.5-397B-A17B FP8.

Tests Qwen3.5-397B-A17B-FP8 (MoE, Hybrid Attention with Gated Delta Networks)
on 8 GPUs with triton attention backend.

Model path can be configured via environment variable:
- QWEN35_FP8_MODEL_PATH: Path to Qwen3.5-FP8 model
(default: Qwen/Qwen3.5-397B-A17B-FP8)

Example usage:
python -m pytest test_qwen35_fp8_perf_amd.py -v
"""

import os
import unittest
from typing import List

from sglang.test.ci.ci_register import register_amd_ci
from sglang.test.nightly_bench_utils import BenchmarkResult
from sglang.test.nightly_utils import NightlyBenchmarkRunner
from sglang.test.test_utils import DEFAULT_URL_FOR_TEST, _parse_int_list_env

register_amd_ci(est_time=5400, suite="nightly-perf-8-gpu-qwen35-fp8", nightly=True)


def generate_simple_markdown_report(results: List[BenchmarkResult]) -> str:
"""Generate a simplified markdown report without traces and cost columns.

Skips the first result if it's a warmup run (duplicate batch_size).
"""
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 = os.getenv("GPU_CONFIG", "MI325")
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 = 1 / (result.output_throughput / result.batch_size) * 1000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The calculation for itl could lead to a ZeroDivisionError if result.output_throughput is zero. It's safer to check for this case to prevent the test from crashing during report generation. Rewriting the expression also improves readability.

Suggested change
itl = 1 / (result.output_throughput / result.batch_size) * 1000
itl = (result.batch_size / result.output_throughput) * 1000 if result.output_throughput > 0 else 0

summary += f"| {result.batch_size} | {result.input_len} | {result.latency:.2f} | {result.input_throughput:.2f} | {result.output_throughput:.2f} | {itl:.2f} |\n"

return summary


QWEN35_FP8_MODEL_PATH = os.environ.get(
"QWEN35_FP8_MODEL_PATH", "Qwen/Qwen3.5-397B-A17B-FP8"
)
PROFILE_DIR = "performance_profiles_qwen35_fp8"


class TestNightlyQwen35Fp8Performance(unittest.TestCase):
"""Nightly performance benchmark for Qwen3.5-397B-A17B FP8.

Tests Qwen3.5 FP8 with triton attention backend on TP=8.
Runtime: ~90 minutes
"""

@classmethod
def setUpClass(cls):
cls.base_url = DEFAULT_URL_FOR_TEST
cls.batch_sizes = [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.model_config = {
"name": "qwen35-fp8",
"model_path": QWEN35_FP8_MODEL_PATH,
"other_args": [
"--trust-remote-code",
"--tp",
"8",
"--attention-backend",
"triton",
"--mem-fraction-static",
"0.8",
"--model-loader-extra-config",
'{"enable_multithread_load": true}',
"--watchdog-timeout",
"1200",
],
"env_vars": {
"SGLANG_USE_AITER": "1",
},
}

cls.runner = NightlyBenchmarkRunner(PROFILE_DIR, cls.__name__, cls.base_url)
cls.runner.setup_profile_directory()
cls.runner.full_report = f"## {cls.__name__}\n"

def test_bench_qwen35_fp8(self):
"""Run benchmark for Qwen3.5-397B-A17B FP8."""
old_env = {}
for key, value in self.model_config.get("env_vars", {}).items():
old_env[key] = os.environ.get(key)
os.environ[key] = value

try:
result_tuple = self.runner.run_benchmark_for_model(
model_path=self.model_config["model_path"],
batch_sizes=self.batch_sizes,
input_lens=self.input_lens,
output_lens=self.output_lens,
other_args=self.model_config["other_args"],
variant=self.model_config["name"],
extra_bench_args=["--trust-remote-code"],
enable_profile=False,
timeout=5400,
)
results = result_tuple[0]
success = result_tuple[1]

if results:
self.runner.full_report += (
generate_simple_markdown_report(results) + "\n"
)

self.assertTrue(success, f"Benchmark failed for {QWEN35_FP8_MODEL_PATH}")
finally:
for key, value in old_env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
self.runner.write_final_report()
Comment thread
michaelzhang-ai marked this conversation as resolved.


if __name__ == "__main__":
unittest.main()
139 changes: 139 additions & 0 deletions test/registered/amd/perf/mi35x/test_qwen35_fp8_perf_mi35x.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""MI35x Nightly performance benchmark for Qwen3.5-397B-A17B FP8.

Tests Qwen3.5-397B-A17B-FP8 (MoE, Hybrid Attention with Gated Delta Networks)
on 8 GPUs with triton attention backend.

Registry: nightly-perf-8-gpu-mi35x-qwen35-fp8 suite
"""

import os

os.environ.setdefault("HF_HOME", "/data2/models/huggingface")
os.environ.setdefault("HF_HUB_CACHE", "/data2/models/huggingface/hub")
Comment on lines +11 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoding paths like /data2/models/huggingface makes the test less portable and dependent on a specific machine's setup. It's better to rely on the CI environment to set these environment variables, or use a more generic default that works in different environments (e.g., a path relative to the user's home directory).


import unittest
from typing import List

from sglang.test.ci.ci_register import register_amd_ci
from sglang.test.nightly_bench_utils import BenchmarkResult
from sglang.test.nightly_utils import NightlyBenchmarkRunner
from sglang.test.test_utils import DEFAULT_URL_FOR_TEST, _parse_int_list_env

register_amd_ci(
est_time=5400, suite="nightly-perf-8-gpu-mi35x-qwen35-fp8", nightly=True
)


def generate_simple_markdown_report(results: List[BenchmarkResult]) -> str:
"""Generate a simplified markdown report without traces and cost columns.

Skips the first result if it's a warmup run (duplicate batch_size).
"""
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 = os.getenv("GPU_CONFIG", "MI35x")
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 = 1 / (result.output_throughput / result.batch_size) * 1000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The calculation for itl could lead to a ZeroDivisionError if result.output_throughput is zero. It's safer to check for this case to prevent the test from crashing during report generation. Rewriting the expression also improves readability.

Suggested change
itl = 1 / (result.output_throughput / result.batch_size) * 1000
itl = (result.batch_size / result.output_throughput) * 1000 if result.output_throughput > 0 else 0

summary += f"| {result.batch_size} | {result.input_len} | {result.latency:.2f} | {result.input_throughput:.2f} | {result.output_throughput:.2f} | {itl:.2f} |\n"

return summary


QWEN35_FP8_MODEL_PATH = os.environ.get(
"QWEN35_FP8_MODEL_PATH", "Qwen/Qwen3.5-397B-A17B-FP8"
)
PROFILE_DIR = "performance_profiles_qwen35_fp8_mi35x"


class TestQwen35Fp8PerfMI35x(unittest.TestCase):
"""Test suite for Qwen3.5-397B-A17B FP8 performance benchmarks on MI35x."""

@classmethod
def setUpClass(cls):
cls.base_url = DEFAULT_URL_FOR_TEST
cls.batch_sizes = [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.model_config = {
"name": "qwen35-fp8-mi35x",
"model_path": QWEN35_FP8_MODEL_PATH,
"other_args": [
"--trust-remote-code",
"--tp",
"8",
"--attention-backend",
"triton",
"--mem-fraction-static",
"0.8",
"--model-loader-extra-config",
'{"enable_multithread_load": true}',
"--watchdog-timeout",
"1200",
],
"env_vars": {
"SGLANG_USE_AITER": "1",
},
}

cls.runner = NightlyBenchmarkRunner(PROFILE_DIR, cls.__name__, cls.base_url)
cls.runner.setup_profile_directory()
cls.runner.full_report = f"## {cls.__name__}\n"

def test_qwen35_fp8_perf(self):
"""Run Qwen3.5-397B-A17B FP8 performance benchmark on MI35x."""
old_env = {}
for key, value in self.model_config.get("env_vars", {}).items():
old_env[key] = os.environ.get(key)
os.environ[key] = value

try:
result_tuple = self.runner.run_benchmark_for_model(
model_path=self.model_config["model_path"],
batch_sizes=self.batch_sizes,
input_lens=self.input_lens,
output_lens=self.output_lens,
other_args=self.model_config["other_args"],
variant=self.model_config["name"],
extra_bench_args=["--trust-remote-code"],
enable_profile=False,
timeout=5400,
)
results = result_tuple[0]
success = result_tuple[1]

if results:
self.runner.full_report += (
generate_simple_markdown_report(results) + "\n"
)

self.assertTrue(
success,
f"Benchmark failed for {QWEN35_FP8_MODEL_PATH} on MI35x",
)
finally:
for key, value in old_env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
self.runner.write_final_report()


if __name__ == "__main__":
unittest.main()
Loading