mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 05:28:15 +02:00
Auto-réglage GPU : détection, num_ctx optimal et bouton Réglage auto
Backend : - agent_config : num_ctx dans la config, envoyé à Ollama (0 = défaut modèle) - ollama_client : méthode ps() (placement GPU/CPU via /api/ps) - autotune : placement() lit size vs size_vram pour savoir si le modèle est sur GPU - routes/config : POST /api/config/auto (détecte GPU + modèle, calcule et applique num_ctx/max_tokens, renvoie la détection et le placement) Frontend : - client/store : autoTune(), état tuning + résultat - SettingsView : bouton ⚡ Réglage auto, bannière de détection (GPU/VRAM, contexte, placement GPU/CPU), slider Contexte (num_ctx) Config : - GPU_VRAM_MB / GPU_NAME pour déclarer la VRAM si Ollama est distant Tests : recommandation (8B sur 12 Go -> ctx 32768), route /auto + persistance, transmission num_ctx aux options Ollama, placement via /api/ps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
10 files changed
+231
-5
No files matched your search
@@ -34,6 +34,8 @@ DEFAULT_CONFIG: dict = {
|
||||
"top_p": 0.9,
|
||||
"top_k": 40,
|
||||
"max_tokens": 2048,
|
||||
# Fenêtre de contexte envoyée à Ollama (0 = laisser le défaut du modèle).
|
||||
"num_ctx": 0,
|
||||
"tools": dict(DEFAULT_TOOL_STATE),
|
||||
# Demander une validation utilisateur avant toute commande shell.
|
||||
"confirm_shell": True,
|
||||
@@ -69,12 +71,16 @@ def save_config(patch: dict) -> dict:
|
||||
|
||||
def ollama_options(cfg: dict) -> dict:
|
||||
"""Traduit la config en options de génération Ollama."""
|
||||
return {
|
||||
opts = {
|
||||
"temperature": cfg["temperature"],
|
||||
"top_p": cfg["top_p"],
|
||||
"top_k": cfg["top_k"],
|
||||
"num_predict": cfg["max_tokens"],
|
||||
}
|
||||
# num_ctx n'est envoyé que s'il est défini (> 0), sinon défaut du modèle.
|
||||
if cfg.get("num_ctx"):
|
||||
opts["num_ctx"] = cfg["num_ctx"]
|
||||
return opts
|
||||
|
||||
|
||||
def enabled_tool_names(cfg: dict) -> list[str]:
|
||||
|
||||
@@ -210,6 +210,31 @@ async def recommend(model: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
async def placement(model: str) -> dict:
|
||||
"""Lit /api/ps : le modèle est-il chargé sur GPU, CPU, ou un mix ?"""
|
||||
try:
|
||||
loaded = await ollama.ps()
|
||||
except (httpx.HTTPError, OSError):
|
||||
return {"loaded": False}
|
||||
|
||||
for m in loaded:
|
||||
if m.get("name") == model or m.get("model") == model:
|
||||
size = m.get("size", 0) or 0
|
||||
size_vram = m.get("size_vram", 0) or 0
|
||||
if size <= 0:
|
||||
where = "inconnu"
|
||||
elif size_vram >= size * 0.99:
|
||||
where = "gpu"
|
||||
elif size_vram <= size * 0.01:
|
||||
where = "cpu"
|
||||
else:
|
||||
where = "mixte"
|
||||
pct = int(size_vram / size * 100) if size else 0
|
||||
return {"loaded": True, "where": where, "gpu_percent": pct,
|
||||
"size_mb": size // (1024 * 1024)}
|
||||
return {"loaded": False}
|
||||
|
||||
|
||||
def _fallback_model_mb(prof: dict) -> int:
|
||||
"""Estime la taille des poids si /api/tags n'a rien donné."""
|
||||
ps = (prof.get("parameter_size") or "").upper().replace("B", "")
|
||||
|
||||
@@ -33,6 +33,13 @@ class OllamaClient:
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("models", [])
|
||||
|
||||
async def ps(self) -> list[dict]:
|
||||
"""Modèles actuellement chargés et leur répartition VRAM/CPU (/api/ps)."""
|
||||
async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
|
||||
resp = await client.get(f"{self.host}/api/ps")
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("models", [])
|
||||
|
||||
async def show(self, name: str) -> dict:
|
||||
"""Métadonnées détaillées d'un modèle (/api/show)."""
|
||||
async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""Routes de configuration de l'agent (lecture / mise à jour)."""
|
||||
"""Routes de configuration de l'agent (lecture / mise à jour / auto-réglage)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .. import agent_config
|
||||
from .. import agent_config, autotune
|
||||
from ..config import settings
|
||||
|
||||
router = APIRouter(prefix="/api/config", tags=["config"])
|
||||
|
||||
@@ -15,7 +16,14 @@ class ConfigPatch(BaseModel):
|
||||
top_p: float | None = None
|
||||
top_k: int | None = None
|
||||
max_tokens: int | None = None
|
||||
num_ctx: int | None = None
|
||||
tools: dict[str, bool] | None = None
|
||||
confirm_shell: bool | None = None
|
||||
|
||||
|
||||
class AutoTuneRequest(BaseModel):
|
||||
model: str
|
||||
apply: bool = True
|
||||
|
||||
|
||||
@router.get("")
|
||||
@@ -30,3 +38,27 @@ async def get_config() -> dict:
|
||||
async def put_config(patch: ConfigPatch) -> dict:
|
||||
cfg = agent_config.save_config(patch.model_dump(exclude_none=True))
|
||||
return {"config": cfg}
|
||||
|
||||
|
||||
@router.post("/auto")
|
||||
async def auto_tune(req: AutoTuneRequest) -> dict:
|
||||
"""Détecte le GPU + le modèle, calcule les réglages optimaux, et (par
|
||||
défaut) les applique. Renvoie le détail de la détection pour l'UI."""
|
||||
reco = await autotune.recommend(req.model)
|
||||
placement = await autotune.placement(req.model)
|
||||
|
||||
applied = None
|
||||
if req.apply:
|
||||
applied = agent_config.save_config(
|
||||
{
|
||||
"num_ctx": reco["recommended"]["num_ctx"],
|
||||
"max_tokens": reco["recommended"]["max_tokens"],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"detection": reco,
|
||||
"placement": placement,
|
||||
"config": applied or agent_config.get_config(),
|
||||
"vram_override": settings.gpu_vram_mb,
|
||||
}
|
||||
Reference in new issue
Block a user