[diffusion] CI: add 5090 job (#29791)
This commit is contained in:
@@ -42,7 +42,7 @@ def _all_cases() -> list[DiffusionTestCase]:
|
||||
def _baseline_path() -> Path:
|
||||
import sglang.multimodal_gen.test.server.testcase_configs as cfg
|
||||
|
||||
return Path(cfg.__file__).with_name("perf_baselines.json")
|
||||
return cfg.get_perf_baseline_path()
|
||||
|
||||
|
||||
def _openai_client(port: int) -> OpenAI:
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"cases": {
|
||||
"zimage_image_t2i": {
|
||||
"clip_threshold": 0.92,
|
||||
"ssim_threshold": 0.86,
|
||||
"psnr_threshold": 19.5,
|
||||
"mean_abs_diff_threshold": 8.5
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cases": {}
|
||||
}
|
||||
@@ -849,6 +849,80 @@ ONE_GPU_CASES += ONE_GPU_MODELOPT_FP8_CASES
|
||||
TWO_GPU_CASES = _with_default_num_gpus(TWO_GPU_CASES, 2)
|
||||
|
||||
|
||||
ONE_GPU_5090_PERF_CASE_IDS = frozenset(
|
||||
{
|
||||
"zimage_image_t2i",
|
||||
"flux_2_klein_base_image_t2i",
|
||||
"wan2_1_t2v_1.3b",
|
||||
}
|
||||
)
|
||||
ONE_GPU_5090_SKIP_CONSISTENCY_CASE_IDS = frozenset(
|
||||
{
|
||||
"turbo_wan2_1_t2v_1.3b",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _select_5090_canary_cases(case_ids: tuple[str, ...]) -> list[DiffusionTestCase]:
|
||||
cases_by_id = {case.id: case for case in ONE_GPU_CASES}
|
||||
missing = [case_id for case_id in case_ids if case_id not in cases_by_id]
|
||||
if missing:
|
||||
raise RuntimeError(f"Unknown 5090 diffusion canary case(s): {missing}")
|
||||
|
||||
return [
|
||||
replace(
|
||||
cases_by_id[case_id],
|
||||
run_perf_check=case_id in ONE_GPU_5090_PERF_CASE_IDS,
|
||||
run_consistency_check=(
|
||||
cases_by_id[case_id].run_consistency_check
|
||||
and case_id not in ONE_GPU_5090_SKIP_CONSISTENCY_CASE_IDS
|
||||
),
|
||||
)
|
||||
for case_id in case_ids
|
||||
]
|
||||
|
||||
|
||||
def _make_5090_flux_layerwise_cpu_offload_case() -> DiffusionTestCase:
|
||||
base_case = next(case for case in ONE_GPU_CASES if case.id == "flux_image_t2i")
|
||||
|
||||
return replace(
|
||||
base_case,
|
||||
id="flux_image_t2i_layerwise_cpu_offload_5090",
|
||||
server_args=replace(
|
||||
base_case.server_args,
|
||||
dit_layerwise_offload=True,
|
||||
dit_offload_prefetch_size=5,
|
||||
text_encoder_cpu_offload=True,
|
||||
extras=[
|
||||
*base_case.server_args.extras,
|
||||
"--dit-cpu-offload",
|
||||
"--pin-cpu-memory",
|
||||
],
|
||||
),
|
||||
sampling_params=replace(
|
||||
T2I_sampling_params,
|
||||
output_size="512x512",
|
||||
extras={"num_inference_steps": 4, "seed": 0},
|
||||
),
|
||||
run_perf_check=False,
|
||||
run_consistency_check=False,
|
||||
run_component_accuracy_check=False,
|
||||
run_models_api_check=False,
|
||||
run_t2v_input_reference_check=False,
|
||||
)
|
||||
|
||||
|
||||
ONE_GPU_5090_CASES = _select_5090_canary_cases(
|
||||
(
|
||||
"zimage_image_t2i",
|
||||
"flux_2_klein_base_image_t2i",
|
||||
"wan2_1_t2v_1.3b",
|
||||
"turbo_wan2_1_t2v_1.3b",
|
||||
)
|
||||
)
|
||||
ONE_GPU_5090_CASES.append(_make_5090_flux_layerwise_cpu_offload_case())
|
||||
|
||||
|
||||
def _discover_unit_tests() -> list[str]:
|
||||
unit_dir = Path(__file__).resolve().parent.parent / "unit"
|
||||
if not unit_dir.is_dir():
|
||||
@@ -879,6 +953,9 @@ PARAMETRIZED_CASE_GROUPS = {
|
||||
"1-gpu": [
|
||||
("test_server_1_gpu.py", ONE_GPU_CASES),
|
||||
],
|
||||
"1-gpu-5090": [
|
||||
("test_server_1_gpu_5090.py", ONE_GPU_5090_CASES),
|
||||
],
|
||||
"2-gpu": [
|
||||
("test_server_2_gpu.py", TWO_GPU_CASES),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"metadata": {
|
||||
"model": "Diffusion Server",
|
||||
"hardware": "CI RTX 5090 pool",
|
||||
"description": "Reference numbers captured from real 5090 CI runner history (PR 29791 run 28492141274, job 84450974283).",
|
||||
"last_updated": "2026-07-01"
|
||||
},
|
||||
"tolerances": {
|
||||
"long_term": {
|
||||
"e2e": 0.15,
|
||||
"denoise_stage": 0.1,
|
||||
"non_denoise_stage": 0.5,
|
||||
"denoise_step": 0.25,
|
||||
"denoise_agg": 0.15
|
||||
},
|
||||
"pr_test": {
|
||||
"e2e": 0.25,
|
||||
"denoise_stage": 0.25,
|
||||
"non_denoise_stage": 0.8,
|
||||
"denoise_step": 0.3,
|
||||
"denoise_agg": 0.2
|
||||
}
|
||||
},
|
||||
"improvement_reporting": {
|
||||
"threshold": 0.2
|
||||
},
|
||||
"sampling": {
|
||||
"step_fractions": [
|
||||
0.0,
|
||||
0.2,
|
||||
0.4,
|
||||
0.6,
|
||||
0.8,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"scenarios": {
|
||||
"flux_2_klein_base_image_t2i": {
|
||||
"stages_ms": {
|
||||
"DecodingStage": 18.19,
|
||||
"DenoisingStage": 18213.48,
|
||||
"ImageVAEEncodingStage": 0.01,
|
||||
"InputValidationStage": 0.08,
|
||||
"LatentPreparationStage": 4.87,
|
||||
"TextEncodingStage": 104.46,
|
||||
"TimestepPreparationStage": 142.18
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 219.58,
|
||||
"10": 359.79,
|
||||
"20": 361.55,
|
||||
"29": 361.99,
|
||||
"39": 362.57,
|
||||
"49": 362.89
|
||||
},
|
||||
"expected_e2e_ms": 18641.09,
|
||||
"expected_avg_denoise_ms": 357.93,
|
||||
"expected_median_denoise_ms": 361.8,
|
||||
"estimated_full_test_time_s": 94.0
|
||||
},
|
||||
"wan2_1_t2v_1.3b": {
|
||||
"stages_ms": {
|
||||
"DecodingStage": 1202.43,
|
||||
"DenoisingStage": 21452.59,
|
||||
"InputValidationStage": 0.09,
|
||||
"LatentPreparationStage": 0.23,
|
||||
"TextEncodingStage": 1210.22,
|
||||
"TimestepPreparationStage": 3.91
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 480.39,
|
||||
"10": 426.11,
|
||||
"20": 427.32,
|
||||
"29": 428.09,
|
||||
"39": 428.04,
|
||||
"49": 422.31
|
||||
},
|
||||
"expected_e2e_ms": 23877.73,
|
||||
"expected_avg_denoise_ms": 428.74,
|
||||
"expected_median_denoise_ms": 427.94,
|
||||
"estimated_full_test_time_s": 160.9
|
||||
},
|
||||
"zimage_image_t2i": {
|
||||
"stages_ms": {
|
||||
"DecodingStage": 7.11,
|
||||
"DenoisingStage": 2229.48,
|
||||
"InputValidationStage": 0.04,
|
||||
"LatentPreparationStage": 0.17,
|
||||
"TextEncodingStage": 252.73,
|
||||
"TimestepPreparationStage": 38.57
|
||||
},
|
||||
"denoise_step_ms": {
|
||||
"0": 39.34,
|
||||
"2": 277.35,
|
||||
"3": 267.94,
|
||||
"5": 279.01,
|
||||
"6": 273.01,
|
||||
"8": 275.12
|
||||
},
|
||||
"expected_e2e_ms": 2533.95,
|
||||
"expected_avg_denoise_ms": 246.97,
|
||||
"expected_median_denoise_ms": 273.01,
|
||||
"estimated_full_test_time_s": 329.8
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"metadata": {
|
||||
"model": "Diffusion Server",
|
||||
"hardware": "CI B200 pool",
|
||||
"description": "Reference estimates for B200-only diffusion cases, split out from the shared diffusion baseline file.",
|
||||
"last_updated": "2026-07-01"
|
||||
},
|
||||
"tolerances": {
|
||||
"long_term": {
|
||||
"e2e": 0.15,
|
||||
"denoise_stage": 0.1,
|
||||
"non_denoise_stage": 0.5,
|
||||
"denoise_step": 0.25,
|
||||
"denoise_agg": 0.15
|
||||
},
|
||||
"pr_test": {
|
||||
"e2e": 0.25,
|
||||
"denoise_stage": 0.25,
|
||||
"non_denoise_stage": 0.8,
|
||||
"denoise_step": 0.3,
|
||||
"denoise_agg": 0.2
|
||||
}
|
||||
},
|
||||
"improvement_reporting": {
|
||||
"threshold": 0.2
|
||||
},
|
||||
"sampling": {
|
||||
"step_fractions": [
|
||||
0.0,
|
||||
0.2,
|
||||
0.4,
|
||||
0.6,
|
||||
0.8,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"scenarios": {
|
||||
"flux1_modelopt_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 71.2
|
||||
},
|
||||
"flux2_modelopt_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 592.3
|
||||
},
|
||||
"qwen_image_2512_modelopt_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 120.0
|
||||
},
|
||||
"wan22_modelopt_nvfp4_t2v": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 181.8
|
||||
},
|
||||
"ideogram4_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 300.0
|
||||
}
|
||||
}
|
||||
}
|
||||
-32
@@ -2829,38 +2829,6 @@
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 73.7
|
||||
},
|
||||
"flux1_modelopt_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 71.2
|
||||
},
|
||||
"flux2_modelopt_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 592.3
|
||||
},
|
||||
"qwen_image_2512_modelopt_nvfp4_t2i": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 120.0
|
||||
},
|
||||
"wan22_modelopt_nvfp4_t2v": {
|
||||
"stages_ms": {},
|
||||
"denoise_step_ms": {},
|
||||
"expected_e2e_ms": 0.0,
|
||||
"expected_avg_denoise_ms": 0.0,
|
||||
"expected_median_denoise_ms": 0.0,
|
||||
"estimated_full_test_time_s": 181.8
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Config-driven diffusion canary tests for the 1-GPU 5090 PR runner.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sglang.multimodal_gen.test.server.common.case_fixtures import (
|
||||
diffusion_case_fixture,
|
||||
)
|
||||
from sglang.multimodal_gen.test.server.gpu_cases import ONE_GPU_5090_CASES
|
||||
from sglang.multimodal_gen.test.server.test_server_common import ( # noqa: F401
|
||||
DiffusionServerBase,
|
||||
diffusion_server,
|
||||
)
|
||||
|
||||
|
||||
class TestDiffusionServerOneGpu5090(DiffusionServerBase):
|
||||
"""Canary tests for lightweight 1-GPU diffusion cases on 5090."""
|
||||
|
||||
case = diffusion_case_fixture(ONE_GPU_5090_CASES)
|
||||
@@ -41,6 +41,7 @@ from sglang.multimodal_gen.test.server.testcase_configs import (
|
||||
PerformanceSummary,
|
||||
ScenarioConfig,
|
||||
get_model_task_type_for_server_args,
|
||||
get_perf_baseline_path,
|
||||
)
|
||||
from sglang.multimodal_gen.test.test_utils import (
|
||||
SGL_TEST_FILES_CI_DATA_REVISION,
|
||||
@@ -50,6 +51,7 @@ from sglang.multimodal_gen.test.test_utils import (
|
||||
extract_key_frames_from_video,
|
||||
get_consistency_gt_candidates,
|
||||
get_consistency_gt_remote_files,
|
||||
get_consistency_threshold_path,
|
||||
get_consistency_thresholds,
|
||||
get_dynamic_server_port,
|
||||
gt_exists,
|
||||
@@ -232,7 +234,7 @@ def diffusion_server(case: DiffusionTestCase) -> ServerContext:
|
||||
logger.error(
|
||||
f'\n{"=" * 60}\n'
|
||||
f'Add "estimated_full_test_time_s" to scenario "{case.id}":\n\n'
|
||||
f"File: python/sglang/multimodal_gen/test/server/perf_baselines.json\n\n"
|
||||
f"File: {get_perf_baseline_path()}\n\n"
|
||||
f' "{case.id}": {{\n'
|
||||
f" ...\n"
|
||||
f' "estimated_full_test_time_s": {_measured_full_time:.1f}\n'
|
||||
@@ -441,7 +443,7 @@ class DiffusionServerBase:
|
||||
self._dump_baseline_for_testcase(case, summary, missing_scenario)
|
||||
if missing_scenario:
|
||||
pytest.fail(
|
||||
f"Testcase '{case.id}' not found in perf_baselines.json"
|
||||
f"Testcase '{case.id}' not found in {get_perf_baseline_path()}"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -552,7 +554,7 @@ class DiffusionServerBase:
|
||||
)
|
||||
action = "add" if missing_scenario else "update"
|
||||
output = f"""
|
||||
{action} this baseline in the "scenarios" section of perf_baselines.json:
|
||||
{action} this baseline in the "scenarios" section of {get_perf_baseline_path()}:
|
||||
|
||||
"{case.id}": {json.dumps(baseline, indent=4)}
|
||||
|
||||
@@ -607,10 +609,10 @@ Add the expected file(s) to sgl-project/ci-data in diffusion-ci/consistency_gt/s
|
||||
|
||||
For this case, expected file(s): {names}
|
||||
|
||||
Repository: https://github.com/sgl-project/ci-data (path: diffusion-ci/consistency_gt/sglang_generated/)
|
||||
Repository: https://github.com/sgl-project/ci-data (path: diffusion-ci/consistency_gt/sglang_generated/, with optional platform subdirectories such as 5090/)
|
||||
Pinned revision used by this check: {SGL_TEST_FILES_CI_DATA_REVISION}
|
||||
|
||||
(Optional) Per-case override in consistency_threshold.json:
|
||||
(Optional) Per-case override in {get_consistency_threshold_path()}:
|
||||
"cases": {{
|
||||
"{case.id}": {{
|
||||
"clip_threshold": 0.92,
|
||||
|
||||
@@ -12,7 +12,7 @@ pytest python/sglang/multimodal_gen/test/server/test_server_1_gpu.py -k qwen_ima
|
||||
To add a new testcase:
|
||||
1. add your testcase with case-id: `my_new_test_case_id` to `ONE_GPU_CASES`, `ONE_GPU_MODELOPT_FP8_CASES`, `ONE_GPU_B200_CASES`, or `TWO_GPU_CASES`
|
||||
2. run `SGLANG_GEN_BASELINE=1 pytest -s python/sglang/multimodal_gen/test/server/ -k my_new_test_case_id`
|
||||
3. insert or override the corresponding scenario in `scenarios` section of perf_baselines.json with the output baseline of step-2
|
||||
3. insert or override the corresponding scenario in the platform JSON under `perf_baselines/`
|
||||
|
||||
|
||||
"""
|
||||
@@ -33,6 +33,7 @@ from sglang.multimodal_gen.registry import (
|
||||
get_model_info,
|
||||
get_pipeline_config_classes,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.perf_logger import RequestPerfRecord
|
||||
|
||||
|
||||
@@ -657,6 +658,57 @@ MODELOPT_WAN22_NVFP4_MODEL = "nvidia/Wan2.2-T2V-A14B-Diffusers-NVFP4"
|
||||
MODELOPT_NVFP4_B200_ENV_VARS = {}
|
||||
MODELOPT_WAN22_NVFP4_B200_ENV_VARS = {}
|
||||
|
||||
PERF_BASELINE_PLATFORM_ENV = "SGLANG_DIFFUSION_PERF_BASELINE_PLATFORM"
|
||||
PERF_BASELINE_DIR = Path(__file__).with_name("perf_baselines")
|
||||
PERF_BASELINE_FILE_BY_PLATFORM = {
|
||||
"h100": "h100.json",
|
||||
"b200": "b200.json",
|
||||
"5090": "5090.json",
|
||||
}
|
||||
PERF_BASELINE_PLATFORM_ALIASES = {
|
||||
"sm90": "h100",
|
||||
"hopper": "h100",
|
||||
"h100": "h100",
|
||||
"sm100": "b200",
|
||||
"blackwell": "b200",
|
||||
"b200": "b200",
|
||||
"sm120": "5090",
|
||||
"rtx5090": "5090",
|
||||
"5090": "5090",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_perf_baseline_platform(platform: str) -> str:
|
||||
normalized = platform.strip().lower().replace("_", "-")
|
||||
normalized = normalized.replace("-", "")
|
||||
if normalized not in PERF_BASELINE_PLATFORM_ALIASES:
|
||||
valid = ", ".join(sorted(PERF_BASELINE_FILE_BY_PLATFORM))
|
||||
raise ValueError(
|
||||
f"Invalid diffusion perf baseline platform {platform!r}. "
|
||||
f"Expected one of: {valid}"
|
||||
)
|
||||
return PERF_BASELINE_PLATFORM_ALIASES[normalized]
|
||||
|
||||
|
||||
def get_perf_baseline_platform() -> str:
|
||||
override = os.getenv(PERF_BASELINE_PLATFORM_ENV)
|
||||
if override:
|
||||
return _normalize_perf_baseline_platform(override)
|
||||
if current_platform.is_sm120():
|
||||
return "5090"
|
||||
if current_platform.is_blackwell():
|
||||
return "b200"
|
||||
return "h100"
|
||||
|
||||
|
||||
def get_perf_baseline_path(platform: str | None = None) -> Path:
|
||||
baseline_platform = (
|
||||
_normalize_perf_baseline_platform(platform)
|
||||
if platform is not None
|
||||
else get_perf_baseline_platform()
|
||||
)
|
||||
return PERF_BASELINE_DIR / PERF_BASELINE_FILE_BY_PLATFORM[baseline_platform]
|
||||
|
||||
|
||||
def _make_modelopt_ci_case(
|
||||
case_id: str,
|
||||
@@ -694,7 +746,7 @@ def _with_default_num_gpus(
|
||||
|
||||
# Load global configuration
|
||||
BASELINE_CONFIG = (
|
||||
BaselineConfig.load(Path(__file__).with_name("perf_baselines.json"))
|
||||
BaselineConfig.load(get_perf_baseline_path())
|
||||
.update(Path(__file__).parent / "ascend" / "perf_baselines_npu.json")
|
||||
.update(Path(__file__).parent / "musa" / "perf_baselines_musa.json")
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "4a271ef34602043f19d253f0d30a5f653fe11325"
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "702d939e23f17b42183329dace60f221d2587056"
|
||||
|
||||
if current_platform.is_npu():
|
||||
SGL_TEST_FILES_CI_DATA_REVISION = "670d66a8a290b62c0c3c077b3e9b0f4a4d9a44e7"
|
||||
@@ -79,9 +79,26 @@ SGL_TEST_FILES_OFFICIAL_CONSISTENCY_GT_CASES = frozenset(
|
||||
}
|
||||
)
|
||||
|
||||
CONSISTENCY_THRESHOLD_JSON_PATH = (
|
||||
Path(__file__).resolve().parent / "server" / "consistency_threshold.json"
|
||||
CONSISTENCY_PLATFORM_ENV = "SGLANG_DIFFUSION_CONSISTENCY_PLATFORM"
|
||||
CONSISTENCY_THRESHOLD_DIR = (
|
||||
Path(__file__).resolve().parent / "server" / "consistency_thresholds"
|
||||
)
|
||||
CONSISTENCY_THRESHOLD_FILE_BY_PLATFORM = {
|
||||
"h100": "h100.json",
|
||||
"b200": "b200.json",
|
||||
"5090": "5090.json",
|
||||
}
|
||||
CONSISTENCY_PLATFORM_ALIASES = {
|
||||
"sm90": "h100",
|
||||
"hopper": "h100",
|
||||
"h100": "h100",
|
||||
"sm100": "b200",
|
||||
"blackwell": "b200",
|
||||
"b200": "b200",
|
||||
"sm120": "5090",
|
||||
"rtx5090": "5090",
|
||||
"5090": "5090",
|
||||
}
|
||||
CLIP_MODEL_NAME = "openai/clip-vit-large-patch14"
|
||||
DEFAULT_CLIP_THRESHOLD_IMAGE = 0.92
|
||||
DEFAULT_CLIP_THRESHOLD_VIDEO = 0.90
|
||||
@@ -694,14 +711,74 @@ def validate_video_file(
|
||||
), f"Video height mismatch: expected {expected_height}, got {actual_height}"
|
||||
|
||||
|
||||
def _load_threshold_json() -> dict[str, Any]:
|
||||
"""Load consistency_threshold.json; returns {} if missing."""
|
||||
if not CONSISTENCY_THRESHOLD_JSON_PATH.exists():
|
||||
def _normalize_consistency_platform(platform: str) -> str:
|
||||
normalized = platform.strip().lower().replace("_", "-")
|
||||
normalized = normalized.replace("-", "")
|
||||
if normalized not in CONSISTENCY_PLATFORM_ALIASES:
|
||||
valid = ", ".join(sorted(CONSISTENCY_THRESHOLD_FILE_BY_PLATFORM))
|
||||
raise ValueError(
|
||||
f"Invalid diffusion consistency platform {platform!r}. "
|
||||
f"Expected one of: {valid}"
|
||||
)
|
||||
return CONSISTENCY_PLATFORM_ALIASES[normalized]
|
||||
|
||||
|
||||
def get_consistency_platform() -> str:
|
||||
override = os.getenv(CONSISTENCY_PLATFORM_ENV)
|
||||
if override:
|
||||
return _normalize_consistency_platform(override)
|
||||
if current_platform.is_sm120():
|
||||
return "5090"
|
||||
if current_platform.is_blackwell():
|
||||
return "b200"
|
||||
return "h100"
|
||||
|
||||
|
||||
def get_consistency_threshold_path(platform: str | None = None) -> Path:
|
||||
threshold_platform = (
|
||||
_normalize_consistency_platform(platform)
|
||||
if platform is not None
|
||||
else get_consistency_platform()
|
||||
)
|
||||
return (
|
||||
CONSISTENCY_THRESHOLD_DIR
|
||||
/ CONSISTENCY_THRESHOLD_FILE_BY_PLATFORM[threshold_platform]
|
||||
)
|
||||
|
||||
|
||||
def _load_threshold_file(path: Path) -> dict[str, Any]:
|
||||
if not path.exists():
|
||||
return {}
|
||||
with CONSISTENCY_THRESHOLD_JSON_PATH.open("r", encoding="utf-8") as f:
|
||||
with path.open("r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _merge_threshold_metadata(
|
||||
base: dict[str, Any], override: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
merged = dict(base)
|
||||
if "cases" in base or "cases" in override:
|
||||
merged["cases"] = {
|
||||
**base.get("cases", {}),
|
||||
**override.get("cases", {}),
|
||||
}
|
||||
for key, value in override.items():
|
||||
if key != "cases":
|
||||
merged[key] = value
|
||||
return merged
|
||||
|
||||
|
||||
def _load_threshold_json() -> dict[str, Any]:
|
||||
metadata = _load_threshold_file(get_consistency_threshold_path("h100"))
|
||||
platform = get_consistency_platform()
|
||||
if platform == "h100":
|
||||
return metadata
|
||||
return _merge_threshold_metadata(
|
||||
metadata,
|
||||
_load_threshold_file(get_consistency_threshold_path(platform)),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConsistencyThresholds:
|
||||
clip_threshold: float
|
||||
@@ -958,10 +1035,9 @@ def _consistency_gt_filenames(
|
||||
return [f"{case_id}_{n}gpu.{ext}"]
|
||||
|
||||
|
||||
def get_consistency_gt_candidates(
|
||||
def _base_consistency_gt_candidates(
|
||||
case_id: str, num_gpus: int, is_video: bool, output_format: str | None = None
|
||||
) -> list[str]:
|
||||
"""Return candidate GT filenames for local consistency data."""
|
||||
n = num_gpus
|
||||
if is_video:
|
||||
return [
|
||||
@@ -975,6 +1051,31 @@ def get_consistency_gt_candidates(
|
||||
return [f"{base}.{e}" for e in exts]
|
||||
|
||||
|
||||
def get_consistency_gt_candidate_sets(
|
||||
case_id: str, num_gpus: int, is_video: bool, output_format: str | None = None
|
||||
) -> list[list[str]]:
|
||||
candidates = _base_consistency_gt_candidates(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
platform = get_consistency_platform()
|
||||
if platform == "h100":
|
||||
return [candidates]
|
||||
return [[f"{platform}/{candidate}" for candidate in candidates], candidates]
|
||||
|
||||
|
||||
def get_consistency_gt_candidates(
|
||||
case_id: str, num_gpus: int, is_video: bool, output_format: str | None = None
|
||||
) -> list[str]:
|
||||
"""Return candidate GT filenames for local consistency data."""
|
||||
return [
|
||||
candidate
|
||||
for candidate_set in get_consistency_gt_candidate_sets(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
for candidate in candidate_set
|
||||
]
|
||||
|
||||
|
||||
def get_consistency_gt_remote_files(
|
||||
case_id: str, num_gpus: int, is_video: bool, output_format: str | None = None
|
||||
) -> list[tuple[str, str]]:
|
||||
@@ -1003,6 +1104,21 @@ def _remote_consistency_gt_candidates(
|
||||
return [(filename, f"{base_url}/{filename}") for filename in filenames]
|
||||
|
||||
|
||||
def _remote_consistency_gt_candidate_sets(
|
||||
base_url: str,
|
||||
case_id: str,
|
||||
num_gpus: int,
|
||||
is_video: bool,
|
||||
output_format: str | None = None,
|
||||
) -> list[list[tuple[str, str]]]:
|
||||
return [
|
||||
[(filename, f"{base_url}/{filename}") for filename in filenames]
|
||||
for filenames in get_consistency_gt_candidate_sets(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def _is_ascend_consistency_case(case_id: str) -> bool:
|
||||
return "npu" in case_id
|
||||
|
||||
@@ -1086,14 +1202,15 @@ def _find_remote_consistency_gt_files(
|
||||
# Avoid accidentally comparing non-comparable CI cases against official GT.
|
||||
bases = (SGL_TEST_FILES_CONSISTENCY_GT_BASE,)
|
||||
for base_url in bases:
|
||||
candidates = _remote_consistency_gt_candidates(
|
||||
candidate_sets = _remote_consistency_gt_candidate_sets(
|
||||
base_url, case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
if is_video:
|
||||
exists = [_remote_file_exists(url) for _, url in candidates]
|
||||
if all(status is not False for status in exists):
|
||||
return candidates
|
||||
else:
|
||||
for candidates in candidate_sets:
|
||||
if is_video:
|
||||
exists = [_remote_file_exists(url) for _, url in candidates]
|
||||
if all(status is not False for status in exists):
|
||||
return candidates
|
||||
continue
|
||||
uncertain_candidate = None
|
||||
for filename, url in candidates:
|
||||
exists = _remote_file_exists(url)
|
||||
@@ -1122,7 +1239,8 @@ def _get_consistency_gt_cache_key(
|
||||
) -> str:
|
||||
gt_dir = _get_consistency_gt_dir()
|
||||
source = str(gt_dir) if gt_dir is not None else "remote"
|
||||
return f"{case_id}:{num_gpus}:{is_video}:{output_format or ''}:{source}"
|
||||
platform = get_consistency_platform()
|
||||
return f"{platform}:{case_id}:{num_gpus}:{is_video}:{output_format or ''}:{source}"
|
||||
|
||||
|
||||
def load_consistency_gt(
|
||||
@@ -1139,29 +1257,43 @@ def load_consistency_gt(
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
filenames = _consistency_gt_filenames(case_id, num_gpus, is_video, output_format)
|
||||
images: list[np.ndarray] = []
|
||||
|
||||
gt_dir = _get_consistency_gt_dir()
|
||||
if gt_dir is not None:
|
||||
candidates = get_consistency_gt_candidates(
|
||||
candidate_sets = get_consistency_gt_candidate_sets(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
if is_video:
|
||||
for fn in candidates:
|
||||
path = gt_dir / fn
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"GT image not found: {path}")
|
||||
arr = np.array(Image.open(path).convert("RGB"))
|
||||
images.append(arr)
|
||||
selected = None
|
||||
for candidates in candidate_sets:
|
||||
if all((gt_dir / fn).exists() for fn in candidates):
|
||||
selected = candidates
|
||||
break
|
||||
if selected is None:
|
||||
tried = ", ".join(
|
||||
candidate
|
||||
for candidates in candidate_sets
|
||||
for candidate in candidates
|
||||
)
|
||||
raise FileNotFoundError(
|
||||
f"GT images not found in {gt_dir}. Tried: {tried}"
|
||||
)
|
||||
for fn in selected:
|
||||
images.append(np.array(Image.open(gt_dir / fn).convert("RGB")))
|
||||
else:
|
||||
path = None
|
||||
for fn in candidates:
|
||||
for fn in get_consistency_gt_candidates(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
):
|
||||
candidate = gt_dir / fn
|
||||
if candidate.exists():
|
||||
path = candidate
|
||||
break
|
||||
if path is None:
|
||||
candidates = get_consistency_gt_candidates(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
raise FileNotFoundError(
|
||||
f"GT image not found in {gt_dir}. Tried: {', '.join(candidates)}"
|
||||
)
|
||||
@@ -1172,8 +1304,11 @@ def load_consistency_gt(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
if not remote_files:
|
||||
candidates = get_consistency_gt_candidates(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
raise FileNotFoundError(
|
||||
f"GT image not found for {case_id}. Tried: {', '.join(filenames)}"
|
||||
f"GT image not found for {case_id}. Tried: {', '.join(candidates)}"
|
||||
)
|
||||
for _, url in remote_files:
|
||||
images.append(_load_remote_gt_image(url))
|
||||
@@ -1210,12 +1345,19 @@ def gt_exists(
|
||||
"""Check whether GT image(s) exist."""
|
||||
gt_dir = _get_consistency_gt_dir()
|
||||
if gt_dir is not None:
|
||||
candidates = get_consistency_gt_candidates(
|
||||
candidate_sets = get_consistency_gt_candidate_sets(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
)
|
||||
if is_video:
|
||||
return all((gt_dir / c).exists() for c in candidates)
|
||||
return any((gt_dir / c).exists() for c in candidates)
|
||||
return any(
|
||||
all((gt_dir / candidate).exists() for candidate in candidate_set)
|
||||
for candidate_set in candidate_sets
|
||||
)
|
||||
return any(
|
||||
(gt_dir / candidate).exists()
|
||||
for candidate_set in candidate_sets
|
||||
for candidate in candidate_set
|
||||
)
|
||||
|
||||
cache_key = _get_consistency_gt_cache_key(
|
||||
case_id, num_gpus, is_video, output_format
|
||||
|
||||
@@ -54,6 +54,53 @@ def test_remote_video_gt_candidates_survive_inconclusive_probe(monkeypatch):
|
||||
]
|
||||
|
||||
|
||||
def test_platform_gt_candidates_prefer_platform_then_default(monkeypatch):
|
||||
monkeypatch.setenv(test_utils.CONSISTENCY_PLATFORM_ENV, "5090")
|
||||
|
||||
assert test_utils.get_consistency_gt_candidates(
|
||||
"unit_image",
|
||||
1,
|
||||
is_video=False,
|
||||
output_format="png",
|
||||
) == [
|
||||
"5090/unit_image_1gpu.png",
|
||||
"5090/unit_image_1gpu.jpg",
|
||||
"5090/unit_image_1gpu.webp",
|
||||
"unit_image_1gpu.png",
|
||||
"unit_image_1gpu.jpg",
|
||||
"unit_image_1gpu.webp",
|
||||
]
|
||||
|
||||
|
||||
def test_threshold_metadata_merges_platform_override():
|
||||
metadata = test_utils._merge_threshold_metadata(
|
||||
{
|
||||
"cases": {
|
||||
"case_a": {
|
||||
"clip_threshold": 0.9,
|
||||
"ssim_threshold": 0.9,
|
||||
"psnr_threshold": 20.0,
|
||||
"mean_abs_diff_threshold": 10.0,
|
||||
}
|
||||
},
|
||||
"default_clip_threshold_image": 0.92,
|
||||
},
|
||||
{
|
||||
"cases": {
|
||||
"case_a": {
|
||||
"clip_threshold": 0.8,
|
||||
"ssim_threshold": 0.7,
|
||||
"psnr_threshold": 12.0,
|
||||
"mean_abs_diff_threshold": 20.0,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert metadata["default_clip_threshold_image"] == 0.92
|
||||
assert metadata["cases"]["case_a"]["psnr_threshold"] == 12.0
|
||||
|
||||
|
||||
def test_pixel_metrics_identical_image():
|
||||
image = _solid_image(128)
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import unittest
|
||||
|
||||
from sglang.multimodal_gen.runtime.layers.attention.turbo_layer import (
|
||||
_resolve_turbo_wan_sparse_backend,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms.interface import AttentionBackendEnum
|
||||
|
||||
|
||||
class TestTurboWanBackendSelection(unittest.TestCase):
|
||||
def test_non_sparse_requested_backend_falls_back_to_attention_type(self):
|
||||
selected, warning = _resolve_turbo_wan_sparse_backend(
|
||||
attention_type="sla",
|
||||
requested_attention_backend="fa",
|
||||
)
|
||||
|
||||
self.assertEqual(selected, AttentionBackendEnum.SLA_ATTN)
|
||||
self.assertIsNotNone(warning)
|
||||
self.assertIn("TurboWan only supports", warning)
|
||||
self.assertIn("attention_backend='fa'", warning)
|
||||
|
||||
def test_sagesla_attention_type_prefers_sage_sparse_backend(self):
|
||||
selected, warning = _resolve_turbo_wan_sparse_backend(
|
||||
attention_type="sagesla",
|
||||
requested_attention_backend="torch_sdpa",
|
||||
)
|
||||
|
||||
self.assertEqual(selected, AttentionBackendEnum.SAGE_SLA_ATTN)
|
||||
self.assertIsNotNone(warning)
|
||||
|
||||
def test_requested_sparse_backend_is_honored(self):
|
||||
selected, warning = _resolve_turbo_wan_sparse_backend(
|
||||
attention_type="sla",
|
||||
requested_attention_backend="sage_sla_attn",
|
||||
)
|
||||
|
||||
self.assertEqual(selected, AttentionBackendEnum.SAGE_SLA_ATTN)
|
||||
self.assertIsNone(warning)
|
||||
|
||||
def test_supported_backend_filter_is_respected(self):
|
||||
selected, warning = _resolve_turbo_wan_sparse_backend(
|
||||
attention_type="sla",
|
||||
requested_attention_backend=None,
|
||||
supported_attention_backends={AttentionBackendEnum.SAGE_SLA_ATTN},
|
||||
)
|
||||
|
||||
self.assertEqual(selected, AttentionBackendEnum.SAGE_SLA_ATTN)
|
||||
self.assertIsNone(warning)
|
||||
|
||||
def test_empty_supported_backend_intersection_keeps_turbowan_choices(self):
|
||||
selected, warning = _resolve_turbo_wan_sparse_backend(
|
||||
attention_type="sla",
|
||||
requested_attention_backend=None,
|
||||
supported_attention_backends={AttentionBackendEnum.FA},
|
||||
)
|
||||
|
||||
self.assertEqual(selected, AttentionBackendEnum.SLA_ATTN)
|
||||
self.assertIsNone(warning)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user