-
Notifications
You must be signed in to change notification settings - Fork 0
workflows: add build-litellm.yml for riscv64 manywheel builds #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
luhenry
wants to merge
3
commits into
main
Choose a base branch
from
litellm
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| # SPDX-FileCopyrightText: 2026 The RISE Project | ||
| # SPDX-License-Identifier: MIT | ||
| --- | ||
| # litellm is no longer pure-Python: since the 1.9x line its build-backend is | ||
| # maturin and it ships a PyO3/Rust extension (litellm.rust_bridge._native, crate | ||
| # at litellm-rust/crates/python-bridge). Upstream's PyPI release publishes exactly | ||
| # ONE Linux wheel per arch - litellm-<ver>-cp310-abi3 - loadable on every CPython | ||
| # 3.10-3.14 (requires-python is ">=3.10, <3.15"), and there is NO free-threaded | ||
| # (cp314t) wheel. The abi3 pyo3 feature that yields that single wheel is added at | ||
| # release time and is NOT in the git tag (see the "abi3 floor" step below), so we | ||
| # set it ourselves - pinned to abi3-py312 (RISE's minimum published Python) - and | ||
| # build one cp312-abi3 wheel, then let cibuildwheel reuse+test it on 3.12/3.13/3.14. | ||
| name: Build litellm wheels (riscv64) | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'litellm version to build (git tag without leading v, e.g. 1.97.0)' | ||
| required: true | ||
| default: '1.97.0' | ||
| pull_request: | ||
| paths: | ||
| - '.github/workflows/build-litellm.yml' | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ inputs.version || '1.97.0' }}-${{ github.head_ref || github.run_id }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read # to fetch code (actions/checkout) | ||
|
|
||
| env: | ||
| # `inputs.version` is empty on pull_request events; default to 1.97.0 there. | ||
| LITELLM_VERSION: ${{ inputs.version || '1.97.0' }} | ||
|
|
||
| jobs: | ||
| # Build the sdist ourselves from an upstream checkout (never consume the | ||
| # prebuilt PyPI sdist). The litellm sdist is self-contained and | ||
| # arch-independent: it bundles the committed litellm-rust/Cargo.lock (so crate | ||
| # versions are pinned - no floating-dep resolve, and upstream's own test-rust.yml | ||
| # builds `--locked`) and the prebuilt Next.js admin UI under | ||
| # litellm/proxy/_experimental/out/** (checked into the repo at the tag), so the | ||
| # riscv64 bdist needs neither npm/codegen nor git history. Build it once on | ||
| # ubuntu-latest (see CLAUDE.md gotcha 4). | ||
| python_sdist: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| sdist_artifact_name: ${{ steps.build_sdist.outputs.sdist_artifact_name }} | ||
| package_version: ${{ steps.build_sdist.outputs.package_version }} | ||
| steps: | ||
| - name: Checkout litellm v${{ env.LITELLM_VERSION }} | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| repository: BerriAI/litellm | ||
| ref: v${{ env.LITELLM_VERSION }} | ||
| persist-credentials: false | ||
|
|
||
| - name: Install Python | ||
| uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 | ||
| with: | ||
| python-version: '3.12' | ||
| activate-environment: true | ||
| enable-cache: false | ||
|
|
||
| - name: Set a fixed abi3 floor for the pyo3 extension | ||
| run: | | ||
| set -euo pipefail | ||
| # litellm ships a single abi3 wheel per arch on PyPI, but the abi3 pyo3 | ||
| # feature that produces it is NOT in the git tag: the tag has | ||
| # `features = ["extension-module"]`, and BerriAI adds the abi3 feature | ||
| # only in their release-time packaging (the PyPI sdist's Cargo.toml has | ||
| # `abi3-py310`; `git checkout v1.97.0` does not). Since we build the sdist | ||
| # from source rather than consuming the prebuilt PyPI sdist, we must set | ||
| # the abi3 feature ourselves - otherwise maturin's abi3 probe finds no | ||
| # abi3 feature and falls back to per-interpreter wheels (cp312-cp312, | ||
| # cp313-cp313, ...), recompiling the Rust world once per Python. | ||
| # | ||
| # We pin an explicit floor of `abi3-py312` (NOT the bare `abi3` feature): | ||
| # * bare `abi3` -> maturin tags Abi3Version::CurrentPython, i.e. a | ||
| # DIFFERENT cpNN-abi3 per build interpreter (cp313-abi3, cp314-abi3), | ||
| # which is exactly the spray we must not publish; | ||
| # * `abi3-py312` -> maturin always tags `cp312-abi3` regardless of the | ||
| # build interpreter, so every interpreter in the matrix produces the | ||
| # one identical wheel (deduped by find_compatible_wheel). | ||
| # 3.12 is RISE's minimum published Python, so a cp312-abi3 floor (loadable | ||
| # on 3.12/3.13/3.14) is the right target - we intentionally do NOT mirror | ||
| # upstream's 3.10 floor. Idempotent: only rewrites the | ||
| # extension-module-only line; a no-op if an abi3 feature is ever committed | ||
| # upstream. Bake it in BEFORE building the sdist so maturin captures it. | ||
| bridge_manifest="litellm-rust/crates/python-bridge/Cargo.toml" | ||
| sed -i -E 's/^(pyo3 = \{ workspace = true, features = \[)("extension-module")(\] \})/\1\2, "abi3-py312"\3/' "${bridge_manifest}" | ||
| echo "pyo3 feature line now:" | ||
| grep -n 'pyo3 = ' "${bridge_manifest}" | ||
| grep -q 'abi3-py312' "${bridge_manifest}" || { echo "::error::failed to set abi3-py312 (upstream line shape changed?)"; exit 1; } | ||
|
|
||
| - name: Build sdist | ||
| id: build_sdist | ||
| run: | | ||
| set -euo pipefail | ||
| rm -rf dist | ||
| # maturin is the build backend; `python -m build --sdist` invokes it to | ||
| # assemble the source tree (Cargo.lock + prebuilt frontend already in the | ||
| # checkout, plus the abi3 floor set above, are packaged in). | ||
| # twine check validates the metadata. | ||
| uv pip install 'maturin==1.9.4' build twine | ||
| python -m build --sdist --outdir dist | ||
| twine check dist/* | ||
|
|
||
| sdist_name="$(ls dist)" | ||
| { | ||
| echo "sdist_artifact_name=${sdist_name}" | ||
| echo "package_version=$(echo "${sdist_name}" | sed -En 's/litellm-(.+)\.tar\.gz/\1/p')" | ||
| } >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Upload sdist artifact | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: ${{ steps.build_sdist.outputs.sdist_artifact_name }} | ||
| path: dist/${{ steps.build_sdist.outputs.sdist_artifact_name }} | ||
| if-no-files-found: error | ||
|
|
||
| build_wheels: | ||
| needs: [python_sdist] | ||
| name: Build litellm ${{ inputs.version || '1.97.0' }} cp312-abi3-manylinux_riscv64 | ||
| runs-on: ubuntu-24.04-riscv | ||
| timeout-minutes: 90 | ||
| steps: | ||
| - name: Fetch sdist artifact | ||
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||
| with: | ||
| name: ${{ needs.python_sdist.outputs.sdist_artifact_name }} | ||
| path: dist | ||
|
|
||
| - name: Build and test wheel | ||
| uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 | ||
| with: | ||
| # cibuildwheel accepts an sdist tarball directly: it extracts it and | ||
| # builds from the self-contained tree (Cargo.lock + prebuilt frontend + | ||
| # the abi3 floor we set are all bundled). | ||
| package-dir: dist/${{ needs.python_sdist.outputs.sdist_artifact_name }} | ||
| env: | ||
| CIBW_ARCHS: riscv64 | ||
| # With the abi3-py312 floor set on the sdist, maturin tags every build | ||
| # `cp312-abi3` regardless of the interpreter. Selecting three interpreters | ||
| # therefore does NOT rebuild three times: cibuildwheel compiles the Rust | ||
| # once (first tag -> cp312-abi3 wheel), then find_compatible_wheel matches | ||
| # that wheel for cp313/cp314 ("Skipping build step...") while STILL running | ||
| # the test phase on each - so we get ONE cp312-abi3 wheel out but genuine | ||
| # load-and-test coverage on 3.12/3.13/3.14. cp314t is excluded (abi3 can't | ||
| # target free-threaded, and fastuuid/other riscv64 runtime deps have no | ||
| # cp314t wheels). | ||
| CIBW_BUILD: "cp312-* cp313-* cp314-*" | ||
| # rustup.rs has no riscv64 musl host toolchain (same reason | ||
| # build-fastuuid/tiktoken skip it). | ||
| CIBW_SKIP: "*-musllinux_*" | ||
| # litellm ships no [tool.cibuildwheel], so install the Rust toolchain its | ||
| # maturin backend needs in-container and put it on PATH. Upstream builds | ||
| # on stable (test-rust.yml); the crate is edition 2024 / rust-version 1.88 | ||
| # with no nightly features, so the default stable rustup satisfies it. | ||
| CIBW_BEFORE_ALL_LINUX: >- | ||
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y | ||
| # CIBW_ENVIRONMENT applies to BOTH build and test. Point pip at our | ||
| # registry (needed so riscv64-only test deps - fastuuid, tiktoken, hf-xet, | ||
| # multidict, frozenlist, pyyaml - resolve; harmless at build time) and add | ||
| # cargo to PATH for the maturin build. We deliberately do NOT set | ||
| # only-binary here - see CIBW_TEST_ENVIRONMENT (gotcha 12). | ||
| CIBW_ENVIRONMENT: >- | ||
| PATH="$PATH:$HOME/.cargo/bin" | ||
| PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ | ||
| # Test phase only: force wheels-only so the full runtime closure (13 deps) | ||
| # must resolve as riscv64 wheels instead of silently source-building a | ||
| # heavy dep on the runner. Verified the closure resolves to riscv64 wheels | ||
| # across public PyPI + our registry for cp312/cp313/cp314. | ||
| CIBW_TEST_ENVIRONMENT: >- | ||
| PIP_ONLY_BINARY=:all: | ||
| # Smoke-test the COMPILED extension, not just `import litellm`. The bridge | ||
| # loader (litellm/rust_bridge/loader.py) catches ImportError and returns | ||
| # None, so a plain import would pass even with a broken/missing _native. | ||
| # Assert the native module is really loaded and call gil_stats() (a | ||
| # zero-arg native fn returning {"releases": int}, no network/side effects). | ||
| CIBW_TEST_COMMAND: >- | ||
| python -c "import litellm; | ||
| from litellm.rust_bridge import _native; | ||
| from litellm.rust_bridge.loader import native_bridge_available, get_native_bridge; | ||
| assert native_bridge_available() is True, 'native bridge unavailable'; | ||
| assert get_native_bridge() is _native; | ||
| s = _native.gil_stats(); | ||
| assert isinstance(s, dict) and 'releases' in s, s; | ||
| print('litellm _native OK on', __import__('sys').version.split()[0], '-> gil_stats()=', s)" | ||
|
|
||
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: litellm-${{ env.LITELLM_VERSION }}-cp312-abi3-manylinux_riscv64 | ||
| path: ./wheelhouse/*.whl | ||
| if-no-files-found: error | ||
|
|
||
| publish: | ||
| name: Publish litellm ${{ inputs.version || '1.97.0' }} to GitLab | ||
| needs: [python_sdist, build_wheels] | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| steps: | ||
| - name: Publish wheels and open docs PR | ||
| uses: riseproject-dev/python-wheels/actions/publish-wheels@main | ||
| with: | ||
| artifact-pattern: litellm-${{ needs.python_sdist.outputs.package_version }}-*-manylinux_riscv64 | ||
| gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }} | ||
| gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} | ||
| gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} | ||
| gh-token: ${{ secrets.GITHUB_TOKEN }} | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.