#!/usr/bin/env bash set -Eeuo pipefail config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance" CONFIG_FILE="${HERMES_LLAMA_CONFIG:-$config_dir/llama.env}" LLAMA_ARGS=() # shellcheck disable=SC1090 source "$CONFIG_FILE" mode=run profile_override="" while (($#)); do case "$1" in --check) mode=check ;; --print) mode=print ;; --profile) [[ $# -ge 2 ]] || exit 2 profile_override="$2" shift ;; *) printf 'Aufruf: %s [--check|--print] [--profile NAME]\n' "$0" >&2 exit 2 ;; esac shift done if [[ -n "${LLAMA_PROFILE:-}" && -r "$config_dir/llama-profile.env" ]]; then # shellcheck disable=SC1090,SC1091 source "$config_dir/llama-profile.env" fi [[ -z "$profile_override" ]] || LLAMA_PROFILE="$profile_override" if [[ -z "${LLAMA_SERVER_BIN:-}" ]]; then LLAMA_SERVER_BIN="$HOME/.local/opt/llama.cpp/current/bin/llama-server" if [[ ! -x "$LLAMA_SERVER_BIN" ]]; then LLAMA_SERVER_BIN="$(command -v llama-server || true)" fi fi [[ -x "$LLAMA_SERVER_BIN" ]] || { printf 'llama-server nicht ausführbar: %s\n' "$LLAMA_SERVER_BIN" >&2 exit 1 } model_args=() if [[ -n "${LLAMA_MODEL:-}" && -n "${LLAMA_HF_REPO:-}" ]]; then printf 'Nur LLAMA_MODEL ODER LLAMA_HF_REPO setzen.\n' >&2 exit 1 elif [[ -n "${LLAMA_MODEL:-}" ]]; then [[ -r "$LLAMA_MODEL" ]] || { printf 'Modell nicht lesbar.\n' >&2 exit 1 } model_args=(--model "$LLAMA_MODEL") elif [[ -n "${LLAMA_HF_REPO:-}" ]]; then model_args=(--hf-repo "$LLAMA_HF_REPO") else printf 'LLAMA_MODEL oder LLAMA_HF_REPO fehlt.\n' >&2 exit 1 fi if [[ -n "${LLAMA_MMPROJ:-}" ]]; then [[ -r "$LLAMA_MMPROJ" ]] || { printf 'Projektor nicht lesbar.\n' >&2 exit 1 } model_args+=(--mmproj "$LLAMA_MMPROJ") fi [[ -z "${LLAMA_ALIAS:-}" ]] || model_args+=(--alias "$LLAMA_ALIAS") # Server defaults are not a wall-clock limit; the independent slot watchdog is. [[ "${LLAMA_MAX_OUTPUT_TOKENS:-2048}" =~ ^[1-9][0-9]*$ ]] || exit 2 profile_args=() if [[ -n "${LLAMA_PROFILE:-}" ]]; then reasoning_budget=512 reasoning_preserve=--reasoning-preserve case "$LLAMA_PROFILE" in desktop-64k) ctx=65536; margin=4096; reasoning_budget=128; reasoning_preserve=--no-reasoning-preserve ;; baseline) ctx=65536; margin=1536 ;; vram-64k) ctx=65536; margin=1280 ;; balanced) ctx=49152; margin=1536 ;; vram-plus) ctx=49152; margin=1280 ;; compact) ctx=32768; margin=1536 ;; *) printf 'Unbekanntes Profil: %s\n' "$LLAMA_PROFILE" >&2 exit 2 ;; esac profile_args=( --ctx-size "$ctx" --parallel 1 --jinja --reasoning auto --reasoning-budget "$reasoning_budget" "$reasoning_preserve" --flash-attn auto --threads 6 --threads-batch 16 --n-gpu-layers auto --fit on --fit-target "$margin" --cache-type-k q8_0 --cache-type-v q8_0 --sleep-idle-seconds 300 --image-min-tokens 1024 --cors-origins localhost --no-ui ) if ((${#LLAMA_ARGS[@]})); then printf 'Bei Profilbetrieb LLAMA_ARGS leeren; eigene vollständige Args nur ohne LLAMA_PROFILE.\n' >&2 exit 2 fi fi args=( "${model_args[@]}" --host "${LLAMA_HOST:-127.0.0.1}" --port "${LLAMA_PORT:-8080}" --n-predict "${LLAMA_MAX_OUTPUT_TOKENS:-2048}" "${profile_args[@]}" "${LLAMA_ARGS[@]}" ) if [[ "$mode" == print ]]; then printf '%q ' "$LLAMA_SERVER_BIN" "${args[@]}" printf '\n' exit 0 fi # Nur Optionsnamen prüfen: kein Modellstart oder Download. help_text="$(timeout 30 "$LLAMA_SERVER_BIN" --help 2>&1)" || { printf 'llama-server --help fehlgeschlagen.\n' >&2 exit 1 } for arg in "${args[@]}"; do [[ "$arg" == --* ]] || continue option="${arg%%=*}" if ! grep -Eq -- "(^|[[:space:],])${option}([[:space:],=]|$)" <<<"$help_text"; then printf 'Die installierte llama.cpp-Version kennt %s nicht. Kein automatisches Weglassen.\n' "$option" >&2 exit 1 fi done if [[ "$mode" == check ]]; then printf 'Konfiguration und Optionsnamen OK; GPU/Modell/Optionswerte noch nicht laufzeitgetestet.\n' exit 0 fi exec "$LLAMA_SERVER_BIN" "${args[@]}"