Repository navigation
468 lines (426 loc) · 20.4 KB
/
Copy pathapple.yml
File metadata and controls
468 lines (426 loc) · 20.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
name: Apple
on:
push:
branches:
- main
- release/*
tags:
- ciflow/trunk/*
pull_request:
paths:
- .ci/docker/ci_commit_pins/pytorch.txt
- .ci/scripts/setup-macos.sh
- .ci/scripts/utils.sh
- .ci/scripts/setup-ios.sh
- .github/workflows/apple.yml
- install_executorch.sh
- backends/apple/**
- scripts/build_apple_frameworks.sh
- scripts/build_apple_llm_demo.sh
- scripts/create_frameworks.sh
- Package.swift
- CMakePresets.json
- third-party/CMakeLists.txt
- backends/mlx/**
- .ci/scripts/test_ios_ci.sh
- extension/apple/**
- extension/benchmark/apple/**
- extension/module/**
- torch_pin.py
workflow_dispatch:
schedule:
- cron: '0 10 * * *' # Runs daily at 2 AM PST
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}-${{ github.ref_type == 'branch' && github.sha }}-${{ github.event_name == 'workflow_dispatch' }}-${{ github.event_name == 'schedule' }}
cancel-in-progress: true
jobs:
set-version:
runs-on: ubuntu-22.04
outputs:
version: ${{ steps.set_version.outputs.version }}
release_branch: ${{ steps.set_version.outputs.release_branch }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Set VERSION variable
id: set_version
shell: bash
run: |
set -eux # Enable strict mode and command tracing
# Read base version from version.txt, extract only X.Y.Z (first match)
BASE_VERSION=$(grep -oE '^[0-9]+\.[0-9]+\.[0-9]+' version.txt | head -1)
if [[ -z "${BASE_VERSION}" ]]; then
echo "ERROR: Could not extract base version from version.txt"
echo "Expected format: X.Y.Z (e.g., 1.2.0 or 1.2.0a0)"
exit 1
fi
# Extract major.minor for release branch matching
MAJOR_MINOR=$(echo "${BASE_VERSION}" | grep -oE '^[0-9]+\.[0-9]+')
RELEASE_BRANCH="refs/heads/release/${MAJOR_MINOR}"
echo "release_branch=${RELEASE_BRANCH}" >> "$GITHUB_OUTPUT"
# For release branch matching version.txt (release/X.Y), use base version without date suffix
# For all other branches, append date for nightly builds
if [[ "${GITHUB_REF}" == "${RELEASE_BRANCH}" ]]; then
VERSION="${BASE_VERSION}"
else
VERSION="${BASE_VERSION}.$(TZ='PST8PDT' date +%Y%m%d)"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "BASE_VERSION=${BASE_VERSION}"
echo "RELEASE_BRANCH=${RELEASE_BRANCH}"
echo "GITHUB_REF=${GITHUB_REF}"
echo "VERSION=${VERSION}"
- name: Guardrail
if: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == steps.set_version.outputs.release_branch) }}
shell: bash
run: |
VERSION="${{ steps.set_version.outputs.version }}"
BRANCH="swiftpm-${VERSION}"
if git ls-remote --exit-code "https://github.com/${{ github.repository }}" "refs/heads/${BRANCH}" > /dev/null 2>&1; then
echo "Branch '${BRANCH}' already exists!"
echo "Aborting workflow to prevent overwriting S3 binaries. The branch existence indicates this version was already published with specific checksums."
echo "Please delete the remote branch '${BRANCH}' and re-run this workflow."
exit 1
fi
build-demo-ios:
name: build-demo-ios
# NB: Don't run this on fork PRs because they won't have access to the secret and would fail anyway
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
secrets: inherit
with:
default-packages: ""
runner: macos-15-xlarge
python-version: '3.11'
submodules: 'recursive'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout: 90
secrets-env: BUILD_CERTIFICATE_BASE64 EXECUTORCH_DEMO_BUILD_PROVISION_PROFILE_BASE64 KEYCHAIN_PASSWORD
upload-artifact: ios-demo-app
script: |
set -eux
# Pinned PyTorch's MPS build requires the Metal 4 SDK.
export DEVELOPER_DIR=/Applications/Xcode_26.0.1.app/Contents/Developer
xcodebuild -version
BUILD_TOOL=cmake
.ci/scripts/setup-conda.sh
# Setup Apple certificate for iOS development
BUILD_PROVISION_PROFILE_BASE64="${SECRET_EXECUTORCH_DEMO_BUILD_PROVISION_PROFILE_BASE64}" \
BUILD_CERTIFICATE_BASE64="${SECRET_BUILD_CERTIFICATE_BASE64}" \
KEYCHAIN_PASSWORD="${SECRET_KEYCHAIN_PASSWORD}" \
.ci/scripts/setup-ios.sh
# Setup MacOS dependencies as there is no Docker support on MacOS atm
GITHUB_RUNNER=1 PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
.ci/scripts/setup-macos.sh --build-tool "${BUILD_TOOL}"
export ARTIFACTS_DIR_NAME=artifacts-to-be-uploaded
# Build and test iOS Demo App
PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
.ci/scripts/test_ios_ci.sh "${ARTIFACTS_DIR_NAME}"
# Upload the test demo app to S3
upload-demo-ios:
needs: build-demo-ios
# Moving an artifact from GitHub to S3 needs no ExecuTorch toolchain, so
# this runs on a GitHub-hosted runner rather than moving to OSDC; the upload
# that used to ride on the EC2 instance role assumes role/arc through OIDC.
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Download the artifacts from GitHub
uses: actions/download-artifact@v4
with:
# The name here needs to match the name of the upload-artifact parameter
name: ios-demo-app
path: ${{ runner.temp }}/artifacts/
- name: Verify the artifacts
shell: bash
working-directory: ${{ runner.temp }}/artifacts/
run: |
set -eux
ls -lah ./
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::308535385114:role/arc
aws-region: us-east-1
- name: Upload the artifacts to S3
uses: seemethere/upload-artifact-s3@v5
with:
s3-bucket: gha-artifacts
s3-prefix: |
${{ github.repository }}/${{ github.run_id }}/artifact
retention-days: 14
if-no-files-found: ignore
path: ${{ runner.temp }}/artifacts/
test-demo-ios:
# Only PR from ExecuTorch itself has permission to access AWS, forked PRs will fail to
# authenticate with the cloud service. So, this job will be skipped on the latter
if: ${{ !github.event.pull_request.head.repo.fork }}
needs: upload-demo-ios
permissions:
id-token: write
contents: read
uses: pytorch/test-infra/.github/workflows/mobile_job.yml@main
with:
device-type: ios
# For iOS testing, the runner just needs to call AWS Device Farm, so there is no need to run this on macOS.
runner: ubuntu-latest
test-infra-ref: main
# This is the ARN of ExecuTorch project on AWS
project-arn: arn:aws:devicefarm:us-west-2:308535385114:project:02a2cf0f-6d9b-45ee-ba1a-a086587469e6
# This is the custom device pool that only includes iOS devices
device-pool-arn: arn:aws:devicefarm:us-west-2:308535385114:devicepool:02a2cf0f-6d9b-45ee-ba1a-a086587469e6/3b5acd2e-92e2-4778-b651-7726bafe129d
# Uploaded to S3 from the previous job
ios-ipa-archive: https://gha-artifacts.s3.amazonaws.com/${{ github.repository }}/${{ github.run_id }}/artifact/ExecuTorchDemo.ipa
ios-xctestrun-zip: https://gha-artifacts.s3.amazonaws.com/${{ github.repository }}/${{ github.run_id }}/artifact/ExecuTorchDemo.xctestrun.zip
test-spec: https://ossci-ios.s3.amazonaws.com/executorch/default-ios-device-farm-appium-test-spec.yml
build-frameworks-ios:
name: build-frameworks-ios
needs: set-version
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
with:
default-packages: ""
runner: macos-15-xlarge
python-version: '3.11'
submodules: 'recursive'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
upload-artifact: executorch-frameworks-ios
timeout: 90
script: |
set -eux
# Pinned PyTorch's MPS build requires the Metal 4 SDK.
export DEVELOPER_DIR=/Applications/Xcode_26.0.1.app/Contents/Developer
xcodebuild -version
BUILD_TOOL=cmake
VERSION="${{ needs.set-version.outputs.version }}"
FRAMEWORKS=(
"executorch"
"executorch_llm"
"executorch_dump"
"backend_coreml"
"backend_mlx"
"backend_xnnpack"
"kernels_llm"
"kernels_optimized"
"kernels_quantized"
"kernels_torchao"
"kleidiai"
"threadpool"
)
.ci/scripts/setup-conda.sh
# Setup MacOS dependencies as there is no Docker support on MacOS atm
GITHUB_RUNNER=1 PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
.ci/scripts/setup-macos.sh --build-tool "${BUILD_TOOL}"
# Install CoreML Backend Requirements
PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
backends/apple/coreml/scripts/install_requirements.sh
# Build iOS Frameworks
PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output scripts/build_apple_frameworks.sh
# Bundle iOS Frameworks
for FRAMEWORK in "${FRAMEWORKS[@]}"; do (
cd cmake-out && \
zip -r "${RUNNER_TEMP}/artifacts/${FRAMEWORK}-${VERSION}.zip" "${FRAMEWORK}.xcframework"
zip -r "${RUNNER_TEMP}/artifacts/${FRAMEWORK}_debug-${VERSION}.zip" "${FRAMEWORK}_debug.xcframework"
) done
# MLX metallibs are data files, so carry them in the artifact for the SwiftPM job to commit.
if [ -d .Package.swift/backend_mlx_resources ]; then
mkdir -p "${RUNNER_TEMP}/artifacts/backend_mlx_resources"
cp .Package.swift/backend_mlx_resources/*.metallib \
"${RUNNER_TEMP}/artifacts/backend_mlx_resources/" 2>/dev/null || true
fi
upload-frameworks-ios:
# NB: Don't run this on fork PRs because they won't have access to the secret and would fail anyway
if: ${{ !github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-22.04
needs: [build-frameworks-ios, set-version]
timeout-minutes: 30
environment: ${{ (github.ref == 'refs/heads/main' || github.ref == needs.set-version.outputs.release_branch) && 'cherry-pick-bot' || '' }}
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.GH_PYTORCHBOT_CHERRY_PICK_TOKEN || secrets.GITHUB_TOKEN }}
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: pip
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::308535385114:role/gha_executorch_upload-frameworks-ios
aws-region: us-east-1
- name: Download the artifact
uses: actions/download-artifact@v4
with:
# NB: The name here needs to match the upload-artifact name from build-frameworks-ios job
name: executorch-frameworks-ios
path: ${{ runner.temp }}/frameworks-ios/
- name: Only push to S3 when running the workflow manually from main or release branch
if: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == needs.set-version.outputs.release_branch) }}
shell: bash
run: |
echo "UPLOAD_ON_MAIN=1" >> "${GITHUB_ENV}"
- name: Upload the artifact to ossci-ios S3 bucket
shell: bash
run: |
set -eux
VERSION="${{ needs.set-version.outputs.version }}"
pip install awscli==1.32.18
AWS_CMD="aws s3 cp --dryrun"
if [[ "${UPLOAD_ON_MAIN:-0}" == "1" ]]; then
AWS_CMD="aws s3 cp"
fi
for FILENAME in "${RUNNER_TEMP}"/frameworks-ios/*.zip; do
[ -e "${FILENAME}" ] || continue
FRAMEWORK_NAME=$(basename "${FILENAME}" | sed "s/-${VERSION}.zip//")
CHECKSUM=$(shasum -a 256 "${FILENAME}" | cut -d ' ' -f1)
echo "${FRAMEWORK_NAME} ${CHECKSUM}" >> "${RUNNER_TEMP}/checksums.txt"
${AWS_CMD} "${FILENAME}" s3://ossci-ios/executorch/ --acl public-read
done
- name: Update SwiftPM
shell: bash
run: |
set -eux
VERSION="${{ needs.set-version.outputs.version }}"
BRANCH="swiftpm-${VERSION}"
git checkout swiftpm
if git show-ref --verify --quiet refs/heads/${BRANCH}; then
git checkout "${BRANCH}"
else
git checkout -b "${BRANCH}"
fi
# Always substitute from the template on the swiftpm branch, never from
# a Package.swift left behind by an earlier run for this same version.
# Nightly versions carry a date, so two runs in one day land on the same
# branch, and a release re-run reuses the version exactly; reusing the
# old file there would publish a manifest missing whatever the template
# gained since, and the failure surfaces as a product that is built but
# has no entry, with nothing pointing at the stale file.
git checkout swiftpm -- Package.swift.template
mv -f Package.swift.template Package.swift
sed -i "s/__VERSION__/${VERSION}/g" Package.swift
while read -r FRAMEWORK CHECKSUM; do
sed -i "s/__SHA256_${FRAMEWORK}__/${CHECKSUM}/g" Package.swift
done < "${RUNNER_TEMP}/checksums.txt"
if [[ "${UPLOAD_ON_MAIN:-0}" == "1" ]]; then
# The manifest and the built frameworks have to describe the same set,
# checked in both directions because each failure is silent on its own.
#
# A placeholder with no framework publishes literal text where a checksum
# belongs, which stops SwiftPM resolving the package for every consumer
# rather than only users of that product. Eight consecutive nightlies
# shipped that way in August 2025.
#
# A framework with no placeholder is the reverse: sed matches nothing, so
# the product silently disappears from the manifest while its archive is
# still uploaded. Release branches share this template, so a branch that
# still builds a removed framework lands here. Compared against the base
# name, because the manifest declares each product once and synthesises
# its debug variant.
#
# Gated on the upload, because only a run that publishes can do harm and a
# branch whose template is momentarily out of step should not redden every
# unrelated pull request.
if grep -q "__SHA256_" Package.swift; then
echo "::error::Package.swift still has unsubstituted checksums:"
grep -o "__SHA256_[A-Za-z_]*__" Package.swift | sort -u
exit 1
fi
while read -r FRAMEWORK CHECKSUM; do
if ! grep -q "\"${FRAMEWORK%_debug}\"" Package.swift; then
echo "::error::built framework ${FRAMEWORK} has no product in Package.swift"
exit 1
fi
done < "${RUNNER_TEMP}/checksums.txt"
# If MLX is being published, its Metal kernels have to come with it.
# The copy below is conditional and the capture upstream is best
# effort, so without this an incomplete set publishes a manifest
# declaring three resources that are not on the branch, and the
# consumer only finds out at Metal device init.
if grep -q "^backend_mlx " "${RUNNER_TEMP}/checksums.txt"; then
for SLICE in mlx-ios mlx-ios-simulator mlx-macos; do
METALLIB="${RUNNER_TEMP}/frameworks-ios/backend_mlx_resources/${SLICE}.metallib"
if [ ! -s "${METALLIB}" ]; then
echo "::error::backend_mlx is being published but ${SLICE}.metallib is missing or empty"
exit 1
fi
done
fi
# Nothing else evaluates this manifest before consumers do: the checks
# above are text greps, the swiftpm branch has no CI of its own, and a
# .template is not a manifest. Resolve it here so a Swift-level defect
# fails this run rather than every consumer of the published branch.
# Use describe, not dump-package: dump-package parses the manifest but
# returns 0 even when a target's directory is absent, while describe
# loads the package and fails, which is the case that reaches
# consumers when a stub directory is missed.
if ! swift package describe > /dev/null; then
echo "::error::the substituted Package.swift does not evaluate"
exit 1
fi
git config --global user.name "PyTorch Bot"
git config --global user.email "pytorchbot@users.noreply.github.com"
# Commit the MLX metallibs onto the package branch beside the manifest.
if [ -d "${RUNNER_TEMP}/frameworks-ios/backend_mlx_resources" ]; then
mkdir -p .Package.swift/backend_mlx_resources
cp "${RUNNER_TEMP}"/frameworks-ios/backend_mlx_resources/*.metallib \
.Package.swift/backend_mlx_resources/
git add -f .Package.swift/backend_mlx_resources/*.metallib
fi
git add Package.swift
git commit -am "${VERSION}"
git push -f origin "${BRANCH}"
else
echo "Draft Package.swift:"
cat Package.swift
fi
build-benchmark-app:
name: build-benchmark-app
# NB: Don't run this on fork PRs because they won't have access to the secret and would fail anyway
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
secrets: inherit
with:
default-packages: ""
runner: macos-15-xlarge
python-version: '3.11'
submodules: 'recursive'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
upload-artifact: ios-benchmark-app
secrets-env: BUILD_CERTIFICATE_BASE64 EXECUTORCH_BENCHMARK_BUILD_PROVISION_PROFILE_BASE64 KEYCHAIN_PASSWORD
timeout: 90
script: |
set -eux
# Pinned PyTorch's MPS build requires the Metal 4 SDK.
export DEVELOPER_DIR=/Applications/Xcode_26.0.1.app/Contents/Developer
xcodebuild -version
echo "::group::Setting up CI environment"
.ci/scripts/setup-conda.sh
BUILD_TOOL=cmake
# Setup MacOS dependencies as there is no Docker support on MacOS atm
GITHUB_RUNNER=1 PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
.ci/scripts/setup-macos.sh --build-tool "${BUILD_TOOL}"
export ARTIFACTS_DIR_NAME=artifacts-to-be-uploaded
# Setup Apple certificate for iOS development
BUILD_PROVISION_PROFILE_BASE64="${SECRET_EXECUTORCH_BENCHMARK_BUILD_PROVISION_PROFILE_BASE64}" \
BUILD_CERTIFICATE_BASE64="${SECRET_BUILD_CERTIFICATE_BASE64}" \
KEYCHAIN_PASSWORD="${SECRET_KEYCHAIN_PASSWORD}" \
.ci/scripts/setup-ios.sh
# Install CoreML Backend Requirements
PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output \
backends/apple/coreml/scripts/install_requirements.sh
echo "::endgroup::"
echo "::group::Build ExecuTorch iOS frameworks"
PYTHON_EXECUTABLE=python ${CONDA_RUN} --no-capture-output scripts/build_apple_frameworks.sh
echo "::endgroup::"
echo "::group::Build ExecuTorch benchmark app"
mkdir -p extension/benchmark/apple/Benchmark/Models
${CONDA_RUN} --no-capture-output \
scripts/build_apple_llm_demo.sh "${ARTIFACTS_DIR_NAME}"
echo "::endgroup::"