Quality gates / quality (pull_request) Successful in 3m29s
A worker that answers /v1/models has not necessarily loaded its weights, so the app was offering an AI control that could only fail after the upload. Readiness now passes only once the pinned model is loaded, and the not-loaded state reaches manual Bristol entry in one tap. Verified against the real pinned llama.cpp build: it serves 503 while loading and the gate reports the distinct not-loaded state. Adds the reproducible container recipe (digest-pinned bases, pinned llama.cpp commit, hash-verified read-only weights, unprivileged, loopback-only publish), one shared post-load readiness gate used by the host bootstrap and the image healthcheck, and a target-GPU benchmark that refuses to write a receipt it did not measure. Refs #15
155 lines
4.8 KiB
Bash
Executable File
155 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
LLAMA_CPP_COMMIT="${TIMMY_LLAMA_CPP_COMMIT:-6d05498314db1b57f81c271080018aa2d0b89be9}"
|
|
MODEL_SHA256="${TIMMY_MODEL_SHA256:-0cf76814555b8665149075b74ab6b5c1d428ea1d3d01c1918c12012e8d7c9f58}"
|
|
MMPROJ_SHA256="${TIMMY_MMPROJ_SHA256:-ae07ea1facd07dd3230c4483b63e8cda96c6944ad2481f33d531f79e892dd024}"
|
|
HOST="${TIMMY_MODEL_HOST:-127.0.0.1}"
|
|
PORT="${TIMMY_MODEL_PORT:-8080}"
|
|
ROOT="${TIMMY_SELFHOST_ROOT:-${XDG_DATA_HOME:-$HOME/.local/share}/timmy-selfhost}"
|
|
MODEL_DIR="$ROOT/models"
|
|
MODEL="$MODEL_DIR/SmolVLM2-2.2B-Instruct-Q4_K_M.gguf"
|
|
MMPROJ="$MODEL_DIR/mmproj-SmolVLM2-2.2B-Instruct-Q8_0.gguf"
|
|
SOURCE="$ROOT/llama.cpp"
|
|
BUILD="$SOURCE/build"
|
|
HF_BASE="https://huggingface.co/ggml-org/SmolVLM2-2.2B-Instruct-GGUF/resolve/main"
|
|
SERVER="$BUILD/bin/llama-server"
|
|
RUNTIME="$ROOT/run"
|
|
PIDFILE="$RUNTIME/llama-server.pid"
|
|
LOGFILE="$RUNTIME/llama-server.log"
|
|
|
|
verify_files() {
|
|
for file in "$MODEL" "$MMPROJ"; do
|
|
if [[ ! -f "$file" ]]; then
|
|
printf 'Missing required file: %s. Run %s install.\n' "$file" "$0" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
printf '%s %s\n%s %s\n' "$MODEL_SHA256" "$MODEL" "$MMPROJ_SHA256" "$MMPROJ" | sha256sum --check --status
|
|
}
|
|
|
|
download_verified() {
|
|
local url="$1" target="$2" expected="$3" temporary="$2.download"
|
|
rm -f "$temporary"
|
|
curl --fail --location --retry 3 --output "$temporary" "$url"
|
|
printf '%s %s\n' "$expected" "$temporary" | sha256sum --check --status || {
|
|
rm -f "$temporary"
|
|
printf 'Downloaded file failed pinned SHA-256 verification: %s\n' "$target" >&2
|
|
return 1
|
|
}
|
|
mv "$temporary" "$target"
|
|
}
|
|
|
|
install_worker() {
|
|
command -v git >/dev/null
|
|
command -v cmake >/dev/null
|
|
command -v curl >/dev/null
|
|
mkdir -p "$ROOT" "$MODEL_DIR"
|
|
if [[ ! -d "$SOURCE/.git" ]]; then
|
|
git clone https://github.com/ggml-org/llama.cpp "$SOURCE"
|
|
fi
|
|
git -C "$SOURCE" fetch origin "$LLAMA_CPP_COMMIT"
|
|
git -C "$SOURCE" checkout --detach "$LLAMA_CPP_COMMIT"
|
|
cmake -S "$SOURCE" -B "$BUILD" -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF
|
|
cmake --build "$BUILD" --parallel "${TIMMY_BUILD_JOBS:-4}" --target llama-server
|
|
download_verified "$HF_BASE/SmolVLM2-2.2B-Instruct-Q4_K_M.gguf" "$MODEL" "$MODEL_SHA256"
|
|
download_verified "$HF_BASE/mmproj-SmolVLM2-2.2B-Instruct-Q8_0.gguf" "$MMPROJ" "$MMPROJ_SHA256"
|
|
verify_files
|
|
printf 'Pinned model files verified.\n'
|
|
}
|
|
|
|
health_worker() {
|
|
curl --fail --silent --show-error --retry 5 --retry-connrefused --retry-delay 1 --max-time 3 "http://$HOST:$PORT/v1/models" | grep -q 'SmolVLM2-2.2B-Instruct'
|
|
printf 'Self-hosted worker healthy at http://%s:%s/v1\n' "$HOST" "$PORT"
|
|
}
|
|
|
|
start_worker() {
|
|
verify_files
|
|
if [[ ! -x "$SERVER" ]]; then
|
|
printf 'Missing llama-server executable: %s. Run %s install.\n' "$SERVER" "$0" >&2
|
|
return 1
|
|
fi
|
|
mkdir -p "$RUNTIME"
|
|
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
|
health_worker
|
|
return
|
|
fi
|
|
rm -f "$PIDFILE"
|
|
nohup "$SERVER" \
|
|
--model "$MODEL" \
|
|
--mmproj "$MMPROJ" \
|
|
--alias SmolVLM2-2.2B-Instruct \
|
|
--host "$HOST" \
|
|
--port "$PORT" \
|
|
--ctx-size 4096 \
|
|
--parallel 1 \
|
|
--threads "${TIMMY_MODEL_THREADS:-4}" \
|
|
--no-webui >"$LOGFILE" 2>&1 &
|
|
printf '%s\n' "$!" > "$PIDFILE"
|
|
for _ in {1..30}; do
|
|
if health_worker >/dev/null 2>&1; then
|
|
printf 'Self-hosted worker started privately at http://%s:%s/v1\n' "$HOST" "$PORT"
|
|
return
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
kill "$(cat "$PIDFILE")" 2>/dev/null || true
|
|
rm -f "$PIDFILE"
|
|
printf 'Worker did not become healthy; inspect %s\n' "$LOGFILE" >&2
|
|
return 1
|
|
}
|
|
|
|
stop_worker() {
|
|
if [[ ! -f "$PIDFILE" ]]; then
|
|
printf 'Self-hosted worker is not running.\n'
|
|
return
|
|
fi
|
|
local pid
|
|
pid="$(cat "$PIDFILE")"
|
|
kill "$pid" 2>/dev/null || true
|
|
for _ in {1..25}; do
|
|
if ! kill -0 "$pid" 2>/dev/null; then
|
|
rm -f "$PIDFILE"
|
|
printf 'Self-hosted worker stopped.\n'
|
|
return
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
kill -9 "$pid" 2>/dev/null || true
|
|
rm -f "$PIDFILE"
|
|
printf 'Self-hosted worker stopped.\n'
|
|
}
|
|
|
|
case "${1:-}" in
|
|
receipt)
|
|
printf 'llama.cpp commit: %s\n' "$LLAMA_CPP_COMMIT"
|
|
printf 'SmolVLM2-2.2B-Instruct-Q4_K_M.gguf sha256: %s\n' "$MODEL_SHA256"
|
|
printf 'mmproj-SmolVLM2-2.2B-Instruct-Q8_0.gguf sha256: %s\n' "$MMPROJ_SHA256"
|
|
printf 'bind address: %s:%s\n' "$HOST" "$PORT"
|
|
;;
|
|
verify)
|
|
verify_files
|
|
printf 'Pinned model files verified.\n'
|
|
;;
|
|
install)
|
|
install_worker
|
|
;;
|
|
start)
|
|
start_worker
|
|
;;
|
|
ready)
|
|
# Post-load readiness (issue #15): delegate so the host and the container gate cannot drift.
|
|
exec "$(dirname "$0")/worker_ready.sh" --host "$HOST" --port "$PORT" --alias SmolVLM2-2.2B-Instruct "${@:2}"
|
|
;;
|
|
health)
|
|
health_worker
|
|
;;
|
|
stop)
|
|
stop_worker
|
|
;;
|
|
*)
|
|
printf 'Usage: %s {install|start|health|ready|stop|receipt|verify}\n' "$0" >&2
|
|
exit 64
|
|
;;
|
|
esac
|