#!/usr/bin/env bash # llama-watchdog — auto-réparation du llama-server figé (instabilité ROCm gfx1031). # # Le piège (cf. admin/incidents-llm-gpu.md) : un llama-server wedgé répond encore à # /health et /v1/models, mais la GÉNÉRATION est morte. On sonde donc par une VRAIE # génération (max_tokens:1, /no_think). Sur N échecs consécutifs → restart local. # # Tourne en root (service systemd) → peut `systemctl restart` en local, sans SSH ni sudo # distant. Déployé/géré par le rôle Ansible `llama_server`. set -u URL="http://127.0.0.1:{{ llama_watchdog_port }}/v1/chat/completions" MODEL="{{ llama_watchdog_model }}" SERVICE="{{ llama_watchdog_service }}" INTERVAL="{{ llama_watchdog_interval }}" TIMEOUT="{{ llama_watchdog_timeout }}" THRESHOLD="{{ llama_watchdog_failures }}" payload="{\"model\":\"${MODEL}\",\"messages\":[{\"role\":\"user\",\"content\":\"ping /no_think\"}],\"max_tokens\":1}" probe() { # echo le code HTTP ; "000" si pas de réponse (timeout/refus) curl -s -m "$TIMEOUT" -o /tmp/llama-watchdog.body -w '%{http_code}' \ "$URL" -H 'Content-Type: application/json' -d "$payload" 2>/dev/null || echo "000" } fails=0 while true; do code=$(probe) if [ "$code" = "200" ]; then fails=0 elif [ "$code" = "503" ] && grep -q "Loading model" /tmp/llama-watchdog.body 2>/dev/null; then # Rechargement VRAM (juste après un restart) → ni échec ni succès : on patiente. : else fails=$((fails + 1)) echo "watchdog: probe KO (HTTP ${code}) — ${fails}/${THRESHOLD}" if [ "$fails" -ge "$THRESHOLD" ]; then echo "watchdog: ${SERVICE} figé (${THRESHOLD} échecs consécutifs) → restart" systemctl restart "$SERVICE" echo "watchdog: restart émis ; attente du rechargement du modèle…" # Cooldown : attendre que la génération remarche (couvre mort de l'instance + load VRAM). for _ in $(seq 1 30); do sleep 5 [ "$(probe)" = "200" ] && { echo "watchdog: ${SERVICE} de nouveau réactif"; break; } done fails=0 fi fi sleep "$INTERVAL" done