Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/_ci-run-decision.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: CI Run Decision

# Single source of truth for "should this commit force-run all CI jobs
# regardless of path filter?". Used by per-job ``if:`` gates in pull.yml
# and trunk.yml so the sampling logic isn't repeated per job.
#
# Returns ``is-full-run = 'true'`` for:
# - workflow_dispatch (manual run)
# - ciflow/* tag pushes (maintainer-forced full run)
# - push events at every 4th commit by depth from main's root
# (deterministic 25% sample, hard cap of 4 commits between samples)
#
# Returns ``is-full-run = 'false'`` for:
# - pull_request / pull_request_target (use path filter instead)
# - push events not matching any of the above (path-filtered runs)
#
# See ``viable-strict-gate.yml``: viable/strict only advances on
# commits where this is true, so the path-filtered fast path doesn't
# silently advance partial signal.

on:
workflow_call:
outputs:
is-full-run:
description: "'true' if this commit should run all CI jobs regardless of path filter; 'false' otherwise."
value: ${{ jobs.decide.outputs.is-full-run }}

permissions:
contents: read

jobs:
decide:
runs-on: ubuntu-latest
outputs:
is-full-run: ${{ steps.compute.outputs.is-full-run }}
steps:
# Full history needed to compute commit depth via
# `git rev-list --first-parent --count`. The --first-parent flag
# follows only the linear main-branch history through merge
# commits, so the count maps 1:1 to pushes on main regardless of
# how many commits were in any merged PR.
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Compute is-full-run
id: compute
env:
EVENT_NAME: ${{ github.event_name }}
REF: ${{ github.ref }}
SHA: ${{ github.sha }}
run: |
set -eu

IS_FULL=false

case "$EVENT_NAME" in
workflow_dispatch)
IS_FULL=true
;;
esac

