#!/bin/bash
# desktop-build-arm3.sh -- runs ON THE DESKTOP, as an operator, inside the user unit bsw2-arm3-build (Nice 19, CPUQuota 1600 %,
# MemoryMax 24G, idle I/O: the EPYC carries the live seats). Lane bsw2-arm3, 2026-10-09. NO CARD, NO SUDO.
# The one network step is the git fetch of the pinned tag (outside the sandbox, before any build step); the
# configure and build run inside bwrap with NO network, a read-only root and no GPU device node.
#
# Builds llama.cpp at the newest release tag holding "Qwen4Exp: add MTP" (#29761, merge c061df19):
#   b11514 = de7fa0a3c6a2e1b4cd9f22eb8d6bf5b12dbdb63b (read by gh at 2026-10-09T01:53Z; ls-remote on the desktop agrees),
# from source only, for the desktop's smaller card's Ampere (sm_86), with the stage's flags EXACTLY (kit/stage/desktop-build-sm86.sh):
#   Release, GGML_CUDA=ON, CMAKE_CUDA_ARCHITECTURES=86, nvcc = window 0's staged CUDA 13.4, GGML_NATIVE=ON,
#   LLAMA_CURL=OFF, LLAMA_BUILD_TESTS=OFF, LLAMA_USE_PREBUILT_UI=OFF, CMAKE_BUILD_RPATH = the staged 13.4 lib folder;
#   targets llama-server llama-bench llama-cli. MTP needs no build flag (core code, src/models/qwen4exp.cpp).
#
#   bash /workshop/card24-strata/stage/arm3/desktop-build-arm3.sh
#
# Writes only under ~/card24-strata/: src/llama.cpp-b11514/ (the source tree, kept: the engine-identity gate reads
# its HEAD), build/llama.cpp-b11514-sm86/, stage/arm3/{logs,build-home,build-tmp,BUILD-RECEIPT.txt,BINARIES.sha256}.
# Each refused if it exists (launch once). Last receipt line: BUILD DONE llama <sha256 llama-server> |
# BUILD STOP <why> | BUILD FAILED <step> rc <n>.
set -uo pipefail
B=/workshop/bench-flashnext
BS=/workshop/card24-strata
TAG=b11514
FULL=de7fa0a3c6a2e1b4cd9f22eb8d6bf5b12dbdb63b
MTP_MERGE=c061df19838ff60970faf54fd7e414953590125d
REF_SRC=$B/llama.cpp-3cf03257
SRC=$BS/src/llama.cpp-$TAG
OUT=$BS/build/llama.cpp-$TAG-sm86
CUDA=$B/cuda-13.4
LIB=$CUDA/targets/x86_64-linux/lib
ST=$BS/stage/arm3
LOGS=$ST/logs
RCPT=$ST/BUILD-RECEIPT.txt
JOBS=${BSW_JOBS:-12}
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null GIT_OPTIONAL_LOCKS=0 GIT_TERMINAL_PROMPT=0
cd /
say()  { echo "$(date -u +%FT%TZ) $*" | tee -a "$RCPT"; }
stop() { say "BUILD STOP $*"; exit 3; }
fail() { say "BUILD FAILED $1 rc $2"; exit 4; }
[ -e "$RCPT" ] && { echo "BUILD STOP $RCPT exists (launch once: read it)"; exit 3; }
for o in "$SRC" "$OUT"; do [ ! -e "$o" ] || { echo "BUILD STOP $o exists (launch once)"; exit 3; }; done
mkdir -p "$LOGS"
say "desktop-build-arm3 start (host $(hostname), jobs $JOBS, tag $TAG = $FULL)"
[ -x "$CUDA/bin/nvcc" ] || stop "$CUDA/bin/nvcc is absent (window 0's staged CUDA 13.4)"
nv=$("$CUDA/bin/nvcc" --version 2>&1 | grep -o 'V13\.4\.[0-9]*' | head -1); [ -n "$nv" ] || stop "nvcc does not report CUDA 13.4"
for l in libcudart.so.13 libcublas.so.13 libcublasLt.so.13; do [ -e "$LIB/$l" ] || stop "$LIB/$l is absent"; done
# 1. the source: a full local copy of the bench's clone (read only; objects copied, no hardlinks), then the tag fetched
got=$(git -C "$REF_SRC" --no-optional-locks rev-parse HEAD 2>/dev/null); [ "$got" = "3cf03257f219afbe7334045ff7c6a06ac68c627d" ] || stop "$REF_SRC is at '$got'"
mkdir -p "$BS/src"
git clone --quiet --local --no-hardlinks --no-checkout "$REF_SRC" "$SRC" > "$LOGS/git-clone.log" 2>&1 || fail git-clone $?
git -C "$SRC" remote set-url origin https://github.com/ggml-org/llama.cpp || fail git-remote $?
timeout 600 git -C "$SRC" fetch --no-tags origin "refs/tags/$TAG:refs/tags/$TAG" > "$LOGS/git-fetch.log" 2>&1 || fail git-fetch $?
got=$(git -C "$SRC" rev-parse "refs/tags/$TAG^{commit}"); [ "$got" = "$FULL" ] || stop "tag $TAG reads '$got', not $FULL"
git -C "$SRC" -c advice.detachedHead=false checkout --quiet --detach "$FULL" > "$LOGS/git-checkout.log" 2>&1 || fail git-checkout $?
got=$(git -C "$SRC" rev-parse HEAD); [ "$got" = "$FULL" ] || stop "HEAD is '$got', not $FULL"
porc=$(git -C "$SRC" status --porcelain --ignored 2>&1); [ -z "$porc" ] || stop "$SRC is not clean: $(printf '%s' "$porc" | head -3 | tr '\n' ' ')"
git -C "$SRC" merge-base --is-ancestor "$MTP_MERGE" HEAD || stop "the MTP merge $MTP_MERGE is not an ancestor of HEAD"
say "source: HEAD $FULL (rev-list --count $(git -C "$SRC" rev-list --count HEAD)); MTP merge ${MTP_MERGE:0:12} is an ancestor; tree clean"
say "source: draft-mtp in common/speculative.cpp: $(grep -c '"draft-mtp"' "$SRC/common/speculative.cpp") line(s); graph_mtp in src/models/qwen4exp.cpp: $(grep -c 'graph_mtp' "$SRC/src/models/qwen4exp.cpp") line(s)"
# 2. the sandbox: no network, read-only root, no GPU node
/usr/bin/bwrap --ro-bind / / --dev /dev --proc /proc --unshare-net true 2>/dev/null || stop "bwrap --unshare-net does not run here"
mkdir -p "$OUT" "$ST/build-home" "$ST/build-tmp"
SANDBOX=(/usr/bin/bwrap --unshare-net --die-with-parent --ro-bind / / --dev /dev --proc /proc --tmpfs /run
         --bind "$OUT" "$OUT" --bind "$ST/build-home" "$ST/build-home" --bind "$ST/build-tmp" "$ST/build-tmp" --
         /usr/bin/env -i PATH="$CUDA/bin:/usr/bin:/bin" HOME="$ST/build-home" TMPDIR="$ST/build-tmp" LANG=C.UTF-8
         CUDA_VISIBLE_DEVICES= GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null GIT_OPTIONAL_LOCKS=0)
