From b3914e438c49185736cbf9c0bae005e90328ad4b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 11:50:45 +0000 Subject: [PATCH] =?UTF-8?q?Nomme=20la=20cause=20d'un=20d=C3=A9bit=20anorma?= =?UTF-8?q?l=20:=20placement=20du=20mod=C3=A8le=20hors=20GPU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un modèle dont les couches partent en RAM tombe à quelques jetons par seconde. Loki subissait la lenteur sans jamais la nommer : l'utilisateur ne pouvait pas distinguer un blocage de l'application d'un modèle placé sur le CPU par Ollama. Au début de chaque réponse, si /api/ps indique que le modèle n'est pas entièrement en VRAM, une notice donne le pourcentage réellement en VRAM et le volume resté en RAM. Le placement appartient à Ollama — Loki le constate, il ne le corrige pas. Silencieux quand tout est sur GPU, quand le modèle n'est pas encore chargé, ou quand Ollama ne répond pas. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N --- backend/app/routes/chat.py | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/backend/app/routes/chat.py b/backend/app/routes/chat.py index 3e1a3cc..0233b99 100644 --- a/backend/app/routes/chat.py +++ b/backend/app/routes/chat.py @@ -17,6 +17,7 @@ import re import time from contextlib import suppress +import httpx from fastapi import APIRouter, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel @@ -28,6 +29,7 @@ from .. import router as msg_router from ..tools import check_html, _safe_path from ..agent import run_agent from ..config import settings +from ..ollama_client import ollama router = APIRouter(prefix="/api", tags=["chat"]) logger = logging.getLogger(__name__) @@ -62,6 +64,37 @@ def _apply_mode(cfg: dict, mode: str) -> dict: return cfg +async def _placement_notice(model: str) -> str | None: + """Avertit si le modèle tourne (au moins en partie) hors du GPU. + + C'est LA cause d'un débit ridicule (quelques jetons/s) : Ollama a placé + tout ou partie des couches en RAM. Loki ne peut pas le corriger — le + placement appartient à Ollama — mais il peut le NOMMER, au lieu de + laisser croire à un blocage de l'application. + """ + try: + loaded = await ollama.ps() + except (httpx.HTTPError, OSError): + return None + + for m in loaded: + if (m.get("name") or m.get("model")) != model: + continue + size = m.get("size", 0) or 0 + vram = m.get("size_vram", 0) or 0 + if size <= 0 or vram >= size * 0.99: + return None + pct = int(vram / size * 100) + in_ram = (size - vram) / 1e9 + where = "entièrement en RAM" if pct == 0 else f"{pct} % en VRAM" + return ( + f"⚠️ {model} tourne {where} ({in_ram:.1f} Go hors GPU) : " + "le débit sera de quelques jetons par seconde. Réduis le contexte, " + "prends un modèle plus petit, ou répartis sur tes GPU côté Ollama." + ) + return None + + def _loading_message(model: str, seconds: float) -> str: """Message d'attente avant le premier jeton (chargement du modèle).""" waited = int(seconds) @@ -421,6 +454,12 @@ async def chat(req: ChatRequest) -> StreamingResponse: for mcp_notice in mcp_manager.notices(): yield _sse("notice", {"message": mcp_notice}) + # Placement du modèle : dire tout de suite si l'inférence est hors GPU, + # au lieu de laisser l'utilisateur attribuer la lenteur à Loki. + placement = await _placement_notice(model) + if placement: + yield _sse("notice", {"message": placement}) + # ── Consignes additionnelles du tour ────────────────────────────── # Elles sont COLLECTÉES ici puis fusionnées dans l'UNIQUE message # système (voir _merge_system plus bas). Beaucoup de templates