From d8d6a9d64e155572a973755aa0403f12a24eee69 Mon Sep 17 00:00:00 2001 From: Paul Stuart Date: Wed, 15 Apr 2026 11:07:51 -0700 Subject: [PATCH] Add .deb packaging for Ubuntu releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/create_deb.sh and integrates it into the CI build pipeline so each Ubuntu release automatically produces an installable .deb package. Package layout: - Executables → /usr/bin/ (llama-server, llama-cli, etc.) - Bundled ROCm libraries → /usr/lib/llamacpp-rocm/ (RPATH re-patched) - GPU kernel data → /usr/lib/llamacpp-rocm/rocblas/ and hipblaslt/ Key details: - Library soname duplicates are replaced with proper symlinks (~40% size reduction vs zip) - RPATH re-patched from $ORIGIN to /usr/lib/llamacpp-rocm for correct install-time resolution - Per-GPU-target packages (llamacpp-rocm-gfx1151, etc.) with Provides/Conflicts on the virtual "llamacpp-rocm" package so only one GPU variant is active at a time - New build-deb CI job validates packaging on every PR - create-release job builds final versioned .debs and attaches them to GitHub releases - Release notes include dpkg install instructions Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build-llamacpp-rocm.yml | 111 +++++++++++--- scripts/create_deb.sh | 174 ++++++++++++++++++++++ 2 files changed, 267 insertions(+), 18 deletions(-) create mode 100755 scripts/create_deb.sh diff --git a/.github/workflows/build-llamacpp-rocm.yml b/.github/workflows/build-llamacpp-rocm.yml index a138acf..f005033 100644 --- a/.github/workflows/build-llamacpp-rocm.yml +++ b/.github/workflows/build-llamacpp-rocm.yml @@ -898,6 +898,44 @@ jobs: echo "Cleanup completed successfully" + build-deb: + name: Build .deb Package (${{ matrix.gfx_target }}) + needs: [prepare-matrix, build-ubuntu] + runs-on: ubuntu-22.04 + if: needs.build-ubuntu.result == 'success' && needs.prepare-matrix.outputs.should_build_ubuntu == 'true' + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.prepare-matrix.outputs.ubuntu_matrix) }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install packaging tools + run: sudo apt-get install -y patchelf dpkg-dev + + - name: Download Ubuntu build artifact + uses: actions/download-artifact@v4 + with: + name: llama-ubuntu-rocm-${{ matrix.gfx_target }}-x64 + path: ./binaries + + - name: Build .deb package + run: | + chmod +x scripts/create_deb.sh + bash scripts/create_deb.sh \ + ./binaries \ + "0~snapshot~$(date -u +%Y%m%d)" \ + "${{ matrix.gfx_target }}" \ + ./debs + + - name: Upload .deb artifact + uses: actions/upload-artifact@v4 + with: + name: llama-ubuntu-rocm-${{ matrix.gfx_target }}-x64-deb + path: debs/*.deb + retention-days: 30 + test-stx-halo: runs-on: ${{ matrix.runner }} needs: [prepare-matrix, build-windows, build-ubuntu] @@ -1020,50 +1058,64 @@ jobs: echo "tag_exists=false" >> $GITHUB_OUTPUT fi - - name: Create archives for all target artifacts + - name: Install packaging tools + if: steps.check-tag.outputs.tag_exists == 'false' + run: sudo apt-get install -y patchelf dpkg-dev + + - name: Create archives and .deb packages for all target artifacts if: steps.check-tag.outputs.tag_exists == 'false' run: | # Parse targets and operating systems from environment targets="${{ env.GFX_TARGETS }}" operating_systems="${{ env.OPERATING_SYSTEMS }}" TAG="${{ steps.generate-tag.outputs.tag }}" - + echo "Processing targets: $targets" echo "Processing operating systems: $operating_systems" echo "Using release tag: $TAG" - + + mkdir -p debs + # Create individual archives for each target and OS combination IFS=',' read -ra TARGET_ARRAY <<< "$targets" IFS=',' read -ra OS_ARRAY <<< "$operating_systems" - + for os in "${OS_ARRAY[@]}"; do os=$(echo "$os" | xargs) # trim whitespace for target in "${TARGET_ARRAY[@]}"; do target=$(echo "$target" | xargs) # trim whitespace - + echo "Processing OS: $os, target: $target" - + # Use artifact name to find the directory artifact_name="llama-${os}-rocm-${target}-x64" artifact_dir="./all-artifacts/${artifact_name}" - + # Create final archive with release tag final_archive_name="llama-${TAG}-${os}-rocm-${target}-x64" - + if [ -d "$artifact_dir" ]; then echo "Creating archive: ${final_archive_name}.zip" cd "$artifact_dir" zip -r "../../${final_archive_name}.zip" * cd ../../ + + # Build .deb for Ubuntu targets + if [ "$os" = "ubuntu" ]; then + echo "Building .deb for ${target} at version ${TAG}" + bash scripts/create_deb.sh "$artifact_dir" "$TAG" "$target" debs + fi else echo "Warning: Artifact directory not found: $artifact_dir" ls -la ./all-artifacts/ fi done done - + echo "Created archives:" ls -la *.zip + echo "Created .deb packages:" + ls -la debs/*.deb || echo "(none)" - name: Create Release if: steps.check-tag.outputs.tag_exists == 'false' @@ -1086,26 +1138,33 @@ jobs: # Verify archives exist ls -la *.zip - - # Prepare upload files list + + # Prepare upload files list (zips) upload_files="" IFS=',' read -ra TARGET_ARRAY <<< "$targets" IFS=',' read -ra OS_ARRAY <<< "$operating_systems" - + for os in "${OS_ARRAY[@]}"; do os=$(echo "$os" | xargs) # trim whitespace for target in "${TARGET_ARRAY[@]}"; do target=$(echo "$target" | xargs) # trim whitespace - + final_archive_name="llama-${TAG}-${os}-rocm-${target}-x64" if [ -f "${final_archive_name}.zip" ]; then upload_files="${upload_files} ${final_archive_name}.zip" fi done done - - echo "Files to upload: $upload_files" - + + # Collect .deb packages + deb_files="" + for deb in debs/*.deb; do + [ -f "$deb" ] && deb_files="${deb_files} ${deb}" + done + + echo "Zip files to upload: $upload_files" + echo "Deb files to upload: $deb_files" + # Create release with GitHub CLI gh release create "$TAG" \ --title "$TAG" \ @@ -1116,5 +1175,21 @@ jobs: **Llama.cpp Commit Hash**: $LLAMACPP_COMMIT_HASH **Build Date**: $(date -u '+%Y-%m-%d %H:%M:%S UTC') - This release includes compiled llama.cpp binaries with ROCm support for multiple GPU targets and operating systems, with all essential ROCm runtime libraries included." \ - $upload_files + This release includes compiled llama.cpp binaries with ROCm support for multiple GPU targets and operating systems, with all essential ROCm runtime libraries included. + + ### Ubuntu — Install via .deb (recommended) + + \`\`\`bash + # Replace gfx1151 with your GPU target (see GPU Targets above) + wget https://github.com/lemonade-sdk/llamacpp-rocm/releases/download/${TAG}/llamacpp-rocm-gfx1151_${TAG}_amd64.deb + sudo dpkg -i llamacpp-rocm-gfx1151_${TAG}_amd64.deb + llama-server -m /path/to/model.gguf -ngl 99 + \`\`\` + + ### Ubuntu — Manual install from .zip + + \`\`\`bash + unzip llama-${TAG}-ubuntu-rocm--x64.zip -d llamacpp-rocm + cd llamacpp-rocm && ./llama-server -m /path/to/model.gguf -ngl 99 + \`\`\`" \ + $upload_files $deb_files diff --git a/scripts/create_deb.sh b/scripts/create_deb.sh new file mode 100755 index 0000000..9b02472 --- /dev/null +++ b/scripts/create_deb.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# create_deb.sh — Build a .deb package from an extracted llamacpp-rocm zip +# +# Usage: +# create_deb.sh [output_dir] +# +# Arguments: +# input_dir - Directory containing the extracted zip contents (flat layout) +# version - Package version, e.g. "b1241" or "1241" +# gpu_target - GPU target string, e.g. "gfx1151" +# output_dir - Where to write the .deb (default: current directory) +# +# Requires: patchelf, dpkg-deb, python3 +# +# Installed layout: +# /usr/bin/ — llama-* executables and rpc-server +# /usr/lib/llamacpp-rocm/ — shared libraries (RPATH re-patched) +# /usr/lib/llamacpp-rocm/rocblas/ — rocBLAS GPU kernels +# /usr/lib/llamacpp-rocm/hipblaslt/ — hipBLASLt GPU kernels + +set -euo pipefail + +INPUT_DIR="${1:?Usage: $0 [output_dir]}" +VERSION="${2:?}" +GPU_TARGET="${3:?}" +OUTPUT_DIR="${4:-.}" + +LIB_INSTALL_PATH="/usr/lib/llamacpp-rocm" +# Include GPU target in the package name so multiple GPU variants can coexist +# on disk; Provides/Conflicts ensure only one is active at a time. +PKG_NAME="llamacpp-rocm-${GPU_TARGET}" +# Debian version must start with a digit; prefix non-numeric versions with "0~" +if [[ "${VERSION}" =~ ^[0-9] ]]; then + DEB_VERSION="${VERSION}" +else + DEB_VERSION="0~${VERSION}" +fi +ARCH="amd64" + +STAGE_DIR="$(mktemp -d)" +trap 'rm -rf "$STAGE_DIR"' EXIT + +PKG_ROOT="${STAGE_DIR}/${PKG_NAME}_${DEB_VERSION}_${ARCH}" +BIN_DIR="${PKG_ROOT}/usr/bin" +LIB_DIR="${PKG_ROOT}${LIB_INSTALL_PATH}" +DEBIAN_DIR="${PKG_ROOT}/DEBIAN" + +echo "==> Staging package: ${PKG_NAME} ${DEB_VERSION} (${GPU_TARGET}) → ${OUTPUT_DIR}" + +mkdir -p "$BIN_DIR" "$LIB_DIR" "$DEBIAN_DIR" + +# --------------------------------------------------------------------------- +# 1. Copy executables → /usr/bin/ +# --------------------------------------------------------------------------- +echo "--> Copying executables" +for f in "$INPUT_DIR"/llama-* "$INPUT_DIR/rpc-server"; do + [ -f "$f" ] || continue + # Skip non-ELF files (e.g. zip archives that match llama-* glob) + file -b "$f" | grep -q "^ELF" || continue + install -m 755 "$f" "$BIN_DIR/" +done + +# --------------------------------------------------------------------------- +# 2. Copy GPU kernel data directories (preserve tree structure) +# --------------------------------------------------------------------------- +echo "--> Copying rocblas / hipblaslt kernel data" +for kdir in rocblas hipblaslt; do + if [ -d "$INPUT_DIR/$kdir" ]; then + cp -r "$INPUT_DIR/$kdir" "$LIB_DIR/" + fi +done + +# --------------------------------------------------------------------------- +# 3. Install shared libraries with proper symlinks (deduplicate) +# In the zip, libfoo.so / libfoo.so.X / libfoo.so.X.Y.Z all contain +# identical bytes. We install only the most-specific version as a real +# file, and create symlinks for the shorter names. +# --------------------------------------------------------------------------- +echo "--> Installing shared libraries (deduplicating soname copies)" +python3 - "$INPUT_DIR" "$LIB_DIR" <<'PYEOF' +import os, sys, re, shutil +from collections import defaultdict + +src_dir, dst_dir = sys.argv[1], sys.argv[2] + +entries = [f for f in os.listdir(src_dir) + if f.startswith('lib') and '.so' in f + and os.path.isfile(os.path.join(src_dir, f))] + +# Group by the base name (everything up to and including ".so") +groups = defaultdict(list) +for name in entries: + base = re.sub(r'(\.so)(\..*)?$', r'\1', name) # e.g. "libfoo.so" + groups[base].append(name) + +def version_weight(name, base): + """Return a sort key: more version components = higher weight = canonical.""" + suffix = name[len(base):] # e.g. "" or ".3" or ".3.0.0" or ".23.0git" + return (len(suffix.split('.')), suffix) + +for base, members in sorted(groups.items()): + members.sort(key=lambda n: version_weight(n, base), reverse=True) + canonical = members[0] # most-specific version (real file) + shutil.copy2(os.path.join(src_dir, canonical), os.path.join(dst_dir, canonical)) + # Create symlinks from shorter names → canonical + for alt in members[1:]: + link_path = os.path.join(dst_dir, alt) + if os.path.exists(link_path): + os.remove(link_path) + os.symlink(canonical, link_path) + print(f" symlink: {alt} -> {canonical}") + +PYEOF + +# --------------------------------------------------------------------------- +# 4. Re-patch RPATH on executables and shared libraries +# Original build uses $ORIGIN (all files in one flat dir). +# After installation, executables live in /usr/bin/ and libraries in +# /usr/lib/llamacpp-rocm/, so we must update RPATH accordingly. +# --------------------------------------------------------------------------- +echo "--> Re-patching RPATH" + +# Executables: /usr/bin/ → find libraries at /usr/lib/llamacpp-rocm/ +for f in "$BIN_DIR"/llama-* "$BIN_DIR/rpc-server"; do + [ -f "$f" ] || continue + patchelf --set-rpath "$LIB_INSTALL_PATH" "$f" 2>/dev/null || true +done + +# Libraries: they also depend on each other → same RPATH +for f in "$LIB_DIR"/lib*.so*; do + [ -f "$f" ] && [ ! -L "$f" ] || continue + patchelf --set-rpath "$LIB_INSTALL_PATH" "$f" 2>/dev/null || true +done + +# --------------------------------------------------------------------------- +# 5. Create DEBIAN/control +# --------------------------------------------------------------------------- +echo "--> Writing DEBIAN/control" + +# Estimate installed size (kB) +INSTALLED_KB=$(du -sk "$PKG_ROOT/usr" | cut -f1) + +cat > "$DEBIAN_DIR/control" < +Installed-Size: ${INSTALLED_KB} +Depends: libc6 (>= 2.34) +Provides: llamacpp-rocm +Conflicts: llamacpp-rocm +Section: science +Priority: optional +Description: llama.cpp with AMD ROCm GPU acceleration (${GPU_TARGET}) + Self-contained llama.cpp build with bundled ROCm 7 runtime libraries. + Supports AMD GPU inference via HIP/ROCm with no separate ROCm installation + required. GPU target: ${GPU_TARGET}. + . + Includes llama-server, llama-cli, llama-quantize, and all other llama.cpp + tools, along with bundled ROCm libraries (hipBLAS, rocBLAS, HIP runtime, + LLVM/Clang JIT) and GPU kernel data for ${GPU_TARGET}. +CTRL + +# --------------------------------------------------------------------------- +# 6. Build the .deb +# --------------------------------------------------------------------------- +mkdir -p "$OUTPUT_DIR" +DEB_FILE="${OUTPUT_DIR}/${PKG_NAME}_${DEB_VERSION}_${ARCH}.deb" + +echo "--> Building ${DEB_FILE}" +dpkg-deb --build --root-owner-group "$PKG_ROOT" "$DEB_FILE" + +echo "==> Done: ${DEB_FILE}" +dpkg-deb --info "$DEB_FILE"