Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 337 additions & 0 deletions .github/workflows/build-runai-model-streamer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
#
# This workflow is based on: https://github.com/run-ai/runai-model-streamer/blob/master/Makefile
---
name: Build runai-model-streamer wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/runai-model-streamer.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-runai-model-streamer.yml'
- 'docs/packages/runai-model-streamer.yaml'
- 'patches/runai-model-streamer/**'
push:
branches: [main]
paths:
- '.github/workflows/build-runai-model-streamer.yml'
- 'docs/packages/runai-model-streamer.yaml'
- 'patches/runai-model-streamer/**'

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

env:
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
# Upstream's own .bazelversion; bazelisk publishes no riscv64 binary, so it is
# bootstrapped from the dist archive instead.
BAZEL_VERSION: '7.6.1'
# Versions bazel 7.6.1's own MODULE.bazel pins; both need a riscv64 fix below.
RULES_PYTHON_VERSION: '0.33.2'
RULES_JAVA_VERSION: '7.6.5'

jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: runai-model-streamer
version: ${{ inputs.version }}

bazel:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Bootstrap bazel (riscv64)
runs-on: ubuntu-24.04-riscv
timeout-minutes: 120

steps:
- name: Restore bazel binary
id: cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: bazel-bin
key: bazel-${{ env.BAZEL_VERSION }}-manylinux_riscv64

- name: Bootstrap bazel ${{ env.BAZEL_VERSION }}
if: steps.cache.outputs.cache-hit != 'true'
run: |
mkdir -p bazel-bin
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e BAZEL_VERSION="${BAZEL_VERSION}" \
-e RULES_PYTHON_VERSION="${RULES_PYTHON_VERSION}" \
-e RULES_JAVA_VERSION="${RULES_JAVA_VERSION}" \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux

dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME

# rules_python 0.33.2's PLATFORMS has no riscv64 entry, aborting the bootstrap
# (bazelbuild/bazel#23018). Any linux entry is a safe stand-in: the toolchain it names
# is never selected on a riscv64 host. Fixed in bazel 8.2.0; the 7.x backport is open.
mkdir -p /tmp/rules_python
curl -fsSLo /tmp/rules_python.tar.gz "https://github.com/bazel-contrib/rules_python/releases/download/${RULES_PYTHON_VERSION}/rules_python-${RULES_PYTHON_VERSION}.tar.gz"
tar -xzf /tmp/rules_python.tar.gz -C /tmp/rules_python --strip-components=1
sed -i 's|fail("No platform declared for host OS {} on arch {}".format(os_name, arch))|return "x86_64-unknown-linux-gnu"|' \
/tmp/rules_python/python/private/toolchains_repo.bzl

# rules_java 7.x maps riscv64 to a stray-colon include path, so libcpu_profiler.so
# can't find jni_md.h. Fixed in rules_java 8.x, never backported.
mkdir -p /tmp/rules_java
curl -fsSLo /tmp/rules_java.tar.gz "https://github.com/bazelbuild/rules_java/releases/download/${RULES_JAVA_VERSION}/rules_java-${RULES_JAVA_VERSION}.tar.gz"
tar -xzf /tmp/rules_java.tar.gz -C /tmp/rules_java
sed -i 's|\[":include/linux"\]|["include/linux"]|g' /tmp/rules_java/toolchains/BUILD

mkdir -p /tmp/bazel-src
cd /tmp/bazel-src
curl -fsSLo dist.zip "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-dist.zip"
unzip -q dist.zip

EXTRA_BAZEL_ARGS="--tool_java_runtime_version=local_jdk \
--override_module=rules_python=/tmp/rules_python \
--override_module=rules_java=/tmp/rules_java" \
bash ./compile.sh
install -m 0755 output/bazel /work/bazel-bin/bazel
SCRIPT

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
path: bazel-bin/bazel
if-no-files-found: error

build_wheel:
name: Build runai-model-streamer ${{ matrix.version }} py3-none-manylinux_riscv64
needs: [setup, bazel]
if: needs.setup.outputs.versions != '[]'
runs-on: ubuntu-24.04-riscv
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}

env:
RUNAI_MODEL_STREAMER_VERSION: ${{ matrix.version }}

