#!/usr/bin/env bash # Build mainline llama.cpp for Dankserver's two Radeon AI PRO R9700 (gfx1201, # Navi 48) with DFlash2 speculative decoding. # # Run on Dankserver. Produces a self-contained build: the binary carries an # RPATH of $ORIGIN/../lib and the ROCm runtime is copied beside it, so nothing # depends on the host's ROCm packages (Ubuntu's 7.1) or on LD_LIBRARY_PATH. # # bash scripts/build-llama-rdna4.sh # # WHY A PRIVATE TOOLCHAIN (ROCm 7.14.0a20260624, ~17 GiB extracted): the host's # ROCm is Ubuntu's 7.1 packaging, and the pre-existing RDNA4 llama.cpp build on # this box (charlie12345/ROCmFPX) was built against this same TheRock nightly. # Reusing it keeps the toolchain that is known to emit working gfx1201 code # objects; the tarball is only downloaded when the prefix is missing. # # WHY THIS REF: master at fb27a525 carries what this service needs and nothing # older does: # * DFlash2 (`DFlash2DraftModel`): grouped dynamic depthwise convolution plus # the candidate selector, detected from the draft GGUF # (`selector_top_k > 0`) and served through the ordinary `--spec-type # draft-dflash` path. Landed with ggml-org/llama.cpp#27342. # * qwen35 MTP: the `draft-mtp` implementation covers qwen35's single trained # MTP head (common/speculative.cpp), which the target GGUF carries. # Do not "update to latest" casually: both features landed days apart and the # service depends on the pair. # # gfx1201 IS NOT gfx1200. The ROCmFPX fork's docs/BUILD-AMD-ARCHITECTURES.md # records that a gfx1200 build links, loads the model and then segfaults on # these cards, so the arch list here is exactly gfx1201 and no other. set -euo pipefail LLAMA_SRC="${LLAMA_SRC:-/home/dank/GitPackages/llama.cpp-dflash2}" LLAMA_REPO="${LLAMA_REPO:-https://github.com/ggml-org/llama.cpp.git}" LLAMA_REF="${LLAMA_REF:-fb27a525d28381a16a4bb038858a10e4927381ca}" BUILD_DIR="${BUILD_DIR:-$LLAMA_SRC/build-rdna4-rocm714}" JOBS="${JOBS:-$(nproc)}" ROCM_VERSION="${ROCM_VERSION:-7.14.0a20260624}" ROCM_LOCAL_PATH="${ROCM_LOCAL_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/rocm-${ROCM_VERSION}}" HIP_ARCH="${HIP_ARCH:-gfx1201}" GGML_HIP_RCCL="${GGML_HIP_RCCL:-OFF}" # ── Source ──────────────────────────────────────────────────────────── if [[ ! -d "$LLAMA_SRC/.git" ]]; then echo "cloning $LLAMA_REPO into $LLAMA_SRC" git clone "$LLAMA_REPO" "$LLAMA_SRC" fi git -C "$LLAMA_SRC" fetch --quiet origin "$LLAMA_REF" 2>/dev/null || git -C "$LLAMA_SRC" fetch --quiet origin git -C "$LLAMA_SRC" checkout --detach "$LLAMA_REF" echo "building llama.cpp $(git -C "$LLAMA_SRC" rev-parse --short HEAD) from $LLAMA_SRC" # ── ROCm toolchain ──────────────────────────────────────────────────── if [[ ! -x "$ROCM_LOCAL_PATH/llvm/bin/clang" ]]; then TARBALL_URL="https://rocm.nightlies.amd.com/tarball-multi-arch/therock-dist-linux-gfx120X-all-${ROCM_VERSION}.tar.gz" TARBALL="$LLAMA_SRC/rocm-${ROCM_VERSION}.tar.gz" if [[ ! -s "$TARBALL" ]]; then echo "downloading ROCm $ROCM_VERSION from TheRock nightlies" curl -fL --retry 5 -o "$TARBALL" "$TARBALL_URL" fi echo "extracting into $ROCM_LOCAL_PATH" mkdir -p "$ROCM_LOCAL_PATH" tar --use-compress-program=gzip -xf "$TARBALL" -C "$ROCM_LOCAL_PATH" --strip-components=1 fi CLANG="$ROCM_LOCAL_PATH/llvm/bin/clang" CLANGXX="$ROCM_LOCAL_PATH/llvm/bin/clang++" [[ -x "$CLANG" && -x "$CLANGXX" ]] || { echo "no clang/clang++ under $ROCM_LOCAL_PATH" >&2; exit 1; } # ggml-hip's CMake falls back to $ENV{ROCM_PATH} or /opt/rocm when these are # unset, which links a second, mismatched HIP runtime next to the local one. export ROCM_PATH="$ROCM_LOCAL_PATH" export HIP_PATH="$ROCM_LOCAL_PATH" # ── Configure ───────────────────────────────────────────────────────── # GGML_HIP_RCCL defaults OFF: layer-split serving needs no collectives, and # until 2026-09-20 this host's second card could not dispatch a RCCL kernel at # all (its PCIe path had no atomic routing). With RCCL off, llama.cpp logs # "internal AllReduce init failed ... falling back to meta-backend butterfly" # and only --split-mode row/tensor pays for it. Set GGML_HIP_RCCL=ON (with a # distinct BUILD_DIR) for the collective-backed runtime; the atomics # precondition now holds on both cards. cmake -S "$LLAMA_SRC" -B "$BUILD_DIR" \ -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER="$CLANG" \ -DCMAKE_CXX_COMPILER="$CLANGXX" \ -DCMAKE_CXX_FLAGS="-I${ROCM_LOCAL_PATH}/include" \ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ -DGGML_HIP=ON \ -DGGML_HIP_RCCL="$GGML_HIP_RCCL" \ -DGGML_CUDA=OFF \ -DGGML_VULKAN=OFF \ -DGGML_NATIVE=OFF \ -DCMAKE_HIP_ARCHITECTURES="$HIP_ARCH" \ -DGPU_TARGETS="$HIP_ARCH" \ -DLLAMA_BUILD_SERVER=ON \ -DLLAMA_BUILD_WEBUI=OFF \ -DLLAMA_USE_PREBUILT_WEBUI=OFF \ -DLLAMA_BUILD_TESTS=OFF \ -DGGML_BUILD_TESTS=OFF \ -DCMAKE_PREFIX_PATH="$ROCM_LOCAL_PATH" \ -DROCM_DIR="$ROCM_LOCAL_PATH" \ -DBUILD_SHARED_LIBS=OFF # ── Build ───────────────────────────────────────────────────────────── cmake --build "$BUILD_DIR" -j "$JOBS" \ --target llama-server llama-cli llama-quantize llama-bench # ── Bundle the runtime ──────────────────────────────────────────────── if command -v patchelf >/dev/null; then LIB_DIR="$BUILD_DIR/lib" mkdir -p "$LIB_DIR" for pat in libamdhip64 librocblas libhipblas libhipblaslt librocsolver \ libroctx64 libamd_comgr libhsa-runtime64 librocm_kpack \ librocroller librocprofiler-register librocm-core; do cp -v "$ROCM_LOCAL_PATH/lib/$pat".so* "$LIB_DIR/" 2>/dev/null || true done cp -v "$ROCM_LOCAL_PATH/lib/llvm/lib"/libLLVM.so* \ "$ROCM_LOCAL_PATH/lib/llvm/lib"/libclang-cpp.so* \ "$ROCM_LOCAL_PATH/lib/llvm/lib"/libomp.so* \ "$ROCM_LOCAL_PATH/lib/llvm/lib"/libomptarget.so* "$LIB_DIR/" 2>/dev/null || true cp -v "$ROCM_LOCAL_PATH/lib/rocm_sysdeps/lib"/librocm_sysdeps_*.so* "$LIB_DIR/" 2>/dev/null || true # A RCCL build links librccl.so.1 (imported shared even with # BUILD_SHARED_LIBS=OFF), and the binaries resolve libraries through # $ORIGIN/../lib — so the collective library must ride beside the rest or # llama-server fails to exec at all. if [[ "$GGML_HIP_RCCL" == "ON" ]]; then cp -v "$ROCM_LOCAL_PATH"/lib/librccl.so* "$LIB_DIR/" 2>/dev/null || true fi # rocBLAS and hipBLASLt look for their device code at a FIXED depth under # the binary's lib dir: $ORIGIN/../lib/rocblas/library and # $ORIGIN/../lib/hipblaslt/library/. `cp -r SRC DST/` with a missing # DST creates DST as a copy of SRC and flattens the source by one level, so # the engine loads, serves nothing, and dies on the FIRST request with # rocBLAS error: Cannot read .../lib/rocblas/library/TensileLibrary.dat: # No such file or directory for GPU arch : gfx1201 # (measured 2026-09-16, after a build that looked perfectly healthy). # Declare the destination, then copy the contents into it. if [[ -d "$ROCM_LOCAL_PATH/lib/rocblas/library" ]]; then mkdir -p "$LIB_DIR/rocblas/library" cp -r "$ROCM_LOCAL_PATH/lib/rocblas/library/." "$LIB_DIR/rocblas/library/" fi if [[ -d "$ROCM_LOCAL_PATH/lib/hipblaslt/library" ]]; then mkdir -p "$LIB_DIR/hipblaslt/library" cp -r "$ROCM_LOCAL_PATH/lib/hipblaslt/library/." "$LIB_DIR/hipblaslt/library/" fi for bin in "$BUILD_DIR"/bin/llama-*; do [[ -f "$bin" ]] && patchelf --set-rpath '$ORIGIN/../lib' "$bin" done else echo "patchelf missing; run with LD_LIBRARY_PATH=$ROCM_LOCAL_PATH/lib:$ROCM_LOCAL_PATH/lib/llvm/lib" >&2 fi echo echo "built for $HIP_ARCH:" "$BUILD_DIR/bin/llama-server" --version 2>&1 | head -3 ldd "$BUILD_DIR/bin/llama-server" 2>/dev/null | grep -E "not found" && exit 1 || true echo " $BUILD_DIR/bin/llama-server"