Publish Nightly #66
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
| 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 |