steps:
- name: Checkout runai-model-streamer ${{ env.RUNAI_MODEL_STREAMER_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: run-ai/runai-model-streamer
ref: ${{ env.RUNAI_MODEL_STREAMER_VERSION }}
path: runai-model-streamer
persist-credentials: false

- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false

- name: Patch runai-model-streamer source
working-directory: runai-model-streamer
run: git apply -v ../python-wheels/patches/runai-model-streamer/${{ env.RUNAI_MODEL_STREAMER_VERSION }}/*.patch

- name: Download bazel
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
path: bazel-bin

- name: Build the C++ streamer library and package the wheel
run: |
mkdir -p dist
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work/runai-model-streamer \
-e PACKAGE_VERSION="${RUNAI_MODEL_STREAMER_VERSION}" \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux

# cpp/toolchain/template/toolchain.bzl links -static-libstdc++ -l:libstdc++.a
# for every arch; this image's gcc devel package (libstdc++-devel) ships only
# the shared libstdc++.so, so the static archive needs its own CRB package.
dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel libstdc++-static
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME
install -m 0755 /work/bazel-bin/bazel /usr/local/bin/bazel
git config --global --add safe.directory '*'

# cpp/toolchain/rules.bzl resolves every tool as /usr/bin/<arch>-linux-gnu-<tool>,
# the Debian cross-toolchain naming upstream's own devcontainer ships (real
# x86_64-linux-gnu-gcc/aarch64-linux-gnu-gcc binaries there). This image is a
# native (not cross-compiling) riscv64 build of manylinux, so its compiler is
# the unprefixed system gcc/g++ - symlink the triplet-prefixed names the
# toolchain rule expects onto it.
for tool in gcc g++ ar ld cpp gcov nm objdump strip; do
native="$(command -v "$tool" || true)"
[ -n "$native" ] && ln -sf "$native" "/usr/bin/riscv64-linux-gnu-$tool"
done

cd cpp
bazel build streamer:libstreamer.so --config=riscv64

cd ../py/runai_model_streamer
# cp312's venv has no setuptools preinstalled (newer CPython/pip dropped the
# implicit bootstrap); setup.py's own `from setuptools import setup` needs it.
/opt/python/cp312-cp312/bin/pip install -q setuptools wheel
/opt/python/cp312-cp312/bin/python setup.py bdist_wheel --plat-name manylinux_2_39_riscv64
cp dist/*.whl /work/dist/
SCRIPT

- name: Verify the wheel ships a riscv64 shared object
run: |
python3 - dist/*.whl <<'EOF'
import sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as zf:
names = zf.namelist()
so = next(n for n in names if n.endswith("libstreamer/lib/libstreamer.so"))
header = zf.read(so)[:20]
assert header[:4] == b"\x7fELF", header
assert header[18] == 0xF3, header # EM_RISCV
EOF

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: runai-model-streamer-${{ matrix.version }}-py3-none-manylinux_riscv64
path: dist/*.whl
if-no-files-found: error

test_wheel:
name: Test runai-model-streamer ${{ matrix.version }} on Python ${{ matrix.python-version }}
needs: [setup, build_wheel]
if: needs.setup.outputs.versions != '[]'
runs-on: ubuntu-24.04-riscv
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
python-version: ['3.12', '3.13', '3.14']

env:
RUNAI_MODEL_STREAMER_VERSION: ${{ matrix.version }}

steps:
- name: Checkout runai-model-streamer ${{ env.RUNAI_MODEL_STREAMER_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: run-ai/runai-model-streamer
ref: ${{ env.RUNAI_MODEL_STREAMER_VERSION }}
path: runai-model-streamer
persist-credentials: false

- name: Download wheel
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: runai-model-streamer-${{ env.RUNAI_MODEL_STREAMER_VERSION }}-py3-none-manylinux_riscv64
path: wheelhouse

- name: Install Python
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
enable-cache: false

- name: Install the wheel and upstream's test dependencies
env:
UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
UV_INDEX_STRATEGY: unsafe-best-match
# requirements.dev pins numpy==1.24.4, which ships no riscv64 wheel and
# fails to build from source under Python >=3.12 (its legacy setup.py
# needs the stdlib `distutils` module, removed in 3.12). Override it to
# the newer numpy our registry does have a riscv64 wheel for; nothing
# in this test suite depends on numpy 1.24.4 specifically.
run: |
echo "numpy>=1.26,<3" > /tmp/numpy-override.txt
uv pip install --override /tmp/numpy-override.txt -r runai-model-streamer/py/runai_model_streamer/requirements.dev wheelhouse/*.whl

# setup.py's package_data ships libstreamer.so alone; upstream's own dev
# fixtures for the mocked object-storage tests are never part of the
# wheel, so they are copied in from the checkout before running them.
- name: Stage upstream's test fixtures into the installed package
run: |
site="$(python -c 'import runai_model_streamer, os; print(os.path.dirname(runai_model_streamer.__file__))')"
cp -r runai-model-streamer/py/runai_model_streamer/runai_model_streamer/safetensors_streamer/tests/test_files \
"$site/safetensors_streamer/tests/"

# Run from the installed package's own parent directory so this exercises the
# wheel's copy, not the checkout's, matching upstream's `make -C py test-unit-real`
# (minus test-dist, which needs a multi-node torchrun harness).
- name: Run upstream's tests against the installed wheel
run: |
site="$(python -c 'import runai_model_streamer, os; print(os.path.dirname(os.path.dirname(runai_model_streamer.__file__)))')"
cd "$site"
python -m unittest discover -b runai_model_streamer

gpl_sources:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Collect GPL sources for runai-model-streamer ${{ matrix.version }}
runs-on: ubuntu-24.04-riscv

env:
RUNAI_MODEL_STREAMER_VERSION: ${{ matrix.version }}

steps:
- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: ./actions/collect-gpl-sources
with:
image: ${{ env.MANYLINUX_RISCV64_IMAGE }}
packages: gcc
output: gpl-sources.tar

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: runai-model-streamer-${{ env.RUNAI_MODEL_STREAMER_VERSION }}-gpl-sources
path: gpl-sources.tar
if-no-files-found: error

publish:
name: Publish runai-model-streamer ${{ matrix.version }}
needs: [setup, build_wheel, test_wheel, 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: runai-model-streamer-${{ matrix.version }}-py3-none-manylinux_riscv64
gpl-sources-artifact: runai-model-streamer-${{ matrix.version }}-gpl-sources
gpl-sources-description: gcc
5 changes: 5 additions & 0 deletions docs/packages/runai-model-streamer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: runai-model-streamer
source-code: https://github.com/run-ai/runai-model-streamer
license: Apache-2.0
versions:
- version: 0.16.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
From 820707245ffc7f9f363fc5de3f5375daacb0a8b9 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Thu, 24 Sep 2026 03:32:40 +0000
Subject: [PATCH] toolchain: add riscv64 as a supported architecture

configure_toolchain() and its per-arch .bazelrc block are already fully
generic over the arch string (native.platform constraint is
@platforms//cpu:<arch>, gcc tools are resolved as
/usr/bin/<arch>-linux-gnu-<tool>) -- riscv64 was simply never added to
either list.

No -march flag is added for riscv64 (unlike aarch64's, which exists only
for a google-cloud-cpp ARM dependency libstreamer.so does not build
against): a native riscv64 build uses the compiler's own default target.

Upstream-Status: To upstream [this port does not open issues/PRs against third-party repos; riscv64 toolchain support is exactly the kind of change upstream would want once a downstream build proves it works]
---
cpp/.bazelrc | 3 +++
cpp/toolchain/configure.bzl | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/cpp/.bazelrc b/cpp/.bazelrc
index 830e568..4a6ccd2 100644
--- a/cpp/.bazelrc
+++ b/cpp/.bazelrc
@@ -19,5 +19,8 @@ build:aarch64 --copt="-march=armv8-a+crc+crypto"
build:x86_64 --platform_suffix=x86_64
build:x86_64 --platforms=@toolchain//:x86_64

+build:riscv64 --platform_suffix=riscv64
+build:riscv64 --platforms=@toolchain//:riscv64
+
# Azurite testing support (enables AZURE_STORAGE_ACCOUNT_KEY for local testing)
build:azurite --define azurite_testing=true
diff --git a/cpp/toolchain/configure.bzl b/cpp/toolchain/configure.bzl
index 6b89f54..9a04125 100644
--- a/cpp/toolchain/configure.bzl
+++ b/cpp/toolchain/configure.bzl
@@ -4,7 +4,7 @@ load("//toolchain:rules.bzl", "get_target_triplet", "runai_crosstool_tools")

# This should map to the devcontainer Dockerfile
# We install gcc-x86-64-linux-gnu and gcc-aarch64-linux-gnu toolchains
-ARCHITECTURES = ["x86_64", "aarch64"]
+ARCHITECTURES = ["x86_64", "aarch64", "riscv64"]
OS = "linux-gnu"

def _get_gcc_version(repository_ctx, arch):
--
2.43.0

Loading
Loading