Skip to content
99 changes: 99 additions & 0 deletions .github/workflows/nightly-test-amd-rocm720.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ on:
- nightly-test-1-gpu-unit-rocm720
- nightly-test-1-gpu-kernel-rocm720
- nightly-test-1-gpu-mi35x-rocm720
- nightly-2-gpu-mi30x-qwen38-flash-next-fp8-rocm720
- nightly-1-gpu-mi35x-qwen38-flash-next-fp8-rocm720
# 2-GPU and 4-GPU Tests (MI30x + MI35x)
- nightly-accuracy-2-gpu-rocm720
- nightly-accuracy-2-gpu-vlm-rocm720
Expand Down Expand Up @@ -266,6 +268,101 @@ jobs:
echo "$(<github_summary.md )" >> $GITHUB_STEP_SUMMARY || true
exit ${TEST_EXIT_CODE:-0}

# ==============================================================================
# Qwen3.8-Flash-Next-FP8 (MI30x TP2+EP2, MI35x TP1)
# ==============================================================================

nightly-2-gpu-mi30x-qwen38-flash-next-fp8-rocm720:
name: ${{ format('nightly-2-gpu-mi30x-qwen38-flash-next-fp8 ({0}, linux-mi300-2gpu-sglang)', matrix.rocm_version) }}
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-2-gpu-mi30x-qwen38-flash-next-fp8-rocm720,'))
runs-on: linux-mi300-2gpu-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 (${{ matrix.rocm_version }})
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

- 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 \
Qwen/Qwen3.8-Flash-Next-FP8 250

- name: Qwen3.8-Flash-Next-FP8 Accuracy MI30x ROCm 7.2
timeout-minutes: 60
run: |
: > github_summary.md
bash scripts/ci/amd/amd_ci_exec.sh -w /sglang-checkout/test \
-e GITHUB_STEP_SUMMARY="/sglang-checkout/github_summary.md" \
-e QWEN38_FLASH_NEXT_FP8_TP_SIZE=2 \
-e QWEN38_FLASH_NEXT_FP8_EP_SIZE=2 \
-e QWEN38_FLASH_NEXT_FP8_MEM_FRACTION_STATIC=0.75 \
python3 run_suite.py --hw amd --suite nightly-amd-2-gpu-mi30x-qwen38-flash-next-fp8 --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-1-gpu-mi35x-qwen38-flash-next-fp8-rocm720:
name: ${{ format('nightly-1-gpu-mi35x-qwen38-flash-next-fp8 ({0}, linux-mi35x-gpu-1)', matrix.rocm_version) }}
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-1-gpu-mi35x-qwen38-flash-next-fp8-rocm720,'))
runs-on: linux-mi35x-gpu-1
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 (${{ matrix.rocm_version }})
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

- 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 \
Qwen/Qwen3.8-Flash-Next-FP8 250

- name: Qwen3.8-Flash-Next-FP8 Accuracy MI35x ROCm 7.2
timeout-minutes: 60
run: |
: > github_summary.md
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-1-gpu-mi35x-qwen38-flash-next-fp8 --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}

# ==============================================================================
# 2-GPU and 4-GPU Tests (MI30x + MI35x)
# ==============================================================================
Expand Down Expand Up @@ -2317,6 +2414,8 @@ jobs:
# 1-GPU Unit Tests (MI30x + MI35x)
- nightly-test-1-gpu-unit-rocm720
- nightly-test-1-gpu-mi35x-rocm720
- nightly-2-gpu-mi30x-qwen38-flash-next-fp8-rocm720
- nightly-1-gpu-mi35x-qwen38-flash-next-fp8-rocm720
# 2-GPU and 4-GPU Tests (MI30x + MI35x)
- nightly-accuracy-2-gpu-rocm720
- nightly-accuracy-2-gpu-vlm-rocm720
Expand Down
228 changes: 228 additions & 0 deletions test/registered/amd/accuracy/test_qwen38_flash_next_fp8_eval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
"""AMD Qwen3.8-Flash-Next-FP8 graph-mode GSM8K accuracy test.

The released FP8 checkpoint uses TP1 on gfx950 and TP2+EP2 on gfx942, where
the 192 GiB device capacity cannot hold the target, MTP draft, and graph/KV
state on one GPU. These nightly tests pin the checkpoint revision and exercise
the same AITER decode-graph path with EAGLE speculation on both architectures.
Direct AITER paged QSA remains disabled here so the core model correctness gate
does not depend on an unreleased AITER API.

Registries:
nightly-amd-2-gpu-mi30x-qwen38-flash-next-fp8 suite
nightly-amd-1-gpu-mi35x-qwen38-flash-next-fp8 suite
"""

import base64
import io
import os
import unittest
from pathlib import Path
from types import SimpleNamespace

import requests

from sglang.test.ci.ci_register import register_amd_ci
from sglang.test.run_eval import run_eval
from sglang.test.test_utils import (
DEFAULT_URL_FOR_TEST,
CustomTestCase,
is_in_ci,
popen_launch_server,
terminate_and_kill_process_tree,
write_github_step_summary,
)

register_amd_ci(
est_time=1800,
suite="nightly-amd-2-gpu-mi30x-qwen38-flash-next-fp8",
nightly=True,
)
register_amd_ci(
est_time=1800,
suite="nightly-amd-1-gpu-mi35x-qwen38-flash-next-fp8",
nightly=True,
)

