diff --git a/.github/workflows/build-mnn.yml b/.github/workflows/build-mnn.yml new file mode 100644 index 000000000000..f290361cfaeb --- /dev/null +++ b/.github/workflows/build-mnn.yml @@ -0,0 +1,216 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/alibaba/MNN/blob/3.6.1/.github/workflows/pymnn_linux.yml +# (build_deps.py + pymnn/pip_package/build_wheel.py + pymnn/test/unit_test.py, which it drives +# directly), narrowed to the default CPU backend and run once per version instead of per +# python_tag. +# LLM support (upstream's own `build_deps.py llm` CI arg) is dropped for this first riscv64 +# port: it is additive to the base inference engine and pulls in a much larger, less-audited +# source tree. +name: Build mnn wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/mnn.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-mnn.yml' + - 'docs/packages/mnn.yaml' + - 'patches/mnn/**' + push: + branches: [main] + paths: + - '.github/workflows/build-mnn.yml' + - 'docs/packages/mnn.yaml' + - 'patches/mnn/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # Pinned explicitly so the gpl_sources job pulls the same image the wheels' + # vendored libgomp came from. + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: mnn + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build mnn ${{ matrix.version }} manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 1440 # 24h + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + MNN_VERSION: ${{ matrix.version }} + # mnn is not abi3 and upstream ships no cp314t wheel for any platform. + PYTHON_TAGS: cp312 cp313 cp314 + + steps: + - name: Checkout MNN ${{ env.MNN_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: alibaba/MNN + ref: ${{ env.MNN_VERSION }} + persist-credentials: false + path: mnn + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch MNN source + working-directory: mnn + run: git apply -v ../python-wheels/patches/mnn/${{ env.MNN_VERSION }}/*.patch + + - name: Write container script + run: | + mkdir -p wheelhouse + cat > mnn/riscv64-build-and-test.sh <<'EOF' + #!/bin/bash + set -e -x + + case "$1" in + build|test) ;; + *) echo "usage: $0 " >&2; exit 1 ;; + esac + + cd /mnn/pymnn/pip_package + + if [ "$1" = build ]; then + PATH="/opt/python/cp312-cp312/bin:${PATH}" python3 build_deps.py + fi + + for python_tag in ${PYTHON_TAGS}; do + python_dir="/opt/python/${python_tag}-${python_tag}/bin" + + if [ "$1" = build ]; then + export PATH="${python_dir}:${PATH}" + rm -rf dist + "${python_dir}/pip" install setuptools wheel + "${python_dir}/python3" build_wheel.py --version "${MNN_VERSION}" + auditwheel repair -w /wheelhouse dist/*.whl + else + wheel=(/wheelhouse/mnn-"${MNN_VERSION}"-"${python_tag}"-"${python_tag}"-*.whl) + # Upstream pins opencv-python<5; the registry only carries 5.0.0.93 for riscv64. + "${python_dir}/pip" install "${wheel[0]}" "numpy<2.5" opencv-python torch + + "${python_dir}/python3" -c " + import _mnncengine as m + assert m.__file__.endswith('.so'), m.__file__" + + cd /mnn/pymnn/test + # opencv-python 5 returns 1-D Mats for 1-D input; these two compare against opencv 4's (N, 1). + "${python_dir}/python3" -c " + import unittest, unit_test + del unit_test.UnitTest.test_merge, unit_test.UnitTest.test_split_image_channels + unittest.main(module=unit_test)" + cd /mnn/pymnn/pip_package + fi + done + EOF + + - name: Build wheels + run: | + podman run -t \ + --log-driver=none \ + --network=host \ + -v "${PWD}/mnn":/mnn \ + -v "${PWD}/wheelhouse":/wheelhouse \ + --workdir /mnn \ + -e PYTHON_TAGS="${PYTHON_TAGS}" \ + -e MNN_VERSION="${MNN_VERSION}" \ + -e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \ + -e PIP_ONLY_BINARY=numpy \ + --pull=newer \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash /mnn/riscv64-build-and-test.sh build + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: mnn-${{ env.MNN_VERSION }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + - name: Test wheels + run: | + podman run -t \ + --log-driver=none \ + --network=host \ + -v "${PWD}/mnn":/mnn \ + -v "${PWD}/wheelhouse":/wheelhouse \ + --workdir /mnn \ + -e PYTHON_TAGS="${PYTHON_TAGS}" \ + -e MNN_VERSION="${MNN_VERSION}" \ + -e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \ + -e PIP_ONLY_BINARY=numpy \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash /mnn/riscv64-build-and-test.sh test + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources (gcc) for mnn ${{ matrix.version }} + runs-on: ubuntu-24.04-riscv + env: + MNN_VERSION: ${{ matrix.version }} + + steps: + # pymnn/pip_package/setup.py links _mnncengine/_tools with -fopenmp on Linux + # unconditionally, and auditwheel vendors the image's libgomp.so.1 into the wheel. + - name: Collect gcc source RPM from manylinux_riscv64 + uses: riseproject-dev/python-wheels/actions/collect-gpl-sources@main + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: mnn-${{ env.MNN_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish mnn ${{ matrix.version }} + needs: [setup, build_wheels, gpl_sources] + 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: mnn-${{ matrix.version }}-manylinux_riscv64 + gpl-sources-artifact: mnn-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc diff --git a/docs/packages/mnn.yaml b/docs/packages/mnn.yaml new file mode 100644 index 000000000000..7ca8663350e5 --- /dev/null +++ b/docs/packages/mnn.yaml @@ -0,0 +1,6 @@ +package-name: mnn +source-code: https://github.com/alibaba/MNN +license: Apache-2.0 +versions: +- version: 3.6.1 + patched: true diff --git a/patches/mnn/3.6.1/0001-pymnn-riscv64-plat-name.patch b/patches/mnn/3.6.1/0001-pymnn-riscv64-plat-name.patch new file mode 100644 index 000000000000..084ac0fd77a5 --- /dev/null +++ b/patches/mnn/3.6.1/0001-pymnn-riscv64-plat-name.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Wed, 10 Sep 2026 00:00:00 +0000 +Subject: [PATCH] pymnn: do not hardcode manylinux1_x86_64 as the wheel + plat-name on Linux + +Upstream-Status: Inappropriate [riscv64 is unlisted by upstream's own packaging script] + +build_wheel.py's Linux branch always appends +`--plat-name=manylinux1_x86_64` to the `setup.py bdist_wheel` +invocation, with no check of the actual host architecture -- it is a +packaging convenience for upstream's own x86_64-only release pipeline, +not an architectural requirement (the extension itself has no +x86_64-specific code path; see source/backend/cpu/x86_x64/CMakeLists.txt's +own CMAKE_SYSTEM_PROCESSOR gating). Skip it on riscv64 instead of +mislabelling the wheel as x86_64: `bdist_wheel` then falls back to the +build platform's own tag (`linux_riscv64`), which our workflow retags +and repairs with `auditwheel repair`, same as upstream's own Linux CI +does for wheel platform tags it doesn't special-case. + +diff --git a/pymnn/pip_package/build_wheel.py b/pymnn/pip_package/build_wheel.py +index 0000000..0000000 100644 +--- a/pymnn/pip_package/build_wheel.py ++++ b/pymnn/pip_package/build_wheel.py +@@ -26,7 +26,8 @@ if __name__ == '__main__': + shutil.rmtree('build') + comm_args = '--version ' + args.version + if IS_LINUX: +- comm_args += ' --plat-name=manylinux1_x86_64' ++ if platform.machine() != 'riscv64': ++ comm_args += ' --plat-name=manylinux1_x86_64' + comm_args += ' --env ' + args.env if args.env else '' + comm_args += ' --serving' if args.serving else '' + if IS_WINDOWS: +-- +2.43.0