fix(llama_server): embed nomic sur CPU — supprime la contention GPU (docs=0)

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>
This commit is contained in:
alkatrazz 2026-06-20 12:33:37 +02:00
parent 1d3e1e6eb8
commit 229c3aad44
3 changed files with 61 additions and 7 deletions

View file

@ -207,3 +207,49 @@ 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.

View file

@ -10,8 +10,11 @@ Compile llama.cpp (backend HIP/ROCm) et déploie le service **llama-server GPU**
- Ouvre le port dans le firewall, désactive l'ancien service lm-studio
- **Instance dédiée embeddings** (optionnelle, `llama_embed_enabled`) : service systemd
`llama-embed` servant un modèle d'embedding spécialisé (ex. `nomic-embed-text`) sur
`:1238`, GPU. Télécharge le GGUF depuis `llama_embed_model_url` si absent. Partage le
binaire et le GPU avec `:1234` (modèle ~300 Mo VRAM).
`:1238`. Télécharge le GGUF depuis `llama_embed_model_url` si absent. **Tourne en CPU**
(`llama_embed_n_gpu_layers: 0`) : nomic est minuscule (137M) → ~100 ms déterministe, et
ne **partage pas** la RX 6700XT avec le chat `:1234`. Mettre sur GPU (`99`) provoquait
une latence bimodale (16 ms / 9-12 s) par contention → `docs=0` du STT (cf.
`admin/incidents-llm-gpu.md`).
- **Heartbeat embed** (optionnel, `llama_embed_heartbeat_enabled`, défaut `true`) : service
systemd `llama-embed-heartbeat` qui envoie un petit embed à `:1238` toutes les
`llama_embed_heartbeat_interval` s → garde le **slot d'embedding chaud**. Sans ça, la 1ʳᵉ
@ -64,6 +67,7 @@ Compile llama.cpp (backend HIP/ROCm) et déploie le service **llama-server GPU**
| `llama_embed_model_path` | `""` | `/mnt/models/nomic-ai/nomic-embed-text-v1.5.f16.gguf` |
| `llama_embed_model_url` | `""` | URL HuggingFace du GGUF nomic |
| `llama_embed_model_alias` | `nomic-embed-text` | — |
| `llama_embed_n_gpu_layers` | `0` (CPU — évite la contention GPU avec `:1234`) | — |
## ⚠️ Gap IaC — instances CPU hors Ansible

View file

@ -23,13 +23,17 @@ llama_embed_model_path: "" # chemin GGUF sur /mnt/models (NFS)
llama_embed_model_url: "" # URL de téléchargement (HuggingFace) — get_url si absent
llama_embed_model_alias: "nomic-embed-text"
llama_embed_ctx_size: 2048
llama_embed_n_gpu_layers: 99
# CPU par défaut (0 couche GPU). nomic-embed-text est minuscule (137M) → ~100 ms en CPU,
# DÉTERMINISTE. Sur GPU (99) il partage la RX 6700XT avec le chat qwen3-8b (:1234) et tombe
# dans la file derrière une génération → latence BIMODALE (16 ms ou 9-12 s, voire timeout) →
# dépasse le budget recall 4 s du STT-server → docs=0 intermittent. Le CPU découple
# totalement l'embed du slot chat. Cf. admin/incidents-llm-gpu.md.
llama_embed_n_gpu_layers: 0
llama_embed_pooling: "mean"
# Heartbeat embed — garde CHAUD le slot d'embedding (:1238). Sans ça, la 1ʳᵉ requête
# après une pause part à froid (slot GPU inactif) → latence qui dépassait le timeout 4 s
# du STT-server → recall mémoire vide ET RAG doc raté (docs=0). Petit embed périodique
# (modèle minuscule, ~10-50 ms GPU) → le slot reste résident. Cf. admin/ia/stt.md.
# Heartbeat embed — sonde de vivacité du slot :1238 (petit embed périodique). Depuis le
# passage en CPU (llama_embed_n_gpu_layers: 0), le slot ne refroidit plus et ne subit plus
# la contention GPU → le heartbeat sert surtout de probe (logge les KO). Cf. admin/ia/stt.md.
llama_embed_heartbeat_enabled: true
llama_embed_heartbeat_interval: 20 # s entre deux pings (< idle avant refroidissement)
llama_embed_heartbeat_timeout: 15 # s max par ping