[NPU] Diffusion CI Ground Truth Generation (NPU) (#24630)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Elizaveta Martirosian <you@example.com>
Co-authored-by: Elizaveta Martirosian <elizaveta.martirosian@gmail.com>
Co-authored-by: ronnie_zheng <zl19940307@163.com>
This commit is contained in:
Elizaveta Martirosian
2026-06-04 00:14:37 +03:00
committed by GitHub
co-authored by github-actions[bot] Elizaveta Martirosian Elizaveta Martirosian ronnie_zheng
parent e485ad6ac1
commit c670609ac5
12 changed files with 551 additions and 610 deletions
+33 -98
View File
@@ -19,117 +19,52 @@ from pathlib import Path
import tabulate
from sglang.multimodal_gen.runtime.platforms import current_platform
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.test.partitioning import (
PartitionItem,
partition_items_by_lpt,
)
from sglang.multimodal_gen.test.server.gpu_cases import (
ONE_GPU_CASES,
TWO_GPU_CASES,
)
from sglang.multimodal_gen.test.server.testcase_configs import (
BASELINE_CONFIG,
DiffusionTestCase,
)
logger = init_logger(__name__)
DEFAULT_EST_TIME_SECONDS = 300.0
STARTUP_OVERHEAD_SECONDS = 120.0
DEFAULT_STANDALONE_EST_TIME_SECONDS = 300.0
_UPDATE_WEIGHTS_FROM_DISK_TEST_FILE = "test_update_weights_from_disk.py"
_UPDATE_WEIGHTS_MODEL_PAIR_ENV = "SGLANG_MMGEN_UPDATE_WEIGHTS_PAIR"
_UPDATE_WEIGHTS_MODEL_PAIR_IDS = (
"FLUX.2-klein-base-4B",
"Qwen-Image",
)
def _discover_unit_tests() -> list[str]:
unit_dir = Path(__file__).resolve().parent / "unit"
if not unit_dir.is_dir():
return []
return sorted(
f"../unit/{f.name}" for f in unit_dir.glob("test_*.py") if f.is_file()
# TODO: remove duplicated code
if current_platform.is_npu():
from sglang.multimodal_gen.test.server.ascend.testcase_configs_npu import (
_UPDATE_WEIGHTS_FROM_DISK_TEST_FILE,
COMPONENT_ACCURACY_SUITES,
DEFAULT_EST_TIME_SECONDS,
DEFAULT_STANDALONE_EST_TIME_SECONDS,
FILE_SUITES,
PARAMETRIZED_CASE_GROUPS,
STANDALONE_FILES,
STARTUP_OVERHEAD_SECONDS,
SUITES,
)
else:
from sglang.multimodal_gen.test.server.gpu_cases import ( # noqa: F401 It is used by ci scripts
_UPDATE_WEIGHTS_FROM_DISK_TEST_FILE,
_UPDATE_WEIGHTS_MODEL_PAIR_ENV,
_UPDATE_WEIGHTS_MODEL_PAIR_IDS,
COMPONENT_ACCURACY_FILE_NUM_GPUS,
COMPONENT_ACCURACY_SUITES,
DEFAULT_EST_TIME_SECONDS,
DEFAULT_STANDALONE_EST_TIME_SECONDS,
FILE_SUITES,
ONE_GPU_CASES,
PARAMETRIZED_CASE_GROUPS,
STANDALONE_FILE_EST_TIMES,
STANDALONE_FILES,
STARTUP_OVERHEAD_SECONDS,
STRICT_SUITES,
SUITES,
TWO_GPU_CASES,
)
FILE_SUITES = {
"unit": _discover_unit_tests(),
"component-accuracy": [
"test_component_accuracy_1_gpu.py",
"test_component_accuracy_2_gpu.py",
],
"component-accuracy-1-gpu": [
"test_component_accuracy_1_gpu.py",
],
"component-accuracy-2-gpu": [
"test_component_accuracy_2_gpu.py",
],
"1-gpu-b200": [
"test_server_b200.py",
],
}
PARAMETRIZED_CASE_GROUPS = {
"1-gpu": [
("test_server_1_gpu.py", ONE_GPU_CASES),
],
"2-gpu": [
("test_server_2_gpu.py", TWO_GPU_CASES),
],
}
STANDALONE_FILES = {
"1-gpu": [
"../cli/test_generate_t2i_perf.py",
# Temporarily disabled: 24 timeout failures since 2026-04-09 across
# multimodal-gen-test-1-gpu. Re-enable after the flakiness is fixed.
# "test_update_weights_from_disk.py",
],
"2-gpu": [
"test_disagg_server.py",
],
}
# New standalone files may omit an estimate once to learn the real CI runtime.
# CI will use a fallback estimate for sharding, run the test, then print a
# measured value that must be copied into STANDALONE_FILE_EST_TIMES.
STANDALONE_FILE_EST_TIMES = {
"1-gpu": {
"../cli/test_generate_t2i_perf.py": 240.0,
# See STANDALONE_FILES note above — temporarily disabled.
# "test_update_weights_from_disk.py": 480.0,
},
"2-gpu": {
# Two disagg clusters × (~3 min startup + ~1 min generate) ≈ 8 min.
# Raise if CI reports a higher measured time.
"test_disagg_server.py": 600.0,
},
}
# Backward-compatible suite view for scripts that still operate on file lists.
SUITES = {
**FILE_SUITES,
**{
suite: [filename for filename, _ in case_groups]
+ STANDALONE_FILES.get(suite, [])
for suite, case_groups in PARAMETRIZED_CASE_GROUPS.items()
},
}
STRICT_SUITES = {"unit"}
COMPONENT_ACCURACY_SUITES = {
"component-accuracy",
"component-accuracy-1-gpu",
"component-accuracy-2-gpu",
}
COMPONENT_ACCURACY_FILE_NUM_GPUS = {
"test_component_accuracy_1_gpu.py": 1,
"test_component_accuracy_2_gpu.py": 2,
}
logger = init_logger(__name__)
@dataclass(frozen=True)
@@ -1,299 +0,0 @@
"""
Test runner for multimodal_gen that manages test suites and parallel execution.
Usage:
python3 run_suite_npu.py --suite <suite_name> --partition-id <id> --total-partitions <num>
Example:
python3 run_suite_npu.py --suite 1-npu --partition-id 0 --total-partitions 4
"""
import argparse
import os
import subprocess
import sys
from pathlib import Path
import tabulate
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
SUITES = {
"1-npu": [
"ascend/test_server_1_npu.py",
# add new 1-npu test files here
],
"2-npu": [
"ascend/test_server_2_npu.py",
# add new 2-npu test files here
],
"8-npu": [
"ascend/test_server_8_npu.py",
# add new 8-npu test files here
],
}
def parse_args():
parser = argparse.ArgumentParser(description="Run multimodal_gen test suite")
parser.add_argument(
"--suite",
type=str,
required=True,
choices=list(SUITES.keys()),
help="The test suite to run (valid names are defined in SUITES)",
)
parser.add_argument(
"--partition-id",
type=int,
default=0,
help="Index of the current partition (for parallel execution)",
)
parser.add_argument(
"--total-partitions",
type=int,
default=1,
help="Total number of partitions",
)
parser.add_argument(
"--base-dir",
type=str,
default="server",
help="Base directory for tests relative to this script's parent",
)
parser.add_argument(
"-k",
"--filter",
type=str,
default=None,
help="Pytest filter expression (passed to pytest -k)",
)
parser.add_argument(
"--continue-on-error",
action="store_true",
default=False,
help="Continue running remaining tests even if one fails (for CI consistency; pytest already continues by default)",
)
return parser.parse_args()
def collect_test_items(files, filter_expr=None):
"""Collect test item node IDs from the given files using pytest --collect-only."""
cmd = [sys.executable, "-m", "pytest", "--collect-only", "-q"]
if filter_expr:
cmd.extend(["-k", filter_expr])
cmd.extend(files)
print(f"Collecting tests with command: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
# Check for collection errors
# pytest exit codes:
# 0: success
# 1: tests collected but some had errors during collection
# 2: test execution interrupted
# 3: internal error
# 4: command line usage error
# 5: no tests collected (may be expected with filters)
if result.returncode not in (0, 5):
error_msg = (
f"pytest --collect-only failed with exit code {result.returncode}\n"
f"Command: {' '.join(cmd)}\n"
)
if result.stderr:
error_msg += f"stderr:\n{result.stderr}\n"
if result.stdout:
error_msg += f"stdout:\n{result.stdout}\n"
logger.error(error_msg)
raise RuntimeError(error_msg)
if result.returncode == 5:
print(
"No tests were collected (exit code 5). This may be expected with filters."
)
# Parse the output to extract test node IDs
# pytest -q outputs lines like: test_file.py::TestClass::test_method[param]
test_items = []
for line in result.stdout.strip().split("\n"):
line = line.strip()
# Skip empty lines and summary lines
if line and "::" in line and not line.startswith(("=", "-", " ")):
# Handle lines that might have extra info after the test ID
test_id = line.split()[0] if " " in line else line
if "::" in test_id:
test_items.append(test_id)
print(f"Collected {len(test_items)} test items")
return test_items
def run_pytest(files, filter_expr=None, exitfirst=False):
if not files:
print("No files to run.")
return 0
base_cmd = [sys.executable, "-m", "pytest", "-s", "-v"]
if exitfirst:
base_cmd.append("-x")
# Add pytest -k filter if provided
if filter_expr:
base_cmd.extend(["-k", filter_expr])
max_retries = 6
# retry if the perf assertion failed, for {max_retries} times
for i in range(max_retries + 1):
cmd = list(base_cmd)
if i > 0:
cmd.append("--last-failed")
# Always include files to constrain test discovery scope
# This prevents pytest from scanning the entire rootdir and
# discovering unrelated tests that may have missing dependencies
cmd.extend(files)
if i > 0:
print(
f"Performance assertion failed. Retrying ({i}/{max_retries}) with --last-failed..."
)
print(f"Running command: {' '.join(cmd)}")
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=0,
)
output_bytes = bytearray()
while True:
chunk = process.stdout.read(4096)
if not chunk:
break
sys.stdout.buffer.write(chunk)
sys.stdout.buffer.flush()
output_bytes.extend(chunk)
process.wait()
returncode = process.returncode
if returncode == 0:
return 0
# Exit code 5 means no tests were collected/selected - treat as success
# when using filters, since some partitions may have all tests filtered out
if returncode == 5:
print(
"No tests collected (exit code 5). This is expected when filters "
"deselect all tests in a partition. Treating as success."
)
return 0
# check if the failure is due to an assertion in test_server_utils.py
full_output = output_bytes.decode("utf-8", errors="replace")
is_perf_assertion = (
"multimodal_gen/test/server/test_server_utils.py" in full_output
and "AssertionError" in full_output
)
is_flaky_ci_assertion = (
"SafetensorError" in full_output
or "FileNotFoundError" in full_output
or "TimeoutError" in full_output
)
is_oom_error = (
"out of memory" in full_output.lower()
or "oom killer" in full_output.lower()
)
if not (is_perf_assertion or is_flaky_ci_assertion or is_oom_error):
return returncode
print(f"Max retry exceeded")
return returncode
def main():
args = parse_args()
# 1. resolve base path
current_file_path = Path(__file__).resolve()
test_root_dir = current_file_path.parent
target_dir = test_root_dir / args.base_dir
if not target_dir.exists():
print(f"Error: Target directory {target_dir} does not exist.")
sys.exit(1)
# 2. get files from suite
suite_files_rel = SUITES[args.suite]
suite_files_abs = []
for f_rel in suite_files_rel:
f_abs = target_dir / f_rel
if not f_abs.exists():
msg = f"Test file {f_rel} not found in {target_dir}."
print(f"Warning: {msg} Skipping.")
continue
suite_files_abs.append(str(f_abs))
if not suite_files_abs:
print(f"No valid test files found for suite '{args.suite}'.")
sys.exit(0)
# 3. collect all test items and partition by items (not files)
all_test_items = collect_test_items(suite_files_abs, filter_expr=args.filter)
if not all_test_items:
print(f"No test items found for suite '{args.suite}'.")
sys.exit(0)
# Partition by test items
my_items = [
item
for i, item in enumerate(all_test_items)
if i % args.total_partitions == args.partition_id
]
# Print test info at beginning (similar to test/run_suite.py pretty_print_tests)
partition_info = f"{args.partition_id + 1}/{args.total_partitions} (0-based id={args.partition_id})"
headers = ["Suite", "Partition"]
rows = [[args.suite, partition_info]]
msg = tabulate.tabulate(rows, headers=headers, tablefmt="psql") + "\n"
msg += f"✅ Enabled {len(my_items)} test(s):\n"
for item in my_items:
msg += f" - {item}\n"
print(msg, flush=True)
print(
f"Suite: {args.suite} | Partition: {args.partition_id}/{args.total_partitions}"
)
print(f"Selected {len(suite_files_abs)} files:")
for f in suite_files_abs:
print(f" - {os.path.basename(f)}")
if not my_items:
print("No items assigned to this partition. Exiting success.")
sys.exit(0)
print(f"Running {len(my_items)} items in this shard: {', '.join(my_items)}")
# 4. execute with the specific test items
# Fast-fail: stop on first failure unless --continue-on-error is set
exit_code = run_pytest(my_items, exitfirst=not args.continue_on_error)
# Print tests again at the end for visibility
msg = "\n" + tabulate.tabulate(rows, headers=headers, tablefmt="psql") + "\n"
msg += f"✅ Executed {len(my_items)} test(s):\n"
for item in my_items:
msg += f" - {item}\n"
print(msg, flush=True)
sys.exit(exit_code)
if __name__ == "__main__":
main()
@@ -1,7 +1,7 @@
{
"metadata": {
"model": "Diffusion Server",
"hardware": "CI A2 64GB pool",
"hardware": "Ascend A3",
"description": "Reference numbers captured from the CI diffusion server baseline run"
},
"scenarios": {
@@ -72,69 +72,69 @@
},
"flux_2_image_t2i_2npu": {
"stages_ms": {
"InputValidationStage": 0.06,
"TextEncodingStage": 5628.31,
"InputValidationStage": 0.08,
"TextEncodingStage": 192.4,
"ImageVAEEncodingStage": 0.01,
"LatentPreparationStage": 0.75,
"TimestepPreparationStage": 30.68,
"DenoisingStage": 55002.26,
"DecodingStage": 43.73
"LatentPreparationStage": 0.97,
"TimestepPreparationStage": 34.65,
"DenoisingStage": 45390.41,
"DecodingStage": 5.32
},
"denoise_step_ms": {
"0": 110.35,
"1": 301.82,
"2": 1139.81,
"3": 1114.17,
"4": 1099.34,
"5": 1099.12,
"6": 1100.16,
"7": 1099.67,
"8": 1099.09,
"9": 1089.81,
"10": 1109.73,
"11": 1099.97,
"12": 1100.26,
"13": 1099.67,
"14": 1099.79,
"15": 1099.6,
"16": 1100.16,
"17": 1099.87,
"18": 1100.02,
"19": 1099.34,
"20": 1099.6,
"21": 1099.45,
"22": 1100.2,
"23": 1099.29,
"24": 1098.86,
"25": 1090.38,
"26": 1109.19,
"27": 1099.67,
"28": 1100.06,
"29": 1099.22,
"30": 1100.08,
"31": 1098.86,
"32": 1099.73,
"33": 1099.11,
"34": 1100.13,
"35": 1103.97,
"36": 1095.26,
"37": 1099.38,
"38": 1099.34,
"39": 1099.17,
"40": 1100.08,
"41": 1089.89,
"42": 1106.69,
"43": 1102.57,
"44": 1100.17,
"45": 1099.21,
"46": 1100.42,
"47": 1099.38,
"48": 1099.59,
"49": 1099.47
"0": 84.23,
"1": 80.0,
"2": 874.6,
"3": 918.81,
"4": 900.4,
"5": 910.67,
"6": 903.36,
"7": 904.97,
"8": 906.84,
"9": 906.39,
"10": 904.99,
"11": 909.96,
"12": 901.67,
"13": 908.8,
"14": 902.93,
"15": 906.17,
"16": 906.67,
"17": 905.89,
"18": 906.9,
"19": 907.1,
"20": 905.31,
"21": 907.93,
"22": 903.68,
"23": 904.49,
"24": 905.73,
"25": 907.66,
"26": 906.71,
"27": 912.69,
"28": 901.4,
"29": 909.9,
"30": 901.72,
"31": 904.35,
"32": 905.61,
"33": 905.97,
"34": 906.5,
"35": 921.45,
"36": 892.58,
"37": 909.03,
"38": 903.82,
"39": 906.25,
"40": 905.18,
"41": 905.06,
"42": 906.21,
"43": 911.62,
"44": 901.04,
"45": 909.09,
"46": 904.29,
"47": 905.16,
"48": 907.35,
"49": 905.65
},
"expected_e2e_ms": 64195.08,
"expected_avg_denoise_ms": 1065.0,
"expected_median_denoise_ms": 1099.63
"expected_e2e_ms": 46557.7,
"expected_avg_denoise_ms": 872.7,
"expected_median_denoise_ms": 905.81
},
"wan2_1_t2v_1.3b_1_npu": {
"stages_ms": {
@@ -203,61 +203,61 @@
"expected_median_denoise_ms": 537.54,
"estimated_full_test_time_s": 157.8
},
"wan2_2_t2v_14b_w8a8_8npu": {
"wan2_2_t2v_14b_w8a8_2npu": {
"stages_ms": {
"InputValidationStage": 0.14,
"TextEncodingStage": 3020.73,
"LatentPreparationStage": 0.19,
"TimestepPreparationStage": 5.01,
"DenoisingStage": 82744.33,
"DecodingStage": 932.41,
"InputValidationStage": 0.09,
"TextEncodingStage": 2789.3,
"LatentPreparationStage": 0.28,
"TimestepPreparationStage": 3.19,
"DenoisingStage": 187650.19,
"DecodingStage": 3491.88,
"per_frame_generation": null
},
"denoise_step_ms": {
"0": 1232.32,
"1": 2091.77,
"2": 2097.62,
"3": 2087.53,
"4": 2088.54,
"5": 2087.96,
"6": 2088.28,
"7": 2089.77,
"8": 2101.9,
"9": 2088.73,
"10": 2088.04,
"11": 2087.53,
"12": 2088.89,
"13": 2087.09,
"14": 2088.25,
"15": 2087.96,
"16": 2088.24,
"17": 2088.45,
"18": 2104.7,
"19": 2088.44,
"20": 2087.19,
"21": 2088.19,
"22": 2088.37,
"23": 2087.6,
"24": 2088.13,
"25": 2088.06,
"26": 2126.23,
"27": 2089.92,
"28": 2087.37,
"29": 2089.21,
"30": 2088.29,
"31": 2087.89,
"32": 2073.1,
"33": 2086.71,
"34": 2087.88,
"35": 2088.64,
"36": 2088.1,
"37": 2089.14,
"38": 2087.5,
"39": 2087.86
"0": 1415.84,
"1": 4801.54,
"2": 4781.57,
"3": 4770.19,
"4": 4808.86,
"5": 4737.55,
"6": 4774.02,
"7": 4774.85,
"8": 4773.25,
"9": 4775.33,
"10": 4771.31,
"11": 4773.07,
"12": 4773.18,
"13": 4772.77,
"14": 4773.68,
"15": 4771.91,
"16": 4776.16,
"17": 4773.88,
"18": 4769.87,
"19": 4772.1,
"20": 4774.55,
"21": 4772.54,
"22": 4772.39,
"23": 4776.14,
"24": 4772.84,
"25": 4772.82,
"26": 4789.34,
"27": 4792.58,
"28": 4807.52,
"29": 4740.66,
"30": 4773.46,
"31": 4774.19,
"32": 4772.8,
"33": 4774.84,
"34": 4772.52,
"35": 4773.77,
"36": 4775.43,
"37": 4771.58,
"38": 4772.68,
"39": 4769.1
},
"expected_e2e_ms": 86719.57,
"expected_avg_denoise_ms": 2068.43,
"expected_median_denoise_ms": 2088.21
"expected_e2e_ms": 193947.19,
"expected_avg_denoise_ms": 4691.07,
"expected_median_denoise_ms": 4773.22
},
"qwen_image_t2i_2npu": {
"stages_ms": {
@@ -1,31 +0,0 @@
"""
Config-driven diffusion performance test with pytest parametrization.
If the actual run is significantly better than the baseline, the improved cases with their updated baseline will be printed
"""
from __future__ import annotations
import pytest
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.test.server.ascend.testcase_configs_npu import (
EIGHT_NPU_CASES,
)
from sglang.multimodal_gen.test.server.test_server_common import ( # noqa: F401
DiffusionServerBase,
diffusion_server,
)
from sglang.multimodal_gen.test.server.testcase_configs import DiffusionTestCase
logger = init_logger(__name__)
class TestDiffusionServerEightNpu(DiffusionServerBase):
"""Performance tests for 8-NPU diffusion cases."""
@pytest.fixture(params=EIGHT_NPU_CASES, ids=lambda c: c.id)
def case(self, request) -> DiffusionTestCase:
"""Provide a DiffusionTestCase for each 8-NPU test."""
return request.param
@@ -1,3 +1,5 @@
import os
from sglang.multimodal_gen.test.server.testcase_configs import (
T2V_PROMPT,
DiffusionSamplingParams,
@@ -6,12 +8,31 @@ from sglang.multimodal_gen.test.server.testcase_configs import (
T2I_sampling_params,
)
MODEL_WEIGHTS_DIR = "/root/.cache/modelscope/hub/models/"
FLUX_1_DEV_WEIGHTS_PATH = os.path.join(
MODEL_WEIGHTS_DIR, "black-forest-labs/FLUX.1-dev"
)
FLUX_2_DEV_WEIGHTS_PATH = os.path.join(
MODEL_WEIGHTS_DIR, "black-forest-labs/FLUX.2-dev"
)
QWEN_IMAGE_WEIGHTS_PATH = os.path.join(MODEL_WEIGHTS_DIR, "Qwen/Qwen-Image")
WAN2_1_T2V_1_3B_DIFFUSERS_WEIGHTS_PATH = os.path.join(
MODEL_WEIGHTS_DIR, "Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
)
WAN2_2_T2V_A14B_DIFFUSERS_W8A8_WEIGHTS_PATH = os.path.join(
MODEL_WEIGHTS_DIR, "Eco-Tech/Wan2.2-T2V-A14B-Diffusers-w8a8"
)
EXTRAS_DISABLE_WARMUP = ["--server-warmup", "false"]
ONE_NPU_CASES: list[DiffusionTestCase] = [
# === Text to Image (T2I) ===
DiffusionTestCase(
"flux_image_t2i_npu",
DiffusionServerArgs(
model_path="/root/.cache/modelscope/hub/models/black-forest-labs/FLUX.1-dev",
model_path=FLUX_1_DEV_WEIGHTS_PATH,
extras=EXTRAS_DISABLE_WARMUP,
),
T2I_sampling_params,
run_consistency_check=False,
@@ -20,7 +41,8 @@ ONE_NPU_CASES: list[DiffusionTestCase] = [
DiffusionTestCase(
"wan2_1_t2v_1.3b_1_npu",
DiffusionServerArgs(
model_path="/root/.cache/modelscope/hub/models/Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
model_path=WAN2_1_T2V_1_3B_DIFFUSERS_WEIGHTS_PATH,
extras=EXTRAS_DISABLE_WARMUP,
),
DiffusionSamplingParams(
prompt=T2V_PROMPT,
@@ -34,9 +56,10 @@ TWO_NPU_CASES: list[DiffusionTestCase] = [
DiffusionTestCase(
"flux_2_image_t2i_2npu",
DiffusionServerArgs(
model_path="/root/.cache/modelscope/hub/models/black-forest-labs/FLUX.2-dev",
model_path=FLUX_2_DEV_WEIGHTS_PATH,
num_gpus=2,
tp_size=2,
extras=EXTRAS_DISABLE_WARMUP,
),
T2I_sampling_params,
run_consistency_check=False,
@@ -44,26 +67,25 @@ TWO_NPU_CASES: list[DiffusionTestCase] = [
DiffusionTestCase(
"qwen_image_t2i_2npu",
DiffusionServerArgs(
model_path="/root/.cache/modelscope/hub/models/Qwen/Qwen-Image",
model_path=QWEN_IMAGE_WEIGHTS_PATH,
num_gpus=2,
# test ring attn
ulysses_degree=1,
ring_degree=2,
extras=EXTRAS_DISABLE_WARMUP,
),
T2I_sampling_params,
run_consistency_check=False,
),
]
EIGHT_NPU_CASES: list[DiffusionTestCase] = [
# === Text to Video (T2V) ===
DiffusionTestCase(
"wan2_2_t2v_14b_w8a8_8npu",
"wan2_2_t2v_14b_w8a8_2npu",
DiffusionServerArgs(
model_path="/root/.cache/modelscope/hub/models/Eco-Tech/Wan2.2-T2V-A14B-Diffusers-w8a8",
num_gpus=8,
tp_size=4,
model_path=WAN2_2_T2V_A14B_DIFFUSERS_W8A8_WEIGHTS_PATH,
num_gpus=2,
tp_size=1,
ulysses_degree=2,
extras=EXTRAS_DISABLE_WARMUP,
),
DiffusionSamplingParams(
prompt=T2V_PROMPT,
@@ -71,3 +93,32 @@ EIGHT_NPU_CASES: list[DiffusionTestCase] = [
run_consistency_check=False,
),
]
DEFAULT_EST_TIME_SECONDS = 300.0
STARTUP_OVERHEAD_SECONDS = 120.0
DEFAULT_STANDALONE_EST_TIME_SECONDS = 300.0
SUITES = {
"1-npu": [
"ascend/test_server_1_npu.py",
# add new 1-npu test files here
],
"2-npu": [
"ascend/test_server_2_npu.py",
# add new 2-npu test files here
],
}
PARAMETRIZED_CASE_GROUPS = {
"1-npu": [
("ascend/test_server_1_npu.py", ONE_NPU_CASES),
],
"2-npu": [
("ascend/test_server_2_npu.py", TWO_NPU_CASES),
],
}
FILE_SUITES = {}
STANDALONE_FILES = {}
COMPONENT_ACCURACY_SUITES = {}
_UPDATE_WEIGHTS_FROM_DISK_TEST_FILE = None
@@ -787,3 +787,99 @@ if not current_platform.is_hip():
ONE_GPU_CASES += ONE_GPU_MODELOPT_FP8_CASES
TWO_GPU_CASES = _with_default_num_gpus(TWO_GPU_CASES, 2)
def _discover_unit_tests() -> list[str]:
unit_dir = Path(__file__).resolve().parent.parent / "unit"
if not unit_dir.is_dir():
return []
return sorted(
f"../unit/{f.name}" for f in unit_dir.glob("test_*.py") if f.is_file()
)
FILE_SUITES = {
"unit": _discover_unit_tests(),
"component-accuracy": [
"test_component_accuracy_1_gpu.py",
"test_component_accuracy_2_gpu.py",
],
"component-accuracy-1-gpu": [
"test_component_accuracy_1_gpu.py",
],
"component-accuracy-2-gpu": [
"test_component_accuracy_2_gpu.py",
],
"1-gpu-b200": [
"test_server_b200.py",
],
}
PARAMETRIZED_CASE_GROUPS = {
"1-gpu": [
("test_server_1_gpu.py", ONE_GPU_CASES),
],
"2-gpu": [
("test_server_2_gpu.py", TWO_GPU_CASES),
],
}
STANDALONE_FILES = {
"1-gpu": [
"../cli/test_generate_t2i_perf.py",
# Temporarily disabled: 24 timeout failures since 2026-04-09 across
# multimodal-gen-test-1-gpu. Re-enable after the flakiness is fixed.
# "test_update_weights_from_disk.py",
],
"2-gpu": [
"test_disagg_server.py",
],
}
# New standalone files may omit an estimate once to learn the real CI runtime.
# CI will use a fallback estimate for sharding, run the test, then print a
# measured value that must be copied into STANDALONE_FILE_EST_TIMES.
STANDALONE_FILE_EST_TIMES = {
"1-gpu": {
"../cli/test_generate_t2i_perf.py": 240.0,
# See STANDALONE_FILES note above — temporarily disabled.
# "test_update_weights_from_disk.py": 480.0,
},
"2-gpu": {
# Two disagg clusters × (~3 min startup + ~1 min generate) ≈ 8 min.
# Raise if CI reports a higher measured time.
"test_disagg_server.py": 600.0,
},
}
# Backward-compatible suite view for scripts that still operate on file lists.
SUITES = {
**FILE_SUITES,
**{
suite: [filename for filename, _ in case_groups]
+ STANDALONE_FILES.get(suite, [])
for suite, case_groups in PARAMETRIZED_CASE_GROUPS.items()
},
}
STRICT_SUITES = {"unit"}
COMPONENT_ACCURACY_SUITES = {
"component-accuracy",
"component-accuracy-1-gpu",
"component-accuracy-2-gpu",
}
COMPONENT_ACCURACY_FILE_NUM_GPUS = {
"test_component_accuracy_1_gpu.py": 1,
"test_component_accuracy_2_gpu.py": 2,
}
DEFAULT_EST_TIME_SECONDS = 300.0
STARTUP_OVERHEAD_SECONDS = 120.0
DEFAULT_STANDALONE_EST_TIME_SECONDS = 300.0
_UPDATE_WEIGHTS_FROM_DISK_TEST_FILE = "test_update_weights_from_disk.py"
_UPDATE_WEIGHTS_MODEL_PAIR_ENV = "SGLANG_MMGEN_UPDATE_WEIGHTS_PAIR"
_UPDATE_WEIGHTS_MODEL_PAIR_IDS = (
"FLUX.2-klein-base-4B",
"Qwen-Image",
)
@@ -45,10 +45,18 @@ SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE = (
SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE = (
f"{SGL_TEST_FILES_CONSISTENCY_GT_ROOT}/sglang_generated"
)
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE_ASCEND = (
f"{SGL_TEST_FILES_CONSISTENCY_GT_ROOT}/official_generated/ascend"
)
SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE_ASCEND = (
f"{SGL_TEST_FILES_CONSISTENCY_GT_ROOT}/sglang_generated/ascend"
)
SGL_TEST_FILES_CONSISTENCY_GT_BASE = SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE
SGL_TEST_FILES_CONSISTENCY_GT_BASES = (
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE,
SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE,
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_BASE_ASCEND,
SGL_TEST_FILES_SGLANG_CONSISTENCY_GT_BASE_ASCEND,
)
# LTX cases listed here compare against official-generated GT.
SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_CASES = frozenset(