diff --git a/.github/workflows/publish-native.yml b/.github/workflows/publish-native.yml new file mode 100644 index 0000000..a55cd13 --- /dev/null +++ b/.github/workflows/publish-native.yml @@ -0,0 +1,166 @@ +name: Publish native companion + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + +jobs: + validate: + name: Validate native release + if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'native-v') }} + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - name: Match release tag to package version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + python3 - <<'PY' + import os + import pathlib + import tomllib + + metadata = tomllib.loads(pathlib.Path("native/pyproject.toml").read_text()) + expected = f"native-v{metadata['project']['version']}" + actual = os.environ.get("RELEASE_TAG", "") + if actual and actual != expected: + raise SystemExit(f"release tag {actual!r} must equal {expected!r}") + print(expected) + PY + + build: + name: Build ${{ matrix.platform }} wheels + needs: validate + if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'native-v') }} + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - platform: linux-x86_64 + os: ubuntu-latest + arch: x86_64 + build: cp310-* cp311-* cp312-* cp313-* cp314-* + - platform: macos-x86_64 + os: macos-15-intel + arch: x86_64 + build: cp310-* cp311-* cp312-* + - platform: macos-arm64 + os: macos-15 + arch: arm64 + build: cp310-* cp311-* cp312-* cp313-* cp314-* + - platform: windows-amd64 + os: windows-latest + arch: AMD64 + build: cp310-* cp311-* cp312-* cp313-* cp314-* + + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + env: + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_BUILD: ${{ matrix.build }} + with: + package-dir: native + output-dir: wheelhouse + + - name: Upload wheels + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-wheels-${{ matrix.platform }} + path: wheelhouse/*.whl + if-no-files-found: error + + sdist: + name: Build source distribution + needs: validate + if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'native-v') }} + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - name: Install uv and Python + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.8.15" + python-version: "3.10" + enable-cache: false + + - name: Build source distribution + run: uv build --sdist native --out-dir wheelhouse + + - name: Upload source distribution + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-sdist + path: wheelhouse/*.tar.gz + if-no-files-found: error + + attach: + name: Attach distributions to release + needs: [validate, build, sdist] + if: ${{ github.event_name == 'release' && startsWith(github.event.release.tag_name, 'native-v') }} + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download wheels + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + pattern: native-* + path: wheelhouse + merge-multiple: true + + - name: Attach wheels + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$RELEASE_TAG" wheelhouse/* + + publish: + name: Publish native distributions + needs: [validate, build, sdist] + if: ${{ github.event_name == 'release' && startsWith(github.event.release.tag_name, 'native-v') }} + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: + name: pypi + url: https://pypi.org/p/rosa-torch-native + permissions: + id-token: write + + steps: + - name: Download wheels + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + pattern: native-* + path: wheelhouse + merge-multiple: true + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + with: + packages-dir: wheelhouse diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c1763..feb21bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ semantic versioning while it remains in the 0.x development series. - The public `rosa` imports and existing stateful inference APIs are unchanged. - The distribution remains `rosa-torch` and supports Python 3.10+. -- The optional native companion remains separately versioned and optional. +- The optional `rosa-torch-native 0.3.0` companion remains separately versioned + and requires `rosa-torch[numba]>=0.3,<0.4`. ## 0.2.0 — 2026-08-11 diff --git a/README.md b/README.md index 6ab4106..c63ba2a 100644 --- a/README.md +++ b/README.md @@ -88,20 +88,15 @@ Install the stateful Link-Cut Tree backend with: uv add 'rosa-torch[numba]' ``` -For the lowest CPU step latency, build and install the optional native companion -locally. `rosa-torch-native` is not currently published on PyPI because it -requires per-platform and per-Python ABI wheels: +For the lowest CPU step latency, install the optional native companion. Wheels +are published separately for each supported platform and Python ABI: ```bash -git clone https://github.com/aabbdev/rosa.git -cd rosa -uv sync --extra numba -uv build --wheel native --out-dir native/dist -uv pip install native/dist/rosa_torch_native-0.2.0-*.whl +uv add rosa-torch-native ``` -The native sources are available from the Git repository and are not included -in the pure-Python `rosa-torch` source distribution on PyPI. +The native sources remain available from the Git repository and are not +included in the pure-Python `rosa-torch` source distribution. The stateful backend detects it lazily and otherwise falls back to Numba. diff --git a/native/LICENSE b/native/LICENSE new file mode 100644 index 0000000..0e66ebe --- /dev/null +++ b/native/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Lucas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/native/README.md b/native/README.md index 09cf72e..8ca867b 100644 --- a/native/README.md +++ b/native/README.md @@ -9,12 +9,19 @@ main Python package. The C++ core implements the validated exact production state machine. It binds the NumPy arrays of a `_StatefulInferenceState` once, updates them in place, and releases the GIL during computation. It neither includes nor calls -libtorch. The runtime dependency `rosa-torch[numba]>=0.2,<0.3` provides the +libtorch. The runtime dependency `rosa-torch[numba]>=0.3,<0.4` provides the compatible state contract together with PyTorch, NumPy, and Numba. The constructor validates every shape, dtype, counter, and ABI version before retaining any pointer. The current native state ABI is `1`. +The module also provides fused online RLBWT inference for exact dense top-1 +retrieval. `NativeRLBWTState` is the explicit baseline, +`NativeRLBWTCompactState` stores vocabularies of at most 256 IDs in adaptive +packed leaves, and `NativeRLBWTStateMC` exposes the separately named opt-in +Monte-Carlo variants. The Python package keeps these backends explicit; +`backend="auto"` continues to select the production suffix-automaton path. + The module also exposes `NativeCandidateState` for the exact rich state in `rosa._stateful_candidates_numba`. Its batched `step` maintains the same K suffixes, R newest occurrences, unbounded frequencies, and @@ -37,17 +44,10 @@ wheel does not provide a newer optional method. ## Installation and usage -`rosa-torch-native` is not currently published on PyPI. Build a wheel from a -Git checkout using the target Python interpreter, then install the matching -wheel for the current platform and ABI: +Install the matching wheel for the current platform and Python ABI from PyPI: ```bash -git clone https://github.com/aabbdev/rosa.git -cd rosa -uv sync --extra numba -uv build --python .venv/bin/python --wheel native --out-dir native/dist -uv pip install --python .venv/bin/python \ - native/dist/rosa_torch_native-0.2.0-*.whl +uv add rosa-torch-native ``` `rosa-torch` detects the extension automatically from its Numba inference @@ -102,7 +102,7 @@ uv build --python .venv/bin/python --wheel native \ --out-dir /tmp/rosa-native-dist uv run --isolated \ --with '.[numba]' \ - --with /tmp/rosa-native-dist/rosa_torch_native-0.2.0-*.whl \ + --with /tmp/rosa-native-dist/rosa_torch_native-0.3.0-*.whl \ native/tests/smoke.py ``` @@ -115,8 +115,8 @@ respectively, in the same isolated environment. ## Multi-platform publication -The next publication step is a dedicated `cibuildwheel` workflow after defining -the supported Python and architecture matrix, manylinux targets, and release -policy. No such workflow is included yet because an unvalidated matrix must not -be published. Every wheel contains native code and must be built separately for -each Python ABI and platform. +The release workflow builds CPython 3.10–3.14 wheels for Linux x86-64, macOS +arm64, and Windows x64, plus CPython 3.10–3.12 wheels for macOS x86-64. Each +wheel is smoke-tested by importing the extension and checking its production +and compact RLBWT ABI versions before it is attached to the GitHub Release and +published through PyPI Trusted Publishing. diff --git a/native/pyproject.toml b/native/pyproject.toml index 3f2f74e..2e1ccb9 100644 --- a/native/pyproject.toml +++ b/native/pyproject.toml @@ -8,11 +8,12 @@ build-backend = "setuptools.build_meta" [project] name = "rosa-torch-native" -version = "0.2.0" -description = "Optional native CPU step companion for rosa-torch" +version = "0.3.0" +description = "Optional native CPU inference companion for rosa-torch" readme = "README.md" requires-python = ">=3.10" license = "MIT" +license-files = ["LICENSE"] classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: C++", @@ -25,7 +26,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence", ] dependencies = [ - "rosa-torch[numba]>=0.2,<0.3", + "rosa-torch[numba]>=0.3,<0.4", ] [project.urls] @@ -34,3 +35,8 @@ Issues = "https://github.com/aabbdev/rosa/issues" [tool.setuptools] include-package-data = false + +[tool.cibuildwheel] +build = ["cp310-*", "cp311-*", "cp312-*", "cp313-*", "cp314-*"] +skip = ["*-musllinux_*", "*-manylinux_i686", "*-win32"] +test-command = "python -c \"import rosa_native_step as native; assert native.candidate_abi_version == 1; assert native.rlbwt_compact_abi_version == 1\"" diff --git a/native/uv.lock b/native/uv.lock index 4d3b479..f495566 100644 --- a/native/uv.lock +++ b/native/uv.lock @@ -684,14 +684,14 @@ wheels = [ [[package]] name = "rosa-torch" -version = "0.2.0" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/a7/f7fe036ff6f52e49f4a4a1a77abccb111d4545d9bf84e9968d301dc9a8e7/rosa_torch-0.2.0.tar.gz", hash = "sha256:86495aaa5102bcc89573e81386b292231e88493193e40f396838e3086dbfd815", size = 41687, upload-time = "2026-08-11T13:25:33.697Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/20/88b88b9b6307f72a2c3892405897efd370c7614abf49489569069ba7340d/rosa_torch-0.3.0.tar.gz", hash = "sha256:19009a36049c994bbc2950d1c9c9f113819f047dccc235a88ccd5d70eb35a677", size = 46505, upload-time = "2026-08-18T13:15:59.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/43/1ec2e812c6571a374ab1e9b17926ca96d250ffe9b10ad4052c86ad0f35ed/rosa_torch-0.2.0-py3-none-any.whl", hash = "sha256:992b66de7845b5fc3e537c5cf53e1f69aab2fb4b5ea5f292e00c5ea1c583cb10", size = 43645, upload-time = "2026-08-11T13:25:32.114Z" }, + { url = "https://files.pythonhosted.org/packages/53/bc/5477145f95f309938cd271e48892ca3721a1f425551ed0600f9c005aef3d/rosa_torch-0.3.0-py3-none-any.whl", hash = "sha256:8c143a8cd681ec30ca7783b80254057f750cc371542d35f2504ecf123a3b14a5", size = 48752, upload-time = "2026-08-18T13:15:58.144Z" }, ] [package.optional-dependencies] @@ -701,14 +701,14 @@ numba = [ [[package]] name = "rosa-torch-native" -version = "0.2.0" +version = "0.3.0" source = { editable = "." } dependencies = [ { name = "rosa-torch", extra = ["numba"] }, ] [package.metadata] -requires-dist = [{ name = "rosa-torch", extras = ["numba"], specifier = ">=0.2,<0.3" }] +requires-dist = [{ name = "rosa-torch", extras = ["numba"], specifier = ">=0.3,<0.4" }] [[package]] name = "setuptools"