94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
518 lines
18 KiB
Python
Executable File
518 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
AST-based parser for diffusion test cases.
|
|
|
|
This module parses the diffusion case source and run_suite.py using AST to
|
|
extract test case information without requiring sglang dependencies. The case
|
|
source file is discovered from ONE_GPU_CASES/TWO_GPU_CASES imports in
|
|
run_suite.py so CI keeps a single source of truth.
|
|
|
|
Usage:
|
|
# From sibling scripts in this directory:
|
|
from diffusion_case_parser import collect_diffusion_suites, resolve_case_config_path
|
|
case_config_path = resolve_case_config_path(repo_root, run_suite_path)
|
|
suites = collect_diffusion_suites(case_config_path, run_suite_path, baseline_path)
|
|
"""
|
|
|
|
import ast
|
|
import json
|
|
import os
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
# Mapping from list variable names to suite names
|
|
CASE_LIST_TO_SUITE = {
|
|
"ONE_GPU_CASES": "1-gpu",
|
|
"ONE_GPU_CASES_A": "1-gpu",
|
|
"ONE_GPU_CASES_B": "1-gpu",
|
|
"ONE_GPU_CASES_C": "1-gpu-b200",
|
|
"ONE_GPU_MODELOPT_FP8_CASES": "1-gpu",
|
|
"ONE_GPU_MODELOPT_CASES": "1-gpu-b200",
|
|
"ONE_GPU_B200_CASES": "1-gpu-b200",
|
|
"TWO_GPU_CASES": "2-gpu",
|
|
"TWO_GPU_CASES_A": "2-gpu",
|
|
"TWO_GPU_CASES_B": "2-gpu",
|
|
}
|
|
|
|
# Default estimated time for cases without baseline (5 minutes)
|
|
DEFAULT_EST_TIME_SECONDS = 300.0
|
|
|
|
# Fixed overhead for server startup when estimated_full_test_time_s is not set
|
|
STARTUP_OVERHEAD_SECONDS = 120.0
|
|
|
|
# Paths relative to repository root
|
|
BASELINE_REL_PATH = "python/sglang/multimodal_gen/test/server/perf_baselines"
|
|
BASELINE_PLATFORM_ORDER = ("h100", "b200", "5090")
|
|
RUN_SUITE_REL_PATH = "python/sglang/multimodal_gen/test/run_suite.py"
|
|
|
|
USE_NPU_CONFIGS = os.getenv("USE_NPU_CONFIGS", "0").lower() in ("1", "true")
|
|
|
|
if USE_NPU_CONFIGS:
|
|
BASELINE_REL_PATH = (
|
|
"python/sglang/multimodal_gen/test/server/perf_baselines_npu.json"
|
|
)
|
|
CASE_LIST_TO_SUITE = {
|
|
"ONE_NPU_CASES": "1-npu",
|
|
"TWO_NPU_CASES": "2-npu",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class DiffusionCaseInfo:
|
|
"""Information about a single diffusion test case."""
|
|
|
|
case_id: str # e.g., "qwen_image_t2i"
|
|
suite: str # "1-gpu" or "2-gpu"
|
|
est_time: float # estimated time in seconds
|
|
|
|
|
|
@dataclass
|
|
class DiffusionSuiteInfo:
|
|
"""Complete information for a test suite."""
|
|
|
|
suite: str # "1-gpu" or "2-gpu"
|
|
cases: List[DiffusionCaseInfo] # parametrized test cases
|
|
standalone_files: List[str] # standalone test files
|
|
standalone_est_times: Dict[str, float] # standalone file -> estimated seconds
|
|
missing_standalone_estimates: List[
|
|
str
|
|
] # standalone files without configured estimate
|
|
|
|
|
|
class DiffusionTestCaseVisitor(ast.NodeVisitor):
|
|
"""
|
|
AST visitor to extract DiffusionTestCase definitions from the case config.
|
|
|
|
Parses assignments like:
|
|
ONE_GPU_CASES_A: list[DiffusionTestCase] = [
|
|
DiffusionTestCase("case_id", ...),
|
|
...
|
|
]
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.cases: Dict[str, List[str]] = {} # list_name -> [case_id, ...]
|
|
self.factory_case_ids: Dict[str, str] = {}
|
|
|
|
def visit_Module(self, node: ast.Module):
|
|
for stmt in node.body:
|
|
if not isinstance(stmt, ast.FunctionDef):
|
|
continue
|
|
case_id = self._extract_factory_case_id(stmt)
|
|
if case_id:
|
|
self.factory_case_ids[stmt.name] = case_id
|
|
|
|
self.generic_visit(node)
|
|
|
|
def visit_Expr(self, node: ast.Expr):
|
|
"""Handle ``LIST.append(...)`` mutations at any nesting level.
|
|
|
|
Previously only module-top-level ``ast.Expr`` statements were scanned for
|
|
``.append()`` calls, so cases registered under a platform guard such as
|
|
``if not current_platform.is_hip(): ONE_GPU_CASES.append(...)`` (used by
|
|
``hunyuan3d_shape_gen`` and ``turbo_wan2_1_t2v_1.3b``) were invisible to
|
|
the partition planner and therefore never scheduled in CI. Visiting every
|
|
``Expr`` lets ``generic_visit`` reach appends inside ``if``/``else`` blocks.
|
|
"""
|
|
self._process_expr(node.value)
|
|
self.generic_visit(node)
|
|
|
|
def visit_Assign(self, node: ast.Assign):
|
|
self._process_assignment(node.targets, node.value)
|
|
self.generic_visit(node)
|
|
|
|
def visit_AnnAssign(self, node: ast.AnnAssign):
|
|
if node.target and node.value:
|
|
self._process_assignment([node.target], node.value)
|
|
self.generic_visit(node)
|
|
|
|
def visit_AugAssign(self, node: ast.AugAssign):
|
|
self._process_aug_assignment(node.target, node.op, node.value)
|
|
self.generic_visit(node)
|
|
|
|
def _process_assignment(self, targets: List[ast.AST], value: ast.AST):
|
|
"""Process an assignment to extract case IDs."""
|
|
for target in targets:
|
|
if isinstance(target, ast.Name):
|
|
list_name = target.id
|
|
case_ids = self._extract_case_ids(value)
|
|
if case_ids is not None:
|
|
self.cases[list_name] = case_ids
|
|
|
|
def _process_aug_assignment(self, target: ast.AST, op: ast.AST, value: ast.AST):
|
|
"""Process `+=` style assignment to merge case lists."""
|
|
if not isinstance(target, ast.Name) or not isinstance(op, ast.Add):
|
|
return
|
|
|
|
if isinstance(value, ast.Name):
|
|
target_suite = CASE_LIST_TO_SUITE.get(target.id)
|
|
value_suite = CASE_LIST_TO_SUITE.get(value.id)
|
|
if target_suite and value_suite and target_suite != value_suite:
|
|
return
|
|
|
|
rhs_case_ids = self._extract_case_ids(value)
|
|
if rhs_case_ids is None:
|
|
return
|
|
|
|
lhs_case_ids = self.cases.get(target.id, [])
|
|
self.cases[target.id] = [*lhs_case_ids, *rhs_case_ids]
|
|
|
|
def _process_expr(self, node: ast.AST):
|
|
"""Process list mutation calls such as `ONE_GPU_CASES.append(...)`."""
|
|
if not isinstance(node, ast.Call):
|
|
return
|
|
if not isinstance(node.func, ast.Attribute):
|
|
return
|
|
if node.func.attr != "append":
|
|
return
|
|
if not isinstance(node.func.value, ast.Name):
|
|
return
|
|
list_name = node.func.value.id
|
|
if list_name not in CASE_LIST_TO_SUITE:
|
|
return
|
|
if len(node.args) != 1:
|
|
return
|
|
|
|
case_id = self._extract_case_id_from_call(node.args[0])
|
|
if case_id:
|
|
self.cases.setdefault(list_name, []).append(case_id)
|
|
|
|
def _extract_case_ids(self, node: ast.AST) -> Optional[List[str]]:
|
|
"""Extract case IDs from a supported expression."""
|
|
if isinstance(node, ast.List):
|
|
return self._extract_case_ids_from_list(node)
|
|
|
|
if isinstance(node, ast.Name):
|
|
# Reference to a previously parsed list variable.
|
|
if node.id not in self.cases:
|
|
return None
|
|
return list(self.cases[node.id])
|
|
|
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
left_ids = self._extract_case_ids(node.left)
|
|
right_ids = self._extract_case_ids(node.right)
|
|
if left_ids is None or right_ids is None:
|
|
return None
|
|
return [*left_ids, *right_ids]
|
|
|
|
return None
|
|
|
|
def _extract_case_ids_from_list(self, node: ast.List) -> List[str]:
|
|
"""Extract case IDs from a literal list of DiffusionTestCase calls."""
|
|
case_ids = []
|
|
for elt in node.elts:
|
|
if isinstance(elt, ast.Starred):
|
|
starred_case_ids = self._extract_case_ids(elt.value)
|
|
if starred_case_ids:
|
|
case_ids.extend(starred_case_ids)
|
|
continue
|
|
case_id = self._extract_case_id_from_call(elt)
|
|
if case_id:
|
|
case_ids.append(case_id)
|
|
return case_ids
|
|
|
|
def _extract_case_id_from_call(self, node: ast.AST) -> Optional[str]:
|
|
"""Extract case_id from DiffusionTestCase(...) call."""
|
|
if not isinstance(node, ast.Call):
|
|
return None
|
|
|
|
# First positional argument is the case_id.
|
|
if isinstance(node.func, ast.Name) and node.func.id in {
|
|
"DiffusionTestCase",
|
|
"_make_modelopt_ci_case",
|
|
}:
|
|
if node.args and isinstance(node.args[0], ast.Constant):
|
|
return node.args[0].value
|
|
if isinstance(node.func, ast.Name) and not node.args:
|
|
return self.factory_case_ids.get(node.func.id)
|
|
|
|
return None
|
|
|
|
def _extract_factory_case_id(self, node: ast.FunctionDef) -> Optional[str]:
|
|
for child in ast.walk(node):
|
|
if not isinstance(child, ast.Return) or child.value is None:
|
|
continue
|
|
case_id = self._extract_case_id_from_call(child.value)
|
|
if case_id:
|
|
return case_id
|
|
return None
|
|
|
|
|
|
def resolve_case_config_path(repo_root: Path, run_suite_path: Path) -> Path:
|
|
"""
|
|
Resolve the diffusion case config path from run_suite imports.
|
|
|
|
run_suite.py must import BOTH ONE_GPU_CASES and TWO_GPU_CASES from the same
|
|
module. That imported module is treated as the single source of truth.
|
|
"""
|
|
with open(run_suite_path, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
tree = ast.parse(content, filename=str(run_suite_path))
|
|
one_gpu_module: Optional[str] = None
|
|
two_gpu_module: Optional[str] = None
|
|
|
|
for node in ast.walk(tree):
|
|
if not isinstance(node, ast.ImportFrom) or not node.module:
|
|
continue
|
|
imported_names = {alias.name for alias in node.names}
|
|
if "ONE_GPU_CASES" in imported_names:
|
|
one_gpu_module = node.module
|
|
if "TWO_GPU_CASES" in imported_names:
|
|
two_gpu_module = node.module
|
|
|
|
if one_gpu_module is None or two_gpu_module is None:
|
|
raise RuntimeError(
|
|
"run_suite.py must import BOTH ONE_GPU_CASES and TWO_GPU_CASES."
|
|
)
|
|
if one_gpu_module != two_gpu_module:
|
|
raise RuntimeError(
|
|
"run_suite.py imports ONE_GPU_CASES and TWO_GPU_CASES from different "
|
|
f"modules: {one_gpu_module} vs {two_gpu_module}"
|
|
)
|
|
|
|
rel_path = Path(*one_gpu_module.split(".")).with_suffix(".py")
|
|
candidates = [repo_root / rel_path, repo_root / "python" / rel_path]
|
|
case_config_path = next((path for path in candidates if path.exists()), None)
|
|
if case_config_path is None:
|
|
raise FileNotFoundError(
|
|
"Resolved case config from run_suite does not exist. Checked: "
|
|
+ ", ".join(str(path) for path in candidates)
|
|
)
|
|
return case_config_path
|
|
|
|
|
|
class RunSuiteVisitor(ast.NodeVisitor):
|
|
"""
|
|
AST visitor to extract standalone metadata from run_suite.py.
|
|
|
|
Parses:
|
|
STANDALONE_FILES = {
|
|
"1-gpu": ["test_lora_format_adapter.py"],
|
|
"2-gpu": [],
|
|
}
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.standalone_files: Dict[str, List[str]] = {}
|
|
self.standalone_est_times: Dict[str, Dict[str, float]] = {}
|
|
|
|
def visit_Assign(self, node: ast.Assign):
|
|
for target in node.targets:
|
|
if isinstance(target, ast.Name) and target.id == "STANDALONE_FILES":
|
|
self.standalone_files = self._extract_file_dict(node.value)
|
|
if (
|
|
isinstance(target, ast.Name)
|
|
and target.id == "STANDALONE_FILE_EST_TIMES"
|
|
):
|
|
self.standalone_est_times = self._extract_est_time_dict(node.value)
|
|
self.generic_visit(node)
|
|
|
|
def _extract_file_dict(self, node: ast.AST) -> Dict[str, List[str]]:
|
|
"""Extract dictionary of suite -> file list."""
|
|
result = {}
|
|
if isinstance(node, ast.Dict):
|
|
for key, value in zip(node.keys, node.values):
|
|
if isinstance(key, ast.Constant) and isinstance(value, ast.List):
|
|
suite = key.value
|
|
files = [
|
|
elt.value for elt in value.elts if isinstance(elt, ast.Constant)
|
|
]
|
|
result[suite] = files
|
|
return result
|
|
|
|
def _extract_est_time_dict(self, node: ast.AST) -> Dict[str, Dict[str, float]]:
|
|
"""Extract dictionary of suite -> standalone file -> estimated seconds."""
|
|
result = {}
|
|
if not isinstance(node, ast.Dict):
|
|
return result
|
|
|
|
for key, value in zip(node.keys, node.values):
|
|
if not isinstance(key, ast.Constant) or not isinstance(value, ast.Dict):
|
|
continue
|
|
|
|
suite = key.value
|
|
suite_est_times = {}
|
|
for inner_key, inner_value in zip(value.keys, value.values):
|
|
if not (
|
|
isinstance(inner_key, ast.Constant)
|
|
and isinstance(inner_value, ast.Constant)
|
|
):
|
|
continue
|
|
suite_est_times[inner_key.value] = float(inner_value.value)
|
|
result[suite] = suite_est_times
|
|
|
|
return result
|
|
|
|
|
|
def _iter_baseline_paths(baseline_path: Path) -> List[Path]:
|
|
if baseline_path.is_file():
|
|
return [baseline_path]
|
|
if not baseline_path.is_dir():
|
|
return []
|
|
|
|
ordered_paths = [
|
|
baseline_path / f"{platform}.json" for platform in BASELINE_PLATFORM_ORDER
|
|
]
|
|
ordered_paths.extend(
|
|
path
|
|
for path in sorted(baseline_path.glob("*.json"))
|
|
if path not in ordered_paths
|
|
)
|
|
return [path for path in ordered_paths if path.exists()]
|
|
|
|
|
|
def load_baselines(baseline_path: Path) -> Dict[str, float]:
|
|
"""
|
|
Load performance baselines from a JSON file or platform baseline directory.
|
|
|
|
Returns:
|
|
Dictionary mapping case_id to estimated time in seconds.
|
|
"""
|
|
baselines = {}
|
|
for path in _iter_baseline_paths(baseline_path):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
scenarios = data.get("scenarios", {})
|
|
for case_id, scenario in scenarios.items():
|
|
if scenario.get("estimated_full_test_time_s") is not None:
|
|
est_time = scenario["estimated_full_test_time_s"]
|
|
else:
|
|
expected_e2e_ms = scenario.get("expected_e2e_ms", 0)
|
|
est_time = expected_e2e_ms / 1000.0 + STARTUP_OVERHEAD_SECONDS
|
|
baselines.setdefault(case_id, est_time)
|
|
|
|
return baselines
|
|
|
|
|
|
def get_case_est_time(case_id: str, baselines: Dict[str, float]) -> float:
|
|
"""Get estimated time for a case, with fallback to default."""
|
|
return baselines.get(case_id, DEFAULT_EST_TIME_SECONDS)
|
|
|
|
|
|
def parse_testcase_configs(config_path: Path) -> Dict[str, List[str]]:
|
|
"""
|
|
Parse a diffusion case config file to extract case IDs.
|
|
|
|
Returns:
|
|
Dictionary mapping list name to case IDs.
|
|
e.g., {"ONE_GPU_CASES_A": ["qwen_image_t2i", ...], ...}
|
|
"""
|
|
with open(config_path, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
tree = ast.parse(content, filename=str(config_path))
|
|
visitor = DiffusionTestCaseVisitor()
|
|
visitor.visit(tree)
|
|
|
|
return visitor.cases
|
|
|
|
|
|
def parse_run_suite_standalone_data(
|
|
run_suite_path: Path,
|
|
) -> tuple[Dict[str, List[str]], Dict[str, Dict[str, float]]]:
|
|
"""
|
|
Parse run_suite.py to extract standalone file metadata.
|
|
|
|
Returns:
|
|
Tuple of:
|
|
- suite -> standalone file list
|
|
- suite -> standalone file -> estimated seconds
|
|
"""
|
|
with open(run_suite_path, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
tree = ast.parse(content, filename=str(run_suite_path))
|
|
visitor = RunSuiteVisitor()
|
|
visitor.visit(tree)
|
|
|
|
return visitor.standalone_files, visitor.standalone_est_times
|
|
|
|
|
|
def validate_standalone_est_times(
|
|
standalone_files: Dict[str, List[str]],
|
|
standalone_est_times: Dict[str, Dict[str, float]],
|
|
) -> Dict[str, List[str]]:
|
|
missing_by_suite = {}
|
|
for suite, files in standalone_files.items():
|
|
suite_est_times = standalone_est_times.get(suite, {})
|
|
missing = [
|
|
standalone_file
|
|
for standalone_file in files
|
|
if standalone_file not in suite_est_times
|
|
]
|
|
if missing:
|
|
missing_by_suite[suite] = missing
|
|
return missing_by_suite
|
|
|
|
|
|
def collect_diffusion_suites(
|
|
case_config_path: Path,
|
|
run_suite_path: Path,
|
|
baseline_path: Path,
|
|
) -> Dict[str, DiffusionSuiteInfo]:
|
|
"""
|
|
Collect all diffusion test suite information using AST parsing.
|
|
|
|
Args:
|
|
case_config_path: Path to case config (resolved from run_suite.py)
|
|
run_suite_path: Path to run_suite.py
|
|
baseline_path: Path to perf_baselines/ or a single baseline JSON file
|
|
|
|
Returns:
|
|
Dictionary mapping suite name to DiffusionSuiteInfo.
|
|
"""
|
|
# Parse case IDs from the single source case config.
|
|
case_lists = parse_testcase_configs(case_config_path)
|
|
|
|
# Parse standalone files from run_suite.py
|
|
standalone_files, standalone_est_times = parse_run_suite_standalone_data(
|
|
run_suite_path
|
|
)
|
|
missing_standalone_estimates = validate_standalone_est_times(
|
|
standalone_files, standalone_est_times
|
|
)
|
|
|
|
# Load baselines for time estimation
|
|
baselines = load_baselines(baseline_path)
|
|
|
|
# Build suite info
|
|
suites = {}
|
|
for list_name, suite in CASE_LIST_TO_SUITE.items():
|
|
case_ids = case_lists.get(list_name, [])
|
|
cases = [
|
|
DiffusionCaseInfo(
|
|
case_id=cid,
|
|
suite=suite,
|
|
est_time=get_case_est_time(cid, baselines),
|
|
)
|
|
for cid in case_ids
|
|
]
|
|
|
|
if suite not in suites:
|
|
suites[suite] = DiffusionSuiteInfo(
|
|
suite=suite,
|
|
cases=[],
|
|
standalone_files=standalone_files.get(suite, []),
|
|
standalone_est_times=dict(standalone_est_times.get(suite, {})),
|
|
missing_standalone_estimates=list(
|
|
missing_standalone_estimates.get(suite, [])
|
|
),
|
|
)
|
|
suites[suite].cases.extend(cases)
|
|
|
|
# Dedupe duplicated case IDs while preserving first-seen order.
|
|
for suite_info in suites.values():
|
|
seen_case_ids = set()
|
|
deduped_cases = []
|
|
for case in suite_info.cases:
|
|
if case.case_id in seen_case_ids:
|
|
continue
|
|
seen_case_ids.add(case.case_id)
|
|
deduped_cases.append(case)
|
|
suite_info.cases = deduped_cases
|
|
|
|
return suites
|