Source files / install · raw
install.sh
#!/usr/bin/env bash # tersler all-in-one installer for Ubuntu 24.04 + 2x Tesla P100. # # sudo ./install/install.sh # run all stages, resumes after reboots # sudo ./install/install.sh <stage>... # run selected stages # sudo ./install/install.sh --list # show stages and their state # # Stages are idempotent: re-running one is safe. Configuration: # install/tersler.conf (+ install/tersler.local.conf). set -euo pipefail REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) JR_CONF=${JR_CONF:-} # shellcheck source=../scripts/lib.sh source "$REPO/scripts/lib.sh" [[ ${1:-} == --list || ${1:-} == -h || ${1:-} == --help ]] && LIST_ONLY=1 || LIST_ONLY=0 # Config can be loaded before root check (for --list); repo copy wins during install. if [[ -z $JR_CONF ]]; then JR_CONF="$REPO/install/tersler.conf" fi # LLAMA_COMMIT from the environment beats the config: build a candidate with # LLAMA_COMMIT=<commit> sudo -E ./install/install.sh build env_commit=${LLAMA_COMMIT:-} # shellcheck source=tersler.conf source "$JR_CONF" # shellcheck source=/dev/null [[ -f $REPO/install/tersler.local.conf ]] && source "$REPO/install/tersler.local.conf" LLAMA_COMMIT=${env_commit:-$LLAMA_COMMIT} : "${MODEL_FILE:=${MODEL_URL##*/}}"; MODEL_FILE=${MODEL_FILE%%\?*} STAGES=(preflight config base driver cuda kernel build gpu model services verify) declare -A STAGE_HELP=( [preflight]="check OS, GPUs, BAR1 sizes, RAM, disk" [config]="copy config to /etc/tersler, scripts to \$JR_PREFIX/bin" [base]="build tools and utilities" [driver]="NVIDIA $DRIVER_PKG (proprietary, Pascal-capable) [reboot]" [cuda]="CUDA toolkit only, NVIDIA-repo drivers pinned out" [kernel]="kernel parameters (iommu=pt) [reboot]" [build]="clone + build the P100 llama.cpp fork into a versioned dir" [gpu]="persistence mode + power limit service" [model]="download the GGUF (if MODEL_URL is set)" [services]="llama-server (+ optional fan control) systemd units" [verify]="driver, BAR1, P2P, build and server sanity checks" ) STATE_DIR=$JR_PREFIX/state REBOOT_FLAG=$STATE_DIR/reboot-required stage_done() { [[ -f $STATE_DIR/stage.$1 ]]; } mark_done() { mkdir -p "$STATE_DIR"; date -Is > "$STATE_DIR/stage.$1"; } need_reboot(){ mkdir -p "$STATE_DIR"; echo "$1" >> "$REBOOT_FLAG"; } if (( LIST_ONLY )); then printf 'Stages (in order):\n' for s in "${STAGES[@]}"; do st="pending"; stage_done "$s" && st="done $(cat "$STATE_DIR/stage.$s")" printf ' %-10s %-55s %s\n' "$s" "${STAGE_HELP[$s]}" "$st" done exit 0 fi [[ $EUID -eq 0 ]] || jr_die "run with sudo" TARGET_USER=${JR_USER:-${SUDO_USER:-root}} TARGET_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6) export DEBIAN_FRONTEND=noninteractive # ---------------------------------------------------------------- stages stage_preflight() { # shellcheck source=/dev/null source /etc/os-release [[ $ID == ubuntu && $VERSION_ID == 24.04 ]] || jr_warn "tested on Ubuntu 24.04, this is $PRETTY_NAME" [[ $(uname -m) == x86_64 ]] || jr_die "x86_64 required" local gpus gpus=$(lspci -Dnn | grep -i '\[10de:' | grep -Ei '3D controller|VGA' || true) if [[ -z $gpus ]]; then jr_warn "no NVIDIA GPU visible on PCI. Cards seated? Above 4G Decoding enabled?" else jr_log "NVIDIA devices:"; sed 's/^/ /' <<<"$gpus" >&2 local n; n=$(grep -ci 'P100' <<<"$gpus" || true) (( n == 2 )) || jr_warn "expected 2x Tesla P100, found $n" # Pipes below read their input to the end: an early exit (awk exit, # head, grep -q) SIGPIPEs the writer, and pipefail + set -e then end # the script without a message. local addr while read -r addr _; do local bar1 bar1=$(lspci -vv -s "$addr" 2>/dev/null | awk '/Region 1:/ && !f {print; f=1}') jr_log " $addr BAR1: ${bar1:-<not assigned>}" [[ $bar1 == *'[disabled]'* || -z $bar1 ]] && \ jr_warn " $addr: BAR1 not mapped. Enable Above 4G Decoding (docs/hardware-notes.md)" done <<<"$gpus" fi local mem_gb disk_gb mem_gb=$(awk '/MemTotal/ {printf "%d", $2/1024/1024}' /proc/meminfo) mkdir -p "$JR_PREFIX" disk_gb=$(df -BG --output=avail "$JR_PREFIX" | tail -1 | tr -dc 0-9) jr_log "RAM: ${mem_gb} GB, free disk at $JR_PREFIX: ${disk_gb} GB" (( disk_gb >= 40 )) || jr_warn "less than 40 GB free: one build + one 27B model need ~30 GB" grep -qw iommu=pt /proc/cmdline && jr_log "iommu=pt active" || jr_log "iommu=pt not active yet (kernel stage)" if [[ -d /sys/module/nouveau ]]; then jr_warn "nouveau loaded; the driver stage blacklists it"; fi } stage_config() { install -d -m 0755 "$JR_ETC" "$JR_PREFIX/bin" "$JR_PREFIX/builds" "$JR_MODEL_DIR" "$STATE_DIR" install -m 0644 "$JR_CONF" "$JR_ETC/tersler.conf" if [[ -f $REPO/install/tersler.local.conf ]]; then install -m 0644 "$REPO/install/tersler.local.conf" "$JR_ETC/tersler.local.conf" fi install -m 0644 "$REPO/install/upstreams.list" "$JR_ETC/upstreams.list" install -m 0644 "$REPO/scripts/lib.sh" "$JR_PREFIX/bin/lib.sh" local s for s in gpu-setup server strata-server fan-control bench loadtest check-updates; do install -m 0755 "$REPO/scripts/$s.sh" "$JR_PREFIX/bin/tersler-$s" done install -d -m 0755 "$JR_PREFIX/bench" cp -r "$REPO/bench/prompts" "$JR_PREFIX/bench/" # long prompt for prompt-processing speed: this repo's own docs (~5k tokens) { echo "Summarize the following build notes as 10 concise bullet points, then list the three biggest risks." echo; cat "$REPO/docs/research.md" "$REPO/docs/hardware-notes.md"; } > "$JR_PREFIX/bench/prompts/document.txt" chown -R "$TARGET_USER": "$JR_MODEL_DIR" "$JR_PREFIX/builds" "$JR_PREFIX/bench" # put tools on PATH for everyone cat > /etc/profile.d/tersler.sh <<EOF export PATH="$JR_PREFIX/bin:\$PATH" EOF jr_log "config -> $JR_ETC, scripts -> $JR_PREFIX/bin" } stage_base() { apt-get update apt-get install -y --no-install-recommends \ build-essential cmake ninja-build git curl ca-certificates jq pciutils \ lm-sensors python3 ccache libcurl4-openssl-dev "linux-headers-$(uname -r)" \ dkms ubuntu-drivers-common } stage_driver() { [[ $DRIVER_PKG != *-open* ]] || jr_die "DRIVER_PKG=$DRIVER_PKG: open kernel modules do not support Pascal" # keep nouveau away cat > /etc/modprobe.d/tersler-blacklist-nouveau.conf <<'EOF' blacklist nouveau options nouveau modeset=0 EOF local pkgs=("$DRIVER_PKG" "${DRIVER_EXTRA_PKGS[@]}") missing=() p for p in "${pkgs[@]}"; do dpkg -s "$p" >/dev/null 2>&1 || missing+=("$p"); done if (( ${#missing[@]} == 0 )); then jr_log "${pkgs[*]} already installed" else apt-get install -y --no-install-recommends "${missing[@]}" update-initramfs -u need_reboot driver fi dpkg -s xserver-xorg-core >/dev/null 2>&1 && \ jr_warn "xserver-xorg-core is installed; the rig is meant to be headless (apt-cache rdepends --installed xserver-xorg-core)" # never let unattended upgrades or ubuntu-drivers swap the branch apt-mark hold "${pkgs[@]}" >/dev/null jr_log "held ${pkgs[*]} (apt-mark unhold to change on purpose)" } stage_cuda() { if ! dpkg -s cuda-keyring >/dev/null 2>&1; then local tmp; tmp=$(mktemp -d) curl -fsSL -o "$tmp/cuda-keyring.deb" "$CUDA_KEYRING_URL" dpkg -i "$tmp/cuda-keyring.deb"; rm -rf "$tmp" fi # NVIDIA's CUDA repo also carries drivers. Pin them out so they can never # replace the Ubuntu R580 packages; only the toolkit comes from there. cat > /etc/apt/preferences.d/tersler-no-nvidia-repo-drivers <<'EOF' Package: nvidia-* libnvidia-* xserver-xorg-video-nvidia* cuda-drivers* nvidia-open* libxnvctrl* Pin: origin developer.download.nvidia.com Pin-Priority: -1 EOF apt-get update apt-get install -y --no-install-recommends "$CUDA_PKG" cat > /etc/profile.d/tersler-cuda.sh <<EOF export CUDA_HOME=$CUDA_HOME export PATH=$CUDA_HOME/bin:\$PATH EOF "$CUDA_HOME/bin/nvcc" --version | tail -2 } stage_kernel() { local want missing=() p for p in $KERNEL_PARAMS; do grep -qw -- "$p" /proc/cmdline || missing+=("$p"); done cat > /etc/default/grub.d/99-tersler.cfg <<EOF # tersler: kernel parameters (P2P between the P100s needs iommu=pt) GRUB_CMDLINE_LINUX_DEFAULT="\$GRUB_CMDLINE_LINUX_DEFAULT $KERNEL_PARAMS" EOF update-grub want=${missing[*]:-} if [[ -n $want ]]; then need_reboot "kernel: $want"; fi } build_jobs() { if [[ $BUILD_JOBS != auto ]]; then echo "$BUILD_JOBS"; return; fi local mem_gb cpus jobs mem_gb=$(awk '/MemAvailable/ {printf "%d", $2/1024/1024}' /proc/meminfo) cpus=$(nproc) jobs=$(( mem_gb * 10 / 25 )) # ~2.5 GB per nvcc job (FA_QUANTS=all is heavy) (( jobs < 1 )) && jobs=1 (( jobs > cpus )) && jobs=$cpus echo "$jobs" } stage_build() { [[ -x $CUDA_HOME/bin/nvcc ]] || jr_die "nvcc not found at $CUDA_HOME (run the cuda stage)" local src=$JR_PREFIX/src/llama.cpp as_user() { sudo -u "$TARGET_USER" -H "$@"; } install -d -o "$TARGET_USER" "$JR_PREFIX/src" if [[ ! -d $src/.git ]]; then as_user git clone --branch "$LLAMA_BRANCH" "$LLAMA_REPO" "$src" else as_user git -C "$src" remote set-url origin "$LLAMA_REPO" as_user git -C "$src" fetch --tags origin "$LLAMA_BRANCH" fi local commit=${LLAMA_COMMIT:-origin/$LLAMA_BRANCH} as_user git -C "$src" checkout --detach "$commit" local full short dest jobs full=$(as_user git -C "$src" rev-parse HEAD); short=${full:0:9} dest=$JR_PREFIX/builds/$short if [[ -x $dest/bin/llama-server ]]; then jr_log "build $short already exists" else jobs=$(build_jobs) jr_log "building $short with $jobs parallel jobs (CUDA $CUDA_HOME, sm_60 only)" as_user env PATH="$CUDA_HOME/bin:$PATH" CUDACXX="$CUDA_HOME/bin/nvcc" \ cmake -S "$src" -B "$src/build-$short" -G Ninja "${LLAMA_CMAKE_FLAGS[@]}" \ -DCMAKE_INSTALL_PREFIX="$dest" -DCMAKE_INSTALL_RPATH='$ORIGIN/../lib' \ -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache as_user env PATH="$CUDA_HOME/bin:$PATH" cmake --build "$src/build-$short" -j "$jobs" install -d -o "$TARGET_USER" "$dest" as_user cmake --install "$src/build-$short" jq -n --arg repo "$LLAMA_REPO" --arg branch "$LLAMA_BRANCH" --arg commit "$full" \ --arg cuda "$("$CUDA_HOME/bin/nvcc" --version | sed -n 's/.*release \([0-9.]*\).*/\1/p')" \ --arg date "$(date -Is)" --argjson flags "$(printf '%s\n' "${LLAMA_CMAKE_FLAGS[@]}" | jq -R . | jq -s .)" \ '{repo:$repo, branch:$branch, commit:$commit, cuda:$cuda, built:$date, cmake_flags:$flags}' \ > "$dest/build-info.json" fi # activate: $JR_PREFIX/current -> this build (previous one kept for rollback) if [[ -L $JR_PREFIX/current && $(readlink -f "$JR_PREFIX/current") != "$dest" ]]; then ln -sfn "$(readlink -f "$JR_PREFIX/current")" "$JR_PREFIX/previous" fi ln -sfn "$dest" "$JR_PREFIX/current" jr_log "active build: $short ($JR_PREFIX/current)" } # nvidia-smi -L exits 0 with "No devices found" when the driver is loaded # but no GPU initialized (e.g. power cables missing), so look for a GPU line. gpus_ready() { [[ $(nvidia-smi -L 2>/dev/null) == GPU* ]]; } stage_gpu() { install -m 0644 "$REPO/install/systemd/tersler-gpu.service" /etc/systemd/system/ sed -i "s|@PREFIX@|$JR_PREFIX|g" /etc/systemd/system/tersler-gpu.service systemctl daemon-reload systemctl enable tersler-gpu.service if gpus_ready; then systemctl restart tersler-gpu.service else jr_log "no GPU initialized yet (reboot pending?); tersler-gpu runs at next boot" fi } stage_model() { if [[ -z $MODEL_URL ]]; then jr_warn "MODEL_URL not set (install/tersler.local.conf); skipping download" return 0 fi local dst; dst=$JR_MODEL_DIR/$MODEL_FILE install -d -o "$TARGET_USER" "$JR_MODEL_DIR" jr_log "downloading $MODEL_FILE (resumable)" sudo -u "$TARGET_USER" curl -fL -C - --retry 5 -o "$dst" "$MODEL_URL" if [[ -n $MODEL_SHA256 ]]; then echo "$MODEL_SHA256 $dst" | sha256sum -c - || jr_die "checksum mismatch for $dst" fi if [[ -n ${MMPROJ_URL:-} ]]; then dst=$JR_MODEL_DIR/$MMPROJ_FILE jr_log "downloading $MMPROJ_FILE (vision encoder)" sudo -u "$TARGET_USER" curl -fL -C - --retry 5 -o "$dst" "$MMPROJ_URL" if [[ -n ${MMPROJ_SHA256:-} ]]; then echo "$MMPROJ_SHA256 $dst" | sha256sum -c - || jr_die "checksum mismatch for $dst" fi fi } stage_services() { local u for u in tersler-llama.service tersler-strata.service tersler-fan.service; do install -m 0644 "$REPO/install/systemd/$u" /etc/systemd/system/ sed -i -e "s|@PREFIX@|$JR_PREFIX|g" -e "s|@USER@|$TARGET_USER|g" /etc/systemd/system/$u done systemctl daemon-reload if [[ ${SERVE_ENGINE:-llama} == strata ]]; then [[ -x $STRATA_SRC/strata/build-p100/strata && -f $STRATA_WORK/flash-next-p100.json ]] || jr_die "SERVE_ENGINE=strata, but Strata is not set up (run: scripts/strata-test.sh prep)" systemctl disable tersler-llama.service 2>/dev/null || true systemctl enable tersler-strata.service jr_log "tersler-strata enabled (start: systemctl start tersler-strata)" elif [[ -n $MODEL_FILE && -f $JR_MODEL_DIR/$MODEL_FILE ]]; then systemctl disable tersler-strata.service 2>/dev/null || true systemctl enable tersler-llama.service jr_log "tersler-llama enabled (start: systemctl start tersler-llama)" else jr_warn "model file missing; tersler-llama installed but not enabled" fi if [[ $FAN_CONTROL == 1 ]]; then systemctl enable --now tersler-fan.service else systemctl disable --now tersler-fan.service 2>/dev/null || true jr_log "fan control off (FAN_CONTROL=0); see tersler-fan --discover" fi } stage_verify() { local ok=1 if ! gpus_ready; then jr_warn "no GPU initialized: reboot pending, or see 'sudo dmesg | grep NVRM'"; ok=0 else nvidia-smi --query-gpu=index,name,driver_version,pcie.link.gen.max,pcie.link.width.current,power.limit,ecc.mode.current,temperature.gpu --format=csv local drv; drv=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader | sed -n 1p) [[ $drv == "$DRIVER_BRANCH".* ]] || { jr_warn "driver $drv is not the $DRIVER_BRANCH branch"; ok=0; } nvidia-smi -q -d MEMORY | grep -A3 'BAR1 Memory Usage' | grep -E 'Total' | sed 's/^/ BAR1 /' jr_log "topology / P2P:" nvidia-smi topo -m || true nvidia-smi topo -p2p r 2>/dev/null || true local w; w=$(nvidia-smi --query-gpu=pcie.link.width.max --format=csv,noheader | sort -u | tr '\n' ' ') [[ $w == "16 " ]] || jr_warn "PCIe max link widths: $w (expected 16 on both; PCIE3/PCIE5 occupied?)" fi grep -qw iommu=pt /proc/cmdline || { jr_warn "iommu=pt not active (reboot after kernel stage)"; ok=0; } if [[ -x $JR_PREFIX/current/bin/llama-server ]]; then local missing; missing=$(ldd "$JR_PREFIX/current/bin/llama-server" | awk '/not found/ {print $1}') if [[ -n $missing ]]; then jr_warn "llama-server can't find: $(echo $missing)"; ok=0 else "$JR_PREFIX/current/bin/llama-server" --version 2>&1 | grep -E '^(version|built with)' || true fi jq -r '"build: \(.commit[0:9]) (\(.branch)), CUDA \(.cuda)"' "$JR_PREFIX/current/build-info.json" else jr_warn "no active llama.cpp build"; ok=0 fi [[ -s $REBOOT_FLAG ]] && { jr_warn "reboot required: $(sort -u "$REBOOT_FLAG" | tr '\n' ' ')"; ok=0; } (( ok )) && jr_log "verify: all good" || jr_warn "verify: see warnings above" } # ---------------------------------------------------------------- main run_stage() { local s=$1 declare -F "stage_$s" >/dev/null || jr_die "unknown stage: $s (try --list)" jr_log "=== stage: $s — ${STAGE_HELP[$s]}" "stage_$s" mark_done "$s" } # a reboot clears the pending-reboot flag if [[ -f $REBOOT_FLAG && $REBOOT_FLAG -ot /proc/1 ]]; then rm -f "$REBOOT_FLAG"; fi if (( $# )); then for s in "$@"; do run_stage "$s"; done else for s in "${STAGES[@]}"; do # verify needs the rebooted system (driver loaded, iommu=pt active) if [[ -s $REBOOT_FLAG ]] && [[ $s == verify ]]; then jr_warn "reboot required ($(sort -u "$REBOOT_FLAG" | tr '\n' ' ')). Reboot, then run this script again; it continues from here." exit 0 fi if stage_done "$s" && [[ $s != preflight && $s != config && $s != verify ]]; then jr_log "stage $s already done (re-run explicitly: install.sh $s)" continue fi run_stage "$s" done fi