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
149 changes: 149 additions & 0 deletions .github/workflows/build-vllm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on: https://github.com/vllm-project/vllm/blob/main/docker/Dockerfile.cpu
name: Build vllm wheels (riscv64)

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

run-name: build-vllm ${{ inputs.version && format('- {0}', inputs.version) || '' }}

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

jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: vllm
version: ${{ inputs.version }}

build_wheels:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
name: Build vllm ${{ matrix.version }} manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}

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

steps:
# The wheel carries vLLM's CPU-build local segment (0.29.0+cpu); the tag does not.
- id: ref
run: echo "ref=v${VLLM_VERSION%+cpu}" >> "$GITHUB_OUTPUT"

- name: Checkout vllm ${{ steps.ref.outputs.ref }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: vllm-project/vllm
ref: ${{ steps.ref.outputs.ref }}
path: vllm
persist-credentials: false

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

- name: Patch vllm source
working-directory: vllm
run: |
git apply ../python-wheels/patches/vllm/${{ env.VLLM_VERSION }}/*.patch

- uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
package-dir: vllm
output-dir: wheelhouse/
env:
CIBW_BUILD: "cp312-manylinux_riscv64"
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
# csrc/cpu/utils.cpp #includes <numa.h> unconditionally on the CPU backend;
# the manylinux image doesn't ship it.
CIBW_BEFORE_ALL_LINUX: >-
dnf install -y --setopt=install_weak_deps=False numactl-devel
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs
| sh -s -- -y --default-toolchain none
CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation"
# Upstream's Dockerfile.cpu passes --py-limited-api to bdist_wheel; cibuildwheel
# does not, and without it the abi3 extensions get a cp312-only wheel tag. cp312
# rather than upstream's cp38 because torch 2.13.0 starts at cp312 on riscv64, so
# cp312 is the oldest interpreter this wheel is ever built on (gotcha 96).
CIBW_CONFIG_SETTINGS: "--build-option=--py-limited-api=cp312"
CIBW_BEFORE_BUILD: >-
pip install --only-binary=:all: -r {package}/requirements/build/cpu.txt
CIBW_ENVIRONMENT: >-
VLLM_TARGET_DEVICE=cpu
VLLM_VERSION_OVERRIDE=${{ env.VLLM_VERSION }}
CMAKE_ARGS=-DVLLM_RVV_VLEN=0
PATH="$PATH:$HOME/.cargo/bin"
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
# The torch wheel already carries these and loads them RTLD_GLOBAL.
CIBW_REPAIR_WHEEL_COMMAND: >-
auditwheel repair -w {dest_dir} {wheel}
--exclude libtorch.so
--exclude libtorch_cpu.so
--exclude libtorch_python.so
--exclude libtorch_global_deps.so
--exclude libc10.so
--exclude libgomp.so.1
CIBW_TEST_ENVIRONMENT: >-
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
VLLM_TARGET_DEVICE=cpu
CIBW_TEST_COMMAND: >-
python -c "import vllm, vllm._C, torch;
from vllm.platforms import current_platform;
print(vllm.__version__, current_platform.get_cpu_architecture())"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vllm-${{ env.VLLM_VERSION }}-abi3-manylinux_riscv64
path: ./wheelhouse/*.whl
if-no-files-found: error

publish:
name: Publish vllm ${{ 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: vllm-${{ matrix.version }}-*-manylinux_riscv64
5 changes: 5 additions & 0 deletions docs/packages/vllm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: vllm
source-code: https://github.com/vllm-project/vllm
license: Apache-2.0
versions:
- version: 0.29.0+cpu
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
From 0000000000000000000000000000000000000001 Mon Sep 17 00:00:00 2001
From: RISE Project CI <ci@riseproject.dev>
Date: Sat, 19 Sep 2026 00:00:00 +0000
Subject: [PATCH] [CPU] Do not require numba on riscv64

requirements/cpu.txt requires numba on every CPU architecture except
s390x. numba has no riscv64 wheel on any index, and it cannot be built
from its sdist either: it hard-depends on llvmlite, whose build links a
patched LLVM distributed only as a conda package on a channel that
publishes no linux-riscv64 subdir. An unpatched riscv64 CPU wheel is
therefore buildable but not installable.

numba is optional to vLLM by construction - vllm/utils/import_utils.py
declares is_numba_available() "Whether the optional `numba` package is
available" and the Kimi-K2.5 fused vision processor falls back to the
remote HF processor when it is absent. Excluding it costs riscv64 the
same features s390x already goes without (n-gram speculative decoding
and the fused vision/inkling processors), which is exactly the
configuration upstream already ships for s390x.

Upstream-Status: To upstream [vLLM publishes no riscv64 wheels and runs no riscv64 CI, so there is no upstream build this marker would affect yet; resubmit once riscv64 joins docs/getting_started/installation/cpu.md and the CPU release matrix]

Signed-off-by: RISE Project CI <ci@riseproject.dev>
---
requirements/cpu.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements/cpu.txt b/requirements/cpu.txt
index 30e47a8..e4ce77b 100644
--- a/requirements/cpu.txt
+++ b/requirements/cpu.txt
@@ -3,7 +3,7 @@

setuptools==77.0.3 # this version can reuse CMake build dir

-numba == 0.65.0; platform_machine != "s390x" # Required for N-gram speculative decoding
+numba == 0.65.0; platform_machine != "s390x" and platform_machine != "riscv64" # Required for N-gram speculative decoding

# Dependencies for CPUs
torch==2.13.0+cpu; platform_machine == "x86_64" or platform_machine == "s390x" or platform_machine == "aarch64"
--
2.51.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
From 0000000000000000000000000000000000000002 Mon Sep 17 00:00:00 2001
From: RISE Project CI <ci@riseproject.dev>
Date: Sun, 20 Sep 2026 00:00:00 +0000
Subject: [PATCH] [CPU] Probe the torch wheel's OpenBLAS for sbgemm_

cpu_extension.cmake decides whether to compile the OpenBLAS bf16 GEMM path
by globbing the installed torch wheel for libopenblas*.so*, and defines
VLLM_HAS_OPENBLAS when the glob hits. Presence of the library is not the
same thing as presence of the symbol: blas_gemm() in
csrc/cpu/sgl-kernels/blas_gemm.h calls sbgemm_, and OpenBLAS only builds
sbgemm_ for the architectures it has BFLOAT16 kernels for (x86_64
Cooper Lake and later, ARM64 Neoverse, POWER10, z14 and later). RISC-V is
not one of them.

The miss cannot be caught at link time, because the comment right above
the glob is accurate - _C deliberately does not link OpenBLAS and expects
libtorch.so to have loaded it RTLD_GLOBAL - so a shared object with an
unresolved sbgemm_ links happily and the build succeeds. It fails on the
first "import vllm._C" instead:

ImportError: .../vllm/_C.abi3.so: undefined symbol: sbgemm_

blas_gemm.h already carries the right fallback for this case: its #else
branch dispatches through at::native::cpublas::gemm_no_downcast_stub,
which libtorch_cpu.so exports on every architecture. So ask the library
whether it actually provides sbgemm_ and take that fallback when it does
not, rather than assuming every non-x86 torch wheel ships a bf16-capable
OpenBLAS. When the probe itself cannot run the previous behaviour is kept,
so no platform that works today changes.

Upstream-Status: To upstream [Architecture-agnostic: the probe is a no-op wherever OpenBLAS does export sbgemm_, and it fixes any platform whose torch wheel ships an OpenBLAS built without BUILD_BFLOAT16, riscv64 being the one this repo builds for]

Signed-off-by: RISE Project CI <ci@riseproject.dev>
---
cmake/cpu_extension.cmake | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/cmake/cpu_extension.cmake b/cmake/cpu_extension.cmake
index 92c3daa..a7a229c 100644
--- a/cmake/cpu_extension.cmake
+++ b/cmake/cpu_extension.cmake
@@ -430,8 +430,24 @@ if (NOT ENABLE_X86_ISA)
"${TORCH_INSTALL_PREFIX}/lib/libopenblas*.so*")
# Note: we don't link openblas directly to _C extension, as it's available through libtorch.so
if (_VLLM_TORCH_OPENBLAS_LIBS)
- list(GET _VLLM_TORCH_OPENBLAS_LIBS 0 VLLM_OPENBLAS_LIB)
- message(STATUS "CPU OpenBLAS library: ${VLLM_OPENBLAS_LIB}")
+ list(GET _VLLM_TORCH_OPENBLAS_LIBS 0 _VLLM_TORCH_OPENBLAS_LIB)
+ # blas_gemm() calls OpenBLAS' bf16 GEMM, which OpenBLAS only builds for
+ # the architectures it has BFLOAT16 kernels for. Elsewhere the library is
+ # present but sbgemm_ is not, and since _C resolves it through
+ # libtorch.so instead of linking OpenBLAS itself, the miss is not
+ # reported until the extension is imported. Probe for the symbol and keep
+ # the PyTorch reference BLAS path when it is absent.
+ execute_process(
+ COMMAND ${CMAKE_NM} --dynamic --defined-only "${_VLLM_TORCH_OPENBLAS_LIB}"
+ OUTPUT_VARIABLE _VLLM_TORCH_OPENBLAS_SYMBOLS
+ ERROR_QUIET)
+ if (_VLLM_TORCH_OPENBLAS_SYMBOLS STREQUAL "" OR
+ _VLLM_TORCH_OPENBLAS_SYMBOLS MATCHES "[ \t]sbgemm_[\r\n]")
+ set(VLLM_OPENBLAS_LIB "${_VLLM_TORCH_OPENBLAS_LIB}")
+ message(STATUS "CPU OpenBLAS library: ${VLLM_OPENBLAS_LIB}")
+ else()
+ message(STATUS "CPU OpenBLAS library ${_VLLM_TORCH_OPENBLAS_LIB} provides no sbgemm_, using the PyTorch reference BLAS path")
+ endif()
endif()
endif()

--
2.51.0
Loading