* feat(litellm): heartbeat anti-502 + request_timeout 20s
Cause racine du 502 « 1ʳᵉ demande après une pause » : la connexion keep-alive
LiteLLM↔llama-server devient inactive → llama-server la ferme → LiteLLM garde le
socket mort → la requête suivante part dans le vide → timeout → 502.
- llm-heartbeat : service systemd qui appelle hermes-default toutes les 15s
(max_tokens:1, /no_think → ~10ms GPU) → la connexion n'est jamais inactive,
jamais périmée. Logge les échecs → sert aussi de sonde (vraie génération).
- request_timeout 60→20s : un socket périmé échoue vite, dans la fenêtre où
num_retries:2 peut rejouer sur une connexion neuve (sinon le client abandonnait
avant le retry → 502 sec).
- Doc : admin/incidents-llm-gpu.md (fix racine) + README rôle.
⚠️ heartbeat appelle hermes-default en continu → si bascule sur Claude (facturé),
mettre llm_heartbeat_enabled: false. request_timeout global → remonter si Claude.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(llama_server): watchdog auto-réparation du wedge ROCm
Service systemd local sur gpu-01 qui sonde une vraie génération sur :1234
(pas juste /health, qui ment quand le slot d'inférence est figé). Sur N
échecs consécutifs → systemctl restart llama-server en local (root, sans
SSH/sudo distant). Gère le 503 "Loading model" post-restart sans le compter
comme échec.
Complète le llm-heartbeat (rôle litellm) : le heartbeat empêche la péremption
par inactivité de la connexion ; le watchdog répare le figeage du serveur lui-même.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
8.7 KiB
Incidents LLM / GPU — llama-server (gpu-01)
Journal dédié des incidents d'inférence : llama-server (ROCm / RX 6700XT sur
gpu-01) et chaîne LLM STT-server / Hermes → LiteLLM → llama-server. Voir aussi
incidents.md (matériel & services storage-01/k8s) et admin/ia/llama_server.md.
2026-06-19 — llama-server gelé : /health OK mais inférence bloquée → 502 sur tout le LLM
Symptômes
- STT (HUD, composer texte) :
502 Server Error: Bad Gateway for url: http://stt.lab.local/v1/ask - Petite activité GPU visible sur gpu-01 (le slot d'inférence tourne en rond)
- STT-server :
GET /healthz→ 200, maisPOST /v1/ask→ 502 - llama-server
:1234:/health→{"status":"ok"}et/v1/models→qwen3-8blisté → l'apparence est « up », ce qui masque la panne - Mais une génération réelle ne renvoie jamais rien :
curl /v1/chat/completionshang (45 s sans un seul token) - LiteLLM (storage-01) :
litellm.Timeout: APITimeoutError - Request timed out. timeout value=600.0, time taken=600.0s. Received Model Group=hermes-default(après 2 retries)
Cause
Le llama-server (gpu-01, :1234, qwen3-8b) était dans un état wedgé après
~2 jours d'uptime (démarré le 2026-06-17 21:04). Les endpoints de contrôle
(/health, /v1/models) répondaient encore, mais le slot d'inférence ROCm était
bloqué (vraisemblablement une requête avortée laissée dans le slot). Chaîne du 502 :
HUD composer → STT-server (/v1/ask) → LiteLLM :4000 (hermes-default) → llama-server gpu-01 :1234 ← GELÉ
LiteLLM attend request_timeout = 600 s, échoue, et le STT-server renvoie 502.
Diagnostic (commandes utiles)
# 1) STT-server : confirmer que le 502 vient de lui (et pas de Traefik)
kubectl -n ai logs <pod stt-server> --tail=50 # → "POST /v1/ask HTTP/1.1" 502
# 2) LiteLLM : voir le timeout
ssh storage-01 'journalctl -u litellm -n 40 --no-pager --output=cat'
# → litellm.Timeout ... timeout value=600.0 ... Model Group=hermes-default
# 3) llama-server : /health MENT — tester une VRAIE génération
ssh gpu-01 'curl -s -m 30 http://127.0.0.1:1234/v1/chat/completions \
-H "Content-Type: application/json" \
-d "{\"model\":\"qwen3-8b\",\"messages\":[{\"role\":\"user\",\"content\":\"Dis bonjour. /no_think\"}],\"max_tokens\":30}"'
# gelé (aucune sortie) = wedgé | réponse JSON = sain
Résolution
Redémarrer le service d'inférence sur gpu-01 :
ssh gpu-01 'sudo systemctl restart llama-server'
- L'ancienne instance gelée met ~40 s à mourir (état
deactivatingle temps que le slot bloqué se libère) —systemctl restartpeut dépasser un timeout SSH court, c'est normal ; vérifier avecsystemctl is-active llama-serverjusqu'àactive. - Au redémarrage,
/v1/chat/completionsrenvoie503 {"error":"Loading model"}pendant ~30 s (rechargement du modèle en VRAM) — normal, attendre. - Validation : génération directe (
"Bonjour."en ~80 ms) puis bout-en-boutPOST http://stt.lab.local/v1/ask→{"reply":"Bonjour.","model":"hermes-default"}.
Observations / suites (hors présent incident)
- ⚠️ Piège du health-check :
/healthet/v1/modelsrestent verts quand le serveur est wedgé → un check qui ne fait pas une vraie génération ne voit pas la panne (même angle mort que le crash-loop Qdrant enactivating, cf.incidents.md). - Latence cerveau : post-restart, 30-45 s pour une réponse triviale via STT-server
alors que le llama-server direct est à ~80 ms → le surcoût vient du pipeline du
cerveau (mémoire/embeddings Qdrant
stt-memory+ LiteLLM). À investiguer séparément. - Bruit LiteLLM :
Invalid model name model=qwen3-1.7b-monitor(profil monitor Hermes) dans les logs — pré-existant, non lié à cet incident.
Prévention (pistes)
- Probe applicative : sonde qui fait une vraie génération
/v1/chat/completions(pas seulement/health) → alerte si le LLM ne produit plus de tokens. - Watchdog : si LiteLLM accumule des
Timeoutsurhermes-default, redémarrer automatiquementllama-serversur gpu-01. - Réduire le
request_timeoutLiteLLM (600 s est très long → un 502 tardif et opaque).
2026-06-19 (suite) — burst de 502 « cold-start » : connexions keep-alive périmées (LiteLLM)
Symptôme
Après le déploiement du STT-server résilient (latence ramenée à ~340 ms en régime), un nouveau pod STT (ou une reprise de trafic après inactivité) voyait ses 1-2 premières requêtes hanger ~60 s puis 502, ensuite tout repartait. Reproductible à chaque rollout.
Cause
Connexions keep-alive périmées dans le pool LiteLLM → llama-server. Les redémarrages de
llama-server (cf. incident ci-dessus) tuent ses connexions, mais LiteLLM garde les
sockets morts dans son pool. La requête suivante part sur un socket mort → attend dans le
vide jusqu'au timeout. Or litellm_settings.request_timeout valait 600 s → LiteLLM
pouvait hanger 10 min sans rien logguer (d'où « LiteLLM ne logge aucune erreur » pendant
la fenêtre de 502 : il attendait encore). Le STT, lui, abandonne avant (timeout par
tentative) et renvoie 502.
Diagnostic clé : llama-server direct est stable (10/10 à 0,32 s) ; le hang est dans la couche LiteLLM↔llama-server, pas dans llama-server ni le code STT.
Correctifs
- Côté STT-server (déployé, v0.2.1) : 2 tentatives avec fermeture du client httpx entre les essais (connexion neuve) + timeout 30 s/tentative. Tolère un blip, mais ne couvre pas un hang > 60 s.
- Côté LiteLLM (
ansible/roles/litellm/templates/config.yaml.j2) :
Appliquer :litellm_settings: request_timeout: 60 # était 600 — borne le hang d'une connexion morte num_retries: 2 # rejoue → évince le socket périmé, repart sur une connexion neuveansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm(⚠️ redémarre LiteLLM — service partagé Hermes/n8n/Open WebUI/Ghostfolio). - Purge immédiate (sans attendre l'IaC) :
sudo systemctl restart litellmvide le pool.
Fix racine — heartbeat (déployé)
Le timeout/retry ne fait que borner la casse : la 1ʳᵉ demande après une pause restait lente (la connexion keep-alive devient inactive → llama-server la ferme → LiteLLM garde le socket mort). La vraie cause = la connexion qui devient périmée pendant l'inactivité.
Solution : llm-heartbeat (rôle litellm) — un service systemd qui appelle hermes-default
toutes les llm_heartbeat_interval s (défaut 15 s, max_tokens:1, /no_think → ~10 ms GPU).
La connexion LiteLLM↔llama-server n'est jamais inactive → jamais périmée → la 1ʳᵉ vraie
demande tombe toujours sur un tuyau vivant. Logge les échecs (journalctl -u llm-heartbeat),
donc sert aussi de sonde (génération réelle, pas juste /health).
ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm # déploie heartbeat + config
systemctl status llm-heartbeat ; journalctl -u llm-heartbeat -f # vérifier
⚠️ Le heartbeat appelle hermes-default en continu → si on bascule cet alias sur Claude
(API facturée), mettre llm_heartbeat_enabled: false.
Fix racine #2 — watchdog gpu-01 (déployé)
Le heartbeat empêche la péremption par inactivité, mais si llama-server se fige
carrément (wedge ROCm), il le détecte sans le réparer. C'est désormais couvert par le
llama-watchdog (rôle llama_server) : un service systemd local sur gpu-01 qui sonde une
vraie génération sur :1234 toutes les llama_watchdog_interval s (défaut 20 s,
max_tokens:1, /no_think). Sur llama_watchdog_failures échecs consécutifs (défaut 3) →
systemctl restart llama-server en local (tourne en root, ni SSH ni sudo distant), puis
cooldown jusqu'à ce que la génération remarche (gère le 503 "Loading model" du rechargement
VRAM sans le compter comme échec).
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server # déploie le watchdog
systemctl status llama-watchdog ; journalctl -u llama-watchdog -f # vérifier
Pourquoi sur gpu-01 (et pas piloté depuis storage-01 via le heartbeat) : le restart est local → pas de SSH/sudo inter-hôtes, répare même si LiteLLM ou le réseau cluster est en vrac, et sonde au plus près du serveur figé.
Défense en profondeur (3 couches) :
llm-heartbeat(storage-01, rôlelitellm) — garde la connexion chaude (anti-péremption).request_timeout: 20+num_retries: 2(LiteLLM) + retry STT-server — bornent le blip.llama-watchdog(gpu-01, rôlellama_server) — répare le wedge ROCm à la source.