From 938198e91c61166bd01f20e5d4ab3327d6ef07d4 Mon Sep 17 00:00:00 2001 From: Liangsheng Yin Date: Tue, 12 May 2026 22:05:11 -0700 Subject: [PATCH] ci: extract check-changes into reusable workflow (#25132) --- .github/workflows/_pr-test-check-changes.yml | 325 +++++++++++++++++++ .github/workflows/pr-test.yml | 285 +--------------- 2 files changed, 334 insertions(+), 276 deletions(-) create mode 100644 .github/workflows/_pr-test-check-changes.yml diff --git a/.github/workflows/_pr-test-check-changes.yml b/.github/workflows/_pr-test-check-changes.yml new file mode 100644 index 000000000..a657dac69 --- /dev/null +++ b/.github/workflows/_pr-test-check-changes.yml @@ -0,0 +1,325 @@ +name: Check Changes + +on: + workflow_call: + inputs: + pr_head_sha: + type: string + default: '' + git_ref: + type: string + default: '' + target_stage: + type: string + default: '' + include_wheel_build: + type: boolean + default: false + run_all_tests: + type: boolean + default: false + force_continue_on_error: + type: boolean + default: false + outputs: + main_package: + value: ${{ jobs.run.outputs.main_package }} + sgl_kernel: + value: ${{ jobs.run.outputs.sgl_kernel }} + sgl_kernel_raw: + value: ${{ jobs.run.outputs.sgl_kernel_raw }} + jit_kernel: + value: ${{ jobs.run.outputs.jit_kernel }} + multimodal_gen: + value: ${{ jobs.run.outputs.multimodal_gen }} + max_parallel: + value: ${{ jobs.run.outputs.max_parallel }} + max_parallel_small: + value: ${{ jobs.run.outputs.max_parallel_small }} + max_parallel_2gpu: + value: ${{ jobs.run.outputs.max_parallel_2gpu }} + b200_runner: + value: ${{ jobs.run.outputs.b200_runner }} + enable_retry: + value: ${{ jobs.run.outputs.enable_retry }} + continue_on_error: + value: ${{ jobs.run.outputs.continue_on_error }} + +jobs: + run: + runs-on: ubuntu-latest + outputs: + # 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 }} + # sgl_kernel is forced to false when target_stage is set AND include_wheel_build is NOT set, + # since sgl-kernel-build-wheels normally skips in target_stage mode. When include_wheel_build + # is true, keep the real value so the wheel build runs and the target stage downloads its + # 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) + 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 }} + multimodal_gen: ${{ steps.filter-api.outputs.multimodal_gen || steps.filter.outputs.multimodal_gen || steps.run-mode.outputs.run_all_tests }} + max_parallel: ${{ steps.set-parallel.outputs.max_parallel }} + max_parallel_small: ${{ steps.set-parallel.outputs.max_parallel_small }} + max_parallel_2gpu: ${{ steps.set-parallel.outputs.max_parallel_2gpu }} + b200_runner: ${{ steps.set-runner.outputs.b200_runner }} + enable_retry: ${{ steps.set-retry.outputs.enable_retry }} + continue_on_error: ${{ steps.set-continue-on-error.outputs.continue_on_error }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.pr_head_sha || inputs.git_ref || github.sha }} + + - uses: ./.github/actions/check-maintenance + + - name: Determine run mode + id: run-mode + run: | + # Run all tests for scheduled runs and workflow_call (when ref input is provided) + # Note: github.event_name is inherited from caller, so we detect workflow_call by checking inputs.git_ref + if [[ "${{ github.event_name }}" == "schedule" || "${{ inputs.run_all_tests }}" == "true" ]]; then + echo "run_all_tests=true" >> $GITHUB_OUTPUT + echo "Run mode: ALL TESTS (schedule=${{ github.event_name == 'schedule' }}, run_all_tests=${{ inputs.run_all_tests }})" + else + echo "run_all_tests=false" >> $GITHUB_OUTPUT + echo "Run mode: FILTERED (triggered by ${{ github.event_name }})" + fi + + - name: Detect file changes + id: filter + uses: dorny/paths-filter@v3 + # Only use paths-filter for pull_request events (where it works correctly) + # For workflow_dispatch with target_stage, we use GitHub API in the next step + if: steps.run-mode.outputs.run_all_tests != 'true' && !inputs.target_stage + with: + filters: | + main_package: + - ".github/workflows/pr-test.yml" + - ".github/workflows/pr-gate.yml" + - ".github/actions/**" + - "python/pyproject.toml" + - "python/sglang/!(multimodal_gen)/**/!(*.md)" + - "scripts/ci/cuda/*" + - "scripts/ci/utils/*" + - "test/**/!(*.md)" + multimodal_gen: + - ".github/workflows/pr-test.yml" + - ".github/workflows/pr-test-multimodal-gen.yml" + - "python/pyproject.toml" + - "python/sglang/multimodal_gen/**/!(*.md|*.ipynb)" + - "python/sglang/jit_kernel/**" + - "python/sglang/jit_kernel/tests/diffusion/**" + - "python/sglang/jit_kernel/benchmark/diffusion/**" + - "python/sglang/cli/**" + jit_kernel: + - ".github/workflows/pr-test.yml" + - ".github/workflows/pr-test-jit-kernel.yml" + - "python/pyproject.toml" + - "python/sglang/jit_kernel/**" + sgl_kernel: + # Intentionally excludes ".github/workflows/pr-test-sgl-kernel.yml" — + # see API-side detector below for rationale. + - "sgl-kernel/**/!(*.md|THIRDPARTYNOTICES.txt|LICENSE)" + + # For /rerun-stage (workflow_dispatch with target_stage), dorny/paths-filter doesn't work + # correctly because it falls back to "last commit" detection which breaks for merge commits. + # Instead, we use the GitHub API to compare the PR commit against main. + - name: Detect file changes via API (for target_stage) + id: filter-api + if: inputs.target_stage && inputs.pr_head_sha + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "Detecting file changes via GitHub API for target_stage mode..." + echo "PR head SHA: ${{ inputs.pr_head_sha }}" + + # Get the list of changed files by comparing PR commit against main + # This correctly handles merge commits by looking at the actual PR diff + CHANGED_FILES=$(gh api "repos/${{ github.repository }}/compare/main...${{ inputs.pr_head_sha }}" \ + --jq '[.files[].filename] | .[]' 2>/dev/null || echo "") + + if [ -z "$CHANGED_FILES" ]; then + echo "Warning: Could not fetch changed files from API, assuming no changes" + echo "sgl_kernel=false" >> $GITHUB_OUTPUT + echo "main_package=false" >> $GITHUB_OUTPUT + echo "jit_kernel=false" >> $GITHUB_OUTPUT + echo "multimodal_gen=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Changed files:" + echo "$CHANGED_FILES" | head -20 + echo "..." + + # Check for sgl-kernel changes + # Note: edits to .github/workflows/pr-test-sgl-kernel.yml are intentionally + # NOT considered sgl-kernel changes. That filter line used to be included + # so workflow refactors got retested, but in practice it only catches the + # workflow's *consumers* (test job definitions), not the wheel build steps + # themselves — and gating sgl_kernel=true on it forces a 20-30 min wheel + # rebuild + the stage-a-test-1-gpu-small gate for pure CI-yaml edits that + # can't actually affect kernel behavior. PRs that touch wheel-build logic + # in scripts/ci/cuda/ or sgl-kernel/ still trigger correctly. + if echo "$CHANGED_FILES" | grep -qE "^sgl-kernel/"; then + echo "sgl_kernel=true" >> $GITHUB_OUTPUT + echo "Detected sgl-kernel changes" + else + echo "sgl_kernel=false" >> $GITHUB_OUTPUT + fi + + # Check for main_package changes (excluding multimodal_gen, jit_kernel/diffusion, jit_kernel/tests/diffusion, jit_kernel/benchmark/diffusion, cli) + # Note: Need to filter out multimodal_gen and diffusion-related paths before checking, not pipe grep -q output + MAIN_PKG_FILES=$(echo "$CHANGED_FILES" | grep -E "^(python/sglang/|python/pyproject\.toml|scripts/ci/cuda/|scripts/ci/utils/|test/|\.github/workflows/pr-test\.yml|\.github/workflows/pr-gate\.yml|\.github/actions/)" | grep -v -E "^(python/sglang/multimodal_gen/|python/sglang/jit_kernel/diffusion/|python/sglang/jit_kernel/tests/diffusion/|python/sglang/jit_kernel/benchmark/diffusion/|python/sglang/cli/)" || true) + if [ -n "$MAIN_PKG_FILES" ]; then + echo "main_package=true" >> $GITHUB_OUTPUT + echo "Detected main_package changes" + else + echo "main_package=false" >> $GITHUB_OUTPUT + fi + + # Check for jit_kernel changes + if echo "$CHANGED_FILES" | grep -qE "^(python/sglang/jit_kernel/|python/pyproject\.toml|\.github/workflows/pr-test\.yml|\.github/workflows/pr-test-jit-kernel\.yml)"; then + echo "jit_kernel=true" >> $GITHUB_OUTPUT + echo "Detected jit_kernel changes" + else + echo "jit_kernel=false" >> $GITHUB_OUTPUT + fi + + # Check for multimodal_gen changes, including diffusion-specific jit_kernel coverage + if echo "$CHANGED_FILES" | grep -qE "^(python/sglang/multimodal_gen/|python/sglang/cli/|python/sglang/jit_kernel/diffusion/|python/sglang/jit_kernel/tests/diffusion/|python/sglang/jit_kernel/benchmark/diffusion/|python/pyproject\.toml|\.github/workflows/pr-test\.yml|\.github/workflows/pr-test-multimodal-gen\.yml)"; then + echo "multimodal_gen=true" >> $GITHUB_OUTPUT + echo "Detected multimodal_gen changes" + else + echo "multimodal_gen=false" >> $GITHUB_OUTPUT + fi + + - name: Set max-parallel based on run type + id: set-parallel + env: + GH_TOKEN: ${{ github.token }} + run: | + # Determine if this run gets full parallelism (scheduled / high priority) + FULL=false + if [[ "${{ github.event_name }}" == "schedule" ]]; then + FULL=true + echo "Scheduled run detected, using full parallelism" + elif [[ "${{ github.event_name }}" == "pull_request" && "${{ contains(github.event.pull_request.labels.*.name, 'high priority') }}" == "true" ]]; then + FULL=true + echo "High priority PR detected, using full parallelism" + elif [[ -n "${{ inputs.target_stage }}" ]]; then + # /rerun-stage (workflow_dispatch): query PR labels via GitHub API + # Try SHA lookup first (fork PRs), fallback to branch name (non-fork PRs) + LABELS="" + PR_HEAD_SHA="${{ inputs.pr_head_sha }}" + if [[ -n "$PR_HEAD_SHA" ]]; then + LABELS=$(gh api "repos/${{ github.repository }}/commits/${PR_HEAD_SHA}/pulls" \ + --jq '.[0].labels[].name' 2>/dev/null || true) + fi + if [[ -z "$LABELS" ]]; then + LABELS=$(gh pr list --head "${{ github.ref_name }}" --repo "${{ github.repository }}" \ + --json labels --jq '.[0].labels[].name' 2>/dev/null || true) + fi + echo "PR labels: ${LABELS:-"(none)"}" + if echo "$LABELS" | grep -Fxq "high priority"; then + FULL=true + echo "High priority PR detected via API (/rerun-stage), using full parallelism" + fi + fi + + # Set max-parallel for each runner type + # 1-gpu-h100: 14 partitions, 1-gpu-5090: 8 partitions, 2-gpu-h100: 4 partitions + if [[ "$FULL" == "true" ]]; then + LEVEL=full + echo "max_parallel=14" >> $GITHUB_OUTPUT + echo "max_parallel_small=8" >> $GITHUB_OUTPUT + echo "max_parallel_2gpu=4" >> $GITHUB_OUTPUT + else + LEVEL=low + echo "max_parallel=3" >> $GITHUB_OUTPUT + echo "max_parallel_small=3" >> $GITHUB_OUTPUT + echo "max_parallel_2gpu=2" >> $GITHUB_OUTPUT + fi + echo "parallel_level=$LEVEL" >> $GITHUB_OUTPUT + echo "Parallelism level: $LEVEL" + + - name: Set B200 runner tag + id: set-runner + run: | + # Use kernel-build runner only when sgl_kernel changes are detected AND we're not in target_stage mode + # (target_stage skips wheel builds, so we can't use custom kernels) + # Use API-based detection (filter-api) for target_stage mode, otherwise use dorny/paths-filter (filter) + sgl_kernel="${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }}" + target_stage="${{ inputs.target_stage }}" + if [[ "$sgl_kernel" == "true" && -z "$target_stage" ]]; then + echo "b200_runner=4-gpu-b200-kernel" >> $GITHUB_OUTPUT + else + echo "b200_runner=4-gpu-b200" >> $GITHUB_OUTPUT + fi + + - name: Enable retry for CI + id: set-retry + run: | + echo "enable_retry=true" >> $GITHUB_OUTPUT + echo "Retry logic enabled for CI" + + - name: Set continue-on-error for full test runs + id: set-continue-on-error + run: | + if [[ "${{ steps.run-mode.outputs.run_all_tests }}" == "true" || "${{ inputs.force_continue_on_error }}" == "true" ]]; then + echo "continue_on_error=true" >> $GITHUB_OUTPUT + echo "Full test run or force flag detected, enabling continue-on-error to run all tests" + else + echo "continue_on_error=false" >> $GITHUB_OUTPUT + echo "Filtered run, continue-on-error disabled" + fi + + - name: Validate target_stage with kernel changes + # Fail only when PR has sgl-kernel changes AND the caller didn't opt into include_wheel_build. + # 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: | + 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 by default, but this PR modifies sgl-kernel/ files." + echo "::error::The slash-command handler should have set include_wheel_build=true automatically; falling back to /tag-and-rerun-ci." + echo "" + echo "ERROR: Cannot use /rerun-stage when PR has sgl-kernel changes without include_wheel_build." + echo "" + echo "This PR modifies files in sgl-kernel/, which requires building custom kernel wheels." + echo "Running the target stage without rebuilding the kernel would use the wrong (PyPI)" + echo "version of sgl-kernel instead of your changes." + echo "" + 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 " /rerun-ci - Re-run the full workflow" + echo "" + exit 1 + + - name: Show filter results in summary (table) + run: | + { + echo "## Change Detection" + echo "" + echo "| Component | Changed |" + echo "|-------------------|---------|" + 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 (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 "| 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 "| detection_method | ${{ inputs.target_stage && 'GitHub API' || 'dorny/paths-filter' }} |" + echo "| max_parallel | ${{ steps.set-parallel.outputs.parallel_level }} (h100=${{ steps.set-parallel.outputs.max_parallel }}, 5090=${{ steps.set-parallel.outputs.max_parallel_small }}, 2gpu=${{ steps.set-parallel.outputs.max_parallel_2gpu }}) |" + echo "| b200_runner | ${{ steps.set-runner.outputs.b200_runner }} |" + echo "| enable_retry | ${{ steps.set-retry.outputs.enable_retry }} |" + echo "| continue_on_error | ${{ steps.set-continue-on-error.outputs.continue_on_error }} |" + } >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 8fb97d36a..8f663897f 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -83,282 +83,15 @@ permissions: jobs: # =============================================== check changes ==================================================== check-changes: - runs-on: ubuntu-latest - outputs: - # 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 }} - # sgl_kernel is forced to false when target_stage is set AND include_wheel_build is NOT set, - # since sgl-kernel-build-wheels normally skips in target_stage mode. When include_wheel_build - # is true, keep the real value so the wheel build runs and the target stage downloads its - # 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) - 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 }} - multimodal_gen: ${{ steps.filter-api.outputs.multimodal_gen || steps.filter.outputs.multimodal_gen || steps.run-mode.outputs.run_all_tests }} - max_parallel: ${{ steps.set-parallel.outputs.max_parallel }} - max_parallel_small: ${{ steps.set-parallel.outputs.max_parallel_small }} - max_parallel_2gpu: ${{ steps.set-parallel.outputs.max_parallel_2gpu }} - b200_runner: ${{ steps.set-runner.outputs.b200_runner }} - enable_retry: ${{ steps.set-retry.outputs.enable_retry }} - continue_on_error: ${{ steps.set-continue-on-error.outputs.continue_on_error }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ inputs.pr_head_sha || inputs.git_ref || github.sha }} - - - uses: ./.github/actions/check-maintenance - - - name: Determine run mode - id: run-mode - run: | - # Run all tests for scheduled runs and workflow_call (when ref input is provided) - # Note: github.event_name is inherited from caller, so we detect workflow_call by checking inputs.git_ref - if [[ "${{ github.event_name }}" == "schedule" || "${{ inputs.run_all_tests }}" == "true" ]]; then - echo "run_all_tests=true" >> $GITHUB_OUTPUT - echo "Run mode: ALL TESTS (schedule=${{ github.event_name == 'schedule' }}, run_all_tests=${{ inputs.run_all_tests }})" - else - echo "run_all_tests=false" >> $GITHUB_OUTPUT - echo "Run mode: FILTERED (triggered by ${{ github.event_name }})" - fi - - - name: Detect file changes - id: filter - uses: dorny/paths-filter@v3 - # Only use paths-filter for pull_request events (where it works correctly) - # For workflow_dispatch with target_stage, we use GitHub API in the next step - if: steps.run-mode.outputs.run_all_tests != 'true' && !inputs.target_stage - with: - filters: | - main_package: - - ".github/workflows/pr-test.yml" - - ".github/workflows/pr-gate.yml" - - ".github/actions/**" - - "python/pyproject.toml" - - "python/sglang/!(multimodal_gen)/**/!(*.md)" - - "scripts/ci/cuda/*" - - "scripts/ci/utils/*" - - "test/**/!(*.md)" - multimodal_gen: - - ".github/workflows/pr-test.yml" - - ".github/workflows/pr-test-multimodal-gen.yml" - - "python/pyproject.toml" - - "python/sglang/multimodal_gen/**/!(*.md|*.ipynb)" - - "python/sglang/jit_kernel/**" - - "python/sglang/jit_kernel/tests/diffusion/**" - - "python/sglang/jit_kernel/benchmark/diffusion/**" - - "python/sglang/cli/**" - jit_kernel: - - ".github/workflows/pr-test.yml" - - ".github/workflows/pr-test-jit-kernel.yml" - - "python/pyproject.toml" - - "python/sglang/jit_kernel/**" - sgl_kernel: - # Intentionally excludes ".github/workflows/pr-test-sgl-kernel.yml" — - # see API-side detector below for rationale. - - "sgl-kernel/**/!(*.md|THIRDPARTYNOTICES.txt|LICENSE)" - - # For /rerun-stage (workflow_dispatch with target_stage), dorny/paths-filter doesn't work - # correctly because it falls back to "last commit" detection which breaks for merge commits. - # Instead, we use the GitHub API to compare the PR commit against main. - - name: Detect file changes via API (for target_stage) - id: filter-api - if: inputs.target_stage && inputs.pr_head_sha - env: - GH_TOKEN: ${{ github.token }} - run: | - echo "Detecting file changes via GitHub API for target_stage mode..." - echo "PR head SHA: ${{ inputs.pr_head_sha }}" - - # Get the list of changed files by comparing PR commit against main - # This correctly handles merge commits by looking at the actual PR diff - CHANGED_FILES=$(gh api "repos/${{ github.repository }}/compare/main...${{ inputs.pr_head_sha }}" \ - --jq '[.files[].filename] | .[]' 2>/dev/null || echo "") - - if [ -z "$CHANGED_FILES" ]; then - echo "Warning: Could not fetch changed files from API, assuming no changes" - echo "sgl_kernel=false" >> $GITHUB_OUTPUT - echo "main_package=false" >> $GITHUB_OUTPUT - echo "jit_kernel=false" >> $GITHUB_OUTPUT - echo "multimodal_gen=false" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Changed files:" - echo "$CHANGED_FILES" | head -20 - echo "..." - - # Check for sgl-kernel changes - # Note: edits to .github/workflows/pr-test-sgl-kernel.yml are intentionally - # NOT considered sgl-kernel changes. That filter line used to be included - # so workflow refactors got retested, but in practice it only catches the - # workflow's *consumers* (test job definitions), not the wheel build steps - # themselves — and gating sgl_kernel=true on it forces a 20-30 min wheel - # rebuild + the stage-a-test-1-gpu-small gate for pure CI-yaml edits that - # can't actually affect kernel behavior. PRs that touch wheel-build logic - # in scripts/ci/cuda/ or sgl-kernel/ still trigger correctly. - if echo "$CHANGED_FILES" | grep -qE "^sgl-kernel/"; then - echo "sgl_kernel=true" >> $GITHUB_OUTPUT - echo "Detected sgl-kernel changes" - else - echo "sgl_kernel=false" >> $GITHUB_OUTPUT - fi - - # Check for main_package changes (excluding multimodal_gen, jit_kernel/diffusion, jit_kernel/tests/diffusion, jit_kernel/benchmark/diffusion, cli) - # Note: Need to filter out multimodal_gen and diffusion-related paths before checking, not pipe grep -q output - MAIN_PKG_FILES=$(echo "$CHANGED_FILES" | grep -E "^(python/sglang/|python/pyproject\.toml|scripts/ci/cuda/|scripts/ci/utils/|test/|\.github/workflows/pr-test\.yml|\.github/workflows/pr-gate\.yml|\.github/actions/)" | grep -v -E "^(python/sglang/multimodal_gen/|python/sglang/jit_kernel/diffusion/|python/sglang/jit_kernel/tests/diffusion/|python/sglang/jit_kernel/benchmark/diffusion/|python/sglang/cli/)" || true) - if [ -n "$MAIN_PKG_FILES" ]; then - echo "main_package=true" >> $GITHUB_OUTPUT - echo "Detected main_package changes" - else - echo "main_package=false" >> $GITHUB_OUTPUT - fi - - # Check for jit_kernel changes - if echo "$CHANGED_FILES" | grep -qE "^(python/sglang/jit_kernel/|python/pyproject\.toml|\.github/workflows/pr-test\.yml|\.github/workflows/pr-test-jit-kernel\.yml)"; then - echo "jit_kernel=true" >> $GITHUB_OUTPUT - echo "Detected jit_kernel changes" - else - echo "jit_kernel=false" >> $GITHUB_OUTPUT - fi - - # Check for multimodal_gen changes, including diffusion-specific jit_kernel coverage - if echo "$CHANGED_FILES" | grep -qE "^(python/sglang/multimodal_gen/|python/sglang/cli/|python/sglang/jit_kernel/diffusion/|python/sglang/jit_kernel/tests/diffusion/|python/sglang/jit_kernel/benchmark/diffusion/|python/pyproject\.toml|\.github/workflows/pr-test\.yml|\.github/workflows/pr-test-multimodal-gen\.yml)"; then - echo "multimodal_gen=true" >> $GITHUB_OUTPUT - echo "Detected multimodal_gen changes" - else - echo "multimodal_gen=false" >> $GITHUB_OUTPUT - fi - - - name: Set max-parallel based on run type - id: set-parallel - env: - GH_TOKEN: ${{ github.token }} - run: | - # Determine if this run gets full parallelism (scheduled / high priority) - FULL=false - if [[ "${{ github.event_name }}" == "schedule" ]]; then - FULL=true - echo "Scheduled run detected, using full parallelism" - elif [[ "${{ github.event_name }}" == "pull_request" && "${{ contains(github.event.pull_request.labels.*.name, 'high priority') }}" == "true" ]]; then - FULL=true - echo "High priority PR detected, using full parallelism" - elif [[ -n "${{ inputs.target_stage }}" ]]; then - # /rerun-stage (workflow_dispatch): query PR labels via GitHub API - # Try SHA lookup first (fork PRs), fallback to branch name (non-fork PRs) - LABELS="" - PR_HEAD_SHA="${{ inputs.pr_head_sha }}" - if [[ -n "$PR_HEAD_SHA" ]]; then - LABELS=$(gh api "repos/${{ github.repository }}/commits/${PR_HEAD_SHA}/pulls" \ - --jq '.[0].labels[].name' 2>/dev/null || true) - fi - if [[ -z "$LABELS" ]]; then - LABELS=$(gh pr list --head "${{ github.ref_name }}" --repo "${{ github.repository }}" \ - --json labels --jq '.[0].labels[].name' 2>/dev/null || true) - fi - echo "PR labels: ${LABELS:-"(none)"}" - if echo "$LABELS" | grep -Fxq "high priority"; then - FULL=true - echo "High priority PR detected via API (/rerun-stage), using full parallelism" - fi - fi - - # Set max-parallel for each runner type - # 1-gpu-h100: 14 partitions, 1-gpu-5090: 8 partitions, 2-gpu-h100: 4 partitions - if [[ "$FULL" == "true" ]]; then - LEVEL=full - echo "max_parallel=14" >> $GITHUB_OUTPUT - echo "max_parallel_small=8" >> $GITHUB_OUTPUT - echo "max_parallel_2gpu=4" >> $GITHUB_OUTPUT - else - LEVEL=low - echo "max_parallel=3" >> $GITHUB_OUTPUT - echo "max_parallel_small=3" >> $GITHUB_OUTPUT - echo "max_parallel_2gpu=2" >> $GITHUB_OUTPUT - fi - echo "parallel_level=$LEVEL" >> $GITHUB_OUTPUT - echo "Parallelism level: $LEVEL" - - - name: Set B200 runner tag - id: set-runner - run: | - # Use kernel-build runner only when sgl_kernel changes are detected AND we're not in target_stage mode - # (target_stage skips wheel builds, so we can't use custom kernels) - # Use API-based detection (filter-api) for target_stage mode, otherwise use dorny/paths-filter (filter) - sgl_kernel="${{ steps.filter-api.outputs.sgl_kernel || steps.filter.outputs.sgl_kernel }}" - target_stage="${{ inputs.target_stage }}" - if [[ "$sgl_kernel" == "true" && -z "$target_stage" ]]; then - echo "b200_runner=4-gpu-b200-kernel" >> $GITHUB_OUTPUT - else - echo "b200_runner=4-gpu-b200" >> $GITHUB_OUTPUT - fi - - - name: Enable retry for CI - id: set-retry - run: | - echo "enable_retry=true" >> $GITHUB_OUTPUT - echo "Retry logic enabled for CI" - - - name: Set continue-on-error for full test runs - id: set-continue-on-error - run: | - if [[ "${{ steps.run-mode.outputs.run_all_tests }}" == "true" || "${{ inputs.force_continue_on_error }}" == "true" ]]; then - echo "continue_on_error=true" >> $GITHUB_OUTPUT - echo "Full test run or force flag detected, enabling continue-on-error to run all tests" - else - echo "continue_on_error=false" >> $GITHUB_OUTPUT - echo "Filtered run, continue-on-error disabled" - fi - - - name: Validate target_stage with kernel changes - # Fail only when PR has sgl-kernel changes AND the caller didn't opt into include_wheel_build. - # 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: | - 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 by default, but this PR modifies sgl-kernel/ files." - echo "::error::The slash-command handler should have set include_wheel_build=true automatically; falling back to /tag-and-rerun-ci." - echo "" - echo "ERROR: Cannot use /rerun-stage when PR has sgl-kernel changes without include_wheel_build." - echo "" - echo "This PR modifies files in sgl-kernel/, which requires building custom kernel wheels." - echo "Running the target stage without rebuilding the kernel would use the wrong (PyPI)" - echo "version of sgl-kernel instead of your changes." - echo "" - 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 " /rerun-ci - Re-run the full workflow" - echo "" - exit 1 - - - name: Show filter results in summary (table) - run: | - { - echo "## Change Detection" - echo "" - echo "| Component | Changed |" - echo "|-------------------|---------|" - 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 (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 "| 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 "| detection_method | ${{ inputs.target_stage && 'GitHub API' || 'dorny/paths-filter' }} |" - echo "| max_parallel | ${{ steps.set-parallel.outputs.parallel_level }} (h100=${{ steps.set-parallel.outputs.max_parallel }}, 5090=${{ steps.set-parallel.outputs.max_parallel_small }}, 2gpu=${{ steps.set-parallel.outputs.max_parallel_2gpu }}) |" - echo "| b200_runner | ${{ steps.set-runner.outputs.b200_runner }} |" - echo "| enable_retry | ${{ steps.set-retry.outputs.enable_retry }} |" - echo "| continue_on_error | ${{ steps.set-continue-on-error.outputs.continue_on_error }} |" - } >> $GITHUB_STEP_SUMMARY + uses: ./.github/workflows/_pr-test-check-changes.yml + with: + pr_head_sha: ${{ inputs.pr_head_sha || '' }} + git_ref: ${{ inputs.git_ref || '' }} + target_stage: ${{ inputs.target_stage || '' }} + include_wheel_build: ${{ inputs.include_wheel_build == true }} + run_all_tests: ${{ inputs.run_all_tests == true }} + force_continue_on_error: ${{ inputs.force_continue_on_error == true }} + secrets: inherit # =============================================== Wait Jobs for Sequential PR Execution ==================================================== # These jobs poll GitHub API to wait for previous stages to complete.