probe=$("${SANDBOX[@]}" /bin/sh -c 'echo "$(awk -F: "NR>2 {gsub(/ /, \"\", \$1); print \$1}" /proc/net/dev | tr "\n" " ")| nvidia nodes: $(ls /dev | grep -c nvidia)"' 2>&1)
say "sandbox probe: interfaces ${probe}"
case "$probe" in "lo | nvidia nodes: 0") ;; *) stop "the sandbox is not sealed: '$probe'" ;; esac
# 3. configure and build: the stage's flags, unchanged
say "llama.cpp $TAG configure (CUDA $nv, arch 86)"
"${SANDBOX[@]}" cmake -S "$SRC" -B "$OUT" -G Ninja -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON \
  -DCMAKE_CUDA_ARCHITECTURES=86 -DCMAKE_CUDA_COMPILER="$CUDA/bin/nvcc" -DGGML_NATIVE=ON -DLLAMA_CURL=OFF \
  -DLLAMA_BUILD_TESTS=OFF -DLLAMA_USE_PREBUILT_UI=OFF -DCMAKE_BUILD_RPATH="$LIB" > "$LOGS/llama-configure.log" 2>&1 || fail llama-configure $?
say "configure: unused-variable warnings: $(grep -A6 'Manually-specified variables were not used' "$LOGS/llama-configure.log" | grep -oE '^ +[A-Z_]+' | tr -s ' \n' ' ')"
t0=$(date +%s)
"${SANDBOX[@]}" cmake --build "$OUT" --target llama-server llama-bench llama-cli -j "$JOBS" > "$LOGS/llama-build.log" 2>&1 || fail llama-build $?
say "llama.cpp $TAG built in $(( $(date +%s) - t0 )) s"
for b in llama-server llama-bench llama-cli; do [ -f "$OUT/bin/$b" ] || fail "llama-no-$b" 5; done
# 4. receipts
for f in "$OUT"/bin/llama-server "$OUT"/bin/llama-bench "$OUT"/bin/llama-cli "$OUT"/bin/*.so*; do
  [ -f "$f" ] && [ ! -L "$f" ] && sha256sum "$f"
done > "$ST/BINARIES.sha256"
say "binaries: $(wc -l < "$ST/BINARIES.sha256") files hashed into stage/arm3/BINARIES.sha256"
say "CMakeCache: $(grep -E '^(CMAKE_BUILD_TYPE|GGML_CUDA|GGML_NATIVE|GGML_VULKAN|CMAKE_CUDA_ARCHITECTURES|LLAMA_CURL|LLAMA_BUILD_TESTS|LLAMA_USE_PREBUILT_UI|CMAKE_HOME_DIRECTORY):' "$OUT/CMakeCache.txt" | tr '\n' ' ')"
arch=$("$CUDA/bin/cuobjdump" --list-elf "$OUT/bin/libggml-cuda.so" 2>/dev/null | grep -o 'sm_[0-9]*a\?' | sort | uniq -c | tr '\n' ' ')
say "libggml-cuda.so ELF arches: ${arch:-unread}"
say "llama-server RUNPATH: $(readelf -d "$OUT/bin/llama-server" 2>/dev/null | grep -o 'R.*PATH.*' | head -1)"
say "llama-server --version: $("${SANDBOX[@]}" "$OUT/bin/llama-server" --version 2>&1 | grep -iE 'version|built' | tr '\n' ' ')"
ls_sha=$(sha256sum "$OUT/bin/llama-server" | cut -c1-64)
say "BUILD DONE llama $ls_sha"
