[CI] /rerun-stage: auto-include wheel build when PR modifies sgl-kernel/ (#23492)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kangyan-Zhou
2026-04-22 11:28:06 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent de962f3274
commit 14ac14287c
2 changed files with 92 additions and 39 deletions
+66 -25
View File
@@ -25,6 +25,11 @@ on:
required: false required: false
type: string type: string
default: "" default: ""
include_wheel_build:
description: "When set with target_stage, also run sgl-kernel-build-wheels so the target stage uses the freshly-built kernel (for /rerun-stage on PRs that modify sgl-kernel/)"
required: false
type: boolean
default: false
test_parallel_dispatch: test_parallel_dispatch:
description: "Test parallel dispatch behavior (simulates scheduled run)" description: "Test parallel dispatch behavior (simulates scheduled run)"
required: false required: false
@@ -80,10 +85,14 @@ jobs:
outputs: outputs:
# Use API-based detection for target_stage mode (filter-api), otherwise use dorny/paths-filter (filter) # Use API-based detection for target_stage mode (filter-api), otherwise use dorny/paths-filter (filter)
main_package: ${{ steps.filter-api.outputs.main_package || steps.filter.outputs.main_package || steps.run-mode.outputs.run_all_tests }} main_package: ${{ steps.filter-api.outputs.main_package || steps.filter.outputs.main_package || steps.run-mode.outputs.run_all_tests }}
# sgl_kernel is forced to false when target_stage is set, since sgl-kernel-build-wheels won't run # sgl_kernel is forced to false when target_stage is set AND include_wheel_build is NOT set,
# This prevents CUSTOM_BUILD_SGL_KERNEL=true when the wheel artifacts aren't available # since sgl-kernel-build-wheels normally skips in target_stage mode. When include_wheel_build
# Note: If PR has kernel changes AND target_stage is set, the validate-target-stage step will fail # is true, keep the real value so the wheel build runs and the target stage downloads its
sgl_kernel: ${{ !inputs.target_stage && (steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel) }} # artifact (used by /rerun-stage on PRs that modify sgl-kernel/).
# This prevents CUSTOM_BUILD_SGL_KERNEL=true when the wheel artifacts aren't available.
# Note: If PR has kernel changes AND target_stage is set AND include_wheel_build is NOT set,
# the validate-target-stage step will fail.
sgl_kernel: ${{ (!inputs.target_stage || inputs.include_wheel_build) && (steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel) }}
# Raw sgl_kernel value before target_stage override (used for validation) # Raw sgl_kernel value before target_stage override (used for validation)
sgl_kernel_raw: ${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }} sgl_kernel_raw: ${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }}
jit_kernel: ${{ steps.filter-api.outputs.jit_kernel || steps.filter.outputs.jit_kernel || steps.run-mode.outputs.run_all_tests }} jit_kernel: ${{ steps.filter-api.outputs.jit_kernel || steps.filter.outputs.jit_kernel || steps.run-mode.outputs.run_all_tests }}
@@ -298,20 +307,26 @@ jobs:
fi fi
- name: Validate target_stage with kernel changes - name: Validate target_stage with kernel changes
# Use API-based detection (filter-api) for target_stage mode, otherwise use dorny/paths-filter (filter) # Fail only when PR has sgl-kernel changes AND the caller didn't opt into include_wheel_build.
if: inputs.target_stage && (steps.filter-api.outputs.sgl_kernel == 'true' || steps.filter.outputs.sgl_kernel == 'true') # include_wheel_build=true means sgl-kernel-build-wheels will run alongside the target stage
# (see the sgl_kernel output and sgl-kernel-build-wheels if-conditions above/below), so it's
# safe to proceed.
if: inputs.target_stage && !inputs.include_wheel_build && (steps.filter-api.outputs.sgl_kernel == 'true' || steps.filter.outputs.sgl_kernel == 'true')
run: | run: |
echo "::error::Cannot use /rerun-stage when PR has sgl-kernel changes." echo "::error::Cannot use /rerun-stage when PR has sgl-kernel changes without include_wheel_build."
echo "::error::The sgl-kernel-build-wheels job is skipped in target_stage mode, but this PR modifies sgl-kernel/ files." echo "::error::The sgl-kernel-build-wheels job is skipped in target_stage mode by default, but this PR modifies sgl-kernel/ files."
echo "::error::Please use /tag-and-rerun-ci to run the full workflow including kernel builds." echo "::error::The slash-command handler should have set include_wheel_build=true automatically; falling back to /tag-and-rerun-ci."
echo "" echo ""
echo "ERROR: Cannot use /rerun-stage when PR has sgl-kernel changes." echo "ERROR: Cannot use /rerun-stage when PR has sgl-kernel changes without include_wheel_build."
echo "" echo ""
echo "This PR modifies files in sgl-kernel/, which requires building custom kernel wheels." echo "This PR modifies files in sgl-kernel/, which requires building custom kernel wheels."
echo "The /rerun-stage command skips the wheel build job, so the test would run against" echo "Running the target stage without rebuilding the kernel would use the wrong (PyPI)"
echo "the wrong (PyPI) version of sgl-kernel instead of your changes." echo "version of sgl-kernel instead of your changes."
echo "" echo ""
echo "To properly test your kernel changes, use one of these commands instead:" echo "The /rerun-stage handler sets include_wheel_build=true automatically when it detects"
echo "sgl-kernel/ changes on the PR. If you see this error, the handler may be outdated."
echo ""
echo "Alternatives:"
echo " /tag-and-rerun-ci - Re-run the full workflow including kernel builds" echo " /tag-and-rerun-ci - Re-run the full workflow including kernel builds"
echo " /rerun-ci - Re-run the full workflow" echo " /rerun-ci - Re-run the full workflow"
echo "" echo ""
@@ -326,7 +341,7 @@ jobs:
echo "|-------------------|---------|" echo "|-------------------|---------|"
echo "| main_package | ${{ steps.filter-api.outputs.main_package || steps.filter.outputs.main_package || steps.run-mode.outputs.run_all_tests }} |" echo "| main_package | ${{ steps.filter-api.outputs.main_package || steps.filter.outputs.main_package || steps.run-mode.outputs.run_all_tests }} |"
echo "| sgl_kernel (raw) | ${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }} |" echo "| sgl_kernel (raw) | ${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }} |"
echo "| sgl_kernel (used) | ${{ !inputs.target_stage && (steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel) }} |" echo "| sgl_kernel (used) | ${{ (!inputs.target_stage || inputs.include_wheel_build) && (steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel) }} |"
echo "| jit_kernel | ${{ steps.filter-api.outputs.jit_kernel || steps.filter.outputs.jit_kernel || steps.run-mode.outputs.run_all_tests }} |" echo "| jit_kernel | ${{ steps.filter-api.outputs.jit_kernel || steps.filter.outputs.jit_kernel || steps.run-mode.outputs.run_all_tests }} |"
echo "| multimodal_gen | ${{ steps.filter-api.outputs.multimodal_gen || steps.filter.outputs.multimodal_gen || steps.run-mode.outputs.run_all_tests }} |" echo "| multimodal_gen | ${{ steps.filter-api.outputs.multimodal_gen || steps.filter.outputs.multimodal_gen || steps.run-mode.outputs.run_all_tests }} |"
echo "| target_stage | ${{ inputs.target_stage || '(none)' }} |" echo "| target_stage | ${{ inputs.target_stage || '(none)' }} |"
@@ -421,8 +436,22 @@ jobs:
sgl-kernel-build-wheels: sgl-kernel-build-wheels:
needs: [check-changes, call-gate] needs: [check-changes, call-gate]
# Skip for scheduled runs (they run stages independently) and when target_stage is set # Skip for scheduled runs (they run stages independently). Runs in target_stage mode only when
if: github.event_name != 'schedule' && inputs.test_parallel_dispatch != true && !inputs.target_stage && needs.check-changes.outputs.sgl_kernel == 'true' # include_wheel_build is true (i.e. /rerun-stage on a PR with sgl-kernel changes), so the
# target stage can download the freshly-built wheel.
#
# `always()` lets us run when call-gate is skipped (which it always is in target_stage mode by
# design). The explicit needs.<x>.result checks preserve old gating for the normal PR path.
if: |
always() &&
github.event_name != 'schedule' &&
inputs.test_parallel_dispatch != true &&
needs.check-changes.result == 'success' &&
needs.check-changes.outputs.sgl_kernel == 'true' &&
(
(!inputs.target_stage && needs.call-gate.result == 'success') ||
(inputs.target_stage && inputs.include_wheel_build)
)
runs-on: x64-kernel-build-node runs-on: x64-kernel-build-node
timeout-minutes: 240 timeout-minutes: 240
strategy: strategy:
@@ -469,8 +498,20 @@ jobs:
sgl-kernel-build-wheels-arm: sgl-kernel-build-wheels-arm:
needs: [check-changes, call-gate] needs: [check-changes, call-gate]
# Skip for scheduled runs (they run stages independently) and when target_stage is set # Skip for scheduled runs (they run stages independently). Runs in target_stage mode only when
if: github.event_name != 'schedule' && inputs.test_parallel_dispatch != true && !inputs.target_stage && needs.check-changes.outputs.sgl_kernel == 'true' # include_wheel_build is true (i.e. /rerun-stage on a PR with sgl-kernel changes).
#
# See sgl-kernel-build-wheels above for the always() + result-check rationale.
if: |
always() &&
github.event_name != 'schedule' &&
inputs.test_parallel_dispatch != true &&
needs.check-changes.result == 'success' &&
needs.check-changes.outputs.sgl_kernel == 'true' &&
(
(!inputs.target_stage && needs.call-gate.result == 'success') ||
(inputs.target_stage && inputs.include_wheel_build)
)
runs-on: arm-kernel-build-node runs-on: arm-kernel-build-node
timeout-minutes: 240 timeout-minutes: 240
strategy: strategy:
@@ -946,7 +987,7 @@ jobs:
secrets: inherit secrets: inherit
stage-c-test-4-gpu-h100: stage-c-test-4-gpu-h100:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
@@ -1004,7 +1045,7 @@ jobs:
run: bash scripts/ci/cuda/ci_cleanup_venv.sh run: bash scripts/ci/cuda/ci_cleanup_venv.sh
stage-c-test-8-gpu-h200: stage-c-test-8-gpu-h200:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
@@ -1081,7 +1122,7 @@ jobs:
run: bash scripts/ci/cuda/ci_cleanup_venv.sh run: bash scripts/ci/cuda/ci_cleanup_venv.sh
stage-c-test-8-gpu-h20: stage-c-test-8-gpu-h20:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
@@ -1142,7 +1183,7 @@ jobs:
run: bash scripts/ci/cuda/ci_cleanup_venv.sh run: bash scripts/ci/cuda/ci_cleanup_venv.sh
stage-c-test-deepep-4-gpu-h100: stage-c-test-deepep-4-gpu-h100:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
@@ -1211,7 +1252,7 @@ jobs:
run: bash scripts/ci/cuda/ci_cleanup_venv.sh run: bash scripts/ci/cuda/ci_cleanup_venv.sh
stage-c-test-deepep-8-gpu-h200: stage-c-test-deepep-8-gpu-h200:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
@@ -1281,7 +1322,7 @@ jobs:
run: bash scripts/ci/cuda/ci_cleanup_venv.sh run: bash scripts/ci/cuda/ci_cleanup_venv.sh
stage-c-test-4-gpu-b200: stage-c-test-4-gpu-b200:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
@@ -1340,7 +1381,7 @@ jobs:
run: bash scripts/ci/cuda/ci_cleanup_venv.sh run: bash scripts/ci/cuda/ci_cleanup_venv.sh
stage-c-test-4-gpu-b200-small: stage-c-test-4-gpu-b200-small:
needs: [check-changes, call-gate, wait-for-stage-b] needs: [check-changes, call-gate, wait-for-stage-b, sgl-kernel-build-wheels]
if: | if: |
always() && always() &&
( (
+26 -14
View File
@@ -367,6 +367,18 @@ def handle_rerun_stage(
) )
print(f"PR is from fork: {is_fork}") print(f"PR is from fork: {is_fork}")
# If the PR modifies sgl-kernel/, the target stage would otherwise use the
# PyPI sgl-kernel wheel instead of the PR's changes (sgl-kernel-build-wheels
# skips in target_stage mode by default). Set include_wheel_build=true so the
# workflow runs sgl-kernel-build-wheels alongside the target stage; the target
# stage waits for the build via its needs list.
kernel_changes = has_sgl_kernel_changes(pr)
if kernel_changes:
print(
"PR modifies sgl-kernel/ - setting include_wheel_build=true so the "
"target stage gets the freshly-built wheel instead of the PyPI one."
)
# pr_head_sha is used for fork PRs (passed to workflow and used for URL lookup) # pr_head_sha is used for fork PRs (passed to workflow and used for URL lookup)
pr_head_sha = None pr_head_sha = None
@@ -378,25 +390,25 @@ def handle_rerun_stage(
print( print(
f"Triggering {workflow_name} workflow on ref: {ref}, PR head SHA: {pr_head_sha}" f"Triggering {workflow_name} workflow on ref: {ref}, PR head SHA: {pr_head_sha}"
) )
if is_amd_stage: inputs = {
inputs = { "target_stage": stage_name,
"target_stage": stage_name, "pr_head_sha": pr_head_sha,
"pr_head_sha": pr_head_sha, }
}
else:
inputs = {
"target_stage": stage_name,
"pr_head_sha": pr_head_sha,
}
else: else:
# For non-fork PRs: dispatch on the PR branch directly # For non-fork PRs: dispatch on the PR branch directly
# This allows testing workflow changes before merge # This allows testing workflow changes before merge
ref = pr.head.ref ref = pr.head.ref
print(f"Triggering {workflow_name} workflow on branch: {ref}") print(f"Triggering {workflow_name} workflow on branch: {ref}")
if is_amd_stage: inputs = {"target_stage": stage_name}
inputs = {"target_stage": stage_name}
else: # For NVIDIA stages, honor the sgl-kernel / include_wheel_build flow. AMD is
inputs = {"target_stage": stage_name} # a separate workflow that doesn't share the same wheel-build pipeline.
if kernel_changes and not is_amd_stage:
inputs["include_wheel_build"] = "true"
# include_wheel_build relies on filter-api detecting kernel changes, which
# requires pr_head_sha. Ensure it's set even for non-fork PRs.
if not is_fork:
inputs["pr_head_sha"] = pr.head.sha
# Record dispatch time before triggering # Record dispatch time before triggering
dispatch_time = time.time() dispatch_time = time.time()