From 8198b0321bdb628df6850ef6d01e6fe3cb1d614f Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 15 Oct 2026 03:25:39 +0000 Subject: [PATCH 1/2] pnnx: Add version 20260526 --- .github/workflows/build-pnnx.yml | 142 ++++++++++++++++++ docs/packages/pnnx.yaml | 5 + ...-cmake-require-c-20-for-pytorch-2.13.patch | 36 +++++ 3 files changed, 183 insertions(+) create mode 100644 .github/workflows/build-pnnx.yml create mode 100644 docs/packages/pnnx.yaml create mode 100644 patches/pnnx/20260526/0001-cmake-require-c-20-for-pytorch-2.13.patch diff --git a/.github/workflows/build-pnnx.yml b/.github/workflows/build-pnnx.yml new file mode 100644 index 00000000000..9e03d9aa368 --- /dev/null +++ b/.github/workflows/build-pnnx.yml @@ -0,0 +1,142 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `build`/`test` jobs of: +# https://github.com/Tencent/ncnn/blob/master/.github/workflows/pnnx.yml +name: Build pnnx wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/pnnx.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-pnnx.yml' + - 'docs/packages/pnnx.yaml' + - 'patches/pnnx/**' + push: + branches: [main] + paths: + - '.github/workflows/build-pnnx.yml' + - 'docs/packages/pnnx.yaml' + - 'patches/pnnx/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + # pnnx ships no sdist and no git tag; its PyPI version is the UTC build date + # (tools/pnnx/python/setup.py's set_version() is time.strftime("%Y%m%d")). + # Pinned to the last commit touching tools/pnnx or src/layer/* before the + # 20260526 release's upload_time_iso_8601 (2026-05-26T08:12Z). + PNNX_REF: b76824882e68d4a675c96e3f7e70d0104b2ff9c7 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: pnnx + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: ["cp312", "cp313", "cp314", "cp314t"] + name: Build pnnx ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 180 + + env: + PNNX_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout ncnn ${{ env.PNNX_REF }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Tencent/ncnn + ref: ${{ env.PNNX_REF }} + sparse-checkout: | + tools/pnnx + LICENSE.txt + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch pnnx source + run: git apply python-wheels/patches/pnnx/${{ env.PNNX_VERSION }}/*.patch + + # setuptools only auto-discovers LICENSE* next to setup.py; the repo's + # licence file lives three directories up (ncnn covers the whole tree). + - name: Stage the licence + run: cp LICENSE.txt tools/pnnx/python/LICENSE.txt + + - uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: tools/pnnx/python + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # pnnx links libtorch at build time (CMakeLists.txt's find_package(Torch + # REQUIRED), probed via `import torch` when no Torch_INSTALL_DIR is set); + # torch has no riscv64 wheel on public PyPI, only on our own registry. + CIBW_BEFORE_BUILD: "pip install --only-binary=:all: torch" + CIBW_ENVIRONMENT: >- + CMAKE_BUILD_PARALLEL_LEVEL=8 + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=torch + # setup.py's CMakeBuild shells out to cmake, which re-probes `import + # torch` itself; an isolated build env would not see the torch + # CIBW_BEFORE_BUILD already installed. + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + # TorchVision and onnxruntime are optional (CMakeLists.txt's own + # find_package(... QUIET) / find_library() fall back to "without" + # when absent); neither has a build-ready riscv64 recipe here, so + # this wheel converts plain TorchScript graphs but skips ONNX + # import/export and torchvision-op fusion. + CIBW_TEST_SOURCES: tools/pnnx/python/tests + CIBW_TEST_REQUIRES: pytest packaging + CIBW_TEST_ENVIRONMENT: >- + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=torch + CIBW_TEST_COMMAND: python -m pytest tools/pnnx/python/tests -v + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pnnx-${{ env.PNNX_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish pnnx ${{ 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: pnnx-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/pnnx.yaml b/docs/packages/pnnx.yaml new file mode 100644 index 00000000000..94d4959ff0f --- /dev/null +++ b/docs/packages/pnnx.yaml @@ -0,0 +1,5 @@ +package-name: pnnx +source-code: https://github.com/Tencent/ncnn/tree/master/tools/pnnx +license: BSD-3-Clause +versions: +- version: 20260526 diff --git a/patches/pnnx/20260526/0001-cmake-require-c-20-for-pytorch-2.13.patch b/patches/pnnx/20260526/0001-cmake-require-c-20-for-pytorch-2.13.patch new file mode 100644 index 00000000000..18a110846ec --- /dev/null +++ b/patches/pnnx/20260526/0001-cmake-require-c-20-for-pytorch-2.13.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 15 Oct 2026 03:21:35 +0000 +Subject: [PATCH] cmake: require c++20 for pytorch 2.13+ + +Upstream already fixed this (nihui/ncnn@61ea75123d0ea71e4b029742664e3d49b7c8eec6, +merged 2026-09-17), but this port pins pnnx at a commit from before that fix +(2026-05-26, matching the 20260526 PyPI release date). The riscv64 torch wheels +this repo publishes are 2.13.0+cpu and 2.14.0+cpu, both >= 2.13, so without this +backport the build fails: ATen's headers at torch>=2.13 require a C++20 +compiler, and the unpatched CMakeLists.txt here only ever requests C++17. + +Upstream-Status: Backport [https://github.com/Tencent/ncnn/commit/61ea75123d0ea71e4b029742664e3d49b7c8eec6] +--- + tools/pnnx/CMakeLists.txt | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/tools/pnnx/CMakeLists.txt b/tools/pnnx/CMakeLists.txt +index e50ab47..210eb73 100644 +--- a/tools/pnnx/CMakeLists.txt ++++ b/tools/pnnx/CMakeLists.txt +@@ -59,7 +59,10 @@ if(Torch_VERSION VERSION_LESS "1.8") + message(FATAL_ERROR "pnnx only supports PyTorch >= 1.8") + endif() + +-if(Torch_VERSION VERSION_GREATER_EQUAL "2.1") ++if(Torch_VERSION VERSION_GREATER_EQUAL "2.13") ++ # c++20 is required for using torch 2.13+ headers ++ set(CMAKE_CXX_STANDARD 20) ++elseif(Torch_VERSION VERSION_GREATER_EQUAL "2.1") + # c++17 is required for using torch 2.1+ headers + set(CMAKE_CXX_STANDARD 17) + endif() +-- +2.43.0 + From e7b1c1792994c595c2a12938396b72a0f27d5e93 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 23 Oct 2026 23:59:17 +0000 Subject: [PATCH 2/2] pnnx: fix the silent pnnx executable crash in the wheel tests All four build jobs of run 38676705597 built and repaired the wheel, then failed every test with FileNotFoundError on the *_pnnx.py the converter should have written: the bundled pnnx executable never ran (no stderr, although main() prints its parameters first). The executable is non-PIE, linked at riscv64's default base 0x10000. When auditwheel grafts libtorch/libc10/libtorch_cpu it rewrites the executable with patchelf, which prepends a segment at 0xe000, below the runners' vm.mmap_min_addr, so the kernel kills it at exec without output (gotcha 595). Link it with -Wl,-Ttext-segment=0x200000 via LDFLAGS. Once the executable runs, the generated *_pnnx.py modules import numpy unconditionally, so add numpy to the test requirements (binary-only: PyPI's newest numpy has no riscv64 wheel), as upstream's own test job does. Also pass the release version to setup.py, which otherwise stamps the wheel with the build date (the run above built pnnx-20261015). --- .github/workflows/build-pnnx.yml | 9 +++-- ...-the-wheel-version-from-PNNX_VERSION.patch | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 patches/pnnx/20260526/0002-setup-take-the-wheel-version-from-PNNX_VERSION.patch diff --git a/.github/workflows/build-pnnx.yml b/.github/workflows/build-pnnx.yml index 9e03d9aa368..11c82cb7f78 100644 --- a/.github/workflows/build-pnnx.yml +++ b/.github/workflows/build-pnnx.yml @@ -98,10 +98,14 @@ jobs: # REQUIRED), probed via `import torch` when no Torch_INSTALL_DIR is set); # torch has no riscv64 wheel on public PyPI, only on our own registry. CIBW_BEFORE_BUILD: "pip install --only-binary=:all: torch" + # The pnnx executable is non-PIE; keep its base above vm.mmap_min_addr once auditwheel's + # patchelf prepends a segment, or it dies silently at exec (gotcha 595). CIBW_ENVIRONMENT: >- CMAKE_BUILD_PARALLEL_LEVEL=8 PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ PIP_ONLY_BINARY=torch + PNNX_VERSION=${{ env.PNNX_VERSION }} + LDFLAGS="-Wl,-Ttext-segment=0x200000" # setup.py's CMakeBuild shells out to cmake, which re-probes `import # torch` itself; an isolated build env would not see the torch # CIBW_BEFORE_BUILD already installed. @@ -112,10 +116,11 @@ jobs: # this wheel converts plain TorchScript graphs but skips ONNX # import/export and torchvision-op fusion. CIBW_TEST_SOURCES: tools/pnnx/python/tests - CIBW_TEST_REQUIRES: pytest packaging + # The *_pnnx.py modules pnnx generates import numpy unconditionally. + CIBW_TEST_REQUIRES: pytest packaging numpy CIBW_TEST_ENVIRONMENT: >- PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ - PIP_ONLY_BINARY=torch + PIP_ONLY_BINARY=torch,numpy CIBW_TEST_COMMAND: python -m pytest tools/pnnx/python/tests -v - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/patches/pnnx/20260526/0002-setup-take-the-wheel-version-from-PNNX_VERSION.patch b/patches/pnnx/20260526/0002-setup-take-the-wheel-version-from-PNNX_VERSION.patch new file mode 100644 index 00000000000..d7d79843820 --- /dev/null +++ b/patches/pnnx/20260526/0002-setup-take-the-wheel-version-from-PNNX_VERSION.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 23 Oct 2026 23:50:00 +0000 +Subject: [PATCH] setup: take the wheel version from PNNX_VERSION + +setup.py stamps the wheel with the build date (time.strftime("%Y%m%d")), +which is how upstream versions each release: the release workflow builds +on the day it publishes. This port rebuilds an already-released version +from the commit it was cut from, so the build date is the wrong version: +CI run 38676705597 built pnnx-20261015 wheels for the 20260526 release, +and jobs straddling midnight UTC would produce mixed versions that the +publish step rejects. Let the workflow pass the release's version in +PNNX_VERSION, falling back to the build date when it is unset. + +Upstream-Status: Inappropriate [riscv64 port rebuilds a past date-stamped release; upstream versions wheels by their build date] +--- + tools/pnnx/python/setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/pnnx/python/setup.py b/tools/pnnx/python/setup.py +index bb3ca546..73560f8a 100644 +--- a/tools/pnnx/python/setup.py ++++ b/tools/pnnx/python/setup.py +@@ -10,7 +10,7 @@ from setuptools.command.build_ext import build_ext + from setuptools.command.install import install + + def set_version(): +- pnnx_version = time.strftime("%Y%m%d", time.localtime()) ++ pnnx_version = os.environ.get("PNNX_VERSION") or time.strftime("%Y%m%d", time.localtime()) + return pnnx_version + + # Parse environment variables +-- +2.43.0