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
328 lines
11 KiB
Python
Executable File
328 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Compute dynamic partitions for diffusion CI tests.
|
|
|
|
This script runs on lightweight CI runners without sglang dependencies and uses
|
|
AST parsing to extract parametrized cases plus standalone files from source.
|
|
"""
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import json
|
|
import math
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from diffusion_case_parser import (
|
|
BASELINE_REL_PATH,
|
|
RUN_SUITE_REL_PATH,
|
|
DiffusionSuiteInfo,
|
|
collect_diffusion_suites,
|
|
resolve_case_config_path,
|
|
)
|
|
|
|
|
|
def _load_partitioning_helpers():
|
|
repo_root = Path(__file__).resolve().parents[4]
|
|
helper_path = repo_root / "python/sglang/multimodal_gen/test/partitioning.py"
|
|
spec = importlib.util.spec_from_file_location(
|
|
"diffusion_test_partitioning", helper_path
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module.PartitionItem, module.partition_items_by_lpt
|
|
|
|
|
|
PartitionItem, partition_items_by_lpt = _load_partitioning_helpers()
|
|
|
|
SUITE_OUTPUT_NAMES = {"1-gpu": "1gpu", "2-gpu": "2gpu", "1-gpu-b200": "b200"}
|
|
|
|
USE_NPU_CONFIGS = os.getenv("USE_NPU_CONFIGS", "0").lower() in ("1", "true")
|
|
|
|
if USE_NPU_CONFIGS:
|
|
SUITE_OUTPUT_NAMES = {"1-npu": "1npu", "2-npu": "2npu"}
|
|
|
|
DEFAULT_STANDALONE_EST_TIME_SECONDS = 300.0
|
|
|
|
|
|
def validate_suite_case_coverage(suites: dict[str, DiffusionSuiteInfo]) -> None:
|
|
"""
|
|
Guardrail: dynamic diffusion suites must contain parametrized cases.
|
|
"""
|
|
suites_with_no_cases = []
|
|
for suite_name in SUITE_OUTPUT_NAMES:
|
|
suite_info = suites.get(suite_name)
|
|
if suite_info is None:
|
|
print(f"Error: Required suite '{suite_name}' not found in parsed suites.")
|
|
sys.exit(1)
|
|
if len(suite_info.cases) == 0:
|
|
suites_with_no_cases.append(suite_name)
|
|
|
|
if suites_with_no_cases:
|
|
joined = ", ".join(suites_with_no_cases)
|
|
print(
|
|
"Error: Parsed zero parametrized cases for diffusion suites: "
|
|
f"{joined}. This usually means run_suite case imports changed but "
|
|
"diffusion parser logic was not updated."
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
def compute_partition_count(
|
|
total_time_seconds: float,
|
|
min_time_seconds: float,
|
|
target_time_seconds: float,
|
|
max_time_seconds: float,
|
|
max_partitions: int,
|
|
) -> int:
|
|
if total_time_seconds <= 0:
|
|
return 0
|
|
|
|
min_partition_count = max(1, math.ceil(total_time_seconds / max_time_seconds))
|
|
max_partition_count = max(1, math.floor(total_time_seconds / min_time_seconds))
|
|
|
|
min_partition_count = min(min_partition_count, max_partitions)
|
|
max_partition_count = min(max_partition_count, max_partitions)
|
|
|
|
if max_partition_count < min_partition_count:
|
|
fallback_count = math.ceil(total_time_seconds / target_time_seconds)
|
|
return max(1, min(fallback_count, max_partitions))
|
|
|
|
preferred_count = math.ceil(total_time_seconds / target_time_seconds)
|
|
preferred_count = max(1, min(preferred_count, max_partitions))
|
|
return max(min_partition_count, min(preferred_count, max_partition_count))
|
|
|
|
|
|
def build_partition_items(
|
|
suite_info: DiffusionSuiteInfo, include_standalone: bool = True
|
|
) -> list[PartitionItem]:
|
|
items = [
|
|
PartitionItem(kind="case", item_id=case.case_id, est_time=case.est_time)
|
|
for case in suite_info.cases
|
|
]
|
|
if not include_standalone:
|
|
return items
|
|
|
|
items.extend(
|
|
PartitionItem(
|
|
kind="standalone",
|
|
item_id=standalone_file,
|
|
est_time=suite_info.standalone_est_times.get(
|
|
standalone_file, DEFAULT_STANDALONE_EST_TIME_SECONDS
|
|
),
|
|
used_fallback_estimate=(
|
|
standalone_file in suite_info.missing_standalone_estimates
|
|
),
|
|
)
|
|
for standalone_file in suite_info.standalone_files
|
|
)
|
|
return items
|
|
|
|
|
|
def build_matrix(partition_count: int) -> dict:
|
|
if partition_count <= 0:
|
|
return {"include": []}
|
|
return {"include": [{"part": i} for i in range(partition_count)]}
|
|
|
|
|
|
def build_partition_plan(
|
|
suite_name: str,
|
|
partitions: list[list[PartitionItem]],
|
|
) -> dict:
|
|
return {
|
|
"suite": suite_name,
|
|
"partition_count": len(partitions),
|
|
"partitions": [
|
|
{
|
|
"part": idx,
|
|
"case_ids": [item.item_id for item in partition if item.kind == "case"],
|
|
"standalone_files": [
|
|
item.item_id for item in partition if item.kind == "standalone"
|
|
],
|
|
"missing_standalone_estimates": [
|
|
item.item_id
|
|
for item in partition
|
|
if item.kind == "standalone" and item.used_fallback_estimate
|
|
],
|
|
"estimated_time": round(sum(item.est_time for item in partition), 1),
|
|
}
|
|
for idx, partition in enumerate(partitions)
|
|
],
|
|
}
|
|
|
|
|
|
def output_github_value(name: str, value: dict) -> None:
|
|
value_json = json.dumps(value, separators=(",", ":"))
|
|
github_output = os.environ.get("GITHUB_OUTPUT")
|
|
if github_output:
|
|
with open(github_output, "a", encoding="utf-8") as f:
|
|
f.write(f"{name}={value_json}\n")
|
|
print(f"{name}={value_json}")
|
|
|
|
|
|
def output_github_scalar(name: str, value: str) -> None:
|
|
github_output = os.environ.get("GITHUB_OUTPUT")
|
|
if github_output:
|
|
with open(github_output, "a", encoding="utf-8") as f:
|
|
f.write(f"{name}={value}\n")
|
|
print(f"{name}={value}")
|
|
|
|
|
|
def print_suite_summary(
|
|
suite_name: str,
|
|
suite_info: DiffusionSuiteInfo,
|
|
partitions: list[list[PartitionItem]],
|
|
include_standalone: bool = True,
|
|
) -> None:
|
|
total_time = sum(
|
|
item.est_time
|
|
for item in build_partition_items(
|
|
suite_info, include_standalone=include_standalone
|
|
)
|
|
)
|
|
print(f"{suite_name.upper()} suite:")
|
|
print(f" Cases: {len(suite_info.cases)}")
|
|
standalone_label = "Standalone files"
|
|
if not include_standalone:
|
|
standalone_label = "Standalone files ignored"
|
|
print(f" {standalone_label}: {len(suite_info.standalone_files)}")
|
|
print(
|
|
f" Missing standalone estimates: {len(suite_info.missing_standalone_estimates)}"
|
|
)
|
|
if suite_info.missing_standalone_estimates:
|
|
print(
|
|
f" Fallback standalone estimate: "
|
|
f"{DEFAULT_STANDALONE_EST_TIME_SECONDS:.1f}s"
|
|
)
|
|
for standalone_file in suite_info.missing_standalone_estimates:
|
|
print(f" - {standalone_file}")
|
|
print(f" Total estimated time: {total_time:.1f}s ({total_time/60:.1f} min)")
|
|
print(f" Selected partitions: {len(partitions)}")
|
|
print()
|
|
|
|
print(" Partition assignments:")
|
|
for idx, partition in enumerate(partitions):
|
|
partition_time = sum(item.est_time for item in partition)
|
|
print(f" Partition {idx}:")
|
|
print(
|
|
f" Estimated time: {partition_time:.1f}s ({partition_time/60:.1f} min)"
|
|
)
|
|
for item in partition:
|
|
fallback_suffix = (
|
|
", fallback estimate"
|
|
if item.kind == "standalone" and item.used_fallback_estimate
|
|
else ""
|
|
)
|
|
print(
|
|
f" - {item.kind}: {item.item_id} "
|
|
f"({item.est_time:.1f}s{fallback_suffix})"
|
|
)
|
|
print()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Compute diffusion test partitions for CI"
|
|
)
|
|
parser.add_argument(
|
|
"--min-time",
|
|
type=float,
|
|
default=1200.0,
|
|
help="Minimum desired partition time in seconds (default: 1200 = 20 minutes)",
|
|
)
|
|
parser.add_argument(
|
|
"--target-time",
|
|
type=float,
|
|
default=1800.0,
|
|
help="Preferred partition time in seconds (default: 1800 = 30 minutes)",
|
|
)
|
|
parser.add_argument(
|
|
"--max-time",
|
|
type=float,
|
|
default=2400.0,
|
|
help="Maximum desired partition time in seconds (default: 2400 = 40 minutes)",
|
|
)
|
|
parser.add_argument(
|
|
"--max-partitions",
|
|
type=int,
|
|
default=10,
|
|
help="Maximum number of partitions (default: 10)",
|
|
)
|
|
parser.add_argument(
|
|
"--parametrized-only",
|
|
action="store_true",
|
|
help="Only partition DiffusionTestCase parametrized cases.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
script_dir = Path(__file__).resolve().parent
|
|
repo_root = script_dir.parent.parent.parent.parent
|
|
|
|
baseline_path = repo_root / BASELINE_REL_PATH
|
|
run_suite_path = repo_root / RUN_SUITE_REL_PATH
|
|
|
|
if not run_suite_path.exists():
|
|
print(f"Error: Run suite not found: {run_suite_path}")
|
|
sys.exit(1)
|
|
try:
|
|
if USE_NPU_CONFIGS:
|
|
case_config_path = (
|
|
repo_root
|
|
/ "python/sglang/multimodal_gen/test/server/ascend/testcase_configs_npu.py"
|
|
)
|
|
else:
|
|
case_config_path = resolve_case_config_path(repo_root, run_suite_path)
|
|
except (RuntimeError, FileNotFoundError) as exc:
|
|
print(f"Error: {exc}")
|
|
sys.exit(1)
|
|
|
|
suites = collect_diffusion_suites(
|
|
case_config_path,
|
|
run_suite_path,
|
|
baseline_path,
|
|
)
|
|
validate_suite_case_coverage(suites)
|
|
|
|
print("=== Diffusion Partition Computation ===")
|
|
print(f"Min partition time: {args.min_time}s ({args.min_time/60:.1f} min)")
|
|
print(f"Target partition time: {args.target_time}s ({args.target_time/60:.1f} min)")
|
|
print(f"Max partition time: {args.max_time}s ({args.max_time/60:.1f} min)")
|
|
print()
|
|
|
|
for suite_name, suite_info in suites.items():
|
|
if suite_name not in SUITE_OUTPUT_NAMES:
|
|
continue
|
|
|
|
items = build_partition_items(
|
|
suite_info, include_standalone=not args.parametrized_only
|
|
)
|
|
total_time = sum(item.est_time for item in items)
|
|
partition_count = compute_partition_count(
|
|
total_time_seconds=total_time,
|
|
min_time_seconds=args.min_time,
|
|
target_time_seconds=args.target_time,
|
|
max_time_seconds=args.max_time,
|
|
max_partitions=args.max_partitions,
|
|
)
|
|
partitions = partition_items_by_lpt(items, partition_count)
|
|
|
|
print_suite_summary(
|
|
suite_name,
|
|
suite_info,
|
|
partitions,
|
|
include_standalone=not args.parametrized_only,
|
|
)
|
|
|
|
output_name = SUITE_OUTPUT_NAMES[suite_name]
|
|
output_github_value(f"matrix-{output_name}", build_matrix(partition_count))
|
|
output_github_scalar(f"partition-count-{output_name}", str(partition_count))
|
|
output_github_value(
|
|
f"plan-{output_name}", build_partition_plan(suite_name, partitions)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|