ci: enable /rerun-test for multimodal gen PR tests (#22828)

This commit is contained in:
Alison Shao
2026-04-21 21:34:14 -07:00
committed by GitHub
parent 7607e4d180
commit 04b1caf75b
2 changed files with 144 additions and 24 deletions
+18 -5
View File
@@ -38,6 +38,11 @@ on:
required: false
type: string
default: "false"
install_diffusion:
description: "Install diffusion dependencies (for multimodal gen tests)"
required: false
type: string
default: "false"
env:
SGLANG_IS_IN_CI: true
@@ -73,6 +78,8 @@ jobs:
fi
if [[ "${{ inputs.use_deepep }}" == "true" ]]; then
bash scripts/ci/cuda/ci_install_deepep.sh
elif [[ "${{ inputs.install_diffusion }}" == "true" ]]; then
bash scripts/ci/cuda/ci_install_dependency.sh diffusion
else
bash scripts/ci/cuda/ci_install_dependency.sh
fi
@@ -83,7 +90,6 @@ jobs:
if [[ "${{ inputs.runner_label }}" == "1-gpu-5090" ]]; then
source /etc/profile.d/sglang-ci.sh
fi
cd test/
# Collect non-empty commands into an array for counting.
cmds=()
while IFS= read -r cmd; do
@@ -97,10 +103,17 @@ jobs:
cmd="${cmds[$idx]}"
echo ""
echo "."
echo "Begin ($i/$total): python3 $cmd"
echo "."
file_start=$SECONDS
python3 $cmd -f || exit 1
if [[ "${{ inputs.install_diffusion }}" == "true" ]]; then
echo "Begin ($i/$total): python3 -m pytest $cmd -x"
echo "."
file_start=$SECONDS
python3 -m pytest $cmd -x || exit 1
else
echo "Begin ($i/$total): python3 $cmd"
echo "."
file_start=$SECONDS
(cd test/ && python3 $cmd -f) || exit 1
fi
elapsed=$(( SECONDS - file_start ))
echo "."
echo "End ($i/$total): elapsed=${elapsed}s"
+126 -19
View File
@@ -489,40 +489,104 @@ DEEPEP_SUITES = {
}
MULTIMODAL_TEST_DIR = "python/sglang/multimodal_gen/test"
MULTIMODAL_PATH_TO_RUNNER = {
"2_gpu": "2-gpu-h100",
"2-gpu": "2-gpu-h100",
}
MULTIMODAL_DEFAULT_RUNNER = "1-gpu-h100"
def resolve_test_file(file_part):
"""
Resolve a user-provided file path to a path relative to test/.
Resolve a user-provided file path to a path relative to test/ or full path for multimodal.
Supports:
- Full path: test/registered/core/test_srt_endpoint.py
- Relative to test/: registered/core/test_srt_endpoint.py
- Bare filename: test_srt_endpoint.py (glob-matched, must be unique)
- Multimodal paths: python/sglang/multimodal_gen/test/server/test_server_a.py
Returns (resolved_path, error_message). On success error_message is None.
Returns (resolved_path, is_multimodal, error_message). On success error_message is None.
"""
# Check if it's explicitly a multimodal path
multimodal_prefixes = [
"python/sglang/multimodal_gen/test/",
"sglang/multimodal_gen/test/",
"multimodal_gen/test/",
]
for prefix in multimodal_prefixes:
if file_part.startswith(prefix):
full_path = (
file_part
if file_part.startswith("python/")
else f"python/sglang/multimodal_gen/test/{file_part[len(prefix):]}"
)
if not os.path.isfile(full_path):
return None, False, f"File not found: `{full_path}`"
return full_path, True, None
# Existing logic for test/registered/ paths
if file_part.startswith("test/"):
file_part = file_part[len("test/") :]
if "/" not in file_part:
# Try test/registered/ first
matches = glob.glob(f"test/registered/**/{file_part}", recursive=True)
if len(matches) == 0:
# Try multimodal test directory
mm_matches = glob.glob(f"{MULTIMODAL_TEST_DIR}/**/{file_part}", recursive=True)
# Filter to only test files
mm_matches = [m for m in mm_matches if os.path.basename(m).startswith("test_")]
if len(matches) == 1 and len(mm_matches) == 0:
return matches[0][len("test/") :], False, None
if len(matches) == 0 and len(mm_matches) == 1:
return mm_matches[0], True, None
all_matches = matches + mm_matches
if len(all_matches) == 0:
return (
None,
f"No test file found matching `{file_part}` under `test/registered/`.",
False,
f"No test file found matching `{file_part}` under `test/registered/` or `{MULTIMODAL_TEST_DIR}/`.",
)
if len(matches) > 1:
match_list = "\n".join(f"- `{m}`" for m in sorted(matches))
return None, (
f"Ambiguous filename `{file_part}` — matched {len(matches)} files:\n\n"
f"{match_list}\n\n"
f"Please provide the full path, e.g. `/rerun-test {matches[0]}`"
if len(all_matches) > 1:
match_list = "\n".join(f"- `{m}`" for m in sorted(all_matches))
return (
None,
False,
(
f"Ambiguous filename `{file_part}` — matched {len(all_matches)} files:\n\n"
f"{match_list}\n\n"
f"Please provide the full path, e.g. `/rerun-test {all_matches[0]}`"
),
)
return matches[0][len("test/") :], None
# Shouldn't reach here, but handle gracefully
if mm_matches:
return mm_matches[0], True, None
return matches[0][len("test/") :], False, None
# Path with directory - check test/ location
full_path = f"test/{file_part}"
if not os.path.isfile(full_path):
return None, f"File not found: `{full_path}`"
return file_part, None
if os.path.isfile(full_path):
return file_part, False, None
return None, False, f"File not found: `{full_path}`"
def detect_multimodal_suite(file_path):
"""
Determine runner for a multimodal gen test file based on its path.
Returns (runner_label, error_message).
"""
# Check path components and basename for GPU count hints
for pattern, runner in MULTIMODAL_PATH_TO_RUNNER.items():
if pattern in file_path:
return runner, None
return MULTIMODAL_DEFAULT_RUNNER, None
def detect_suite(file_path_from_test):
@@ -598,10 +662,35 @@ def _resolve_test_spec(test_spec):
if test_selector:
test_selector = test_selector.strip()
resolved_path, err = resolve_test_file(file_part)
resolved_path, is_multimodal, err = resolve_test_file(file_part)
if err:
return {"spec": test_spec, "error": err}
if is_multimodal:
runner_label, err = detect_multimodal_suite(resolved_path)
if err:
return {"spec": test_spec, "error": err}
# For multimodal pytest tests, use :: separator for test selection
test_command = resolved_path
if test_selector:
test_command = f"{resolved_path}::{test_selector}"
print(
f"Resolved (multimodal): file={resolved_path}, selector={test_selector}, "
f"runner={runner_label}, command='{test_command}'"
)
return {
"spec": test_spec,
"test_command": test_command,
"suite": "multimodal",
"runner_label": runner_label,
"use_deepep": False,
"is_cpu": False,
"install_diffusion": True,
"error": None,
}
suite, runner_label, use_deepep, is_cpu, err = detect_suite(resolved_path)
if err:
return {"spec": test_spec, "error": err}
@@ -622,6 +711,7 @@ def _resolve_test_spec(test_spec):
"runner_label": runner_label,
"use_deepep": use_deepep,
"is_cpu": is_cpu,
"install_diffusion": False,
"error": None,
}
@@ -637,6 +727,7 @@ def _dispatch_batch(gh_repo, pr, batch, token):
runner_label = batch[0]["runner_label"]
use_deepep = batch[0]["use_deepep"]
is_cpu = batch[0]["is_cpu"]
install_diffusion = batch[0].get("install_diffusion", False)
# Join multiple commands with newlines for the workflow to iterate over
combined_command = "\n".join(test_commands)
@@ -667,6 +758,7 @@ def _dispatch_batch(gh_repo, pr, batch, token):
"runner_label": runner_label,
"use_deepep": str(use_deepep).lower(),
"is_cpu": str(is_cpu).lower(),
"install_diffusion": str(install_diffusion).lower(),
}
if is_fork:
ref = "main"
@@ -777,10 +869,15 @@ def handle_rerun_test(gh_repo, pr, comment, user_perms, test_specs, token):
else:
resolved.append(r)
# Phase 2: Group by (runner_label, use_deepep, is_cpu)
# Phase 2: Group by (runner_label, use_deepep, is_cpu, install_diffusion)
groups = {}
for r in resolved:
key = (r["runner_label"], r["use_deepep"], r["is_cpu"])
key = (
r["runner_label"],
r["use_deepep"],
r["is_cpu"],
r.get("install_diffusion", False),
)
groups.setdefault(key, []).append(r)
# Phase 3: Dispatch one workflow per group
@@ -792,9 +889,19 @@ def handle_rerun_test(gh_repo, pr, comment, user_perms, test_specs, token):
lines = []
for dr in dispatch_results:
if dr["success"]:
cmds = "\n".join(
f"cd test/ && python3 {cmd}" for cmd in dr["test_commands"]
install_diff = any(
r.get("install_diffusion", False)
for r in resolved
if r["spec"] in dr["specs"]
)
if install_diff:
cmds = "\n".join(
f"python3 -m pytest {cmd} -x" for cmd in dr["test_commands"]
)
else:
cmds = "\n".join(
f"cd test/ && python3 {cmd}" for cmd in dr["test_commands"]
)
if dr.get("run_url"):
lines.append(
f"✅ `{dr['runner_label']}` ({len(dr['test_commands'])} test{'s' if len(dr['test_commands']) > 1 else ''}): "