Skip to content

executorch: Add version 1.4.1 #11

executorch: Add version 1.4.1

executorch: Add version 1.4.1 #11

# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on:
# https://github.com/pytorch/executorch/blob/v1.4.1/.github/workflows/build-wheels-linux.yml
name: Build executorch wheels (riscv64)
on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/executorch.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-executorch.yml'
- 'docs/packages/executorch.yaml'
- 'patches/executorch/**'
push:
branches: [main]
paths:
- '.github/workflows/build-executorch.yml'
- 'docs/packages/executorch.yaml'
- 'patches/executorch/**'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
env:
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: executorch
version: ${{ inputs.version }}
build_wheels:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
# cp310/cp311 are dropped: torch and pytorch-tokenizers (both hard runtime
# deps) have no riscv64 wheel for either on pypi.riseproject.dev. cp314t
# is dropped too: upstream itself ships no free-threaded wheel.
python: ["cp312", "cp313", "cp314"]
name: Build executorch ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 720
env:
EXECUTORCH_VERSION: ${{ matrix.version }}
steps:
- name: Checkout executorch v${{ env.EXECUTORCH_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: pytorch/executorch
ref: v${{ env.EXECUTORCH_VERSION }}
submodules: recursive
persist-credentials: false
- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false
- name: Apply patches
run: git apply python-wheels/patches/executorch/${{ env.EXECUTORCH_VERSION }}/*.patch
# Every library below is statically linked into the compiled extensions, and
# setuptools' license-files (widened by the patch above) only reaches the
# project root.
- name: Stage the vendored licences
run: |
set -euo pipefail
cp backends/xnnpack/third-party/FP16/LICENSE LICENSE.FP16
cp backends/xnnpack/third-party/FXdiv/LICENSE LICENSE.FXdiv
cp backends/xnnpack/third-party/XNNPACK/LICENSE LICENSE.XNNPACK
cp backends/xnnpack/third-party/cpuinfo/LICENSE LICENSE.cpuinfo
cp backends/xnnpack/third-party/pthreadpool/LICENSE LICENSE.pthreadpool
cp third-party/flatbuffers/LICENSE LICENSE.flatbuffers
cp third-party/flatcc/LICENSE LICENSE.flatcc
cp kernels/optimized/third-party/eigen/LICENSE LICENSE.eigen
cp third-party/pocketfft/LICENSE.md LICENSE.pocketfft
cp third-party/json/LICENSE.MIT LICENSE.json
cp third-party/pybind11/LICENSE LICENSE.pybind11
# Upstream's own smoke test (.ci/scripts/wheel/test_linux.py) exercises the
# examples/ tree, which needs torchvision for its MobileNetV3 model;
# torchvision has no riscv64 wheel. This test keeps upstream's own
# assertions (registered backend names) and replaces the model with a small
# hand-built one, run through both the portable (non-delegated) path and a
# real XNNPACK-delegated export, so the compiled extension, the portable
# kernels and the XNNPACK backend are all genuinely exercised end to end.
- name: Write the riscv64 smoke test
run: |
cat > riscv64_smoke_test.py <<'EOF'
import platform
import torch
from torch.export import export
from executorch.backends.xnnpack.partition.xnnpack_partitioner import (
XnnpackPartitioner,
)
from executorch.exir import to_edge, to_edge_transform_and_lower
from executorch.extension.pybindings.portable_lib import (
_get_registered_backend_names,
_load_for_executorch_from_buffer,
)
registered = _get_registered_backend_names()
assert "XnnpackBackend" in registered, registered
assert "OpenvinoBackend" in registered, registered
if platform.machine() in ("x86_64", "amd64"):
assert "QnnBackend" in registered, registered
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(4, 4)
def forward(self, x):
return self.linear(x).relu()
model = Model().eval()
example_inputs = (torch.randn(2, 4),)
expected = model(*example_inputs)
portable_prog = to_edge(export(model, example_inputs, strict=True)).to_executorch()
portable_module = _load_for_executorch_from_buffer(portable_prog.buffer)
portable_out = portable_module.forward(example_inputs)[0]
assert torch.allclose(portable_out, expected, atol=1e-4), (portable_out, expected)
print("portable (non-delegated) execution OK")
xnnpack_prog = to_edge_transform_and_lower(
export(model, example_inputs, strict=True),
partitioner=[XnnpackPartitioner()],
).to_executorch()
xnnpack_module = _load_for_executorch_from_buffer(xnnpack_prog.buffer)
xnnpack_out = xnnpack_module.forward(example_inputs)[0]
assert torch.allclose(xnnpack_out, expected, atol=1e-4), (xnnpack_out, expected)
print("XNNPACK-delegated execution OK")
EOF
- uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
only: ${{ matrix.python }}-manylinux_riscv64
env:
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
# No riscv64 cmake wheel exists, so pip builds it from source as a
# build dependency; its own CMake bootstrap needs OpenSSL's dev
# headers to find libcrypto.
CIBW_BEFORE_ALL_LINUX: dnf -y install openssl-devel
# get_torch_base_path (tools/cmake/Utils.cmake) runs `import torch` in
# the same interpreter as the build backend to add it to
# CMAKE_PREFIX_PATH, but torch is only a runtime dependency
# (setup.py), not a build-system.requires entry, so PEP 517 isolation
# hides it from that interpreter. Preinstall build-system.requires
# plus torch and turn isolation off (mirrors build-torchaudio.yml /
# build-torchcodec.yml).
CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation"
# torch_pin.py pins TORCH_VERSION="2.13.0": this CMake build compiles
# against the installed torch's real headers alongside executorch's own
# vendored, 2.13.0-frozen copy of them (runtime/core/portable_type/c10),
# so an unbounded floor (setup.py's own unpinned "torch>=2.13.0a0") lets
# pip resolve a newer registry release whose headers conflict with the
# vendored ones in the same translation unit (gotcha 615).
CIBW_BEFORE_BUILD: >-
pip install "cmake>=3.24,<4.0.0" "packaging>=24.2" pyyaml
"setuptools>=77.0.3" wheel zstd certifi &&
pip install --only-binary=:all: "torch==2.13.0"
CIBW_ENVIRONMENT: >-
CMAKE_BUILD_PARALLEL_LEVEL=8
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
# The torch wheel already carries these and loads them RTLD_GLOBAL (gotcha 17).
CIBW_REPAIR_WHEEL_COMMAND: >-
auditwheel repair -w {dest_dir} {wheel}
--exclude libtorch.so
--exclude libtorch_cpu.so
--exclude libtorch_python.so
--exclude libtorch_global_deps.so
--exclude libc10.so
--exclude libgomp.so.1
--exclude libgfortran.so.5
--exclude libopenblas.so.0
# Match the build-time pin (gotcha 615): the wheel's own metadata still
# says "torch>=2.13.0a0", so without this the test install would resolve
# a newer torch than the extension was actually compiled against.
CIBW_TEST_REQUIRES: torch==2.13.0
CIBW_TEST_SOURCES: riscv64_smoke_test.py
CIBW_TEST_COMMAND: python -X faulthandler riscv64_smoke_test.py
- name: Check the extensions and the vendored licences made it into the wheel
run: |
python3 - wheelhouse/*.whl <<'EOF'
import sys, zipfile
expected = {"LICENSE", "LICENSE.FP16", "LICENSE.FXdiv", "LICENSE.XNNPACK",
"LICENSE.cpuinfo", "LICENSE.pthreadpool", "LICENSE.flatbuffers",
"LICENSE.flatcc", "LICENSE.eigen", "LICENSE.pocketfft",
"LICENSE.json", "LICENSE.pybind11"}
for whl in sys.argv[1:]:
names = zipfile.ZipFile(whl).namelist()
assert [n for n in names
if n.startswith("executorch/extension/pybindings/_portable_lib.")
and n.endswith(".so")], whl
licenses = {n.split(".dist-info/licenses/", 1)[1] for n in names
if ".dist-info/licenses/" in n and not n.endswith("/")}
assert licenses == expected, f"{whl}: {sorted(licenses)}"
print(whl, "ok")
EOF
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: executorch-${{ env.EXECUTORCH_VERSION }}-${{ matrix.python }}-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error
publish:
name: Publish executorch ${{ matrix.version }}
needs: [setup, build_wheels]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
secrets:
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
with:
artifact-pattern: executorch-${{ matrix.version }}-*-manylinux_riscv64