Conversation
litellm is no longer pure-Python: since the 1.9x line it builds via maturin and ships a PyO3/Rust extension (litellm.rust_bridge._native). The pyo3 crate enables abi3-py310, so upstream publishes a single cp310-abi3 wheel per arch (loadable on CPython 3.10-3.14) and no free-threaded wheel. Build shape (sdist -> bdist): - python_sdist (ubuntu-latest): build the sdist ourselves from the upstream v1.97.0 checkout. It is self-contained and arch-independent - bundles the committed litellm-rust/Cargo.lock (pinned crates, no floating resolve) and the prebuilt Next.js admin UI under litellm/proxy/_experimental/out/** - so the riscv64 bdist needs no npm/codegen. - build_wheels (ubuntu-24.04-riscv): one cibuildwheel run selecting cp312/cp313/cp314. It compiles the Rust once (-> cp310-abi3 wheel), reuses that wheel for the other interpreters (find_compatible_wheel "Skipping build step"), and still runs the test phase on each - one wheel out, load-tested on three CPythons. cp314t is excluded (no upstream free-threaded wheel; fastuuid and other riscv64 runtime deps have no cp314t wheels). Rust toolchain via rustup in CIBW_BEFORE_ALL_LINUX; musllinux skipped (no riscv64 musl host toolchain). - Smoke test exercises the compiled extension directly: the bridge loader swallows ImportError and returns None, so it asserts native_bridge_available() and calls _native.gil_stats() rather than relying on `import litellm` alone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| # litellm builds a single abi3 wheel (pyo3 `abi3-py310`). Selecting three | ||
| # interpreters does NOT rebuild three times: cibuildwheel compiles the | ||
| # Rust once (first tag -> cp310-abi3 wheel), then find_compatible_wheel | ||
| # matches that wheel for the others ("Skipping build step...") while STILL |
There was a problem hiding this comment.
Indeed, this is true on PyPI but, our build does not produce only a abi3-py310 wheel, but this is not true for our build here:
- 3.13 and 3.14 wheels are built after 3.12: https://github.com/riseproject-dev/python-wheels/actions/runs/32461915374/job/96710524640#step:4:1000
- the dry run publish job would have upload 3 wheels: https://github.com/riseproject-dev/python-wheels/actions/runs/32461915374/job/96722849675#step:2:456
So the build need to be fixed
| # upstream ships no free-threaded wheel, and our fastuuid/other riscv64 | ||
| # runtime deps have no cp314t wheels yet. Only manylinux: rustup.rs has no | ||
| # riscv64 musl host toolchain (same reason build-fastuuid/tiktoken skip it). | ||
| CIBW_BUILD: "cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64" |
There was a problem hiding this comment.
nit maybe this is more concise and clear?
| CIBW_BUILD: "cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64" | |
| CIBW_BUILD: "cp312 cp313 cp314" | |
| CIBW_SKIP: "*-musllinux_*" |
| # runtime deps have no cp314t wheels yet. Only manylinux: rustup.rs has no | ||
| # riscv64 musl host toolchain (same reason build-fastuuid/tiktoken skip it). | ||
| CIBW_BUILD: "cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64" | ||
| CIBW_BUILD_VERBOSITY: '1' |
There was a problem hiding this comment.
nitpick: do we need this, or it's only for debugging?
| UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/ | ||
| UV_INDEX_STRATEGY: unsafe-best-match | ||
| UV_ONLY_BINARY: ':all:' | ||
| MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 |
There was a problem hiding this comment.
any reason to define this? this is the default image for riscv64 selected by cibuildwheel.
One issue I can imagine is that: setting this, will try to use the latest tag available which may not be equivalent to the image selected by cibuildwheel which pin a tag for each cibuildwheel version https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/resources/pinned_docker_images.cfg#L51. So unless really necessary, I would just drop this and CIBW_MANYLINUX_RISCV64_IMAGE bollow
| # riscv64 musl host toolchain (same reason build-fastuuid/tiktoken skip it). | ||
| CIBW_BUILD: "cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64" | ||
| CIBW_BUILD_VERBOSITY: '1' | ||
| CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} |
There was a problem hiding this comment.
drop this, see previous comment
| tar zxf "${{ steps.fetch_sdist.outputs.download-path }}/${{ needs.python_sdist.outputs.sdist_artifact_name }}" \ | ||
| --strip-components=1 -C litellm | ||
| uv pip install --upgrade cibuildwheel | ||
| python -m cibuildwheel --output-dir wheelhouse ./litellm |
There was a problem hiding this comment.
why not using the official pypa/cibuildwheel actions? we could also avoid the uv install step before
| UV_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ | ||
| UV_INDEX_STRATEGY=unsafe-best-match |
There was a problem hiding this comment.
do we need the UV variable? I think cibuildwheel uses venv + pip and not uv unless we specifically ask it
| with: | ||
| name: ${{ needs.python_sdist.outputs.sdist_artifact_name }} | ||
|
|
||
| - name: Install uv |
There was a problem hiding this comment.
I think we can skip this one, see comment below
Git tag has features=["extension-module"] (no abi3); upstream adds abi3-py310 only at release time. Building from source produced 3 per-interpreter wheels + 3x Rust compile. Set abi3-py312 on the sdist so maturin emits one cp312-abi3 wheel, deduped across cp312/313/314. - use pypa/cibuildwheel action with sdist as package-dir; drop manual tar + uv install - drop MANYLINUX_RISCV64_IMAGE override (cibuildwheel pins by digest) - CIBW_BUILD "cp312 cp313 cp314" + CIBW_SKIP musllinux - drop UV_* env and CIBW_BUILD_VERBOSITY Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Julien Stephan <jstephan@baylibre.com>
What
Adds
.github/workflows/build-litellm.ymlto build litellm 1.97.0 riscv64 wheels and publish them topypi.riseproject.dev.Why litellm is special
As of the 1.9x line, litellm is no longer pure-Python: its build backend is maturin and it ships a PyO3/Rust extension (
litellm.rust_bridge._native, crate atlitellm-rust/crates/python-bridge). The pyo3 crate enablesabi3-py310, so upstream publishes exactly onecp310-abi3wheel per arch (loadable on CPython 3.10–3.14) and no free-threaded wheel.Build shape (sdist → bdist)
python_sdist(ubuntu-latest): builds the sdist itself from the upstreamv1.97.0checkout (never the prebuilt PyPI sdist). The sdist is self-contained and arch-independent — it bundles the committedlitellm-rust/Cargo.lock(pinned crates, no floating resolve; upstream's owntest-rust.ymlbuilds--locked) and the prebuilt Next.js admin UI underlitellm/proxy/_experimental/out/**— so the riscv64 bdist needs no npm/codegen.build_wheels(ubuntu-24.04-riscv): a singlecibuildwheelrun selectingcp312/cp313/cp314. It compiles the Rust once (→cp310-abi3wheel), reuses that wheel for the other interpreters (find_compatible_wheel→ "Skipping build step…"), and still runs the test phase on each — one wheel out, load-tested on three CPythons.cp314tis excluded (no upstream free-threaded wheel;fastuuidand other riscv64 runtime deps have no cp314t wheels yet). Rust toolchain installed viarustupinCIBW_BEFORE_ALL_LINUX; musllinux skipped (rustup ships no riscv64 musl host toolchain).publish: the sharedpublish-wheelsaction (dry-runs off PR branches).Testing
The bridge loader (
litellm/rust_bridge/loader.py) swallowsImportErrorand returnsNone, soimport litellmalone would pass even with a broken/missing_native. The smoke test therefore exercises the compiled extension directly: assertsnative_bridge_available() is Trueand calls the zero-arg native_native.gil_stats().Local validation done
yaml.safe_load+actionlintclean (only the expectedubuntu-24.04-riscvunknown-label warning).cp310-abi3.soloads on CPython 3.12 and the exactCIBW_TEST_COMMANDruns green with the full runtime-dep chain installed.--only-binaryfor cp312/cp313/cp314 (public PyPI + RISE registry).aws-lc-sys 0.43.0(pinned inCargo.lock) ships a prebuiltriscv64gc_unknown_linux_gnu_crypto.rsbinding at its tag, andring 0.17.14builds for riscv64.🤖 Generated with Claude Code