Skip to content

Commit a08a035

Browse files
authored
Merge branch 'main' into main
2 parents f0386df + 3b3f621 commit a08a035

127 files changed

Lines changed: 5863 additions & 1755 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ci/docker/build.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ case "${IMAGE_NAME}" in
8989
OS_VERSION=24.04
9090
GCC_VERSION=14
9191
;;
92+
executorch-ubuntu-26.04-gcc15)
93+
LINTRUNNER=""
94+
OS_VERSION=26.04
95+
GCC_VERSION=15
96+
;;
9297
*)
9398
echo "Invalid image name ${IMAGE_NAME}"
9499
exit 1

.ci/docker/common/install_docs_reqs.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ if [ -n "$BUILD_DOCS" ]; then
1515
curl --retry 3 --retry-all-errors -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
1616
sudo apt-get install -y nodejs
1717

18-
curl --retry 3 --retry-all-errors -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
19-
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
18+
curl --retry 3 --retry-all-errors -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/yarn-archive-keyring.gpg
19+
echo "deb [signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
2020

2121
apt-get update
2222
apt-get install -y --no-install-recommends yarn
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: CI Run Decision
2+
3+
# Single source of truth for "should this commit force-run all CI jobs
4+
# regardless of path filter?". Used by per-job ``if:`` gates in pull.yml
5+
# and trunk.yml so the sampling logic isn't repeated per job.
6+
#
7+
# Returns ``is-full-run = 'true'`` for:
8+
# - workflow_dispatch (manual run)
9+
# - ciflow/* tag pushes (maintainer-forced full run)
10+
# - push events at every 4th commit by depth from main's root
11+
# (deterministic 25% sample, hard cap of 4 commits between samples)
12+
#
13+
# Returns ``is-full-run = 'false'`` for:
14+
# - pull_request / pull_request_target (use path filter instead)
15+
# - push events not matching any of the above (path-filtered runs)
16+
#
17+
# See ``viable-strict-gate.yml``: viable/strict only advances on
18+
# commits where this is true, so the path-filtered fast path doesn't
19+
# silently advance partial signal.
20+
21+
on:
22+
workflow_call:
23+
outputs:
24+
is-full-run:
25+
description: "'true' if this commit should run all CI jobs regardless of path filter; 'false' otherwise."
26+
value: ${{ jobs.decide.outputs.is-full-run }}
27+
28+
permissions:
29+
contents: read
30+
31+
jobs:
32+
decide:
33+
runs-on: ubuntu-latest
34+
outputs:
35+
is-full-run: ${{ steps.compute.outputs.is-full-run }}
36+
steps:
37+
# Full history needed to compute commit depth via
38+
# `git rev-list --first-parent --count`. The --first-parent flag
39+
# follows only the linear main-branch history through merge
40+
# commits, so the count maps 1:1 to pushes on main regardless of
41+
# how many commits were in any merged PR.
42+
- name: Checkout
43+
uses: actions/checkout@v4
44+
with:
45+
fetch-depth: 0
46+
47+
- name: Compute is-full-run
48+
id: compute
49+
env:
50+
EVENT_NAME: ${{ github.event_name }}
51+
REF: ${{ github.ref }}
52+
SHA: ${{ github.sha }}
53+
run: |
54+
set -eu
55+
56+
IS_FULL=false
57+
58+
case "$EVENT_NAME" in
59+
workflow_dispatch)
60+
IS_FULL=true
61+
;;
62+
esac
63+
64+
case "$REF" in
65+
refs/tags/ciflow/*)
66+
IS_FULL=true
67+
;;
68+
esac
69+
70+
# Depth-based 25% sample on push: every 4th commit on the
71+
# linear main-branch history (depth %% 4 == 0). --first-parent
72+
# is required — plain `git rev-list --count` would walk all
73+
# merge parents, so the count would jump by (1 + PR_size) at
74+
# each merge commit and the sample rate would be unpredictable.
75+
# Hard guarantees with --first-parent:
76+
# - Exactly 25% of pushes on main are sampled.
77+
# - At most 3 non-sampled commits between any two samples.
78+
# Re-runs of the same commit always have the same outcome.
79+
if [ "$IS_FULL" = "false" ] && [ "$EVENT_NAME" = "push" ]; then
80+
DEPTH=$(git rev-list --first-parent --count "$SHA")
81+
if [ $((DEPTH % 4)) -eq 0 ]; then
82+
IS_FULL=true
83+
fi
84+
echo "Depth: $DEPTH (first-parent; depth %% 4 = $((DEPTH % 4)))"
85+
fi
86+
87+
echo "Event: $EVENT_NAME"
88+
echo "Ref: $REF"
89+
echo "SHA: $SHA"
90+
echo "is-full-run: $IS_FULL"
91+
echo "is-full-run=$IS_FULL" >> "$GITHUB_OUTPUT"

.github/workflows/_get-changed-files.yml

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,24 @@ name: Get Changed Files
22

33
on:
44
workflow_call:
5+
inputs:
6+
include-push-diff:
7+
description: |
8+
When true, on push events the output is the diff between
9+
`github.event.before` and `github.sha` (computed via the
10+
GitHub Compare API). Default is false: push events emit '*',
11+
matching the historical behavior.
12+
type: boolean
13+
required: false
14+
default: false
515
outputs:
616
changed-files:
7-
description: "List of changed files (space-separated) or '*' if not in a PR"
17+
description: "Space-separated list of changed files for PR events (and push events when include-push-diff=true); '*' otherwise."
818
value: ${{ jobs.get-changed-files.outputs.changed-files }}
919

20+
permissions:
21+
contents: read
22+
1023
jobs:
1124
get-changed-files:
1225
runs-on: ubuntu-latest
@@ -18,26 +31,65 @@ jobs:
1831
id: get-files
1932
env:
2033
GH_TOKEN: ${{ github.token }}
34+
INCLUDE_PUSH_DIFF: ${{ inputs.include-push-diff }}
2135
run: |
22-
# Check if we're in a pull request context
23-
if [ "${{ github.event_name }}" = "pull_request" ] || [ "${{ github.event_name }}" = "pull_request_target" ]; then
24-
echo "Running in PR context"
36+
set -eu
2537
26-
# Get the PR number from the github context
27-
PR_NUMBER="${{ github.event.number }}"
38+
EVENT_NAME="${{ github.event_name }}"
39+
REPO="${{ github.repository }}"
2840
29-
# Use gh CLI to get changed files in the PR with explicit repo
30-
CHANGED_FILES=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/files --paginate --jq '.[] | select(.status != "removed") | .filename' | tr '\n' ' ' | sed 's/ $//')
41+
# PR context: list files modified by the PR.
42+
if [ "$EVENT_NAME" = "pull_request" ] || [ "$EVENT_NAME" = "pull_request_target" ]; then
43+
echo "Running in PR context"
44+
PR_NUMBER="${{ github.event.number }}"
45+
CHANGED_FILES=$(gh api "repos/$REPO/pulls/$PR_NUMBER/files" --paginate \
46+
--jq '.[] | select(.status != "removed") | .filename' | tr '\n' ' ' | sed 's/ $//')
3147
3248
if [ -z "$CHANGED_FILES" ]; then
3349
echo "No changed files found, setting to '*'"
3450
CHANGED_FILES="*"
3551
fi
36-
3752
echo "Changed files: $CHANGED_FILES"
3853
echo "changed-files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
54+
exit 0
55+
fi
3956
40-
else
41-
echo "Not in PR context, setting changed files to '*'"
42-
echo "changed-files=*" >> "$GITHUB_OUTPUT"
57+
# Push context with opt-in: diff between previous tip and new
58+
# tip via the GitHub Compare API. This is what lets path-
59+
# filtered jobs skip on push commits that don't touch their
60+
# relevant paths. Callers must explicitly request this with
61+
# `include-push-diff: true` because some workflows (e.g.
62+
# lint.yml) historically rely on the '*' value to take a
63+
# broader code path.
64+
if [ "$EVENT_NAME" = "push" ] && [ "$INCLUDE_PUSH_DIFF" = "true" ]; then
65+
BEFORE="${{ github.event.before }}"
66+
AFTER="${{ github.sha }}"
67+
ZERO_SHA="0000000000000000000000000000000000000000"
68+
69+
if [ -z "$BEFORE" ] || [ "$BEFORE" = "$ZERO_SHA" ]; then
70+
echo "No 'before' SHA on push event (tag/branch creation or initial push); setting changed files to '*'"
71+
echo "changed-files=*" >> "$GITHUB_OUTPUT"
72+
exit 0
73+
fi
74+
75+
echo "Running in push context: comparing $BEFORE..$AFTER"
76+
CHANGED_FILES=$(gh api "repos/$REPO/compare/$BEFORE...$AFTER" --paginate \
77+
--jq '.files[]? | select(.status != "removed") | .filename' 2>/dev/null \
78+
| tr '\n' ' ' | sed 's/ $//' || echo "")
79+
80+
if [ -z "$CHANGED_FILES" ]; then
81+
echo "Compare returned empty; setting changed files to '*'"
82+
echo "changed-files=*" >> "$GITHUB_OUTPUT"
83+
exit 0
84+
fi
85+
86+
echo "Changed files: $CHANGED_FILES"
87+
echo "changed-files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
88+
exit 0
4389
fi
90+
91+
# Default for non-PR events (push without opt-in,
92+
# workflow_dispatch, schedule, etc.): no diff. Emit '*' to
93+
# preserve the historical behavior.
94+
echo "Event '$EVENT_NAME' (or include-push-diff=false): emitting '*'"
95+
echo "changed-files=*" >> "$GITHUB_OUTPUT"

.github/workflows/cuda-perf.yml

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ on:
1212
- .github/workflows/cuda-perf.yml
1313
- .ci/scripts/cuda_benchmark.py
1414
- .ci/scripts/cuda_perf_prompts/**
15+
- .ci/scripts/export_model_artifact.sh
16+
- .ci/scripts/test_model_e2e.sh
1517
workflow_dispatch:
1618
inputs:
1719
models:
@@ -32,8 +34,33 @@ concurrency:
3234
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
3335
cancel-in-progress: true
3436

37+
permissions:
38+
contents: read
39+
3540
jobs:
41+
changed-files:
42+
name: Get changed files
43+
uses: ./.github/workflows/_get-changed-files.yml
44+
with:
45+
include-push-diff: true
46+
47+
run-decision:
48+
name: CI run decision
49+
uses: ./.github/workflows/_ci-run-decision.yml
50+
3651
set-parameters:
52+
needs: [changed-files, run-decision]
53+
# Path-filtered: mirrors the workflow-level pull_request `paths:`
54+
# filter so push commits that don't touch perf-relevant paths skip
55+
# this whole workflow on non-sampled commits. Sampling preserves
56+
# perf time-series at every 4th commit (vs every commit pre-PR).
57+
if: |
58+
contains(needs.changed-files.outputs.changed-files, '.github/workflows/cuda-perf.yml') ||
59+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/cuda_benchmark.py') ||
60+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/cuda_perf_prompts') ||
61+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/export_model_artifact.sh') ||
62+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/test_model_e2e.sh') ||
63+
needs.run-decision.outputs.is-full-run == 'true'
3764
runs-on: ubuntu-22.04
3865
outputs:
3966
benchmark_configs: ${{ steps.set-parameters.outputs.benchmark_configs }}
@@ -145,9 +172,26 @@ jobs:
145172
benchmark-cuda:
146173
name: benchmark-cuda
147174
needs:
175+
- changed-files
176+
- run-decision
148177
- set-parameters
149178
- export-models
150-
if: always()
179+
# Inherit the gate from set-parameters/export-models (they cascade-
180+
# skip when the gate evaluates false). `always()` keeps benchmark-
181+
# cuda running even when some export-models matrix cells fail —
182+
# but only if the gate itself is open. Without the explicit gate
183+
# here, `always()` would fire benchmark-cuda even when set-
184+
# parameters was gated out.
185+
if: |
186+
always() &&
187+
(
188+
contains(needs.changed-files.outputs.changed-files, '.github/workflows/cuda-perf.yml') ||
189+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/cuda_benchmark.py') ||
190+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/cuda_perf_prompts') ||
191+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/export_model_artifact.sh') ||
192+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/test_model_e2e.sh') ||
193+
needs.run-decision.outputs.is-full-run == 'true'
194+
)
151195
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
152196
permissions:
153197
id-token: write
@@ -316,8 +360,21 @@ jobs:
316360
317361
upload-benchmark-results:
318362
needs:
363+
- changed-files
364+
- run-decision
319365
- benchmark-cuda
320-
if: always()
366+
# Same gate as benchmark-cuda — skip the upload when the gate
367+
# closed (no benchmarks ran).
368+
if: |
369+
always() &&
370+
(
371+
contains(needs.changed-files.outputs.changed-files, '.github/workflows/cuda-perf.yml') ||
372+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/cuda_benchmark.py') ||
373+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/cuda_perf_prompts') ||
374+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/export_model_artifact.sh') ||
375+
contains(needs.changed-files.outputs.changed-files, '.ci/scripts/test_model_e2e.sh') ||
376+
needs.run-decision.outputs.is-full-run == 'true'
377+
)
321378
runs-on: ubuntu-22.04
322379
environment: upload-benchmark-results
323380
permissions:

.github/workflows/cuda-windows.yml

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,36 @@ concurrency:
2222
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}-${{ github.event_name == 'schedule' }}
2323
cancel-in-progress: false
2424

25+
permissions:
26+
contents: read
27+
2528
jobs:
29+
changed-files:
30+
name: Get changed files
31+
uses: ./.github/workflows/_get-changed-files.yml
32+
with:
33+
include-push-diff: true
34+
35+
run-decision:
36+
name: CI run decision
37+
uses: ./.github/workflows/_ci-run-decision.yml
38+
2639
export-model-cuda-windows-artifact:
2740
name: export-model-cuda-windows-artifact
28-
# Skip this job if the pull request is from a fork (HuggingFace secrets are not available)
29-
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
41+
# Skip this job if the pull request is from a fork (HuggingFace secrets are not available).
42+
# Path-filtered on push: mirrors the workflow-level pull_request `paths:`
43+
# filter so push commits that don't touch CUDA-relevant paths skip
44+
# this job on non-sampled commits. See _ci-run-decision.yml for
45+
# the sampling policy.
46+
needs: [changed-files, run-decision]
47+
if: |
48+
(github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') &&
49+
(
50+
contains(needs.changed-files.outputs.changed-files, 'backends/cuda') ||
51+
contains(needs.changed-files.outputs.changed-files, 'backends/aoti') ||
52+
contains(needs.changed-files.outputs.changed-files, '.github/workflows/cuda-windows.yml') ||
53+
needs.run-decision.outputs.is-full-run == 'true'
54+
)
3055
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
3156
permissions:
3257
id-token: write
@@ -114,7 +139,20 @@ jobs:
114139
115140
test-model-cuda-windows-e2e:
116141
name: test-model-cuda-windows-e2e
117-
needs: export-model-cuda-windows-artifact
142+
# Same path filter as the export job above. Also explicitly gated
143+
# on the export job succeeding — when needs: jobs are *skipped*
144+
# (e.g. fork PR), GitHub still evaluates this if:, so without the
145+
# explicit success-check this job would run and then fail trying
146+
# to download an artifact that was never produced.
147+
needs: [changed-files, export-model-cuda-windows-artifact, run-decision]
148+
if: |
149+
needs.export-model-cuda-windows-artifact.result == 'success' &&
150+
(
151+
contains(needs.changed-files.outputs.changed-files, 'backends/cuda') ||
152+
contains(needs.changed-files.outputs.changed-files, 'backends/aoti') ||
153+
contains(needs.changed-files.outputs.changed-files, '.github/workflows/cuda-windows.yml') ||
154+
needs.run-decision.outputs.is-full-run == 'true'
155+
)
118156
uses: pytorch/test-infra/.github/workflows/windows_job.yml@main
119157
strategy:
120158
fail-fast: false

0 commit comments

Comments
 (0)