name: PR Gate # This pipeline is the minimum bar a PR must pass before it can be merged. # It is intended to be fast and lightweight to trigger automatically on every # change in every PR and provide quick feedback without overloading the CI. # Requirements for all jobs in this workflow: # - A new job must cancel a previously scheduled/running job. # PRs only care about the latest commit and multiple pushes may happen in quick succession. # - End-to-end (excluding wait times for runners) must be less than 5mins. # This includes the cost of checking out the code, preparing a runner, etc. run-name: >- ${{ github.event_name == 'merge_group' && github.event.action == 'destroyed' && github.event.reason == 'merged' && format('[Merge Queue] đŸŽ¯ Merged {0}', github.sha) || github.event_name == 'merge_group' && github.event.action == 'destroyed' && github.event.reason == 'dequeued' && format('[Merge Queue] â†Šī¸ Dequeued {0}', github.sha) || github.event_name == 'merge_group' && github.event.action == 'destroyed' && github.event.reason == 'invalidated' && format('[Merge Queue] 💔 Invalidated {0}', github.sha) || github.event_name == 'merge_group' && github.event.action == 'destroyed' && format('[Merge Queue] {0} {1}', github.event.reason, github.sha) || github.event_name == 'merge_group' && format('[Merge Queue] đŸ’Ģ Processing {0}: {1}', github.actor, github.sha) || '' }} on: workflow_dispatch: inputs: platform: required: false type: choice default: "Ubuntu 24.04" options: - "Ubuntu 22.04" - "Ubuntu 24.04" description: "Platform to build and test" pull_request: types: - opened - reopened - synchronize - ready_for_review merge_group: types: - checks_requested - destroyed push: branches: # Builds on main will populate the shared ccache to speed up builds on branches - main concurrency: # Groups to control cancelling of obsolete runs. # main (or any protected branch): run_id (ie: never cancel other jobs) # PRs: PR # # Merge Queue: branch name; will be paired with the 'destroyed' trigger to # cause a cancel when it leaves or moves in the queue. # See https://github.com/orgs/community/discussions/137976#discussioncomment-14004793 # All others: branch name (ie: one run per branch) group: >- ${{ github.workflow }}- ${{ (github.ref_protected && github.run_id) || github.event.pull_request.number || github.ref }}-default cancel-in-progress: true permissions: contents: write pull-requests: write checks: write packages: write actions: read jobs: # NOTE: Platform parsing is handled by build-docker-artifact.yaml and build-artifact.yaml internally. # This workflow only needs to pass the platform string to downstream workflows. # Check Harbor availability first - outputs harbor-prefix for downstream jobs check-harbor: if: github.event.action != 'destroyed' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) uses: ./.github/workflows/check-harbor.yaml with: runs-on: ${{ github.event_name == 'merge_group' && 'tt-ubuntu-light-prio' || 'tt-ubuntu-light' }} # Single docker build job - deduplicates container builds across asan-build, build, and clang-tidy build-docker-images: if: github.event.action != 'destroyed' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) needs: check-harbor uses: ./.github/workflows/build-docker-artifact.yaml secrets: inherit with: platform: ${{ inputs.platform || 'Ubuntu 24.04' }} architecture: amd64 harbor-prefix: ${{ needs.check-harbor.outputs.harbor-prefix }} # Use the merge commit (github.sha) rather than pr.head.sha. # GHA always resolves workflow *files* from the merge commit; using head.sha for the # working-tree checkout creates a mismatch where the workflow calls scripts that don't # exist on the PR branch yet (e.g. a new .github/scripts/ file lands on main and # immediately breaks unrelated open PRs that haven't rebased). # Consistency: code and workflow definitions are both from the same merge commit, # so "re-run after a main update" failures are explainable as code conflicts rather # than invisible infrastructure drift. resolve-docker-pull-refs: if: github.event.action != 'destroyed' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) needs: [check-harbor, build-docker-images] uses: ./.github/workflows/resolve-docker-pull-refs.yaml with: harbor-prefix: ${{ needs.check-harbor.outputs.harbor-prefix }} ci-build-docker-image: ${{ needs.build-docker-images.outputs.ci-build-tag }} ci-test-docker-image: ${{ needs.build-docker-images.outputs.ci-test-tag }} dev-docker-image: ${{ needs.build-docker-images.outputs.dev-tag }} basic-dev-docker-image: ${{ needs.build-docker-images.outputs.basic-dev-tag }} basic-ttnn-runtime-docker-image: ${{ needs.build-docker-images.outputs.basic-ttnn-runtime-tag }} manylinux-docker-image: ${{ needs.build-docker-images.outputs.manylinux-tag }} llk-ci-docker-image: ${{ needs.build-docker-images.outputs.llk-ci-tag }} asan-build: if: >- ${{ github.event.action != 'destroyed' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && ((github.event_name != 'pull_request' && github.event_name != 'merge_group') || needs.find-changed-files.outputs.any-code-changed == 'true') }} needs: [resolve-docker-pull-refs, build-docker-images, find-changed-files] uses: ./.github/workflows/build-artifact.yaml permissions: contents: read packages: write secrets: inherit with: platform: ${{ inputs.platform || 'Ubuntu 24.04' }} toolchain: cmake/x86_64-linux-clang-20-libstdcpp-toolchain.cmake build-type: 'ASan' # No manylinux wheel in the PR gate: build-docs imports ttnn in-place # from the lean ttnn-import artifact instead (no duplicate compile). build-wheel: false build-ttnn-import-artifact: true publish-artifact: false publish-package: true tracy: false skip-tt-train: false # CI-internal only: consumers of these packages all run in docker images that # already stage SFPI, so skip the redundant download. Never set for release builds. use-system-sfpi: true # Treeless clone: skip the trees/blobs that dominate the tag fetch, but keep commits + tags # so git describe still yields a real version. Depth stays at the default. checkout-filter: tree:0 # Use the merge commit (github.sha) — see build-docker-images job for rationale. # Pass pre-built docker images to avoid duplicate builds ci-build-docker-image: ${{ needs.resolve-docker-pull-refs.outputs.ci-build-docker-image }} ci-test-docker-image: ${{ needs.resolve-docker-pull-refs.outputs.ci-test-docker-image }} dev-docker-image: ${{ needs.resolve-docker-pull-refs.outputs.dev-docker-image }} basic-dev-docker-image: ${{ needs.resolve-docker-pull-refs.outputs.basic-dev-docker-image }} basic-ttnn-runtime-docker-image: ${{ needs.resolve-docker-pull-refs.outputs.basic-ttnn-runtime-docker-image }} manylinux-docker-image: ${{ needs.resolve-docker-pull-refs.outputs.manylinux-docker-image }} runs-on: ${{ github.event_name == 'merge_group' && 'tt-ubuntu-2204-large-prio-stable' || 'tt-ubuntu-2204-large-stable' }} find-changed-files: if: github.event.action != 'destroyed' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) runs-on: ubuntu-slim outputs: any-code-changed: ${{ steps.find-changes.outputs.any-code-changed }} run-clang-tidy: ${{ steps.find-changes.outputs.run-clang-tidy }} docs-changed: ${{ steps.find-changes.outputs.docs-changed }} cmake-changed: ${{ steps.find-changes.outputs.cmake-changed }} tt-metalium-changed: ${{ steps.find-changes.outputs.tt-metalium-changed }} tt-nn-changed: ${{ steps.find-changes.outputs.tt-nn-changed }} tt-metalium-or-tt-nn-tests-changed: ${{ steps.find-changes.outputs.tt-metalium-or-tt-nn-tests-changed }} tt-train-changed: ${{ steps.find-changes.outputs.tt-train-changed }} model-charts-changed: ${{ steps.find-changes.outputs.model-charts-changed }} llk-wormhole-changed: ${{ steps.find-changes.outputs.llk-wormhole-changed }} llk-blackhole-changed: ${{ steps.find-changes.outputs.llk-blackhole-changed }} llk-common-changed: ${{ steps.find-changes.outputs.llk-common-changed }} llk-sfpi-changed: ${{ steps.find-changes.outputs.llk-sfpi-changed }} llk-quasar-changed: ${{ steps.find-changes.outputs.llk-quasar-changed }} llk-tests-changed: ${{ steps.find-changes.outputs.llk-tests-changed }} llk-perf-changed: ${{ steps.find-changes.outputs.llk-perf-changed }} llk-ci-changed: ${{ steps.find-changes.outputs.llk-ci-changed }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 timeout-minutes: 20 with: fetch-depth: 1 sparse-checkout: .github - id: find-changes uses: ./.github/actions/find-changed-files # The ONE place team time budgets are enforced. It has to see every tests yaml # at once: several yamls charge the same (team, budget_type, sku) budget, so the # allowance is the sum across all of them. The per-pipeline checks this replaced # each saw a single yaml, which let every yaml claim the full budget on its own. verify-time-budgets: if: github.event.action != 'destroyed' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) runs-on: ubuntu-slim steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 timeout-minutes: 5 with: fetch-depth: 1 sparse-checkout: | .github tests/pipeline_reorg - name: Install dependencies run: pip3 install PyYAML # A cmd block that records a failed test command and runs the next one starts that # command on devices nothing has reset (#57706). Runs from this job's own checkout. - name: Lint pipeline cmd blocks run: | git fetch --no-tags --depth=1 origin main git show FETCH_HEAD:.github/pipeline_cmd_lint_baseline.yaml > "${RUNNER_TEMP}/base_baseline.yaml" 2>/dev/null || : > "${RUNNER_TEMP}/base_baseline.yaml" python3 .github/scripts/utils/lint_pipeline_cmds.py --base-baseline "${RUNNER_TEMP}/base_baseline.yaml" - name: Verify test timeouts against team time budgets uses: tenstorrent/tt-metal/.github/actions/verify-time-budget@main with: # The action is pinned to @main so the check logic is always current, but that # means GITHUB_ACTION_PATH resolves inside a separate main-pinned checkout, not # this job's own checkout above. Point it explicitly at $GITHUB_WORKSPACE so it # verifies this branch's test yamls and budget file, not main's. args: >- --tests-dir "$GITHUB_WORKSPACE/tests/pipeline_reorg" --budget-file "$GITHUB_WORKSPACE/.github/time_budget.yaml" --comment-file "${{ runner.temp }}/budget-report.md" # Every failure kind gets explained on the PR, not just an over-budget bucket: # a missing budget_type or an undeclared bucket is just as opaque to the author. # One comment is kept up to date across pushes rather than stacking per run, and # a run that passes edits an existing comment to say so instead of leaving a # stale failure behind. A pass with no prior comment posts nothing. - name: Explain a time budget failure on the PR if: ${{ always() }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} BRANCH: ${{ github.head_ref || github.ref_name }} REPO: ${{ github.repository }} BODY_FILE: ${{ runner.temp }}/budget-report.md run: | set -euo pipefail if [ ! -f "$BODY_FILE" ]; then echo "No report was written (the check did not get far enough); nothing to post." exit 0 fi if [ -z "${PR_NUMBER:-}" ]; then PR_NUMBER=$(gh pr list --repo "$REPO" --head "$BRANCH" --state open \ --json number --jq '.[0].number // empty') fi if [ -z "${PR_NUMBER:-}" ]; then echo "No open PR for '$BRANCH'; nothing to post." exit 0 fi existing=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --paginate \ --jq '[.[] | select(.user.login == "github-actions[bot]" and (.body | contains("