diff --git a/admin/incidents-llm-gpu.md b/admin/incidents-llm-gpu.md index 3eed33a..1c9cf23 100644 --- a/admin/incidents-llm-gpu.md +++ b/admin/incidents-llm-gpu.md @@ -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. diff --git a/ansible/roles/llama_server/README.md b/ansible/roles/llama_server/README.md index 69a16f7..d5af199 100644 --- a/ansible/roles/llama_server/README.md +++ b/ansible/roles/llama_server/README.md @@ -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 diff --git a/ansible/roles/llama_server/defaults/main.yml b/ansible/roles/llama_server/defaults/main.yml index a7b6a26..0c8d583 100644 --- a/ansible/roles/llama_server/defaults/main.yml +++ b/ansible/roles/llama_server/defaults/main.yml @@ -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