mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-09 01:14:42 +02:00
Le heartbeat embed (#28) gardait le slot :1238 chargé mais docs=0 revenait : latence bimodale 16ms / 9-12s. Cause = contention GPU, pas cold-start. nomic (:1238, --n-gpu-layers 99) partageait la RX 6700XT avec le chat qwen3-8b (:1234) ; ROCm sérialise → un embed lancé pendant une génération chat attend ~10s → dépasse le budget recall 4s du STT-server → docs=0 intermittent. - llama_embed_n_gpu_layers: 99 → 0 (CPU). nomic = 137M → ~100ms déterministe, découplé du slot chat. Le template lit déjà la variable. - heartbeat embed conservé comme sonde de vivacité (commentaire ajusté). - incidents-llm-gpu.md : section dédiée + README rôle mis à jour. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
255 lines
13 KiB
Markdown
255 lines
13 KiB
Markdown
# 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, mais `POST /v1/ask` → **502**
|
|
- llama-server `:1234` : `/health` → `{"status":"ok"}` et `/v1/models` → `qwen3-8b` listé
|
|
→ **l'apparence est « up »**, ce qui masque la panne
|
|
- Mais une **génération réelle ne renvoie jamais rien** : `curl /v1/chat/completions`
|
|
hang (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)
|
|
|
|
```bash
|
|
# 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 :
|
|
|
|
```bash
|
|
ssh gpu-01 'sudo systemctl restart llama-server'
|
|
```
|
|
|
|
- L'ancienne instance gelée met **~40 s à mourir** (état `deactivating` le temps que
|
|
le slot bloqué se libère) — `systemctl restart` peut dépasser un timeout SSH court,
|
|
c'est normal ; vérifier avec `systemctl is-active llama-server` jusqu'à `active`.
|
|
- Au redémarrage, `/v1/chat/completions` renvoie `503 {"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-bout
|
|
`POST http://stt.lab.local/v1/ask` → `{"reply":"Bonjour.","model":"hermes-default"}`.
|
|
|
|
### Observations / suites (hors présent incident)
|
|
|
|
- ⚠️ **Piège du health-check** : `/health` et `/v1/models` restent 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 en `activating`, 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 `Timeout` sur `hermes-default`, redémarrer
|
|
automatiquement `llama-server` sur gpu-01.
|
|
- Réduire le `request_timeout` LiteLLM (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
|
|
|
|
1. **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.
|
|
2. **Côté LiteLLM** (`ansible/roles/litellm/templates/config.yaml.j2`) :
|
|
```yaml
|
|
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 neuve
|
|
```
|
|
Appliquer : `ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm`
|
|
(⚠️ redémarre LiteLLM — service partagé Hermes/n8n/Open WebUI/Ghostfolio).
|
|
3. **Purge immédiate** (sans attendre l'IaC) : `sudo systemctl restart litellm` vide 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`).
|
|
|
|
```bash
|
|
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).
|
|
|
|
```bash
|
|
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)** :
|
|
1. `llm-heartbeat` (storage-01, rôle `litellm`) — garde la connexion chaude (anti-péremption).
|
|
2. `request_timeout: 20` + `num_retries: 2` (LiteLLM) + retry STT-server — bornent le blip.
|
|
3. `llama-watchdog` (gpu-01, rôle `llama_server`) — répare le wedge ROCm à la source.
|
|
|
|
---
|
|
|
|
## 2026-06-19 (suite) — slot d'embedding `:1238` froid → RAG du STT raté (`docs=0`)
|
|
|
|
### Symptôme
|
|
|
|
Après déploiement du grounding RAG du STT-server (le cerveau interroge `funk-docs`), les
|
|
réponses sur le homelab étaient **intermittentes** : parfois ancrées dans la doc, parfois
|
|
génériques (« serveur XMPP », « worker nodes »…). Log du pod `stt-server` :
|
|
|
|
```
|
|
ask … recall=5471ms … docs=6 ← embed OK → 6 extraits doc injectés → réponse ancrée
|
|
ask … recall=8330ms … docs=0 ← embed à 8 s → timeout → 0 extrait → réponse générique
|
|
```
|
|
|
|
### Cause
|
|
|
|
L'instance d'embedding dédiée (`nomic-embed-text`, gpu-01 `:1238`) part **à froid** après une
|
|
pause : le slot GPU devient inactif, la 1ʳᵉ requête met 5-8 s (réchauffe + VRAM). Or le
|
|
STT-server borne le recall mémoire **et** la recherche doc à `4 s` (chemin de réponse). Au-delà :
|
|
`recall` renvoie `([], None)` → `knowledge.search` ré-embed (2ᵉ timeout) → `docs=0` → le cerveau
|
|
répond sans contexte. Même famille que le wedge chat, sur l'**autre slot** GPU — mais ici pas de
|
|
freeze, juste une latence à froid. Le slot chat `:1234` est gardé chaud par `llm-heartbeat`,
|
|
l'embed `:1238` ne l'était **pas**.
|
|
|
|
### Correctif — heartbeat embed (rôle `llama_server`)
|
|
|
|
`llama-embed-heartbeat` : service systemd local sur gpu-01 qui envoie un petit embed à `:1238`
|
|
toutes les `llama_embed_heartbeat_interval` s (défaut 20). Le slot reste **résident/chaud** → la
|
|
1ʳᵉ vraie requête est rapide → recall + RAG doc tiennent dans les 4 s → `docs>0` fiable.
|
|
|
|
```bash
|
|
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server # déploie le heartbeat embed
|
|
systemctl status llama-embed-heartbeat ; journalctl -u llama-embed-heartbeat -f
|
|
```
|
|
|
|
> Variante moins bonne envisagée : remonter `STT_DOCS_TIMEOUT`/`recall` à ~8 s → couvre le froid
|
|
> mais **ajoute** la latence à chaque requête froide. Le warming l'**élimine** à la source.
|
|
|
|
---
|
|
|
|
## 2026-06-20 — embed `:1238` lent par intermittence MALGRÉ le heartbeat → vraie cause : contention GPU
|
|
|
|
### Symptôme
|
|
|
|
Le heartbeat embed (`#28`) déployé et `active`, mais `docs=0` revenait quand même par
|
|
intermittence. Mesures directes sur `:1238` (5 pings espacés) :
|
|
|
|
```
|
|
embed 9.68s (HTTP 200)
|
|
embed 0.017s (HTTP 200)
|
|
embed 0.016s (HTTP 200)
|
|
embed 12.0s (HTTP 000) ← timeout total
|
|
embed 0.016s (HTTP 200)
|
|
```
|
|
|
|
Latence **bimodale** : 16 ms (chaud) ou 9-12 s (bloqué). Le heartbeat lui-même loggait
|
|
`KO (HTTP 000000)`. Donc le slot n'était **pas froid** (le heartbeat le gardait chargé) —
|
|
il **stallait**.
|
|
|
|
### Cause
|
|
|
|
`llama-embed` (`:1238`, nomic) tournait avec `--n-gpu-layers 99` → il **partageait la
|
|
RX 6700XT** avec le chat `qwen3-8b` (`:1234`, lui aussi `99`). ROCm sérialise l'accès GPU :
|
|
un embed lancé pendant une génération chat (~10 s) tombe dans la file et attend ~10 s. Le
|
|
heartbeat ne pouvait rien — le slot n'est pas inactif, il est **derrière le chat dans la
|
|
queue GPU**. D'où le `recall=8290ms` côté STT-server → timeout 4 s → `docs=0` → réponse
|
|
non ancrée.
|
|
|
|
### Correctif — embed sur CPU (découplage)
|
|
|
|
`llama_embed_n_gpu_layers: 0` (défaut du rôle `llama_server`). nomic-embed-text est
|
|
minuscule (137M) → ~100 ms en CPU, **déterministe**, et surtout **ne touche plus le GPU** →
|
|
zéro contention avec le chat. 100 ms fiable >> 16 ms/10 s bimodal pour le budget recall 4 s.
|
|
|
|
```bash
|
|
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server # redéploie :1238 en CPU
|
|
for i in 1 2 3 4 5; do ssh gpu-01 'curl -s -m12 -o/dev/null -w "%{time_total}s\n" \
|
|
http://127.0.0.1:1238/v1/embeddings -d "{\"model\":\"nomic-embed-text\",\"input\":\"x\"}"'; done
|
|
# attendu : ~0,1 s stable (plus de 9-12 s)
|
|
```
|
|
|
|
> Le heartbeat embed reste en place comme **sonde de vivacité** (il logge les KO), mais il
|
|
> n'est plus le mécanisme qui garantit la latence — c'est le CPU qui la garantit.
|