Repository navigation
Arm backend: Add person_detection_example OOTB test (#23645) #28724
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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::" |