case "$REF" in
refs/tags/ciflow/*)
IS_FULL=true
;;
esac

# Depth-based 25% sample on push: every 4th commit on the
# linear main-branch history (depth %% 4 == 0). --first-parent
# is required — plain `git rev-list --count` would walk all
# merge parents, so the count would jump by (1 + PR_size) at
# each merge commit and the sample rate would be unpredictable.
# Hard guarantees with --first-parent:
# - Exactly 25% of pushes on main are sampled.
# - At most 3 non-sampled commits between any two samples.
# Re-runs of the same commit always have the same outcome.
if [ "$IS_FULL" = "false" ] && [ "$EVENT_NAME" = "push" ]; then
DEPTH=$(git rev-list --first-parent --count "$SHA")
if [ $((DEPTH % 4)) -eq 0 ]; then
IS_FULL=true
fi
echo "Depth: $DEPTH (first-parent; depth %% 4 = $((DEPTH % 4)))"
fi

echo "Event: $EVENT_NAME"
echo "Ref: $REF"
echo "SHA: $SHA"
echo "is-full-run: $IS_FULL"
echo "is-full-run=$IS_FULL" >> "$GITHUB_OUTPUT"
76 changes: 64 additions & 12 deletions .github/workflows/_get-changed-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@ name: Get Changed Files

on:
workflow_call:
inputs:
include-push-diff:
description: |
When true, on push events the output is the diff between
`github.event.before` and `github.sha` (computed via the
GitHub Compare API). Default is false: push events emit '*',
matching the historical behavior.
type: boolean
required: false
default: false
outputs:
changed-files:
description: "List of changed files (space-separated) or '*' if not in a PR"
description: "Space-separated list of changed files for PR events (and push events when include-push-diff=true); '*' otherwise."
value: ${{ jobs.get-changed-files.outputs.changed-files }}

permissions:
contents: read

jobs:
get-changed-files:
runs-on: ubuntu-latest
Expand All @@ -18,26 +31,65 @@ jobs:
id: get-files
env:
GH_TOKEN: ${{ github.token }}
INCLUDE_PUSH_DIFF: ${{ inputs.include-push-diff }}
run: |
# Check if we're in a pull request context
if [ "${{ github.event_name }}" = "pull_request" ] || [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "Running in PR context"
set -eu

# Get the PR number from the github context
PR_NUMBER="${{ github.event.number }}"
EVENT_NAME="${{ github.event_name }}"
REPO="${{ github.repository }}"

# Use gh CLI to get changed files in the PR with explicit repo
CHANGED_FILES=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/files --paginate --jq '.[] | select(.status != "removed") | .filename' | tr '\n' ' ' | sed 's/ $//')
# PR context: list files modified by the PR.
if [ "$EVENT_NAME" = "pull_request" ] || [ "$EVENT_NAME" = "pull_request_target" ]; then
echo "Running in PR context"
PR_NUMBER="${{ github.event.number }}"
CHANGED_FILES=$(gh api "repos/$REPO/pulls/$PR_NUMBER/files" --paginate \
--jq '.[] | select(.status != "removed") | .filename' | tr '\n' ' ' | sed 's/ $//')

if [ -z "$CHANGED_FILES" ]; then
echo "No changed files found, setting to '*'"
CHANGED_FILES="*"
fi

echo "Changed files: $CHANGED_FILES"
echo "changed-files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
exit 0
fi

else
echo "Not in PR context, setting changed files to '*'"
echo "changed-files=*" >> "$GITHUB_OUTPUT"
# Push context with opt-in: diff between previous tip and new
# tip via the GitHub Compare API. This is what lets path-
# filtered jobs skip on push commits that don't touch their
# relevant paths. Callers must explicitly request this with
# `include-push-diff: true` because some workflows (e.g.
# lint.yml) historically rely on the '*' value to take a
# broader code path.
if [ "$EVENT_NAME" = "push" ] && [ "$INCLUDE_PUSH_DIFF" = "true" ]; then
BEFORE="${{ github.event.before }}"
AFTER="${{ github.sha }}"
ZERO_SHA="0000000000000000000000000000000000000000"

if [ -z "$BEFORE" ] || [ "$BEFORE" = "$ZERO_SHA" ]; then
echo "No 'before' SHA on push event (tag/branch creation or initial push); setting changed files to '*'"
echo "changed-files=*" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "Running in push context: comparing $BEFORE..$AFTER"
CHANGED_FILES=$(gh api "repos/$REPO/compare/$BEFORE...$AFTER" --paginate \
--jq '.files[]? | select(.status != "removed") | .filename' 2>/dev/null \
| tr '\n' ' ' | sed 's/ $//' || echo "")

if [ -z "$CHANGED_FILES" ]; then
echo "Compare returned empty; setting changed files to '*'"
echo "changed-files=*" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "Changed files: $CHANGED_FILES"
echo "changed-files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
exit 0
fi

# Default for non-PR events (push without opt-in,
# workflow_dispatch, schedule, etc.): no diff. Emit '*' to
# preserve the historical behavior.
echo "Event '$EVENT_NAME' (or include-push-diff=false): emitting '*'"
echo "changed-files=*" >> "$GITHUB_OUTPUT"
48 changes: 44 additions & 4 deletions .github/workflows/mlx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,19 @@ concurrency:
permissions: {}

jobs:
# Emits is-full-run='true' for workflow_dispatch / ciflow tag /
# sampled-push commits (every 4th main/release commit by depth).
# Returns 'false' for pull_request events — PR jobs use the workflow-
# level `paths:` filter (above) for path-based gating instead.
run-decision:
name: CI run decision
uses: ./.github/workflows/_ci-run-decision.yml

test-mlx:
needs: run-decision
if: |
github.event_name == 'pull_request' ||
needs.run-decision.outputs.is-full-run == 'true'
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
with:
default-packages: ""
Expand Down Expand Up @@ -93,6 +105,10 @@ jobs:
echo "::endgroup::"

test-mlx-qwen35-moe:
needs: run-decision
if: |
github.event_name == 'pull_request' ||
needs.run-decision.outputs.is-full-run == 'true'
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
with:
default-packages: ""
Expand Down Expand Up @@ -145,6 +161,10 @@ jobs:
echo "::endgroup::"

backend-tester:
needs: run-decision
if: |
github.event_name == 'pull_request' ||
needs.run-decision.outputs.is-full-run == 'true'
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -191,6 +211,10 @@ jobs:
fi

test-mlx-parakeet:
needs: run-decision
if: |
github.event_name == 'pull_request' ||
needs.run-decision.outputs.is-full-run == 'true'
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
with:
default-packages: ""
Expand Down Expand Up @@ -248,7 +272,10 @@ jobs:
# Requires HuggingFace secrets — skip on fork PRs.
# Maintainers can opt-in by applying the ciflow/mlx label, which
# pushes a ciflow/mlx/<PR> tag that re-runs this workflow with secrets.
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
needs: run-decision
if: |
(github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') &&
(github.event_name == 'pull_request' || needs.run-decision.outputs.is-full-run == 'true')
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
secrets: inherit
with:
Expand Down Expand Up @@ -309,7 +336,10 @@ jobs:
test-mlx-voxtral-realtime:
# Requires HuggingFace secrets — skip on fork PRs.
# Maintainers can opt-in by applying the ciflow/mlx label.
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
needs: run-decision
if: |
(github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') &&
(github.event_name == 'pull_request' || needs.run-decision.outputs.is-full-run == 'true')
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
secrets: inherit
with:
Expand Down Expand Up @@ -387,7 +417,10 @@ jobs:
test-mlx-whisper:
# Requires HuggingFace secrets — skip on fork PRs.
# Maintainers can opt-in by applying the ciflow/mlx label.
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
needs: run-decision
if: |
(github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') &&
(github.event_name == 'pull_request' || needs.run-decision.outputs.is-full-run == 'true')
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
secrets: inherit
with:
Expand Down Expand Up @@ -439,6 +472,10 @@ jobs:


test-mlx-stories110m:
needs: run-decision
if: |
github.event_name == 'pull_request' ||
needs.run-decision.outputs.is-full-run == 'true'
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
with:
default-packages: ""
Expand Down Expand Up @@ -505,7 +542,10 @@ jobs:
test-mlx-llm:
# Requires HuggingFace secrets — skip on fork PRs.
# Maintainers can opt-in by applying the ciflow/mlx label.
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
needs: run-decision
if: |
(github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') &&
(github.event_name == 'pull_request' || needs.run-decision.outputs.is-full-run == 'true')
strategy:
fail-fast: false
matrix:
Expand Down
Loading
Loading