Sync workstation helpers and preserve baseline deployment
This commit is contained in:
1 parent
8641464a6c
commit
3c1080e152
15 files changed
+254
-110
No files matched your search
@@ -1,3 +1,11 @@
|
|||||||
|
# 2026-09-09 — Workstation-Abgleich
|
||||||
|
|
||||||
|
- Healthcheck und llama-Helfer übernommen; portable Helferpfade ergänzt.
|
||||||
|
- Signal-/Telegram-Helfer übernommen; Repository-Updater mit Retry-Pfaden behalten.
|
||||||
|
- Docker-Netzprüfung optional; vram-64k ergänzt, baseline bleibt Standard.
|
||||||
|
- Ressourcenlimits und Startbegrenzung in die llama-Unit übernommen.
|
||||||
|
- Messgrenzen und ausstehende Übernahme des Updaters dokumentiert.
|
||||||
|
|
||||||
# 2026-09-05 — RTX-3080-/Qwen-Deployment
|
# 2026-09-05 — RTX-3080-/Qwen-Deployment
|
||||||
|
|
||||||
- CUDA-Quellbuild mit expliziter Revision, dokumentierter SHA, drei Tools und
|
- CUDA-Quellbuild mit expliziter Revision, dokumentierter SHA, drei Tools und
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ LLAMA_URL="http://127.0.0.1:8080"
|
|||||||
# 1: inaktiven Dienst wieder starten. HTTP-Timeouts allein starten nie neu.
|
# 1: inaktiven Dienst wieder starten. HTTP-Timeouts allein starten nie neu.
|
||||||
# Standard 0 verhindert unerwünschte Modellstarts während Wartung/Tuning.
|
# Standard 0 verhindert unerwünschte Modellstarts während Wartung/Tuning.
|
||||||
LLAMA_HEALTH_AUTORESTART=0
|
LLAMA_HEALTH_AUTORESTART=0
|
||||||
|
|
||||||
|
# Optional: Name eines bestehenden Docker-Netzes; leer = keine Netzprüfung.
|
||||||
|
DOCKER_NETWORK=""
|
||||||
SIGNAL_SERVICE="signal-cli.service"
|
SIGNAL_SERVICE="signal-cli.service"
|
||||||
|
|
||||||
# Zusammengefasste Maintenance-Meldungen. Credentials/Ziele stehen in HERMES_ENV.
|
# Zusammengefasste Maintenance-Meldungen. Credentials/Ziele stehen in HERMES_ENV.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ LLAMA_HOST="127.0.0.1"
|
|||||||
LLAMA_PORT="8080"
|
LLAMA_PORT="8080"
|
||||||
|
|
||||||
# Erster Start unverändert mit 64K / 1536 MiB Fit-Zielmarge.
|
# Erster Start unverändert mit 64K / 1536 MiB Fit-Zielmarge.
|
||||||
# Anschließend erst balanced, danach ggf. vram-plus vergleichen.
|
# baseline bleibt Standard; vram-64k ist ein optionales Testprofil.
|
||||||
LLAMA_PROFILE="baseline"
|
LLAMA_PROFILE="baseline"
|
||||||
LLAMA_ARGS=()
|
LLAMA_ARGS=()
|
||||||
# HF lädt beim ersten echten Start herunter. Für offline/reproduzierbar:
|
# HF lädt beim ersten echten Start herunter. Für offline/reproduzierbar:
|
||||||
|
|||||||
+3
-1
@@ -10,6 +10,7 @@ helfen; weder Voll-Offload noch eine bestimmte Beschleunigung werden versprochen
|
|||||||
| Profil | Kontextlimit | Fit-Zielmarge | Einsatz |
|
| Profil | Kontextlimit | Fit-Zielmarge | Einsatz |
|
||||||
| --- | ---: | ---: | --- |
|
| --- | ---: | ---: | --- |
|
||||||
| `baseline` | 65536 | 1536 MiB | bisherige Startparameter, Referenz und Rückweg |
|
| `baseline` | 65536 | 1536 MiB | bisherige Startparameter, Referenz und Rückweg |
|
||||||
|
| `vram-64k` | 65536 | 1280 MiB | optionaler Vergleich, kein Standard |
|
||||||
| `balanced` | 49152 | 1536 MiB | erster Vergleich, nur Kontext reduzieren |
|
| `balanced` | 49152 | 1536 MiB | erster Vergleich, nur Kontext reduzieren |
|
||||||
| `vram-plus` | 49152 | 1280 MiB | zweiter Vergleich, kleinere Zielmarge |
|
| `vram-plus` | 49152 | 1280 MiB | zweiter Vergleich, kleinere Zielmarge |
|
||||||
| `compact` | 32768 | 1536 MiB | Alternative für kürzere Sitzungen |
|
| `compact` | 32768 | 1536 MiB | Alternative für kürzere Sitzungen |
|
||||||
@@ -35,7 +36,8 @@ hier bewusst nicht als fertiges Profil vorgesehen.
|
|||||||
## Messung statt Auslastungsziel
|
## Messung statt Auslastungsziel
|
||||||
|
|
||||||
1. Zuerst `baseline` mit deiner normalen Browser-/Hyprland-Nutzung messen.
|
1. Zuerst `baseline` mit deiner normalen Browser-/Hyprland-Nutzung messen.
|
||||||
2. Jobs beenden. `balanced` wählen und kontrolliert neu starten.
|
2. Nur bei erneutem Tuning: Jobs beenden, ein Vergleichsprofil wählen
|
||||||
|
und kontrolliert neu starten. Für 64K Kontext ist dies `vram-64k`.
|
||||||
3. Dieselben Aufgaben erneut ausführen: kurze Textantwort, längerer Agent-Task
|
3. Dieselben Aufgaben erneut ausführen: kurze Textantwort, längerer Agent-Task
|
||||||
mit Tools und eine repräsentative Bildaufgabe. Qualität mitbewerten.
|
mit Tools und eine repräsentative Bildaufgabe. Qualität mitbewerten.
|
||||||
4. Kalten Start nach Idle-Sleep getrennt von warmem Prompt-Cache messen.
|
4. Kalten Start nach Idle-Sleep getrennt von warmem Prompt-Cache messen.
|
||||||
|
|||||||
@@ -95,3 +95,28 @@ alte llama.env und ggf. alte Unit/Wrapper aus der Deployment-Sicherung einzeln
|
|||||||
wiederherstellen, `daemon-reload` und Neustart nach Jobende. Nicht pauschal ein
|
wiederherstellen, `daemon-reload` und Neustart nach Jobende. Nicht pauschal ein
|
||||||
Archiv über dein gesamtes Home extrahieren. Die Datei `llama-profile.env` hat nur
|
Archiv über dein gesamtes Home extrahieren. Die Datei `llama-profile.env` hat nur
|
||||||
bei aktivem Profilbetrieb Vorrang; ihre vorherigen Stände liegen daneben.
|
bei aktivem Profilbetrieb Vorrang; ihre vorherigen Stände liegen daneben.
|
||||||
|
|
||||||
|
## Workstation-Abgleich vom 2026-09-09
|
||||||
|
|
||||||
|
Alltagsprofil bleibt baseline: 65536 Kontext, Fit-Zielmarge 1536 MiB.
|
||||||
|
Die zwei kurzen Texttests vom 5. September ergaben im Mittel rund 2,58 tok/s
|
||||||
|
für baseline und 2,62 tok/s für vram-64k. Das belegt keinen belastbaren
|
||||||
|
Geschwindigkeitsvorteil. Im fünfminütigen Desktop-/Bildtest mit vram-64k
|
||||||
|
blieben zeitweise nur 678 MiB VRAM frei. Es gab keinen Dienstneustart;
|
||||||
|
die Bildaufgabe wurde allerdings abgebrochen, nicht fertiggetestet.
|
||||||
|
Anschließend wurde baseline wieder aktiviert und der Modellendpunkt geprüft.
|
||||||
|
|
||||||
|
Die llama-Unit übernimmt die Limits der Workstation mit 64 GiB RAM:
|
||||||
|
Nice=10, CPUQuota=1600%, MemoryHigh=40G, MemoryMax=48G, OOMPolicy=stop.
|
||||||
|
Neustartabstand: 30 Sekunden; StartLimitBurst=3 bei StartLimitIntervalSec=300.
|
||||||
|
Diese Einstellungen begrenzen keinen GPU-Speicher. Auf anderer Hardware
|
||||||
|
vor dem Start anpassen. Bestehende Drop-ins können die Vorlage übersteuern.
|
||||||
|
|
||||||
|
Die Docker-Netzprüfung ist optional: DOCKER_NETWORK in config.env auf den
|
||||||
|
Namen des verwendeten Netzes setzen. Leer bedeutet keine Netzprüfung.
|
||||||
|
Die Prüfung erstellt kein Netz. Private Konfigurationen bleiben außerhalb Git.
|
||||||
|
|
||||||
|
Der Repository-Updater enthält die aufgerufenen Retry-Pfade und den gemeinsamen
|
||||||
|
Benachrichtigungshelfer. Die zuletzt verglichene installierte Fassung enthält
|
||||||
|
noch ungenutzte Retry-Funktionen. Der Git-Abgleich aktualisiert die Workstation
|
||||||
|
nicht; die Übernahme des Repository-Updaters steht separat aus.
|
||||||
+73
-37
@@ -1,55 +1,91 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Euo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
||||||
NOTIFIER="$HOME/.local/libexec/hermes-stack-notify"
|
|
||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
# Niemals den absichtlich gestoppten Gateway mitten im Update neu starten.
|
log() {
|
||||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
printf '%s\n' "$*" | systemd-cat -t hermes-stack-maintenance
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_user() {
|
||||||
|
if ! "${NOTIFIER:-$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-notify}" "$1"; then
|
||||||
|
log "$1" || true
|
||||||
|
log "Benachrichtigungshelfer fehlgeschlagen; Healthcheck wird fortgesetzt." || true
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK_FILE="${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-health.lock"
|
||||||
|
exec 9>"$LOCK_FILE"
|
||||||
flock -n 9 || exit 0
|
flock -n 9 || exit 0
|
||||||
|
|
||||||
state_file="$STATE_DIR/health-state"
|
# Nicht mit Hermes-Update oder kontrolliertem Backend-Neustart kollidieren.
|
||||||
problems=()
|
exec 8>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||||
|
flock -n 8 || exit 0
|
||||||
|
|
||||||
if ! systemctl --user is-active --quiet "$GATEWAY_SERVICE"; then
|
wait_for_llama() {
|
||||||
problems+=("$GATEWAY_SERVICE war inaktiv; Neustart wurde versucht")
|
local attempts="${1:-90}"
|
||||||
systemctl --user restart "$GATEWAY_SERVICE" || true
|
local i
|
||||||
fi
|
for ((i=1; i<=attempts; i++)); do
|
||||||
|
if curl --fail --silent --max-time 5 "$LLAMA_URL/health" |
|
||||||
if ! hermes gateway status >/dev/null 2>&1; then
|
|
||||||
problems+=("Hermes-Gateway antwortete nicht gesund; Neustart wurde versucht")
|
|
||||||
systemctl --user restart "$GATEWAY_SERVICE" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${LLAMA_HEALTH_AUTORESTART:-0}" == 1 ]] &&
|
|
||||||
! systemctl --user is-active --quiet "$LLAMA_SERVICE"; then
|
|
||||||
problems+=("$LLAMA_SERVICE war inaktiv; Neustart wurde versucht")
|
|
||||||
systemctl --user restart "$LLAMA_SERVICE" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! curl --fail --silent --max-time 10 "$LLAMA_URL/health" |
|
|
||||||
jq -e '.status == "ok"' >/dev/null 2>&1; then
|
jq -e '.status == "ok"' >/dev/null 2>&1; then
|
||||||
problems+=("llama-server antwortete nicht gesund; kein HTTP-bedingter Neustart laufender Inferenz")
|
return 0
|
||||||
fi
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if ((${#problems[@]} == 0)); then
|
failed=0
|
||||||
current="healthy"
|
messages=()
|
||||||
previous="$(cat "$state_file" 2>/dev/null || true)"
|
|
||||||
if [[ "$previous" == unhealthy ]]; then
|
if systemctl --user is-active --quiet "$LLAMA_SERVICE"; then
|
||||||
"$NOTIFIER" "Hermes-Stack: Dienste sind wieder gesund." || true
|
if ! wait_for_llama 3; then
|
||||||
|
messages+=("llama-server antwortete nicht gesund; kein automatischer Neustart laufender Inferenz.")
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
elif [[ "${LLAMA_HEALTH_AUTORESTART:-0}" == "1" ]]; then
|
||||||
|
messages+=("$LLAMA_SERVICE war inaktiv; automatischer Start wurde versucht.")
|
||||||
|
if ! systemctl --user start "$LLAMA_SERVICE"; then
|
||||||
|
failed=1
|
||||||
|
elif ! wait_for_llama 3; then
|
||||||
|
messages+=("llama-server ist nach dem Start noch nicht gesund; kein weiterer Neustart.")
|
||||||
|
failed=1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
current="unhealthy"
|
messages+=("$LLAMA_SERVICE ist inaktiv; automatischer Start ist deaktiviert.")
|
||||||
previous="$(cat "$state_file" 2>/dev/null || true)"
|
failed=1
|
||||||
if [[ "$previous" != unhealthy ]]; then
|
|
||||||
message="$(IFS='; '; printf '%s' "${problems[*]}")"
|
|
||||||
"$NOTIFIER" "Hermes-Stack: Gesundheitsprüfung fehlgeschlagen. $message. Details: journalctl --user -u $LLAMA_SERVICE -u $GATEWAY_SERVICE" || true
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '%s\n' "$current" >"$state_file"
|
if ! systemctl --user is-active --quiet "$GATEWAY_SERVICE"; then
|
||||||
[[ "$current" == healthy ]]
|
messages+=("$GATEWAY_SERVICE war inaktiv; Neustart wurde versucht.")
|
||||||
|
systemctl --user restart "$GATEWAY_SERVICE" || failed=1
|
||||||
|
sleep 5
|
||||||
|
systemctl --user is-active --quiet "$GATEWAY_SERVICE" || failed=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${DOCKER_NETWORK:-}" ]] &&
|
||||||
|
! docker network inspect "$DOCKER_NETWORK" >/dev/null 2>&1; then
|
||||||
|
messages+=("Docker-Netz $DOCKER_NETWORK fehlt.")
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
status_file="$STATE_DIR/health-status"
|
||||||
|
previous="$(cat "$status_file" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if (( failed )); then
|
||||||
|
printf 'failed\n' >"$status_file"
|
||||||
|
notify_user "Hermes-Stack: Gesundheitsprüfung fehlgeschlagen. ${messages[*]} Details: journalctl --user -u $LLAMA_SERVICE -u $GATEWAY_SERVICE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'ok\n' >"$status_file"
|
||||||
|
if [[ "$previous" == "failed" ]]; then
|
||||||
|
notify_user "Hermes-Stack: Dienste sind wieder gesund."
|
||||||
|
elif ((${#messages[@]})); then
|
||||||
|
notify_user "Hermes-Stack: automatische Wiederherstellung erfolgreich. ${messages[*]}"
|
||||||
|
fi
|
||||||
Regular → Executable
+30
-11
@@ -1,35 +1,54 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
umask 077
|
umask 077
|
||||||
|
|
||||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||||
runner="$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-llama-run"
|
runner="$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-llama-run"
|
||||||
|
|
||||||
case "${1:-list}" in
|
case "${1:-list}" in
|
||||||
list)
|
list)
|
||||||
printf '%-12s %8s %10s\n' Profil Kontext Fit-MiB \
|
printf '%-12s %8s %10s\n' Profil Kontext Fit-MiB \
|
||||||
baseline 65536 1536 balanced 49152 1536 vram-plus 49152 1280 compact 32768 1536
|
baseline 65536 1536 vram-64k 65536 1280 \
|
||||||
|
balanced 49152 1536 vram-plus 49152 1280 compact 32768 1536
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
baseline|balanced|vram-plus|compact) selected="$1" ;;
|
baseline|vram-64k|balanced|vram-plus|compact) selected="$1" ;;
|
||||||
*) printf 'Aufruf: %s [list|baseline|balanced|vram-plus|compact]\n' "$0" >&2; exit 2 ;;
|
*)
|
||||||
|
printf 'Unbekanntes Profil. Verfügbare Profile: %s list\n' "$0" >&2
|
||||||
|
exit 2 ;;
|
||||||
esac
|
esac
|
||||||
[[ $# -eq 1 ]] || exit 2
|
[[ $# -eq 1 ]] || exit 2
|
||||||
# Profilwechsel nur für bewusst migrierte Konfigurationen erlauben.
|
|
||||||
|
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||||
|
flock -n 9 || {
|
||||||
|
printf 'Maintenance läuft; nichts geändert.\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$config_dir/llama.env"
|
source "$config_dir/llama.env"
|
||||||
[[ -n "${LLAMA_PROFILE:-}" ]] || {
|
[[ -n "${LLAMA_PROFILE:-}" ]] || {
|
||||||
printf 'Zuerst llama.env nach docs/WORKSTATION.md migrieren. Keine Änderung.\n' >&2; exit 1;
|
printf 'Profilbetrieb ist nicht konfiguriert.\n' >&2
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
"$runner" --profile "$selected" --check
|
"$runner" --profile "$selected" --check
|
||||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
|
||||||
flock -n 9 || { printf 'Maintenance läuft. Später erneut versuchen.\n' >&2; exit 1; }
|
|
||||||
target="$config_dir/llama-profile.env"
|
target="$config_dir/llama-profile.env"
|
||||||
|
[[ ! -L "$target" ]] || {
|
||||||
|
printf 'Abbruch: Profilwahl ist ein Symlink.\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
if [[ -e "$target" ]]; then
|
if [[ -e "$target" ]]; then
|
||||||
|
[[ -f "$target" ]] || exit 1
|
||||||
backup="$(mktemp "$config_dir/llama-profile.backup.XXXXXX")"
|
backup="$(mktemp "$config_dir/llama-profile.backup.XXXXXX")"
|
||||||
cp -p -- "$target" "$backup"
|
cp -p -- "$target" "$backup"
|
||||||
printf 'Vorherige Profilwahl: %s\n' "$backup"
|
printf 'Vorherige Profilwahl gesichert: %s\n' "$backup"
|
||||||
fi
|
fi
|
||||||
candidate="$(mktemp "$config_dir/llama-profile.env.XXXXXX")"
|
|
||||||
|
candidate="$(mktemp "$config_dir/llama-profile.env.pending.XXXXXX")"
|
||||||
|
trap 'rm -f -- "$candidate"' EXIT
|
||||||
printf 'LLAMA_PROFILE=%q\n' "$selected" >"$candidate"
|
printf 'LLAMA_PROFILE=%q\n' "$selected" >"$candidate"
|
||||||
mv -T -- "$candidate" "$target"
|
mv -T -- "$candidate" "$target"
|
||||||
printf 'Profil %s gespeichert. Laufende Jobs bleiben unverändert.\n' "$selected"
|
|
||||||
printf 'Erst nach Jobende: hermes-stack-llama-restart (siehe docs/WORKSTATION.md).\n'
|
printf 'Profil %s für den nächsten Dienststart gespeichert. Kein Neustart.\n' "$selected"
|
||||||
Regular → Executable
File mode changed.
Regular → Executable
+15
-4
@@ -1,14 +1,25 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
if [[ "${1:-}" != --jobs-finished || $# -ne 1 ]]; then
|
if [[ "${1:-}" != --jobs-finished || $# -ne 1 ]]; then
|
||||||
printf 'Stoppt laufende Inferenz! Erst nach Jobende ausführen:\n %s --jobs-finished\n' "$0" >&2
|
printf 'Dieser Neustart unterbricht laufende Backend-Jobs.\n' >&2
|
||||||
|
printf 'Erst nach Jobende ausführen: %s --jobs-finished\n' "$0" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$config_dir/config.env"
|
source "$config_dir/config.env"
|
||||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
|
||||||
flock -n 9 || { printf 'Maintenance läuft; kein Neustart.\n' >&2; exit 1; }
|
exec 8>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||||
|
if ! flock -n 8; then
|
||||||
|
printf 'Maintenance läuft gerade. Kein Neustart ausgeführt.\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-llama-run" --check
|
"$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-llama-run" --check
|
||||||
|
|
||||||
|
printf 'Starte %s neu …\n' "$LLAMA_SERVICE"
|
||||||
systemctl --user restart "$LLAMA_SERVICE"
|
systemctl --user restart "$LLAMA_SERVICE"
|
||||||
printf 'Neustart ausgelöst. Modell kann noch laden; Logs und Inferenz prüfen.\n'
|
|
||||||
|
printf 'Neustart ausgelöst. Das Modell kann noch laden.\n'
|
||||||
@@ -13,11 +13,19 @@ while (($#)); do
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
--check) mode=check ;;
|
--check) mode=check ;;
|
||||||
--print) mode=print ;;
|
--print) mode=print ;;
|
||||||
--profile) [[ $# -ge 2 ]] || exit 2; profile_override="$2"; shift ;;
|
--profile)
|
||||||
*) printf 'Aufruf: %s [--check|--print] [--profile NAME]\n' "$0" >&2; exit 2 ;;
|
[[ $# -ge 2 ]] || exit 2
|
||||||
|
profile_override="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Aufruf: %s [--check|--print] [--profile NAME]\n' "$0" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n "${LLAMA_PROFILE:-}" && -r "$config_dir/llama-profile.env" ]]; then
|
if [[ -n "${LLAMA_PROFILE:-}" && -r "$config_dir/llama-profile.env" ]]; then
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$config_dir/llama-profile.env"
|
source "$config_dir/llama-profile.env"
|
||||||
@@ -30,6 +38,7 @@ if [[ -z "${LLAMA_SERVER_BIN:-}" ]]; then
|
|||||||
LLAMA_SERVER_BIN="$(command -v llama-server || true)"
|
LLAMA_SERVER_BIN="$(command -v llama-server || true)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ -x "$LLAMA_SERVER_BIN" ]] || {
|
[[ -x "$LLAMA_SERVER_BIN" ]] || {
|
||||||
printf 'llama-server nicht ausführbar: %s\n' "$LLAMA_SERVER_BIN" >&2
|
printf 'llama-server nicht ausführbar: %s\n' "$LLAMA_SERVER_BIN" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -40,7 +49,10 @@ if [[ -n "${LLAMA_MODEL:-}" && -n "${LLAMA_HF_REPO:-}" ]]; then
|
|||||||
printf 'Nur LLAMA_MODEL ODER LLAMA_HF_REPO setzen.\n' >&2
|
printf 'Nur LLAMA_MODEL ODER LLAMA_HF_REPO setzen.\n' >&2
|
||||||
exit 1
|
exit 1
|
||||||
elif [[ -n "${LLAMA_MODEL:-}" ]]; then
|
elif [[ -n "${LLAMA_MODEL:-}" ]]; then
|
||||||
[[ -r "$LLAMA_MODEL" ]] || { printf 'Modell nicht lesbar.\n' >&2; exit 1; }
|
[[ -r "$LLAMA_MODEL" ]] || {
|
||||||
|
printf 'Modell nicht lesbar.\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
model_args=(--model "$LLAMA_MODEL")
|
model_args=(--model "$LLAMA_MODEL")
|
||||||
elif [[ -n "${LLAMA_HF_REPO:-}" ]]; then
|
elif [[ -n "${LLAMA_HF_REPO:-}" ]]; then
|
||||||
model_args=(--hf-repo "$LLAMA_HF_REPO")
|
model_args=(--hf-repo "$LLAMA_HF_REPO")
|
||||||
@@ -48,8 +60,12 @@ else
|
|||||||
printf 'LLAMA_MODEL oder LLAMA_HF_REPO fehlt.\n' >&2
|
printf 'LLAMA_MODEL oder LLAMA_HF_REPO fehlt.\n' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${LLAMA_MMPROJ:-}" ]]; then
|
if [[ -n "${LLAMA_MMPROJ:-}" ]]; then
|
||||||
[[ -r "$LLAMA_MMPROJ" ]] || { printf 'Projektor nicht lesbar.\n' >&2; exit 1; }
|
[[ -r "$LLAMA_MMPROJ" ]] || {
|
||||||
|
printf 'Projektor nicht lesbar.\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
model_args+=(--mmproj "$LLAMA_MMPROJ")
|
model_args+=(--mmproj "$LLAMA_MMPROJ")
|
||||||
fi
|
fi
|
||||||
[[ -z "${LLAMA_ALIAS:-}" ]] || model_args+=(--alias "$LLAMA_ALIAS")
|
[[ -z "${LLAMA_ALIAS:-}" ]] || model_args+=(--alias "$LLAMA_ALIAS")
|
||||||
@@ -58,11 +74,16 @@ profile_args=()
|
|||||||
if [[ -n "${LLAMA_PROFILE:-}" ]]; then
|
if [[ -n "${LLAMA_PROFILE:-}" ]]; then
|
||||||
case "$LLAMA_PROFILE" in
|
case "$LLAMA_PROFILE" in
|
||||||
baseline) ctx=65536; margin=1536 ;;
|
baseline) ctx=65536; margin=1536 ;;
|
||||||
|
vram-64k) ctx=65536; margin=1280 ;;
|
||||||
balanced) ctx=49152; margin=1536 ;;
|
balanced) ctx=49152; margin=1536 ;;
|
||||||
vram-plus) ctx=49152; margin=1280 ;;
|
vram-plus) ctx=49152; margin=1280 ;;
|
||||||
compact) ctx=32768; margin=1536 ;;
|
compact) ctx=32768; margin=1536 ;;
|
||||||
*) printf 'Unbekanntes Profil: %s\n' "$LLAMA_PROFILE" >&2; exit 2 ;;
|
*)
|
||||||
|
printf 'Unbekanntes Profil: %s\n' "$LLAMA_PROFILE" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
profile_args=(
|
profile_args=(
|
||||||
--ctx-size "$ctx" --parallel 1 --jinja
|
--ctx-size "$ctx" --parallel 1 --jinja
|
||||||
--reasoning auto --reasoning-budget 512 --reasoning-preserve
|
--reasoning auto --reasoning-budget 512 --reasoning-preserve
|
||||||
@@ -72,25 +93,33 @@ if [[ -n "${LLAMA_PROFILE:-}" ]]; then
|
|||||||
--sleep-idle-seconds 300 --image-min-tokens 1024
|
--sleep-idle-seconds 300 --image-min-tokens 1024
|
||||||
--cors-origins localhost --no-ui
|
--cors-origins localhost --no-ui
|
||||||
)
|
)
|
||||||
# Alte vollständige LLAMA_ARGS nicht still mit einem Profil vermischen.
|
|
||||||
if ((${#LLAMA_ARGS[@]})); then
|
if ((${#LLAMA_ARGS[@]})); then
|
||||||
printf 'Bei Profilbetrieb LLAMA_ARGS leeren; eigene vollständige Args nur ohne LLAMA_PROFILE.\n' >&2
|
printf 'Bei Profilbetrieb LLAMA_ARGS leeren; eigene vollständige Args nur ohne LLAMA_PROFILE.\n' >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
args=("${model_args[@]}" --host "${LLAMA_HOST:-127.0.0.1}" --port "${LLAMA_PORT:-8080}"
|
args=(
|
||||||
"${profile_args[@]}" "${LLAMA_ARGS[@]}")
|
"${model_args[@]}"
|
||||||
|
--host "${LLAMA_HOST:-127.0.0.1}"
|
||||||
|
--port "${LLAMA_PORT:-8080}"
|
||||||
|
"${profile_args[@]}"
|
||||||
|
"${LLAMA_ARGS[@]}"
|
||||||
|
)
|
||||||
|
|
||||||
if [[ "$mode" == print ]]; then
|
if [[ "$mode" == print ]]; then
|
||||||
printf '%q ' "$LLAMA_SERVER_BIN" "${args[@]}"
|
printf '%q ' "$LLAMA_SERVER_BIN" "${args[@]}"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Kein Download/Modellstart: nur prüfen, ob die installierte Version die Namen kennt.
|
# Nur Optionsnamen prüfen: kein Modellstart oder Download.
|
||||||
help_text="$(timeout 30 "$LLAMA_SERVER_BIN" --help 2>&1)" || {
|
help_text="$(timeout 30 "$LLAMA_SERVER_BIN" --help 2>&1)" || {
|
||||||
printf 'llama-server --help fehlgeschlagen.\n' >&2; exit 1;
|
printf 'llama-server --help fehlgeschlagen.\n' >&2
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for arg in "${args[@]}"; do
|
for arg in "${args[@]}"; do
|
||||||
[[ "$arg" == --* ]] || continue
|
[[ "$arg" == --* ]] || continue
|
||||||
option="${arg%%=*}"
|
option="${arg%%=*}"
|
||||||
@@ -99,6 +128,7 @@ for arg in "${args[@]}"; do
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$mode" == check ]]; then
|
if [[ "$mode" == check ]]; then
|
||||||
printf 'Konfiguration und Optionsnamen OK; GPU/Modell/Optionswerte noch nicht laufzeitgetestet.\n'
|
printf 'Konfiguration und Optionsnamen OK; GPU/Modell/Optionswerte noch nicht laufzeitgetestet.\n'
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
+43
-42
@@ -1,83 +1,84 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Euo pipefail
|
set -Euo pipefail
|
||||||
|
|
||||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
printf '%s\n' "$*" | systemd-cat -t hermes-stack-maintenance 2>/dev/null || true
|
printf '%s\n' "$*" | systemd-cat -t hermes-stack-maintenance 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
[[ -n "${*:-}" ]] || exit 2
|
||||||
text="${*:-}"
|
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||||
if [[ -z "$text" ]]; then
|
if [[ ! -r "$CONFIG_FILE" ]]; then
|
||||||
printf 'Verwendung: %s TEXT\n' "${0##*/}" >&2
|
log "$*"
|
||||||
exit 2
|
log "Benachrichtigung übersprungen: Maintenance-Konfiguration nicht lesbar."
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$CONFIG_FILE" || { log "$*"; exit 0; }
|
||||||
|
|
||||||
|
notify_user() {
|
||||||
|
local text="$1"
|
||||||
|
local telegram_notify="${TELEGRAM_NOTIFY:-0}"
|
||||||
|
local signal_notify="${SIGNAL_NOTIFY:-0}"
|
||||||
|
local signal_payload
|
||||||
log "$text"
|
log "$text"
|
||||||
|
|
||||||
if [[ ! -r "$CONFIG_FILE" ]]; then
|
|
||||||
log "Benachrichtigung übersprungen: $CONFIG_FILE ist nicht lesbar."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
set +u
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
source "$CONFIG_FILE" || {
|
|
||||||
set -u
|
|
||||||
log "Benachrichtigung übersprungen: Maintenance-Konfiguration ist ungültig."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
telegram_notify="${TELEGRAM_NOTIFY:-0}"
|
|
||||||
signal_notify="${SIGNAL_NOTIFY:-0}"
|
|
||||||
|
|
||||||
if [[ -r "${HERMES_ENV:-}" ]] &&
|
if [[ -r "${HERMES_ENV:-}" ]] &&
|
||||||
[[ "$telegram_notify" == 1 || "$signal_notify" == 1 ]]; then
|
[[ "$telegram_notify" == "1" || "$signal_notify" == "1" ]]; then
|
||||||
|
set +u
|
||||||
set -a
|
set -a
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$HERMES_ENV" || true
|
source "$HERMES_ENV"
|
||||||
set +a
|
set +a
|
||||||
fi
|
|
||||||
set -u
|
set -u
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$telegram_notify" == 1 &&
|
if [[ "$telegram_notify" == "1" &&
|
||||||
-n "${TELEGRAM_BOT_TOKEN:-}" &&
|
-n "${TELEGRAM_BOT_TOKEN:-}" &&
|
||||||
-n "${TELEGRAM_HOME_CHANNEL:-}" ]]; then
|
-n "${TELEGRAM_HOME_CHANNEL:-}" ]]; then
|
||||||
if ! curl --fail --silent --show-error --max-time 15 \
|
curl --fail --silent --show-error --max-time 15 \
|
||||||
--request POST \
|
--request POST \
|
||||||
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||||
--data-urlencode "chat_id=${TELEGRAM_HOME_CHANNEL}" \
|
--data-urlencode "chat_id=${TELEGRAM_HOME_CHANNEL}" \
|
||||||
--data-urlencode "text=${text}" >/dev/null; then
|
--data-urlencode "text=${text}" \
|
||||||
log "Telegram-Benachrichtigung konnte nicht zugestellt werden."
|
>/dev/null || true
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$signal_notify" == 1 &&
|
if [[ "$signal_notify" == "1" &&
|
||||||
-n "${SIGNAL_HTTP_URL:-}" &&
|
-n "${SIGNAL_HTTP_URL:-}" &&
|
||||||
-n "${SIGNAL_ACCOUNT:-}" &&
|
-n "${SIGNAL_ACCOUNT:-}" &&
|
||||||
-n "${SIGNAL_HOME_CHANNEL:-}" ]]; then
|
-n "${SIGNAL_HOME_CHANNEL:-}" ]]; then
|
||||||
if [[ "$SIGNAL_HOME_CHANNEL" == group:* ]]; then
|
if [[ "$SIGNAL_HOME_CHANNEL" == group:* ]]; then
|
||||||
signal_payload="$(
|
if ! signal_payload="$(
|
||||||
jq -nc --arg account "$SIGNAL_ACCOUNT" --arg message "$text" \
|
jq -nc \
|
||||||
|
--arg account "$SIGNAL_ACCOUNT" \
|
||||||
|
--arg message "$text" \
|
||||||
--arg group_id "${SIGNAL_HOME_CHANNEL#group:}" \
|
--arg group_id "${SIGNAL_HOME_CHANNEL#group:}" \
|
||||||
'{jsonrpc:"2.0",method:"send",params:{account:$account,message:$message,groupId:$group_id},id:"hermes-maintenance"}'
|
'{jsonrpc:"2.0",method:"send",params:{account:$account,message:$message,groupId:$group_id},id:"hermes-maintenance"}'
|
||||||
)" || signal_payload=""
|
)"; then
|
||||||
|
log "Signal-Benachrichtigung konnte nicht vorbereitet werden."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
signal_payload="$(
|
if ! signal_payload="$(
|
||||||
jq -nc --arg account "$SIGNAL_ACCOUNT" --arg message "$text" \
|
jq -nc \
|
||||||
|
--arg account "$SIGNAL_ACCOUNT" \
|
||||||
|
--arg message "$text" \
|
||||||
--arg recipient "$SIGNAL_HOME_CHANNEL" \
|
--arg recipient "$SIGNAL_HOME_CHANNEL" \
|
||||||
'{jsonrpc:"2.0",method:"send",params:{account:$account,message:$message,recipient:[$recipient]},id:"hermes-maintenance"}'
|
'{jsonrpc:"2.0",method:"send",params:{account:$account,message:$message,recipient:[$recipient]},id:"hermes-maintenance"}'
|
||||||
)" || signal_payload=""
|
)"; then
|
||||||
|
log "Signal-Benachrichtigung konnte nicht vorbereitet werden."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$signal_payload" ]] ||
|
if ! curl --fail --silent --show-error --max-time 30 \
|
||||||
! curl --fail --silent --show-error --max-time 30 \
|
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
--data-binary "$signal_payload" \
|
--data-binary "$signal_payload" \
|
||||||
"${SIGNAL_HTTP_URL%/}/api/v1/rpc" |
|
"${SIGNAL_HTTP_URL%/}/api/v1/rpc" |
|
||||||
jq -e '((.error // null) == null) and (.result != null)' >/dev/null; then
|
jq -e '((.error // null) == null) and (.result != null)' \
|
||||||
|
>/dev/null; then
|
||||||
log "Signal-Benachrichtigung konnte nicht zugestellt werden."
|
log "Signal-Benachrichtigung konnte nicht zugestellt werden."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_user "$*" || true
|
||||||
exit 0
|
exit 0
|
||||||
Regular → Executable
File mode changed.
@@ -1,12 +1,19 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Local llama.cpp Qwen model server
|
Description=Local llama.cpp Qwen model server
|
||||||
After=network-online.target
|
After=network-online.target graphical-session.target
|
||||||
|
StartLimitIntervalSec=300
|
||||||
|
StartLimitBurst=3
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=%h/.local/libexec/hermes-stack-llama-run
|
ExecStart=%h/.local/libexec/hermes-stack-llama-run
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=30
|
||||||
|
Nice=10
|
||||||
|
CPUQuota=1600%
|
||||||
|
MemoryHigh=40G
|
||||||
|
MemoryMax=48G
|
||||||
|
OOMPolicy=stop
|
||||||
TimeoutStopSec=60
|
TimeoutStopSec=60
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
+2
-2
@@ -22,8 +22,8 @@ fi
|
|||||||
|
|
||||||
python3 "$ROOT_DIR/tests/test_llama.py" || failures=$((failures + 1))
|
python3 "$ROOT_DIR/tests/test_llama.py" || failures=$((failures + 1))
|
||||||
|
|
||||||
if rg -n --hidden --glob '!tests/smoke.sh' --glob '!tests/test_llama.py' --glob '!.git/**' \
|
if (cd -- "$ROOT_DIR" && rg -n --hidden --glob '!tests/smoke.sh' --glob '!tests/test_llama.py' --glob '!.git/**' \
|
||||||
'(TELEGRAM_BOT_TOKEN|SIGNAL_ACCOUNT|API_KEY)=("|'"'"')?[A-Za-z0-9+]' "$ROOT_DIR"; then
|
'(TELEGRAM_BOT_TOKEN|SIGNAL_ACCOUNT|API_KEY)=("|'"'"')?[A-Za-z0-9+]' .); then
|
||||||
printf 'Möglicher eingecheckter Secret-Wert gefunden.\n' >&2
|
printf 'Möglicher eingecheckter Secret-Wert gefunden.\n' >&2
|
||||||
failures=$((failures + 1))
|
failures=$((failures + 1))
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class LlamaTests(unittest.TestCase):
|
|||||||
self.write_config()
|
self.write_config()
|
||||||
self.program("systemctl", 'printf "%s\\n" "$*" >>"$TEST_RECORD"\nexit 0\n')
|
self.program("systemctl", 'printf "%s\\n" "$*" >>"$TEST_RECORD"\nexit 0\n')
|
||||||
self.program("notify-test", "exit 0\n")
|
self.program("notify-test", "exit 0\n")
|
||||||
|
self.program("sleep", "exit 0\n")
|
||||||
|
|
||||||
def program(self, name, body):
|
def program(self, name, body):
|
||||||
path = self.bin / name
|
path = self.bin / name
|
||||||
@@ -72,6 +73,7 @@ class LlamaTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_profile_values(self):
|
def test_profile_values(self):
|
||||||
for name, ctx, margin in [("baseline", "65536", "1536"),
|
for name, ctx, margin in [("baseline", "65536", "1536"),
|
||||||
|
("vram-64k", "65536", "1280"),
|
||||||
("balanced", "49152", "1536"),
|
("balanced", "49152", "1536"),
|
||||||
("vram-plus", "49152", "1280"),
|
("vram-plus", "49152", "1280"),
|
||||||
("compact", "32768", "1536")]:
|
("compact", "32768", "1536")]:
|
||||||
|
|||||||
Reference in new issue
Block a user