MODEL_ID = "Qwen/Qwen3.8-Flash-Next-FP8"
MODEL_PATH = os.environ.get("QWEN38_FLASH_NEXT_FP8_MODEL_PATH", MODEL_ID)
MODEL_REVISION = "bcd9f01ddc9cff2316eb84281bebcd5b058bddce"
ACCURACY_THRESHOLD = 0.94
SERVER_LAUNCH_TIMEOUT = 1800
NUM_REQUESTED_EXAMPLES = 1319
NUM_SHOTS = 5
NUM_EVALUATED_EXAMPLES = NUM_REQUESTED_EXAMPLES - NUM_SHOTS
IMAGE_PATH = Path(__file__).resolve().parents[4] / "examples/assets/example_image.png"
TP_SIZE = int(os.environ.get("QWEN38_FLASH_NEXT_FP8_TP_SIZE", "1"))
EP_SIZE = int(os.environ.get("QWEN38_FLASH_NEXT_FP8_EP_SIZE", "1"))
MEM_FRACTION_STATIC = os.environ.get(
"QWEN38_FLASH_NEXT_FP8_MEM_FRACTION_STATIC", "0.95"
)

if TP_SIZE % EP_SIZE != 0:
raise ValueError(
f"Qwen3.8 nightly requires TP ({TP_SIZE}) divisible by EP ({EP_SIZE})"
)

SERVER_ARGS = [
"--revision",
MODEL_REVISION,
"--served-model-name",
MODEL_ID,
"--tp-size",
str(TP_SIZE),
"--ep-size",
str(EP_SIZE),
"--attention-backend",
"aiter",
"--moe-runner-backend",
"aiter",
"--kv-cache-dtype",
"auto",
"--chunked-prefill-size",
"16384",
"--watchdog-timeout",
"1200",
"--mem-fraction-static",
MEM_FRACTION_STATIC,
"--max-running-requests",
"4",
"--cuda-graph-max-bs-decode",
"4",
"--speculative-algorithm",
"EAGLE",
"--speculative-num-steps",
"3",
"--speculative-eagle-topk",
"1",
"--speculative-num-draft-tokens",
"4",
]

SERVER_ENV = {
"SGLANG_USE_AITER": "0",
}


class TestQwen38FlashNextFP8AMD(CustomTestCase):
"""Gate Qwen3.8-Flash-Next FP8 accuracy on the graph fallback path."""

def _assert_multimodal_generation(self):
image_data = base64.b64encode(IMAGE_PATH.read_bytes()).decode("ascii")
response = requests.post(
DEFAULT_URL_FOR_TEST + "/v1/chat/completions",
json={
"model": MODEL_ID,
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_data}"
},
},
{
"type": "text",
"text": "What color are the taxis? Answer with one word.",
},
],
}
],
"temperature": 0,
"max_tokens": 32,
"chat_template_kwargs": {"enable_thinking": False},
},
timeout=120,
)
response.raise_for_status()
content = response.json()["choices"][0]["message"]["content"]
self.assertIn("yellow", content.lower())

def test_gsm8k_accuracy(self):
server_stdout = io.StringIO()
server_stderr = io.StringIO()
process = popen_launch_server(
MODEL_PATH,
DEFAULT_URL_FOR_TEST,
timeout=SERVER_LAUNCH_TIMEOUT,
other_args=SERVER_ARGS,
env=SERVER_ENV,
return_stdout_stderr=(server_stdout, server_stderr),
)

try:
self._assert_multimodal_generation()
requests.post(
DEFAULT_URL_FOR_TEST + "/flush_cache",
params={"timeout": 30},
timeout=45,
).raise_for_status()
args = SimpleNamespace(
base_url=DEFAULT_URL_FOR_TEST,
model=MODEL_ID,
eval_name="gsm8k",
num_examples=NUM_REQUESTED_EXAMPLES,
num_threads=512,
num_shots=NUM_SHOTS,
max_tokens=4096,
chat_template_kwargs={"enable_thinking": False},
)
metrics = run_eval(args)
score = metrics["score"]
latency = metrics.get("latency", 0.0)
output_throughput = metrics.get("output_throughput", 0.0)
status = "PASS" if score >= ACCURACY_THRESHOLD else "FAIL"

self.assertIsNone(
process.poll(), "Qwen3.8-Flash-Next server exited during evaluation"
)
server_logs = server_stdout.getvalue() + server_stderr.getvalue()
decode_lines = [
line for line in server_logs.splitlines() if "Decode batch" in line
]
self.assertTrue(
decode_lines,
"Qwen3.8-Flash-Next completed without decode batch evidence",
)
eager_decode_lines = [
line for line in decode_lines if "cuda graph: True" not in line
]
self.assertFalse(
eager_decode_lines,
"Qwen3.8-Flash-Next used eager decode instead of graph replay: "
+ "\n".join(eager_decode_lines[:5]),
)
self.assertNotRegex(
server_logs,
r"Parameter .*not found in params_dict",
"Qwen3.8-Flash-Next skipped checkpoint parameters during loading",
)

if is_in_ci():
summary = "### Qwen3.8-Flash-Next-FP8 GSM8K (AMD)\n\n"
summary += (
"| Model revision | TP | EP | Graph | Responses | Accuracy | "
"Threshold | Latency | Output throughput | Status |\n"
)
summary += "| --- | ---: | ---: | --- | ---: | ---: | ---: | ---: | ---: | --- |\n"
summary += (
f"| `{MODEL_REVISION}` | {TP_SIZE} | {EP_SIZE} | yes | "
f"{NUM_EVALUATED_EXAMPLES} | "
f"{score:.3f} | {ACCURACY_THRESHOLD:.2f} | {latency:.1f}s | "
f"{output_throughput:.1f} tok/s | {status} |\n"
)
write_github_step_summary(summary)

self.assertGreaterEqual(
score,
ACCURACY_THRESHOLD,
f"Qwen3.8-Flash-Next-FP8 accuracy {score:.3f} below "
f"threshold {ACCURACY_THRESHOLD:.2f}",
)
finally:
terminate_and_kill_process_tree(process)


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