Skip to content

Publish Nightly

Publish Nightly #66

Workflow file for this run

name: Publish Nightly
# Manual builds of `pie` for distribution. Each run rewrites the
# `nightly` rolling pre-release on this repo's GitHub Releases —
# every artifact is uploaded with `gh release upload --clobber`, so
# download URLs stay stable across runs.
#
# The prepare-release job creates the release if needed before any
# matrix jobs start uploading assets.
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag to update (assets uploaded with --clobber)"
type: string
default: "nightly"
portable:
description: "Build portable matrix (manylinux x86_64/aarch64 + macOS arm64 + Windows x86_64 Vulkan/CUDA)"
type: boolean
default: true
cuda:
description: "Build native cuda_native on the self-hosted GPU runner (cuda 13.0, sm 80-120)"
type: boolean
default: true
cuda_manylinux:
description: "Build CUDA matrix on GH-hosted runners with manylinux_2_28 base (broader glibc compat, slower)"
type: boolean
default: false
permissions:
contents: write # needed for `gh release upload`
env:
# Release notes body — keeps the release page in sync with the
# latest workflow run.
RELEASE_NOTES: |
Rolling build from commit ${{ github.sha }} on ${{ github.ref_name }}.
Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
# =========================================================================
# Create/update the rolling release before any matrix job tries to upload.
# This keeps `gh release upload` from racing a missing release, and keeps
# the rolling build out of GitHub's "Latest" release slot.
# =========================================================================
prepare-release:
if: inputs.portable || inputs.cuda || inputs.cuda_manylinux
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Ensure rolling release exists
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
run: |
set -euxo pipefail
if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
release_id="$(gh release view "${TAG}" --json databaseId --jq .databaseId --repo "${GITHUB_REPOSITORY}")"
gh api \
-X PATCH \
"repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
-f tag_name="${TAG}" \
-f name="${TAG}" \
-f body="${RELEASE_NOTES}" \
-F draft=false \
-F prerelease=true \
-f make_latest=false
else
gh release create "${TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--target "${GITHUB_SHA}" \
--title "${TAG}" \
--notes "${RELEASE_NOTES}" \
--prerelease \
--latest=false
fi
# =========================================================================
# Portable matrix — Linux/Windows Vulkan, macOS Metal, plus Windows
# portable CUDA. Runs on the larger org runners wherever they exist.
# =========================================================================
portable:
if: inputs.portable
needs: prepare-release
name: portable / ${{ matrix.target.label }}
strategy:
fail-fast: false
matrix:
target:
# Linux portable artifacts ship the Vulkan ggml backend (works on
# NVIDIA/AMD/Intel GPUs). Built directly on the larger Ubuntu
# runners — NOT in a manylinux container — because ggml-vulkan
# hard-requires `glslc` (find_package(Vulkan COMPONENTS glslc)),
# which Rocky 8 / manylinux_2_28 doesn't package but Ubuntu 24.04+
# does. glibc floor is therefore the runner's Ubuntu (>= 2.39),
# acceptable for a GPU artifact.
- label: x86_64-linux-vulkan
runner: ubuntu-large-x64
container: ""
platform: linux
triple: x86_64-unknown-linux-gnu
archive: tar.gz
backend: vulkan
backend_env: "PIE_PORTABLE_VULKAN=1"
- label: aarch64-linux-vulkan
runner: ubuntu-large-arm64
container: ""
platform: linux
triple: aarch64-unknown-linux-gnu
archive: tar.gz
backend: vulkan
backend_env: "PIE_PORTABLE_VULKAN=1"
- label: aarch64-darwin
# macos-15 gets Xcode 16 with libc++ 17, which has
# `std::atomic_ref` (C++20). macos-14's Xcode 15.4 ships
# libc++ 15, which doesn't.
runner: macos-15
container: ""
platform: macos
triple: aarch64-apple-darwin
archive: tar.gz
backend: metal
backend_env: "PIE_PORTABLE_METAL=1"
- label: x86_64-windows-portable-vulkan
runner: windows-large-x64
container: ""
platform: windows
triple: x86_64-pc-windows-msvc
archive: zip
backend: vulkan
backend_env: "PIE_PORTABLE_VULKAN=1"
- label: x86_64-windows-portable-cuda
runner: windows-large-x64
container: ""
platform: windows
triple: x86_64-pc-windows-msvc
archive: zip
backend: cuda
backend_env: "PIE_PORTABLE_CUDA=1"
# ggml-cuda compiles many template instances slowly with nvcc.
# Build one real+PTX arch; the PTX keeps the artifact
# forward-JIT-able on newer NVIDIA GPUs.
cuda_arch: "75"
runs-on: ${{ matrix.target.runner }}
container: ${{ matrix.target.container || '' }}
env:
ARTIFACT_NAME: pie-${{ matrix.target.label }}.${{ matrix.target.archive }}
steps:
- uses: actions/checkout@v6
# manylinux containers ship gcc + cmake + ninja but no rust.
# We dnf-install:
# - `openssl-devel` + `pkg-config`: for `openssl-sys` (reqwest's
# default features).
# - `perl`: openssl-sys build script's vendored fallback.
# - `gcc-toolset-14-gcc(-c++)`: needed for libstdc++ 14, which
# ships <span> and other C++20 headers driver/portable's
# source uses. The container's stock gcc-8 / libstdc++-8
# is missing them.
#
# Then we pin CC/CXX explicitly to the toolset's binaries —
# without that, cmake-rs picks `/opt/clang/bin/cc` (also on
# PATH inside the manylinux image) and we lose the libstdc++ 14
# come link time.
- name: Install build deps + rustup (manylinux only)
if: startsWith(matrix.target.container, 'quay.io/pypa/manylinux')
shell: bash
run: |
set -euxo pipefail
dnf install -y --setopt=install_weak_deps=False \
openssl-devel pkg-config perl git \
gcc-toolset-14-gcc gcc-toolset-14-gcc-c++
# GitHub CLI — used by the upload step. The manylinux image
# doesn't ship `gh` and the upload step's --clobber flow has
# no clean curl equivalent. `gh` rpm depends on `git`,
# installed above.
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --add-repo \
https://cli.github.com/packages/rpm/gh-cli.repo
dnf install -y gh --repo gh-cli
# Pin compiler explicitly. cmake-rs (via cc-rs) reads
# CC/CXX, so this forces CMake to use gcc-toolset-14 even
# though manylinux's `/opt/clang/bin` is also on PATH and
# would otherwise be picked. We need libstdc++ 14 for the
# C++20 headers (`<span>`) the portable driver source uses.
{
echo "CC=/opt/rh/gcc-toolset-14/root/usr/bin/gcc"
echo "CXX=/opt/rh/gcc-toolset-14/root/usr/bin/g++"
echo "AR=/opt/rh/gcc-toolset-14/root/usr/bin/ar"
echo "PATH=/opt/rh/gcc-toolset-14/root/usr/bin:${PATH}"
echo "MANPATH=/opt/rh/gcc-toolset-14/root/usr/share/man:"
echo "LD_LIBRARY_PATH=/opt/rh/gcc-toolset-14/root/usr/lib64:/opt/rh/gcc-toolset-14/root/usr/lib"
echo "PKG_CONFIG_PATH=/opt/rh/gcc-toolset-14/root/usr/lib64/pkgconfig"
} >> "$GITHUB_ENV"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install Rust (non-container runners)
if: matrix.target.container == ''
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target.triple }}
- name: Install Vulkan toolchain + build deps (linux only)
if: matrix.target.platform == 'linux' && matrix.target.backend == 'vulkan'
shell: bash
run: |
set -euxo pipefail
# No manylinux container on this leg: ggml-vulkan hard-requires
# `glslc` (find_package(Vulkan COMPONENTS glslc REQUIRED)), which
# Rocky 8 / manylinux_2_28 doesn't package but Ubuntu 24.04+ does.
# Install the Vulkan headers/loader/compiler plus the C/C++ build
# toolchain that the cmake-rs driver build and openssl-sys need.
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update
# spirv-headers provides <spirv/unified1/spirv.hpp> (namespace spv),
# which ggml-vulkan.cpp includes for SPIR-V reflection — not pulled
# in by libvulkan-dev/glslc alone.
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build pkg-config \
glslc libvulkan-dev spirv-headers libssl-dev
glslc --version
- name: Install Vulkan SDK (windows only)
if: matrix.target.platform == 'windows' && matrix.target.backend == 'vulkan'
uses: humbletim/install-vulkan-sdk@v1.2
with:
version: latest
cache: true
- name: Install Vulkan SDK development files (windows only)
if: matrix.target.platform == 'windows' && matrix.target.backend == 'vulkan'
shell: bash
run: |
set -euxo pipefail
# humbletim/install-vulkan-sdk extracts the SDK tools directly from
# the Windows installer, but CMake's FindVulkan also needs the
# headers and import library. Run the official unattended installer
# into the same VULKAN_SDK root and verify the files ggml needs.
rm -rf "$VULKAN_SDK"
mkdir -p "$VULKAN_SDK"
./vulkan_sdk.exe \
--root "$VULKAN_SDK" \
--accept-licenses \
--default-answer \
--confirm-command install \
copy_only=1
test -f "$VULKAN_SDK/Include/vulkan/vulkan.h"
test -f "$VULKAN_SDK/Lib/vulkan-1.lib"
- name: Install CUDA toolkit (windows portable cuda only)
if: matrix.target.platform == 'windows' && matrix.target.backend == 'cuda'
uses: Jimver/cuda-toolkit@v0.2.35
with:
cuda: '12.9.1'
- name: Verify CUDA toolkit (windows portable cuda only)
if: matrix.target.platform == 'windows' && matrix.target.backend == 'cuda'
shell: bash
run: |
set -euxo pipefail
nvcc --version
test -n "${CUDA_PATH:-${CUDA_HOME:-}}"
- name: Set up MSVC environment (windows cuda only)
# ggml-cuda's enable_language(CUDA) under CMake's Visual Studio
# generator needs the CUDA MSBuild toolset integration, which the
# toolkit installer doesn't reliably register on the hosted image
# ("No CUDA toolset found"). We build with the Ninja generator
# instead (set in the cargo step); sourcing the MSVC dev environment
# here puts cl.exe on PATH so nvcc/CMake can use it as host compiler.
if: matrix.target.platform == 'windows' && matrix.target.backend == 'cuda'
uses: ilammy/msvc-dev-cmd@v1
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: build-${{ matrix.target.label }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: build-${{ matrix.target.label }}-
- name: cargo build (release-min)
shell: bash
run: |
set -euxo pipefail
if [ -n "${{ matrix.target.backend_env }}" ]; then
export ${{ matrix.target.backend_env }}
fi
if [[ "${{ matrix.target.backend }}" == "cuda" ]]; then
export PIE_PORTABLE_CUDA_ARCH='${{ matrix.target.cuda_arch }}'
fi
if [[ "${{ matrix.target.platform }}" == "windows" ]]; then
# ggml-vulkan nests a shader-generator CMake build under Cargo's
# OUT_DIR, and ggml-cuda produces many long object paths. Keep the
# target root short enough for the legacy 260-character path limit
# on Windows, and isolate backend flavors from each other.
export CARGO_TARGET_DIR="C:/t/pie-${{ matrix.target.backend }}"
# 32-core windows-large runner: parallelize the native ggml build
# fully (was capped at 4 for the old small hosted runners).
export CMAKE_BUILD_PARALLEL_LEVEL="$(nproc 2>/dev/null || echo 8)"
if [[ "${{ matrix.target.backend }}" == "cuda" ]]; then
# Drive nvcc via Ninja — the VS generator can't find the CUDA
# toolset on the hosted image. cl.exe is on PATH from the
# preceding msvc-dev-cmd step.
export CMAKE_GENERATOR=Ninja
ninja --version
# Git Bash ships /usr/bin/link.exe (GNU coreutils `link`), which
# shadows MSVC's link.exe for rustc once msvc-dev-cmd activates the
# MSVC env (rustc then resolves the linker via PATH and picks the
# GNU one -> "extra operand" link errors). Drop it so `link`
# resolves to MSVC's linker. (Only needed on the cuda leg, which is
# the only one that runs msvc-dev-cmd.)
rm -f /usr/bin/link /usr/bin/link.exe || true
fi
fi
# `release-min`: thin LTO, panic=abort, strip=symbols.
# Defined in workspace Cargo.toml. Smaller + faster than
# plain `release` (no LTO at all by default), without fat
# LTO's many-hour link cost. Distribution profile.
cargo build --profile release-min \
--target ${{ matrix.target.triple }} \
-p pie-server \
--features driver-portable,driver-dummy
- name: Package
shell: bash
run: |
set -euxo pipefail
mkdir -p dist
TARGET_ROOT="target"
if [[ "${{ matrix.target.platform }}" == "windows" ]]; then
TARGET_ROOT="C:/t/pie-${{ matrix.target.backend }}"
fi
BIN_DIR="${TARGET_ROOT}/${{ matrix.target.triple }}/release-min"
if [[ "${{ matrix.target.archive }}" == "zip" ]]; then
# Windows .exe; 7z preinstalled on windows-latest.
7z a "dist/${ARTIFACT_NAME}" "${BIN_DIR}/pie.exe"
else
tar -C "${BIN_DIR}" -czf "dist/${ARTIFACT_NAME}" pie
fi
ls -la dist/
- name: Upload to release (--clobber)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euxo pipefail
# Many matrix legs --clobber the same rolling release concurrently;
# GitHub's asset API intermittently 5xx/422s under that contention.
# Retry a few times before failing the leg.
for attempt in 1 2 3 4 5; do
if gh release upload "${{ inputs.tag }}" "dist/${ARTIFACT_NAME}" --clobber --repo "${{ github.repository }}"; then
exit 0
fi
echo "upload attempt ${attempt} failed; retrying in $((attempt * 10))s" >&2
sleep $((attempt * 10))
done
echo "upload failed after retries" >&2
exit 1
# =========================================================================
# CUDA matrix — runs on the project's self-hosted GPU runner.
#
# The runner is bare-metal Ubuntu 24.04 (glibc 2.39) with an RTX 4090,
# 32 cores, and side-by-side CUDA toolkits at /usr/local/cuda-X.Y.
# `CARGO_HOME` and `CARGO_TARGET_DIR` are preset to persistent paths
# outside the workspace, so we deliberately do NOT use `actions/cache`
# (would shadow the runner's larger, always-warm caches).
#
# Tradeoff vs the previous nvidia/cuda manylinux containers: the
# resulting binary is glibc-2.39-floored — won't run on EL8 / Ubuntu
# 20.04. CUDA users are typically on recent distros anyway, and
# they need a recent NVIDIA driver to match the toolkit.
#
# Two flavors per CUDA toolkit version:
# * cuda — full bundle: `driver-portable` (ggml-CUDA) + `driver-cuda`
# (native CUDA + flashinfer + NCCL). The heavyweight binary
# with both drivers; what users want for production.
# * portable-cuda — same `driver-portable` with PIE_PORTABLE_CUDA=1,
# but without the native `driver-cuda`. Smaller binary,
# no NCCL / flashinfer dependency. Useful where the ggml
# CUDA backend is enough.
#
# The runner serializes jobs (one at a time), so the matrix entries
# below run sequentially. Total wall-time scales with cuda-version
# count × flavor count.
# =========================================================================
cuda:
if: inputs.cuda
needs: prepare-release
name: cuda / linux-x64 / ${{ matrix.flavor.id }}-${{ matrix.cuda.short }}
runs-on: [self-hosted, linux, x64, gpu, cuda]
strategy:
fail-fast: false
# Force serial execution on the self-hosted GPU runner. All matrix
# entries share the same `CARGO_TARGET_DIR` and write into the
# same `pie-server-<hash>/out/portable/build/` cmake build
# directory; running in parallel races on the per-object .o.d
# dependency files and trips EACCES the moment two jobs configure
# cmake at the same instant. The runner has 32 cores so each
# entry already fans out internally — serializing the matrix
# costs wall-clock but keeps the build cache coherent.
max-parallel: 1
matrix:
# sm_75 (Turing, e.g. T4 / RTX 20-series) intentionally
# excluded: flashinfer's `vllm_custom_all_reduce.cuh` only
# specializes the bf16 cast under `__CUDA_ARCH__ >= 800`, so
# nvcc's sm_75 pass fails to instantiate
# `CustomAllreduce::allreduce<__nv_bfloat16>`. Turing has no
# native bf16 anyway — supporting it here would mean carrying
# an fp16-only fallback that the rest of the cuda driver
# doesn't have. The portable-cuda flavor (no driver-cuda /
# flashinfer) covers Turing users.
cuda:
# Self-hosted runner (Workstation-cuda) currently provides only the
# 13.0 toolkit; re-add 12.8 here once /usr/local/cuda-12.8 exists.
# (12.8 broad-compat artifacts come from the cuda-manylinux job.)
- { version: "13.0.0", short: "13.0", path: "/usr/local/cuda-13.0", arch: "80;86;89;90;100;120" }
flavor:
- { id: "cuda", features: "driver-portable,driver-cuda,driver-dummy" }
env:
ARTIFACT_NAME: pie-x86_64-linux-${{ matrix.flavor.id }}${{ matrix.cuda.short }}.tar.gz
PIE_PORTABLE_CUDA: "1"
PIE_PORTABLE_CUDA_ARCH: ${{ matrix.cuda.arch }}
CMAKE_CUDA_ARCHITECTURES: ${{ matrix.cuda.arch }}
CMAKE_CUDA_RUNTIME_LIBRARY: Shared
# sccache is intentionally disabled here. RUSTC_WRAPPER=sccache
# caused enough collateral problems (CMake build dirs cached
# CMAKE_CUDA_COMPILER_LAUNCHER=sccache stamps; sccache 0.10's
# clap CLI rejected `-E` from nvcc preprocessing) that it
# outweighed the cache benefit. The 32-core runner builds Rust
# crates fast enough cold; we can revisit later.
CARGO_INCREMENTAL: "0"
# Block ggml's CMakeLists from auto-wiring sccache as the global
# `RULE_LAUNCH_COMPILE`. Its detection path is
# `if (GGML_CCACHE AND NOT CMAKE_C_COMPILER_LAUNCHER ...) find_program(... sccache)`
# so any non-empty value short-circuits the AND chain. `env` is
# the unix `env` command — when invoked as `env cc -c foo.c`, it
# executes the trailing args verbatim without altering anything,
# so it acts as a transparent launcher.
#
# Why this matters on a bare-metal runner: sccache's client
# connects to the daemon on the configured port (default 4226)
# without checking the daemon's owner. If a different user on the
# box (e.g. a developer's interactive session) has an sccache
# daemon running on 4226, every `sccache cc …` from this job
# routes through that foreign daemon. The daemon then forks the
# actual compiler under its own UID, the compiler tries to write
# `.o.d` / `.o` into the gh-runner-owned target tree, and EACCES
# kills the build with the misleading "Permission denied" trace
# we hit on run 25346461606.
CMAKE_C_COMPILER_LAUNCHER: env
CMAKE_CXX_COMPILER_LAUNCHER: env
CMAKE_CUDA_COMPILER_LAUNCHER: env
steps:
- uses: actions/checkout@v6
with:
# Workspace persists between jobs on bare-metal; clean to keep
# builds reproducible. The big caches live outside the
# workspace and are unaffected.
clean: true
fetch-depth: 1
- name: Select CUDA toolkit
shell: bash
run: |
set -euxo pipefail
CUDA_HOME='${{ matrix.cuda.path }}'
test -x "${CUDA_HOME}/bin/nvcc"
{
echo "CUDA_HOME=${CUDA_HOME}"
echo "LD_LIBRARY_PATH=${CUDA_HOME}/lib64"
} >> "$GITHUB_ENV"
echo "${CUDA_HOME}/bin" >> "$GITHUB_PATH"
# NCCL is sometimes bundled inside the CUDA toolkit prefix.
# If we find it there, point the cuda-driver build at it via
# `PIE_NCCL_HOME` (`server/build.rs::build_cuda` honors this).
# Only matters for the `cuda` flavor; the `portable-cuda`
# flavor doesn't link NCCL.
if [ -f "${CUDA_HOME}/include/nccl.h" ] \
&& ls "${CUDA_HOME}"/lib64/libnccl.so* >/dev/null 2>&1; then
echo "PIE_NCCL_HOME=${CUDA_HOME}" >> "$GITHUB_ENV"
fi
"${CUDA_HOME}/bin/nvcc" --version
gcc --version | head -1
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: cargo build (release-min)
shell: bash
run: |
set -euxo pipefail
# Force a fresh cmake configure for pie-server's build script.
# The persistent CARGO_TARGET_DIR keeps pie-server-<hash>/out/
# between matrix entries, and cmake caches per-flavor CMake
# variables (compile launchers, define flags, etc.) that we
# don't want bleeding from one flavor's job to the next.
# Scoped to pie-server, so other crates' deps stay cached.
cargo clean -p pie-server --target x86_64-unknown-linux-gnu --profile release-min
# `release-min`: thin LTO, panic=abort, strip=symbols.
# Defined in workspace Cargo.toml.
cargo build --profile release-min \
--target x86_64-unknown-linux-gnu \
-p pie-server \
--features ${{ matrix.flavor.features }}
- name: Package
shell: bash
run: |
set -euxo pipefail
mkdir -p dist
# CARGO_TARGET_DIR is preset on the self-hosted runner to a
# persistent location outside the workspace; fall back to the
# in-tree `target/` for any other environment.
BIN_DIR="${CARGO_TARGET_DIR:-target}/x86_64-unknown-linux-gnu/release-min"
tar -C "${BIN_DIR}" -czf "dist/${ARTIFACT_NAME}" pie
ls -la dist/
- name: Upload to release
# Pure-JS action — no `gh` install required on the bare-metal
# runner. Existing assets with the same name are replaced
# (same effect as `gh release upload --clobber`).
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ inputs.tag }}
files: dist/${{ env.ARTIFACT_NAME }}
fail_on_unmatched_files: true
make_latest: false
# =========================================================================
# Per-compute-capability CUDA matrix on GH-hosted larger runners with a
# manylinux_2_28 base — binaries with a glibc 2.28 floor (RHEL 8 / Ubuntu
# 20.04 / Debian 11).
#
# Matrix = compute-capability × CUDA-version × os-arch; each leg builds a
# SINGLE SM, so the build parallelizes (wall-clock ~= one arch, not a
# 6-arch fatbin) and each binary is small. The installer detects the GPU's
# compute capability and picks the matching asset. Built inside
# nvidia/cuda:X.Y-devel-rockylinux8 containers on ubuntu-large-{x64,arm64}.
# Opt-in via the `cuda_manylinux` workflow input.
# =========================================================================
cuda-manylinux:
if: inputs.cuda_manylinux
needs: prepare-release
name: cuda / ${{ matrix.target.os_arch }}-sm${{ matrix.target.cc }} / cuda-${{ matrix.cuda.short }}
runs-on: ${{ matrix.target.runner }}
# One compute-capability per job (not a 6-arch fatbin), so each leg is
# quick and the legs run in parallel across the larger runners. Keep a
# generous net for cold caches.
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
# Per-compute-capability: CC × CUDA-version × os-arch. Each entry
# builds ONE arch. The installer detects the GPU's compute capability
# and picks the matching asset; the self-hosted `cuda` job's all-arch
# fatbin is the fallback.
cuda:
- { version: "12.8.1", short: "12.8" }
- { version: "13.0.0", short: "13.0" }
target:
# x86_64: Ampere (80/86), Ada (89), Hopper (90), Blackwell DC (100) + consumer (120).
- { os_arch: x86_64, cc: "80", runner: ubuntu-large-x64, triple: x86_64-unknown-linux-gnu }
- { os_arch: x86_64, cc: "86", runner: ubuntu-large-x64, triple: x86_64-unknown-linux-gnu }
- { os_arch: x86_64, cc: "89", runner: ubuntu-large-x64, triple: x86_64-unknown-linux-gnu }
- { os_arch: x86_64, cc: "90", runner: ubuntu-large-x64, triple: x86_64-unknown-linux-gnu }
- { os_arch: x86_64, cc: "100", runner: ubuntu-large-x64, triple: x86_64-unknown-linux-gnu }
- { os_arch: x86_64, cc: "120", runner: ubuntu-large-x64, triple: x86_64-unknown-linux-gnu }
# aarch64: Grace-Hopper (90), Grace-Blackwell (100).
- { os_arch: aarch64, cc: "90", runner: ubuntu-large-arm64, triple: aarch64-unknown-linux-gnu }
- { os_arch: aarch64, cc: "100", runner: ubuntu-large-arm64, triple: aarch64-unknown-linux-gnu }
container: nvidia/cuda:${{ matrix.cuda.version }}-devel-rockylinux8
env:
ARTIFACT_NAME: pie-${{ matrix.target.os_arch }}-manylinux_2_28-cuda${{ matrix.cuda.short }}-sm${{ matrix.target.cc }}.tar.gz
PIE_PORTABLE_CUDA: "1"
PIE_PORTABLE_CUDA_ARCH: ${{ matrix.target.cc }}
CMAKE_CUDA_ARCHITECTURES: ${{ matrix.target.cc }}
CMAKE_CUDA_RUNTIME_LIBRARY: Shared
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v6
- name: Bootstrap container
shell: bash
run: |
set -euxo pipefail
# `ninja-build` lives in the PowerTools repo on Rocky 8;
# EPEL covers extras. `patch` for the vendored llama.cpp
# patches; `git` for CPM's git fetches. NCCL ships in
# NVIDIA's cuda repo (already configured by the base image)
# as `libnccl-devel`; pulls `libnccl` runtime in via deps.
dnf install -y epel-release dnf-plugins-core
dnf config-manager --set-enabled powertools
dnf install -y --setopt=install_weak_deps=False \
openssl-devel pkg-config perl git patch \
cmake ninja-build \
gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ \
libnccl libnccl-devel
# Pin compiler explicitly: `source enable` only adjusts PATH —
# cmake-rs probes CC/CXX env vars and otherwise picks
# `/usr/bin/cc` (gcc-8.5, no <span> / C++20). Same nvcc
# host-compiler issue applies via CUDAHOSTCXX (nvcc
# auto-picks gcc-8.5 and silently drops `-std=c++20`).
{
echo "CC=/opt/rh/gcc-toolset-12/root/usr/bin/gcc"
echo "CXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++"
echo "AR=/opt/rh/gcc-toolset-12/root/usr/bin/ar"
echo "CUDAHOSTCXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++"
echo "PATH=/opt/rh/gcc-toolset-12/root/usr/bin:${PATH}"
echo "LD_LIBRARY_PATH=/opt/rh/gcc-toolset-12/root/usr/lib64:/opt/rh/gcc-toolset-12/root/usr/lib"
echo "PKG_CONFIG_PATH=/opt/rh/gcc-toolset-12/root/usr/lib64/pkgconfig"
# rustc's final link must go through the gcc-toolset-12 gcc driver
# so libstdc++_nonshared.a is pulled in for the C++17/20 libstdc++
# symbols (std::filesystem, std::__throw_bad_array_new_length) the
# C++ drivers use. The stock /usr/bin/cc (gcc-8.5) links the old
# system libstdc++ and fails the link with undefined symbols.
# The linker env var is per-target-triple; derive its name from the
# triple so this one bootstrap works for both x86_64 and aarch64.
echo "CARGO_TARGET_$(echo '${{ matrix.target.triple }}' | tr 'a-z-' 'A-Z_')_LINKER=/opt/rh/gcc-toolset-12/root/usr/bin/gcc"
} >> "$GITHUB_ENV"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: build-cuda-${{ matrix.target.os_arch }}-sm${{ matrix.target.cc }}-${{ matrix.cuda.short }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: build-cuda-${{ matrix.target.os_arch }}-sm${{ matrix.target.cc }}-${{ matrix.cuda.short }}-
- name: cargo build (release-min)
shell: bash
run: |
set -euxo pipefail
# `release-min`: thin LTO, panic=abort, strip=symbols.
# Defined in workspace Cargo.toml.
cargo build --profile release-min \
--target ${{ matrix.target.triple }} \
-p pie-server \
--features driver-portable,driver-cuda,driver-dummy
- name: Package
shell: bash
run: |
set -euxo pipefail
mkdir -p dist
BIN_DIR="target/${{ matrix.target.triple }}/release-min"
tar -C "${BIN_DIR}" -czf "dist/${ARTIFACT_NAME}" pie
ls -la dist/
- name: Upload to release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ inputs.tag }}
files: dist/${{ env.ARTIFACT_NAME }}
fail_on_unmatched_files: true
make_latest: false
# =========================================================================
# Refresh the release notes once after all builds finish.
# =========================================================================
refresh-notes:
if: always() && (inputs.portable || inputs.cuda || inputs.cuda_manylinux)
needs: [portable, cuda, cuda-manylinux]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Edit release notes + body
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
run: |
set -euxo pipefail
release_id="$(gh release view "${TAG}" --json databaseId --jq .databaseId --repo "${GITHUB_REPOSITORY}")"
gh api \
-X PATCH \
"repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
-f tag_name="${TAG}" \
-f name="${TAG}" \
-f body="${RELEASE_NOTES}" \
-F draft=false \
-F prerelease=true \
-f make_latest=false