From e556e57fa3d1a1ec31a76d5509f0aab7d2fab567 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Sat, 17 Oct 2026 06:34:02 +0000 Subject: [PATCH 1/3] Run the required checks in GitHub's merge queue The merge queue builds a temporary branch of the target branch plus the queued pull requests and waits for the required status checks on it. The unit, code quality and docs workflows only ran on pull_request events, and the GPU, example and regression workflows only on pushes to the copy-pr-bot branches (pull-request/N) and on schedule, so no required check would ever report on a merge group. Add the merge_group trigger (checks_requested) to all six workflows and treat a merge group like a PR run: - The gate of the GPU, example and regression workflows and the unit tests' changed-files check diff the group against the commit it was built on (the pr-merge-base action reads it from the merge_group payload), so the same lanes run as on the PR and the wait for the unit `linux` check stays. - The *-pr-required-check aggregators, the single-GPU runners, the example timeouts, hf_ptq's -n 4 and the left-out `manual` unit tests all key off "a PR run", which now includes a merge group. - TruffleHog scans base..head of the group, like a PR. - The Codecov uploads are skipped: a queue commit never lands on main. Nightly, on-demand and PR runs behave as before. The queue itself is a ruleset setting (see the PR description); CONTRIBUTING.md describes it. Signed-off-by: Shengliang Xu --- .github/actions/pr-merge-base/action.yml | 22 +++++++++++++---- .github/workflows/_example_tests_runner.yml | 3 ++- .github/workflows/_pr_gate.yml | 13 +++++----- .github/workflows/code_quality.yml | 11 +++++++-- .github/workflows/example_tests.yml | 27 ++++++++++++--------- .github/workflows/gpu_tests.yml | 8 ++++-- .github/workflows/pages.yml | 3 +++ .github/workflows/regression_tests.yml | 8 ++++-- .github/workflows/unit_tests.yml | 17 +++++++++---- CONTRIBUTING.md | 6 ++++- 10 files changed, 82 insertions(+), 36 deletions(-) diff --git a/.github/actions/pr-merge-base/action.yml b/.github/actions/pr-merge-base/action.yml index cd4c1bc55df..d30d7436718 100644 --- a/.github/actions/pr-merge-base/action.yml +++ b/.github/actions/pr-merge-base/action.yml @@ -1,16 +1,17 @@ name: PR merge base description: > - Resolve the commit to diff a copied PR branch against: the merge base of the PR's head and its - target branch. Outputs are empty for non-PR triggers, which have no diff to inspect. + Resolve the commit to diff a PR run against. For a copied PR branch it is the merge base of the + PR's head and its target branch; for a merge queue group it is the commit the group was built + on. Outputs are empty for non-PR triggers, which have no diff to inspect. Requires a prior actions/checkout with fetch-depth 0. outputs: merge_base: description: Commit to use as the base for changed-file comparisons. - value: ${{ steps.calculate-merge-base.outputs.merge-base }} + value: ${{ steps.calculate-merge-base.outputs.merge-base || steps.merge-group.outputs.base_sha }} head_sha: - description: The PR's head commit. - value: ${{ steps.pr-shas.outputs.head_sha }} + description: The PR's head commit, or the merge queue group's head commit. + value: ${{ steps.pr-shas.outputs.head_sha || steps.merge-group.outputs.head_sha }} runs: using: composite @@ -36,3 +37,14 @@ runs: # status and emit an empty base, silently changing which lanes run. merge_base=$(git merge-base "${{ steps.pr-shas.outputs.base_sha }}" "${{ steps.pr-shas.outputs.head_sha }}") echo "merge-base=$merge_base" | tee --append "${GITHUB_OUTPUT}" + # A merge queue group is the target branch plus the queued PRs on top of it, so its diff + # against the commit it was built on is what the queue is about to merge. + - if: github.event_name == 'merge_group' + id: merge-group + shell: bash + env: + BASE_SHA: ${{ github.event.merge_group.base_sha }} + HEAD_SHA: ${{ github.event.merge_group.head_sha }} + run: | + echo "base_sha=$BASE_SHA" | tee --append "${GITHUB_OUTPUT}" + echo "head_sha=$HEAD_SHA" | tee --append "${GITHUB_OUTPUT}" diff --git a/.github/workflows/_example_tests_runner.yml b/.github/workflows/_example_tests_runner.yml index e3378865b65..3044ea66471 100644 --- a/.github/workflows/_example_tests_runner.yml +++ b/.github/workflows/_example_tests_runner.yml @@ -107,7 +107,7 @@ jobs: # Keep legacy ONNX/TRT tests available manually during deprecation. pytest_args+=(--ignore=tests/examples/diffusers/test_diffusers_onnx_trt.py) fi - if [[ "${{ inputs.example }}" == "hf_ptq" && "${{ github.ref }}" == refs/heads/pull-request/* ]]; then + if [[ "${{ inputs.example }}" == "hf_ptq" && ( "${{ github.ref }}" == refs/heads/pull-request/* || "${{ github.event_name }}" == "merge_group" ) ]]; then # One GPU on PR runners: small-model tests share it and the rest run alone on it # (tests/examples/hf_ptq/conftest.py). The 2-GPU nightly stays serial. pytest_args+=(-n 4) @@ -118,6 +118,7 @@ jobs: run: | echo "::warning title=Allowed example failure::'${{ inputs.example }}' failed but is in the allow-failure list (vars.ALLOW_FAILURE_EXAMPLE_TESTS); not blocking. Remove it from the variable once fixed." - name: Upload coverage to Codecov + if: github.event_name != 'merge_group' uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/_pr_gate.yml b/.github/workflows/_pr_gate.yml index e3605202f7c..2e2de28d657 100644 --- a/.github/workflows/_pr_gate.yml +++ b/.github/workflows/_pr_gate.yml @@ -27,18 +27,19 @@ jobs: outputs: any_changed: ${{ steps.changed-tests.outputs.any_modified || steps.non-pr.outputs.any_changed }} steps: - # For non-PR triggers (schedule, workflow_dispatch), always run tests + # For non-PR triggers (schedule, workflow_dispatch), always run tests. A PR run is a push to a + # copy-pr-bot branch (pull-request/N) or a merge queue group, which both diff against the base. - id: non-pr - if: "!startsWith(github.ref, 'refs/heads/pull-request/')" + if: "!(startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group')" run: echo "any_changed=true" >> $GITHUB_OUTPUT - - if: startsWith(github.ref, 'refs/heads/pull-request/') + - if: startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group' uses: actions/checkout@v6 with: fetch-depth: 0 - - if: startsWith(github.ref, 'refs/heads/pull-request/') + - if: startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group' id: base uses: ./.github/actions/pr-merge-base - - if: startsWith(github.ref, 'refs/heads/pull-request/') + - if: startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group' name: Check for changes in test-relevant directories id: changed-tests uses: step-security/changed-files@v47.0.5 @@ -51,7 +52,7 @@ jobs: wait-checks: needs: [check-file-changes] if: >- - startsWith(github.ref, 'refs/heads/pull-request/') && + (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && needs.check-file-changes.outputs.any_changed == 'true' runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 4743fef960b..abf82233195 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -3,6 +3,9 @@ name: Code Quality on: pull_request: branches: [main, release/*, feature/*] + # The merge queue re-runs the required checks on the PR merged with the target branch. + merge_group: + types: [checks_requested] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: @@ -21,12 +24,16 @@ jobs: steps: - uses: actions/checkout@v6 with: - # PRs: TruffleHog needs the base commit, else it errors, scans nothing, and passes. - fetch-depth: ${{ github.event_name == 'pull_request' && '0' || '1' }} + # PRs and merge queue groups: TruffleHog needs the base commit, else it errors, scans nothing, and passes. + fetch-depth: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '0' || '1' }} - uses: ./.github/actions/ubuntu-setup - name: Secret Scanning uses: trufflesecurity/trufflehog@v3.90.5 with: + # Only set in a merge queue group, where the diff to scan is base..head (empty for the other events, + # which the action handles itself) + base: ${{ github.event.merge_group.base_sha }} + head: ${{ github.event.merge_group.head_sha }} # Exclude lob: its `(live|test)_[a-zA-Z0-9_]{35}` pattern matches any pytest function # whose name happens to be exactly 35 characters after `test_`, and it reports them as # verified. Only nightly sees them, since it scans all history while PRs scan their diff. diff --git a/.github/workflows/example_tests.yml b/.github/workflows/example_tests.yml index 8cc432a22ba..2d87ad0496a 100644 --- a/.github/workflows/example_tests.yml +++ b/.github/workflows/example_tests.yml @@ -4,6 +4,9 @@ on: push: branches: ["pull-request/[0-9]+"] # NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used + # The merge queue re-runs the required checks on the PR merged with the target branch. + merge_group: + types: [checks_requested] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: @@ -37,10 +40,10 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 - - if: startsWith(github.ref, 'refs/heads/pull-request/') + - if: startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group' id: base uses: ./.github/actions/pr-merge-base - - if: startsWith(github.ref, 'refs/heads/pull-request/') + - if: startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group' id: changed uses: step-security/changed-files@v47.0.5 with: @@ -87,7 +90,7 @@ jobs: - id: lanes env: # Nightly and on-demand runs have no diff to inspect, so they run everything. - NON_PR: ${{ !startsWith(github.ref, 'refs/heads/pull-request/') }} + NON_PR: ${{ !(startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') }} COMMON: ${{ steps.changed.outputs.common_any_modified }} TORCH: ${{ steps.changed.outputs.torch_any_modified }} VLLM: ${{ steps.changed.outputs.vllm_any_modified }} @@ -108,7 +111,7 @@ jobs: done echo "any_changed=$any" | tee --append "$GITHUB_OUTPUT" - name: Wait for unit tests before spending GPU runners - if: ${{ steps.lanes.outputs.any_changed == 'true' && startsWith(github.ref, 'refs/heads/pull-request/') }} + if: ${{ steps.lanes.outputs.any_changed == 'true' && (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') }} uses: poseidon/wait-for-status-checks@v0.7.0 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -132,7 +135,7 @@ jobs: example: ${{ matrix.example }} timeout_minutes: 30 pip_install_extras: "[hf,dev-test]" - runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### vLLM Example Tests ##### @@ -153,7 +156,7 @@ jobs: example: ${{ matrix.example }} timeout_minutes: 30 pip_install_extras: "[hf,dev-test]" - runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### TensorRT-LLM Example Tests ##### @@ -172,7 +175,7 @@ jobs: docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc20" example: ${{ matrix.example }} pip_install_extras: "[hf,dev-test]" - runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### Megatron Example Tests ##### @@ -187,9 +190,9 @@ jobs: docker_image: "nvcr.io/nvidia/nemo:26.08" example: megatron_bridge # PR runs single-GPU (~11 min); the nightly runs every test multi-GPU (~58 min). - timeout_minutes: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 30 || 75 }} + timeout_minutes: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 30 || 75 }} pip_install_extras: "[hf,puzzletron,dev-test]" - runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), ',megatron_bridge,') }} ##### Diffusers Example Tests ##### @@ -206,7 +209,7 @@ jobs: example: diffusers timeout_minutes: 60 pip_install_extras: "[onnx,hf,dev-test]" - runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), ',diffusers,') }} ##### ONNX/TensorRT Example Tests ##### @@ -229,13 +232,13 @@ jobs: example: ${{ matrix.example }} timeout_minutes: 45 pip_install_extras: "[onnx,hf,dev-test]" - runner: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### Required Check for PR ##### example-pr-required-check: # Run even if example tests are skipped - if: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && always() }} + if: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && always() }} needs: [pr-gate, torch, vllm, trtllm, megatron, diffusers, onnx] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/gpu_tests.yml b/.github/workflows/gpu_tests.yml index a0389da7b62..906f467ddd2 100644 --- a/.github/workflows/gpu_tests.yml +++ b/.github/workflows/gpu_tests.yml @@ -4,6 +4,9 @@ on: push: branches: ["pull-request/[0-9]+"] # NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used + # The merge queue re-runs the required checks on the PR merged with the target branch. + merge_group: + types: [checks_requested] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: @@ -60,7 +63,7 @@ jobs: - example: gpu_vllm timeout: 15 container_image: docker.io/vllm/vllm-openai:v0.20.0 - runs-on: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runs-on: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} timeout-minutes: ${{ matrix.timeout }} container: image: ${{ matrix.container_image }} @@ -92,6 +95,7 @@ jobs: # Use `python3` (the vllm image has no `python` on PATH) python3 -m pip install nox && nox -s ${{ matrix.example }} - name: Upload GPU coverage to Codecov + if: github.event_name != 'merge_group' uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -102,7 +106,7 @@ jobs: gpu-pr-required-check: # Run even if gpu-tests is skipped - if: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && always() }} + if: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && always() }} needs: [pr-gate, gpu-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 2132cdd302e..8e6720a6759 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -4,6 +4,9 @@ on: pull_request: types: [opened, synchronize, reopened, closed] branches: [main, release/*, feature/*] + # The merge queue re-runs the required checks on the PR merged with the target branch. + merge_group: + types: [checks_requested] push: branches: [main] schedule: diff --git a/.github/workflows/regression_tests.yml b/.github/workflows/regression_tests.yml index 001c4e6af4a..9dfb686c910 100644 --- a/.github/workflows/regression_tests.yml +++ b/.github/workflows/regression_tests.yml @@ -4,6 +4,9 @@ on: push: branches: ["pull-request/[0-9]+"] # NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used + # The merge queue re-runs the required checks on the PR merged with the target branch. + merge_group: + types: [checks_requested] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: @@ -40,7 +43,7 @@ jobs: regression-tests: needs: [pr-gate] if: needs.pr-gate.outputs.any_changed == 'true' - runs-on: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runs-on: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} timeout-minutes: 15 container: image: nvcr.io/nvidia/pytorch:26.07-py3 @@ -62,6 +65,7 @@ jobs: COVERAGE_FILE: ${{ github.workspace }}/.coverage run: python -m pip install nox && nox -s regression - name: Upload regression coverage to Codecov + if: github.event_name != 'merge_group' uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -72,7 +76,7 @@ jobs: regression-pr-required-check: # Run even if regression-tests is skipped - if: ${{ startsWith(github.ref, 'refs/heads/pull-request/') && always() }} + if: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && always() }} needs: [pr-gate, regression-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 6fbca185d9f..f33f7de53a5 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -3,6 +3,9 @@ name: Unit tests on: pull_request: branches: [main, release/*, feature/*] + # The merge queue re-runs the required checks on the PR merged with the target branch. + merge_group: + types: [checks_requested] push: branches: [main, release/*, feature/*] paths: @@ -35,15 +38,18 @@ jobs: - id: non-pr if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' run: echo "any_changed=true" >> $GITHUB_OUTPUT - - if: github.event_name == 'pull_request' + - if: github.event_name == 'pull_request' || github.event_name == 'merge_group' uses: actions/checkout@v6 with: fetch-depth: 0 - - if: github.event_name == 'pull_request' + - if: github.event_name == 'pull_request' || github.event_name == 'merge_group' name: Check for changes in test-relevant paths id: changed uses: step-security/changed-files@v47.0.5 with: + # Only set in a merge queue group (empty for PRs, where the action finds the base itself) + base_sha: ${{ github.event.merge_group.base_sha }} + sha: ${{ github.event.merge_group.head_sha }} files: | .github/workflows/unit_tests.yml modelopt/** @@ -67,10 +73,11 @@ jobs: env: COVERAGE_PROCESS_START: ${{ github.workspace }}/pyproject.toml COVERAGE_FILE: ${{ github.workspace }}/.coverage - # Everything but PRs also runs the slow tests marked ``manual`` (e.g. AutoNAS). - PYTEST_EXTRA: ${{ github.event_name != 'pull_request' && '--run-manual' || '' }} + # Everything but PRs and merge queue groups also runs the slow tests marked ``manual`` (e.g. AutoNAS). + PYTEST_EXTRA: ${{ github.event_name != 'pull_request' && github.event_name != 'merge_group' && '--run-manual' || '' }} run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" -- $PYTEST_EXTRA - name: Upload coverage reports to Codecov + if: github.event_name != 'merge_group' uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -220,7 +227,7 @@ jobs: python -m pytest plugins/modelopt/skills/ -o addopts="" -p no:cacheprovider -v unit-pr-required-check: # Run even if some jobs are skipped - if: ${{ github.event_name == 'pull_request' && always() }} + if: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && always() }} needs: [check-file-changes, linux, windows, multi-version, partial-install, launcher, mcp, skills] runs-on: ubuntu-latest steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45b6ffbf9f2..7c071db098f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -282,7 +282,11 @@ nox -s "unit-3.12(torch_211, tf_latest)" - **Contributors without write access:** When GitHub requests workflow approval, a reviewer with write permission must select **Approve and run workflows** to start GitHub Actions CI. - If any CI/CD checks fail, fix the issues and push again. -- Once your PR is approved and all checks pass, one of the reviewers will merge the PR. +- Once your PR is approved and all checks pass, one of the reviewers will merge the PR. With the merge queue + enabled on `main`, merging adds the PR to the queue: GitHub builds a temporary branch of `main` plus the + queued PRs, re-runs the required checks on it (the same checks, path gating and runners as a PR run, without + the nightly-only tests) and merges the PR only if they pass. A PR that fails in the queue is removed from it; + fix it and queue it again. - If your PR is a bug fix that should also land in an ongoing release branch, add the `cherry-pick-` label (e.g. `cherry-pick-0.47.0`) matching that release. Labeled PRs are cherry-picked into the release branch so the fix is included in the next release candidate. From c5d94389fc851ea63ee00ee0ff86bfd7110d1337 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Sun, 18 Oct 2026 04:09:15 +0000 Subject: [PATCH 2/3] Run the merge queue checks only for main, release/* and feature/* The merge_group triggers had no branch filter, so a merge queue enabled on any branch would start the required checks there. Give the six triggers the branches of their pull_request triggers (main, release/*, feature/*), which are the branches that have the required checks, and say so in CONTRIBUTING.md. Signed-off-by: Shengliang Xu --- .github/workflows/code_quality.yml | 3 ++- .github/workflows/example_tests.yml | 3 ++- .github/workflows/gpu_tests.yml | 3 ++- .github/workflows/pages.yml | 3 ++- .github/workflows/regression_tests.yml | 3 ++- .github/workflows/unit_tests.yml | 3 ++- CONTRIBUTING.md | 9 +++++---- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index abf82233195..054aadb7f43 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -3,9 +3,10 @@ name: Code Quality on: pull_request: branches: [main, release/*, feature/*] - # The merge queue re-runs the required checks on the PR merged with the target branch. + # The merge queue re-runs the required checks on the PR merged with the target branch merge_group: types: [checks_requested] + branches: [main, release/*, feature/*] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: diff --git a/.github/workflows/example_tests.yml b/.github/workflows/example_tests.yml index 2d87ad0496a..7761fc56181 100644 --- a/.github/workflows/example_tests.yml +++ b/.github/workflows/example_tests.yml @@ -4,9 +4,10 @@ on: push: branches: ["pull-request/[0-9]+"] # NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used - # The merge queue re-runs the required checks on the PR merged with the target branch. + # The merge queue re-runs the required checks on the PR merged with the target branch merge_group: types: [checks_requested] + branches: [main, release/*, feature/*] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: diff --git a/.github/workflows/gpu_tests.yml b/.github/workflows/gpu_tests.yml index 906f467ddd2..bd9e39451f4 100644 --- a/.github/workflows/gpu_tests.yml +++ b/.github/workflows/gpu_tests.yml @@ -4,9 +4,10 @@ on: push: branches: ["pull-request/[0-9]+"] # NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used - # The merge queue re-runs the required checks on the PR merged with the target branch. + # The merge queue re-runs the required checks on the PR merged with the target branch merge_group: types: [checks_requested] + branches: [main, release/*, feature/*] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 8e6720a6759..91177043c8f 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -4,9 +4,10 @@ on: pull_request: types: [opened, synchronize, reopened, closed] branches: [main, release/*, feature/*] - # The merge queue re-runs the required checks on the PR merged with the target branch. + # The merge queue re-runs the required checks on the PR merged with the target branch merge_group: types: [checks_requested] + branches: [main, release/*, feature/*] push: branches: [main] schedule: diff --git a/.github/workflows/regression_tests.yml b/.github/workflows/regression_tests.yml index 9dfb686c910..e8c5b0d3db2 100644 --- a/.github/workflows/regression_tests.yml +++ b/.github/workflows/regression_tests.yml @@ -4,9 +4,10 @@ on: push: branches: ["pull-request/[0-9]+"] # NOTE: paths cannot be used since push happens to copied PR and only latest commit to PR is used - # The merge queue re-runs the required checks on the PR merged with the target branch. + # The merge queue re-runs the required checks on the PR merged with the target branch merge_group: types: [checks_requested] + branches: [main, release/*, feature/*] schedule: - cron: "0 0 * * *" # Nightly workflow_dispatch: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index f33f7de53a5..3e2a0524da0 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -3,9 +3,10 @@ name: Unit tests on: pull_request: branches: [main, release/*, feature/*] - # The merge queue re-runs the required checks on the PR merged with the target branch. + # The merge queue re-runs the required checks on the PR merged with the target branch merge_group: types: [checks_requested] + branches: [main, release/*, feature/*] push: branches: [main, release/*, feature/*] paths: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c071db098f..8e1ef903ba3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -283,10 +283,11 @@ nox -s "unit-3.12(torch_211, tf_latest)" must select **Approve and run workflows** to start GitHub Actions CI. - If any CI/CD checks fail, fix the issues and push again. - Once your PR is approved and all checks pass, one of the reviewers will merge the PR. With the merge queue - enabled on `main`, merging adds the PR to the queue: GitHub builds a temporary branch of `main` plus the - queued PRs, re-runs the required checks on it (the same checks, path gating and runners as a PR run, without - the nightly-only tests) and merges the PR only if they pass. A PR that fails in the queue is removed from it; - fix it and queue it again. + enabled on its target branch (the queue runs the checks for `main`, `release/*` and `feature/*`), merging adds + the PR to the queue: GitHub builds a temporary branch of the target branch plus the queued PRs ahead of it, + re-runs the required checks on it (the same checks, path gating and runners as a PR run, without the + nightly-only tests) and merges the PR only if they pass. Each PR still lands on its own. A PR that fails in + the queue is removed from it and the PRs behind it are re-tested without it; fix it and queue it again. - If your PR is a bug fix that should also land in an ongoing release branch, add the `cherry-pick-` label (e.g. `cherry-pick-0.47.0`) matching that release. Labeled PRs are cherry-picked into the release branch so the fix is included in the next release candidate. From e260b71837847f91fa7364247ecccd41c88a2370 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Sun, 18 Oct 2026 04:57:53 +0000 Subject: [PATCH 3/3] Wrap the long lines the merge queue change added to the workflows The PR-like predicate made several runner, if and env values up to 193 columns wide. Fold them into `>-` scalars broken after `&&` / `||` (the parsed YAML value is byte-identical) and wrap two shell conditions and two comments, so no line this PR adds is longer than 110 columns. Signed-off-by: Shengliang Xu --- .github/workflows/_example_tests_runner.yml | 4 ++- .github/workflows/code_quality.yml | 6 ++-- .github/workflows/example_tests.yml | 39 +++++++++++++++------ .github/workflows/gpu_tests.yml | 8 +++-- .github/workflows/regression_tests.yml | 8 +++-- .github/workflows/unit_tests.yml | 7 ++-- 6 files changed, 53 insertions(+), 19 deletions(-) diff --git a/.github/workflows/_example_tests_runner.yml b/.github/workflows/_example_tests_runner.yml index 3044ea66471..e920db48ba8 100644 --- a/.github/workflows/_example_tests_runner.yml +++ b/.github/workflows/_example_tests_runner.yml @@ -107,7 +107,9 @@ jobs: # Keep legacy ONNX/TRT tests available manually during deprecation. pytest_args+=(--ignore=tests/examples/diffusers/test_diffusers_onnx_trt.py) fi - if [[ "${{ inputs.example }}" == "hf_ptq" && ( "${{ github.ref }}" == refs/heads/pull-request/* || "${{ github.event_name }}" == "merge_group" ) ]]; then + if [[ "${{ inputs.example }}" == "hf_ptq" && + ( "${{ github.ref }}" == refs/heads/pull-request/* || + "${{ github.event_name }}" == "merge_group" ) ]]; then # One GPU on PR runners: small-model tests share it and the rest run alone on it # (tests/examples/hf_ptq/conftest.py). The 2-GPU nightly stays serial. pytest_args+=(-n 4) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 054aadb7f43..98898778e3d 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -25,8 +25,10 @@ jobs: steps: - uses: actions/checkout@v6 with: - # PRs and merge queue groups: TruffleHog needs the base commit, else it errors, scans nothing, and passes. - fetch-depth: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '0' || '1' }} + # PRs and merge queue groups: TruffleHog needs the base commit, else it errors, + # scans nothing, and passes. + fetch-depth: >- + ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '0' || '1' }} - uses: ./.github/actions/ubuntu-setup - name: Secret Scanning uses: trufflesecurity/trufflehog@v3.90.5 diff --git a/.github/workflows/example_tests.yml b/.github/workflows/example_tests.yml index 7761fc56181..0d64e692721 100644 --- a/.github/workflows/example_tests.yml +++ b/.github/workflows/example_tests.yml @@ -91,7 +91,8 @@ jobs: - id: lanes env: # Nightly and on-demand runs have no diff to inspect, so they run everything. - NON_PR: ${{ !(startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') }} + NON_PR: >- + ${{ !(startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') }} COMMON: ${{ steps.changed.outputs.common_any_modified }} TORCH: ${{ steps.changed.outputs.torch_any_modified }} VLLM: ${{ steps.changed.outputs.vllm_any_modified }} @@ -112,7 +113,9 @@ jobs: done echo "any_changed=$any" | tee --append "$GITHUB_OUTPUT" - name: Wait for unit tests before spending GPU runners - if: ${{ steps.lanes.outputs.any_changed == 'true' && (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') }} + if: >- + ${{ steps.lanes.outputs.any_changed == 'true' && + (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') }} uses: poseidon/wait-for-status-checks@v0.7.0 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -136,7 +139,9 @@ jobs: example: ${{ matrix.example }} timeout_minutes: 30 pip_install_extras: "[hf,dev-test]" - runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### vLLM Example Tests ##### @@ -157,7 +162,9 @@ jobs: example: ${{ matrix.example }} timeout_minutes: 30 pip_install_extras: "[hf,dev-test]" - runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### TensorRT-LLM Example Tests ##### @@ -176,7 +183,9 @@ jobs: docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc20" example: ${{ matrix.example }} pip_install_extras: "[hf,dev-test]" - runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### Megatron Example Tests ##### @@ -191,9 +200,13 @@ jobs: docker_image: "nvcr.io/nvidia/nemo:26.08" example: megatron_bridge # PR runs single-GPU (~11 min); the nightly runs every test multi-GPU (~58 min). - timeout_minutes: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 30 || 75 }} + timeout_minutes: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || + github.event_name == 'merge_group') && 30 || 75 }} pip_install_extras: "[hf,puzzletron,dev-test]" - runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), ',megatron_bridge,') }} ##### Diffusers Example Tests ##### @@ -210,7 +223,9 @@ jobs: example: diffusers timeout_minutes: 60 pip_install_extras: "[onnx,hf,dev-test]" - runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), ',diffusers,') }} ##### ONNX/TensorRT Example Tests ##### @@ -233,13 +248,17 @@ jobs: example: ${{ matrix.example }} timeout_minutes: 45 pip_install_extras: "[onnx,hf,dev-test]" - runner: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runner: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} allow_failure: ${{ contains(format(',{0},', vars.ALLOW_FAILURE_EXAMPLE_TESTS), format(',{0},', matrix.example)) }} ##### Required Check for PR ##### example-pr-required-check: # Run even if example tests are skipped - if: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && always() }} + if: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || + github.event_name == 'merge_group') && always() }} needs: [pr-gate, torch, vllm, trtllm, megatron, diffusers, onnx] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/gpu_tests.yml b/.github/workflows/gpu_tests.yml index bd9e39451f4..0d315f2af56 100644 --- a/.github/workflows/gpu_tests.yml +++ b/.github/workflows/gpu_tests.yml @@ -64,7 +64,9 @@ jobs: - example: gpu_vllm timeout: 15 container_image: docker.io/vllm/vllm-openai:v0.20.0 - runs-on: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runs-on: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} timeout-minutes: ${{ matrix.timeout }} container: image: ${{ matrix.container_image }} @@ -107,7 +109,9 @@ jobs: gpu-pr-required-check: # Run even if gpu-tests is skipped - if: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && always() }} + if: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || + github.event_name == 'merge_group') && always() }} needs: [pr-gate, gpu-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/regression_tests.yml b/.github/workflows/regression_tests.yml index e8c5b0d3db2..b76f819bc17 100644 --- a/.github/workflows/regression_tests.yml +++ b/.github/workflows/regression_tests.yml @@ -44,7 +44,9 @@ jobs: regression-tests: needs: [pr-gate] if: needs.pr-gate.outputs.any_changed == 'true' - runs-on: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} + runs-on: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && + 'linux-amd64-gpu-rtxpro6000-latest-1' || 'linux-amd64-gpu-rtxpro6000-latest-2' }} timeout-minutes: 15 container: image: nvcr.io/nvidia/pytorch:26.07-py3 @@ -77,7 +79,9 @@ jobs: regression-pr-required-check: # Run even if regression-tests is skipped - if: ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || github.event_name == 'merge_group') && always() }} + if: >- + ${{ (startsWith(github.ref, 'refs/heads/pull-request/') || + github.event_name == 'merge_group') && always() }} needs: [pr-gate, regression-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 3e2a0524da0..ad5371c799d 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -74,8 +74,11 @@ jobs: env: COVERAGE_PROCESS_START: ${{ github.workspace }}/pyproject.toml COVERAGE_FILE: ${{ github.workspace }}/.coverage - # Everything but PRs and merge queue groups also runs the slow tests marked ``manual`` (e.g. AutoNAS). - PYTEST_EXTRA: ${{ github.event_name != 'pull_request' && github.event_name != 'merge_group' && '--run-manual' || '' }} + # Everything but PRs and merge queue groups also runs the slow tests marked ``manual`` + # (e.g. AutoNAS). + PYTEST_EXTRA: >- + ${{ github.event_name != 'pull_request' && + github.event_name != 'merge_group' && '--run-manual' || '' }} run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" -- $PYTEST_EXTRA - name: Upload coverage reports to Codecov if: github.event_name != 'merge_group'