[AMD][CI] Add the Qwen3.8 MXFP4 MI35x nightly (#35383)
This commit is contained in:
@@ -84,9 +84,8 @@ on:
|
|||||||
- nightly-8-gpu-kimi-k26-rocm720
|
- nightly-8-gpu-kimi-k26-rocm720
|
||||||
# 8-GPU Kimi-K3 (MI35x only - native MXFP4 needs gfx95x)
|
# 8-GPU Kimi-K3 (MI35x only - native MXFP4 needs gfx95x)
|
||||||
- nightly-8-gpu-mi35x-kimi-k3-rocm720
|
- nightly-8-gpu-mi35x-kimi-k3-rocm720
|
||||||
# 8-GPU Qwen3-235B (MI30x + MI35x MXFP4)
|
# 8-GPU Qwen3.8-MXFP4 (MI35x only - MXFP4 needs gfx95x)
|
||||||
- nightly-8-gpu-qwen3-235b-rocm720
|
- nightly-8-gpu-mi35x-qwen38-mxfp4-rocm720
|
||||||
- nightly-8-gpu-mi35x-qwen3-235b-mxfp4-rocm720
|
|
||||||
# 8-GPU Qwen 3.5 (MI30x + MI35x)
|
# 8-GPU Qwen 3.5 (MI30x + MI35x)
|
||||||
- nightly-8-gpu-qwen35-rocm720
|
- nightly-8-gpu-qwen35-rocm720
|
||||||
- nightly-8-gpu-mi35x-qwen35-rocm720
|
- nightly-8-gpu-mi35x-qwen35-rocm720
|
||||||
@@ -1754,87 +1753,6 @@ jobs:
|
|||||||
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
|
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
|
||||||
exit ${TEST_EXIT_CODE:-0}
|
exit ${TEST_EXIT_CODE:-0}
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# 8-GPU Qwen3-235B (MI30x + MI35x MXFP4)
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
nightly-8-gpu-qwen3-235b-rocm720:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
rocm_version: ${{ fromJson(inputs.rocm_version && inputs.rocm_version != 'all' && format('["{0}"]', inputs.rocm_version) || '["rocm724", "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-qwen3-235b-rocm720,'))
|
|
||||||
runs-on: linux-mi300-8gpu-sglang
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
|
||||||
|
|
||||||
- name: Ensure VRAM is clear
|
|
||||||
run: bash scripts/ci/amd/ensure_vram_clear.sh rocm
|
|
||||||
|
|
||||||
- name: Setup docker (ROCm 7.2)
|
|
||||||
run: |
|
|
||||||
touch github_summary.md
|
|
||||||
bash scripts/ci/amd/amd_ci_start_container.sh --rocm-version ${{ matrix.rocm_version }}
|
|
||||||
env:
|
|
||||||
GITHUB_WORKSPACE: ${{ github.workspace }}
|
|
||||||
ENABLE_CACHE_HOST: "1"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bash scripts/ci/amd/amd_ci_install_dependency.sh --skip-test-time-deps
|
|
||||||
|
|
||||||
- name: Accuracy Test + Performance Test ROCm 7.2 (8-GPU Qwen3)
|
|
||||||
timeout-minutes: 120
|
|
||||||
run: |
|
|
||||||
> github_summary.md # Clear summary file
|
|
||||||
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
|
|
||||||
-e SGLANG_MOE_COPY_WEIGHT_VIEWS_BEFORE_H2D=1 \
|
|
||||||
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
|
|
||||||
python3 run_suite.py --hw amd --suite nightly-8-gpu-qwen3-235b --nightly --timeout-per-file 3600 ${{ (github.event_name == 'schedule' || 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-qwen3-235b-mxfp4-rocm720:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
rocm_version: ${{ fromJson(inputs.rocm_version && inputs.rocm_version != 'all' && format('["{0}"]', inputs.rocm_version) || '["rocm724", "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-qwen3-235b-mxfp4-rocm720,'))
|
|
||||||
runs-on: linux-mi35x-gpu-8
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
|
||||||
|
|
||||||
- name: Ensure VRAM is clear
|
|
||||||
run: bash scripts/ci/amd/ensure_vram_clear.sh rocm
|
|
||||||
|
|
||||||
- name: Setup docker (ROCm 7.2)
|
|
||||||
run: |
|
|
||||||
touch github_summary.md
|
|
||||||
bash scripts/ci/amd/amd_ci_start_container.sh --rocm-version ${{ matrix.rocm_version }}
|
|
||||||
env:
|
|
||||||
GITHUB_WORKSPACE: ${{ github.workspace }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
bash scripts/ci/amd/amd_ci_install_dependency.sh --skip-test-time-deps
|
|
||||||
# Install tabulate for run_suite.py (missing in MI35x container)
|
|
||||||
bash scripts/ci/amd/amd_ci_exec.sh pip install tabulate
|
|
||||||
|
|
||||||
- name: Accuracy Test + Performance Test MI35x ROCm 7.2 (8-GPU Qwen3-235B-MXFP4)
|
|
||||||
timeout-minutes: 120
|
|
||||||
run: |
|
|
||||||
> github_summary.md # Clear summary file
|
|
||||||
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
|
|
||||||
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
|
|
||||||
python3 run_suite.py --hw amd --suite nightly-8-gpu-mi35x-qwen3-235b-mxfp4 --nightly --timeout-per-file 3600 ${{ (github.event_name == 'schedule' || 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 Qwen 3.5 (MI30x + MI35x)
|
# 8-GPU Qwen 3.5 (MI30x + MI35x)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -1980,6 +1898,63 @@ jobs:
|
|||||||
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
|
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
|
||||||
exit ${TEST_EXIT_CODE:-0}
|
exit ${TEST_EXIT_CODE:-0}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 8-GPU Qwen3.8-MXFP4 (MI35x only - MXFP4 needs gfx95x)
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
nightly-8-gpu-mi35x-qwen38-mxfp4-rocm720:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
rocm_version: ${{ fromJson(inputs.rocm_version && inputs.rocm_version != 'all' && format('["{0}"]', inputs.rocm_version) || '["rocm724", "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-qwen38-mxfp4-rocm720,'))
|
||||||
|
runs-on: linux-mi35x-gpu-8
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
|
|
||||||
|
- name: Ensure VRAM is clear
|
||||||
|
run: bash scripts/ci/amd/ensure_vram_clear.sh rocm
|
||||||
|
|
||||||
|
- name: Setup docker (ROCm 7.2)
|
||||||
|
run: |
|
||||||
|
touch github_summary.md
|
||||||
|
bash scripts/ci/amd/amd_ci_start_container.sh --rocm-version ${{ matrix.rocm_version }}
|
||||||
|
env:
|
||||||
|
GITHUB_WORKSPACE: ${{ github.workspace }}
|
||||||
|
ENABLE_CACHE_HOST: "1"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
bash scripts/ci/amd/amd_ci_install_dependency.sh --skip-test-time-deps
|
||||||
|
# Install tabulate for run_suite.py (missing in MI35x container)
|
||||||
|
bash scripts/ci/amd/amd_ci_exec.sh pip install tabulate
|
||||||
|
|
||||||
|
# ~1.2 TB of weights against a fleet-shared cache that runs at 100% used.
|
||||||
|
# Run 32196787596 died 40 minutes into the download on ENOSPC with no
|
||||||
|
# free-space figure anywhere in its log; record one up front instead.
|
||||||
|
- name: Check model cache space
|
||||||
|
run: |
|
||||||
|
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout \
|
||||||
|
bash scripts/ci/amd/check_hf_cache_space.sh \
|
||||||
|
amd/Qwen3.8-2.4T-A95B-Quark-MXFP4 1250
|
||||||
|
|
||||||
|
# One step, not the usual accuracy/perf pair: both phases live in the
|
||||||
|
# same suite so they launch from one server config. The 2.4T MXFP4
|
||||||
|
# checkpoint is ~1.2 TB and is loaded once per phase, which dominates
|
||||||
|
# wall time well before either the eval or the benchmark starts.
|
||||||
|
- name: Accuracy Test + Performance Test MI35x ROCm 7.2 (8-GPU Qwen3.8-MXFP4)
|
||||||
|
timeout-minutes: 420
|
||||||
|
run: |
|
||||||
|
> github_summary.md # Clear summary file
|
||||||
|
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
|
||||||
|
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
|
||||||
|
python3 run_suite.py --hw amd --suite nightly-amd-8-gpu-mi35x-qwen38-mxfp4 --nightly --timeout-per-file 21600 ${{ (github.event_name == 'schedule' || 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.1 (MI30x + MI35x)
|
# 8-GPU GLM-5.1 (MI30x + MI35x)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -2382,9 +2357,8 @@ jobs:
|
|||||||
- nightly-8-gpu-kimi-k26-rocm720
|
- nightly-8-gpu-kimi-k26-rocm720
|
||||||
# 8-GPU Kimi-K3 (MI35x only - native MXFP4 needs gfx95x)
|
# 8-GPU Kimi-K3 (MI35x only - native MXFP4 needs gfx95x)
|
||||||
- nightly-8-gpu-mi35x-kimi-k3-rocm720
|
- nightly-8-gpu-mi35x-kimi-k3-rocm720
|
||||||
# 8-GPU Qwen3-235B (MI30x + MI35x MXFP4)
|
# 8-GPU Qwen3.8-MXFP4 (MI35x only - MXFP4 needs gfx95x)
|
||||||
- nightly-8-gpu-qwen3-235b-rocm720
|
- nightly-8-gpu-mi35x-qwen38-mxfp4-rocm720
|
||||||
- nightly-8-gpu-mi35x-qwen3-235b-mxfp4-rocm720
|
|
||||||
# 8-GPU Qwen 3.5 (MI30x + MI35x)
|
# 8-GPU Qwen 3.5 (MI30x + MI35x)
|
||||||
- nightly-8-gpu-qwen35-rocm720
|
- nightly-8-gpu-qwen35-rocm720
|
||||||
- nightly-8-gpu-mi35x-qwen35-rocm720
|
- nightly-8-gpu-mi35x-qwen35-rocm720
|
||||||
|
|||||||
Executable
+101
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Report HuggingFace cache headroom before a large checkpoint is used, and
|
||||||
|
# clear stale download artifacts.
|
||||||
|
#
|
||||||
|
# Usage (inside the ci_sglang container, where /sgl-data is the cache mount):
|
||||||
|
# check_hf_cache_space.sh <model_repo_id> [required_gib]
|
||||||
|
#
|
||||||
|
# Why this exists: run 32196787596 died 40 minutes into a 1.2 TB download with
|
||||||
|
# "OSError: [Errno 28] No space left on device", and the only way to find that
|
||||||
|
# out was reading 8,500 lines of job log -- free space was never reported
|
||||||
|
# anywhere. This puts the number in the log every time, before the download
|
||||||
|
# rather than after it fails.
|
||||||
|
#
|
||||||
|
# What it deliberately does NOT do is free space by deleting other checkpoints.
|
||||||
|
# /sgl-data is not a per-runner disk: it is `amdprj3-k8s-2`, a 15 TB volume
|
||||||
|
# shared by the whole AMD fleet, and it sits at 100% used. An earlier version of
|
||||||
|
# this script evicted least-recently-used checkpoints until it hit a free-space
|
||||||
|
# target; on that filesystem it removed 48 of them and free space went from
|
||||||
|
# 298 MB to 227 MB, because concurrent jobs consume anything released as fast as
|
||||||
|
# it appears. So the eviction destroyed other jobs' caches fleet-wide, forcing
|
||||||
|
# them to re-download, and bought nothing. A volume at capacity is an
|
||||||
|
# infrastructure problem and a per-job script cannot fix it by deleting things
|
||||||
|
# other jobs still need.
|
||||||
|
#
|
||||||
|
# Never fails the job: a full cache is not necessarily fatal (the checkpoint may
|
||||||
|
# already be cached, which is the common case), and when it is fatal the
|
||||||
|
# download says so itself -- now against a log that already explained why.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
MODEL_REPO_ID="${1:?model repo id, e.g. amd/Qwen3.8-2.4T-A95B-Quark-MXFP4}"
|
||||||
|
REQUIRED_GIB="${2:-0}"
|
||||||
|
|
||||||
|
HF_CACHE="${HF_HOME:-/sgl-data/hf-cache}/hub"
|
||||||
|
# HuggingFace stores `org/name` as `models--org--name`.
|
||||||
|
MODEL_DIR="$HF_CACHE/models--${MODEL_REPO_ID//\//--}"
|
||||||
|
|
||||||
|
avail_gib() {
|
||||||
|
df -BG --output=avail "$1" 2>/dev/null | tail -1 | tr -dc '0-9'
|
||||||
|
}
|
||||||
|
|
||||||
|
report() {
|
||||||
|
echo "=== HF cache space ($1) ==="
|
||||||
|
df -h "$HF_CACHE" 2>/dev/null || df -h /sgl-data 2>/dev/null || true
|
||||||
|
echo "==========================="
|
||||||
|
}
|
||||||
|
|
||||||
|
check_hf_cache_space() {
|
||||||
|
if [[ ! -d "$HF_CACHE" ]]; then
|
||||||
|
echo "HF cache $HF_CACHE does not exist yet; nothing to report."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
report "before"
|
||||||
|
|
||||||
|
if [[ -d "$MODEL_DIR" ]]; then
|
||||||
|
echo "✓ ${MODEL_REPO_ID} is already cached at ${MODEL_DIR};" \
|
||||||
|
"no download needed regardless of free space."
|
||||||
|
else
|
||||||
|
echo "${MODEL_REPO_ID} is NOT cached; it must be downloaded."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Abandoned partial downloads are pure waste and safe to drop. This is the
|
||||||
|
# shared helper the CUDA runner prep already uses; it only touches
|
||||||
|
# *.incomplete / *.tmp older than two hours, so it cannot pull the rug from
|
||||||
|
# under a download running right now.
|
||||||
|
python3 "${SCRIPT_DIR}/../utils/cleanup_hf_cache.py" || true
|
||||||
|
|
||||||
|
report "after"
|
||||||
|
|
||||||
|
local avail
|
||||||
|
avail=$(avail_gib "$HF_CACHE")
|
||||||
|
if [[ -z "$avail" ]]; then
|
||||||
|
echo "WARNING: could not read free space from df."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Free space: ${avail} GiB."
|
||||||
|
|
||||||
|
if [[ -d "$MODEL_DIR" ]] || (( REQUIRED_GIB == 0 )) || (( avail >= REQUIRED_GIB )); then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=============================================================="
|
||||||
|
echo "WARNING: ${MODEL_REPO_ID} is not cached and only ${avail} GiB is"
|
||||||
|
echo " free, against roughly ${REQUIRED_GIB} GiB of weights. The"
|
||||||
|
echo " download will likely fail with ENOSPC partway through."
|
||||||
|
echo ""
|
||||||
|
echo " /sgl-data is shared by the whole AMD fleet, so this is a"
|
||||||
|
echo " capacity problem rather than something this job can clear:"
|
||||||
|
echo " deleting other checkpoints to make room just moves the"
|
||||||
|
echo " failure onto whichever job needed them next. Raising it"
|
||||||
|
echo " needs the runner owners."
|
||||||
|
echo "=============================================================="
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
check_hf_cache_space "$@"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
"""MI35x Qwen3.8-2.4T-A95B MXFP4 GSM8K accuracy + serving-perf test (8-GPU)
|
||||||
|
|
||||||
|
Tests amd/Qwen3.8-2.4T-A95B-Quark-MXFP4, AMD's day-0 Quark quantization of
|
||||||
|
Qwen/Qwen3.8-2.4T-A95B-FP8, on a single 8-GPU MI35x node.
|
||||||
|
|
||||||
|
Qwen3.8 is a 2.4T-parameter / 95B-active hybrid MoE: 23 repeats of 3 x Gated
|
||||||
|
DeltaNet -> MoE then 1 x Gated Attention -> MoE, 512 experts with 10 routed + 1
|
||||||
|
shared active. It reuses the Qwen3.5 architecture -- the checkpoint reports
|
||||||
|
``Qwen3_5MoeForCausalLM`` -- so no model code is added here. What is missing,
|
||||||
|
and what this test supplies, is nightly evidence that the ROCm kernels behind
|
||||||
|
that path keep producing correct tokens, and at what speed.
|
||||||
|
|
||||||
|
MXFP4 rather than FP8: at 2.4T parameters FP8 is ~2.4 TB against 8 x 288 GB =
|
||||||
|
2.30 TB per MI355X node, so the FP8 checkpoint has no single-node AMD recipe
|
||||||
|
(the cookbook serves it as MI300X TP8 x PP2 over two nodes) and single-node
|
||||||
|
means FP4. Only the routed experts are quantized; attention, the shared expert,
|
||||||
|
the MoE gate and ``lm_head`` stay at source precision, which is why AMD
|
||||||
|
measures the same 97.49 GSM8K as the FP8 baseline (100% recovery) and why this
|
||||||
|
test gates the FP8 checkpoint's quality even though it serves the MXFP4 one.
|
||||||
|
|
||||||
|
Both phases launch from one ``SERVER_ARGS``, which reproduces the recipe
|
||||||
|
published on the AMD model card. That is the reason accuracy and perf share a
|
||||||
|
file rather than splitting into an accuracy suite and a perf suite: the
|
||||||
|
throughput numbers then describe the exact configuration the accuracy gate
|
||||||
|
covers, and a flag change cannot drift one out from under the other. Two of
|
||||||
|
those flags are load-bearing rather than restatements of a default:
|
||||||
|
|
||||||
|
* ``--page-size 1`` -- ``_page_size_default`` bumps the default to 64 on HIP
|
||||||
|
when the container sets SGLANG_AITER_KV_CACHE_LAYOUT=vectorized_5d, so the
|
||||||
|
measured geometry only holds if the page size is pinned.
|
||||||
|
* ``--attention-backend aiter`` -- no arg override picks a backend for
|
||||||
|
``Qwen3_5MoeForCausalLM`` on ROCm, so the AITER path has to be named.
|
||||||
|
|
||||||
|
Perf runs only once accuracy has passed (see ``accuracy_passed``): a server
|
||||||
|
that decodes garbage still benchmarks fine, so publishing its throughput would
|
||||||
|
be worse than publishing nothing.
|
||||||
|
|
||||||
|
The scorer extracts the last number in the reply and the server runs with no
|
||||||
|
``--reasoning-parser``, so a ``<think>`` block still scores: the reasoning
|
||||||
|
stays in ``message.content`` rather than being split into ``reasoning_content``,
|
||||||
|
which would leave ``content`` empty and score 0.
|
||||||
|
|
||||||
|
MXFP4 needs gfx95x, so this is MI35x-only and ROCm 7.2-only; it does not
|
||||||
|
register on gfx942 (MI300/MI325).
|
||||||
|
|
||||||
|
Registry: nightly-amd-8-gpu-mi35x-qwen38-mxfp4 suite
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from sglang.srt.utils import kill_process_tree
|
||||||
|
from 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.run_eval import run_eval
|
||||||
|
from sglang.test.test_utils import (
|
||||||
|
DEFAULT_URL_FOR_TEST,
|
||||||
|
CustomTestCase,
|
||||||
|
_parse_int_list_env,
|
||||||
|
is_in_ci,
|
||||||
|
popen_launch_server,
|
||||||
|
write_github_step_summary,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register for AMD CI - Qwen3.8 MXFP4 accuracy + perf on MI35x (~4h: the 1.2 TB
|
||||||
|
# checkpoint is loaded twice, once per phase, and dominates both)
|
||||||
|
register_amd_ci(
|
||||||
|
est_time=14400, suite="nightly-amd-8-gpu-mi35x-qwen38-mxfp4", nightly=True
|
||||||
|
)
|
||||||
|
|
||||||
|
QWEN38_MXFP4_MODEL_PATH = os.environ.get(
|
||||||
|
"QWEN38_MXFP4_MODEL_PATH", "amd/Qwen3.8-2.4T-A95B-Quark-MXFP4"
|
||||||
|
)
|
||||||
|
SERVER_LAUNCH_TIMEOUT = 9000
|
||||||
|
BENCH_TIMEOUT = 9000
|
||||||
|
TP_SIZE = 8
|
||||||
|
# AMD measures 0.9749 on this checkpoint. The gate sits ~5% below it, matching
|
||||||
|
# the relative tolerance the sibling Qwen3.5 MI35x evals allow.
|
||||||
|
ACCURACY_THRESHOLD = 0.93
|
||||||
|
PERF_RESULT_DIR = "performance_results_qwen38_mxfp4_mi35x"
|
||||||
|
|
||||||
|
# The AMD model card's serve recipe, shared by both phases.
|
||||||
|
SERVER_ARGS = [
|
||||||
|
"--tp",
|
||||||
|
str(TP_SIZE),
|
||||||
|
"--attention-backend",
|
||||||
|
"aiter",
|
||||||
|
"--page-size",
|
||||||
|
"1",
|
||||||
|
"--chunked-prefill-size",
|
||||||
|
"16384",
|
||||||
|
"--mem-fraction-static",
|
||||||
|
"0.9",
|
||||||
|
"--trust-remote-code",
|
||||||
|
"--model-loader-extra-config",
|
||||||
|
'{"enable_multithread_load": true}',
|
||||||
|
"--watchdog-timeout",
|
||||||
|
"1200",
|
||||||
|
]
|
||||||
|
# Gates the AITER MXFP4-MoE / GEMM / norm / rope kernels. The ROCm image sets
|
||||||
|
# it; a bare-pip host does not. popen_launch_server merges this over os.environ.
|
||||||
|
SERVER_ENV = {"SGLANG_USE_AITER": "1"}
|
||||||
|
|
||||||
|
|
||||||
|
class TestQwen38Mxfp4MI35x(CustomTestCase):
|
||||||
|
"""Qwen3.8-2.4T-A95B MXFP4 accuracy + serving perf for AMD MI35x."""
|
||||||
|
|
||||||
|
# Set by the accuracy phase and read by the perf phase. unittest orders
|
||||||
|
# methods alphabetically, so test_a_* lands before test_b_*.
|
||||||
|
accuracy_passed = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
||||||
|
cls.num_examples = int(os.environ.get("GSM8K_NUM_QUESTIONS", "1319"))
|
||||||
|
cls.max_tokens = int(os.environ.get("GSM8K_MAX_NEW_TOKENS", "2048"))
|
||||||
|
|
||||||
|
def test_a_gsm8k_accuracy(self):
|
||||||
|
"""GSM8K few-shot accuracy must clear the AMD-published gate."""
|
||||||
|
process = popen_launch_server(
|
||||||
|
QWEN38_MXFP4_MODEL_PATH,
|
||||||
|
self.base_url,
|
||||||
|
timeout=SERVER_LAUNCH_TIMEOUT,
|
||||||
|
other_args=SERVER_ARGS,
|
||||||
|
env=SERVER_ENV,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
requests.get(self.base_url + "/flush_cache")
|
||||||
|
|
||||||
|
args = SimpleNamespace(
|
||||||
|
base_url=self.base_url,
|
||||||
|
model=QWEN38_MXFP4_MODEL_PATH,
|
||||||
|
eval_name="gsm8k",
|
||||||
|
num_examples=self.num_examples,
|
||||||
|
num_threads=512,
|
||||||
|
max_tokens=self.max_tokens,
|
||||||
|
chat_template_kwargs={"enable_thinking": False},
|
||||||
|
)
|
||||||
|
metrics = run_eval(args)
|
||||||
|
acc = metrics["score"]
|
||||||
|
|
||||||
|
passed = acc >= ACCURACY_THRESHOLD
|
||||||
|
status = "✅ PASS" if passed else "❌ FAIL"
|
||||||
|
print(f" accuracy={acc:.3f} threshold={ACCURACY_THRESHOLD} {status}")
|
||||||
|
|
||||||
|
if is_in_ci():
|
||||||
|
summary = "### Qwen3.8-2.4T-A95B MXFP4 GSM8K (MI35x)\n\n"
|
||||||
|
summary += "| Model | TP | Accuracy | Threshold | Status |\n"
|
||||||
|
summary += "| ----- | -- | -------- | --------- | ------ |\n"
|
||||||
|
summary += (
|
||||||
|
f"| {QWEN38_MXFP4_MODEL_PATH} | {TP_SIZE} | {acc:.3f} | "
|
||||||
|
f"{ACCURACY_THRESHOLD} | {status} |\n"
|
||||||
|
)
|
||||||
|
write_github_step_summary(summary)
|
||||||
|
|
||||||
|
type(self).accuracy_passed = passed
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
acc,
|
||||||
|
ACCURACY_THRESHOLD,
|
||||||
|
f"Qwen3.8 MXFP4 accuracy {acc:.3f} below threshold {ACCURACY_THRESHOLD}",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
kill_process_tree(process.pid)
|
||||||
|
|
||||||
|
def test_b_serving_perf(self):
|
||||||
|
"""Serving benchmark for the configuration the accuracy phase gated."""
|
||||||
|
if not self.accuracy_passed:
|
||||||
|
self.skipTest(
|
||||||
|
"GSM8K accuracy did not pass; throughput for a server that "
|
||||||
|
"decodes incorrectly is not worth publishing"
|
||||||
|
)
|
||||||
|
|
||||||
|
# The leading 1 is repeated so generate_simple_markdown_report drops it
|
||||||
|
# as a warmup: bench_one_batch_server measures every batch as it comes,
|
||||||
|
# and batch 1 is both the first and the row a cold cache distorts most.
|
||||||
|
batch_sizes = _parse_int_list_env("NIGHTLY_BATCH_SIZES", "1,1,8,16,64")
|
||||||
|
input_lens = tuple(_parse_int_list_env("NIGHTLY_INPUT_LENS", "1024"))
|
||||||
|
output_lens = tuple(_parse_int_list_env("NIGHTLY_OUTPUT_LENS", "1024"))
|
||||||
|
|
||||||
|
runner = NightlyBenchmarkRunner(
|
||||||
|
PERF_RESULT_DIR, type(self).__name__, self.base_url
|
||||||
|
)
|
||||||
|
runner.setup_result_directory()
|
||||||
|
|
||||||
|
try:
|
||||||
|
results, success = runner.run_benchmark_for_model(
|
||||||
|
model_path=QWEN38_MXFP4_MODEL_PATH,
|
||||||
|
batch_sizes=batch_sizes,
|
||||||
|
input_lens=input_lens,
|
||||||
|
output_lens=output_lens,
|
||||||
|
other_args=SERVER_ARGS,
|
||||||
|
variant="mxfp4",
|
||||||
|
extra_bench_args=["--trust-remote-code"],
|
||||||
|
timeout=SERVER_LAUNCH_TIMEOUT,
|
||||||
|
env=SERVER_ENV,
|
||||||
|
)[:2]
|
||||||
|
if results:
|
||||||
|
runner.full_report += (
|
||||||
|
generate_simple_markdown_report(results, "MI35x") + "\n"
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
success, f"Perf benchmark failed for {QWEN38_MXFP4_MODEL_PATH} on MI35x"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
runner.write_final_report()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -141,6 +141,7 @@ NIGHTLY_SUITES = {
|
|||||||
"nightly-amd-2-gpu-mi35x-deepseek-r1-mxfp4-tp2",
|
"nightly-amd-2-gpu-mi35x-deepseek-r1-mxfp4-tp2",
|
||||||
"nightly-amd-8-gpu-mi35x-deepseek-r1-mxfp4-tp4",
|
"nightly-amd-8-gpu-mi35x-deepseek-r1-mxfp4-tp4",
|
||||||
"nightly-amd-accuracy-8-gpu-mi35x-kimi-k3",
|
"nightly-amd-accuracy-8-gpu-mi35x-kimi-k3",
|
||||||
|
"nightly-amd-8-gpu-mi35x-qwen38-mxfp4",
|
||||||
"nightly-amd-8-gpu-mi35x-glm52-fp8",
|
"nightly-amd-8-gpu-mi35x-glm52-fp8",
|
||||||
"nightly-amd-4-gpu",
|
"nightly-amd-4-gpu",
|
||||||
"nightly-amd-8-gpu",
|
"nightly-amd-8-gpu",
|
||||||
|
|||||||
Reference in New Issue
Block a user