#!/usr/bin/env bash
# Purpose: start llama-server as the portable control in this lab's engine comparison, with
#          every setting that changes the result stated on the command line rather than left
#          to a default, the load log copied to a file you can search, and the server's
#          process id written where the measuring script can read it
# Platform: all (CUDA, Metal, Vulkan and HIP builds; set LLAMA_BIN to choose which one)
# Minimum memory: 12 GB
# Assumes: llama.cpp built as in Part 6, a GGUF file in $MODEL, the port free and nothing
#          else large running; the server runs in the foreground so that Ctrl-C stops it
#
# Usage: MODEL=~/models/unsloth/Qwen3-8B-GGUF/Qwen3-8B-Q4_K_M.gguf bash serve-llama-cpp.sh
#        LLAMA_BIN=~/llama.cpp/build-hip/bin PIDFILE=./llama-hip.pid LOGFILE=./llama-hip.log \
#            MODEL=... bash serve-llama-cpp.sh
#
# Environment:
#   MODEL       path to the GGUF file                    (required)
#   LLAMA_BIN   directory holding llama-server           (default: $HOME/llama.cpp/build/bin)
#   HOST        address to bind                          (default: 127.0.0.1)
#   PORT        port to listen on                        (default: 8080)
#   CTX         context length, in tokens                (default: 8192)
#   NGL         layers to offload                        (default: 999, meaning all)
#   FLASH_ATTN  on, off or auto                          (default: on)
#   PARALLEL    concurrent slots                         (default: 1)
#   ALIAS       model name reported by the API           (default: the file's base name)
#   PIDFILE     where to write the server's pid          (default: ./llama-server.pid)
#   LOGFILE     copy of the server log                   (default: ./llama-server.log)
#
# One slot and an explicit context mean the KV cache is 8,192 tokens for one sequence, which
# is the allocation every other engine in the lab is configured to match. --jinja (the
# default in current builds, passed anyway) applies the model's own chat template, which is
# what turns a "tools" array into something Qwen3 was trained to answer. --verbosity 4 is the
# level at which the pinned build prints the device, model buffer, KV cache and compute buffer
# sizes during load; at the default level 3 those lines are not shown. --cache-ram 0 disables
# the host-RAM prompt cache: the pinned build otherwise keeps up to 8192 MiB of earlier
# prompts' KV state in host RAM and copies the previous prompt's state into it at the start of
# each new request, which adds to this engine's memory figure and to the timed time to first
# token ("-cram, --cache-ram N ... (default: 8192, -1 - no limit, 0 - disable)" in v0.4.0's
# --help).

set -euo pipefail

MODEL="${MODEL:-}"
LLAMA_BIN="${LLAMA_BIN:-$HOME/llama.cpp/build/bin}"
HOST="${HOST:-127.0.0.1}"
PORT="${PORT:-8080}"
CTX="${CTX:-8192}"
NGL="${NGL:-999}"
FLASH_ATTN="${FLASH_ATTN:-on}"
PARALLEL="${PARALLEL:-1}"
PIDFILE="${PIDFILE:-./llama-server.pid}"
LOGFILE="${LOGFILE:-./llama-server.log}"

die() { echo "serve-llama-cpp: $*" >&2; exit 1; }

[ -n "$MODEL" ] || die "set MODEL to the GGUF file to serve"
[ -f "$MODEL" ] || die "$MODEL does not exist"
[ -x "$LLAMA_BIN/llama-server" ] || die "no llama-server in $LLAMA_BIN; set LLAMA_BIN"
if command -v curl >/dev/null && curl --silent --max-time 2 "http://$HOST:$PORT/" >/dev/null 2>&1; then
  die "something is already answering on $HOST:$PORT; stop it first (one server at a time)"
fi

ALIAS="${ALIAS:-$(basename "$MODEL" .gguf)}"

echo "==> llama-server"
"$LLAMA_BIN/llama-server" --version 2>&1 || true
echo "    model   $MODEL"
echo "    alias   $ALIAS"
echo "    listen  http://$HOST:$PORT/v1"
echo "    context $CTX tokens, ngl $NGL, flash attention $FLASH_ATTN, $PARALLEL slot(s)"
echo "    pid written to $PIDFILE, log copied to $LOGFILE"

rm -f "$LOGFILE"
echo $$ > "$PIDFILE"

# exec keeps this shell's pid, so $PIDFILE names the server process itself.
exec "$LLAMA_BIN/llama-server" \
    --model "$MODEL" \
    --alias "$ALIAS" \
    --host "$HOST" \
    --port "$PORT" \
    --ctx-size "$CTX" \
    --n-gpu-layers "$NGL" \
    --flash-attn "$FLASH_ATTN" \
    --parallel "$PARALLEL" \
    --jinja \
    --cache-ram 0 \
    --verbosity 4 \
    --log-file "$LOGFILE"
