⚠ Modell läuft auf der CPU, Karte leer — Treiber zu alt? (bootstrap.sh --upgrade-driver)
' : ''}
@@ -9220,7 +9225,8 @@
${card}— leer · im Modell-Katalog „Laden" → diese Karte`;
}
- const [c, label] = SLOT_STATE[sl.state] || ['#8888AA', escapeHtml(sl.state || '?')];
+ let [c, label] = SLOT_STATE[sl.state] || ['#8888AA', escapeHtml(sl.state || '?')];
+ if (sl.onCpu) { c = '#FF9F0A'; label = '⚠ läuft auf der CPU — Karte leer (Treiber zu alt?)'; }
return `
${card}${escapeHtml(sl.model)}
diff --git a/diagnostic/server.js b/diagnostic/server.js
index d6bef07..aed388f 100644
--- a/diagnostic/server.js
+++ b/diagnostic/server.js
@@ -762,6 +762,10 @@ function workerList() {
gpus: w.gpus, model: w.model, models: w.models || [], busy: !!w.busy,
openaiPort: w.openaiPort || "", openaiBind: w.openaiBind || "",
openaiKeySet: !!w.openaiKeySet, openaiEnvLocked: !!w.openaiEnvLocked,
+ // LLM-Karten (Belegung, Zustand, VRAM). Fehlte hier — die Box schickte sie,
+ // aber diese Feldliste liess sie nie zum Browser durch ("meldet keine Karten").
+ slots: Array.isArray(w.slots) ? w.slots : null,
+ knownModels: w.knownModels || [],
online: (now - (w.last_seen || 0)) < WORKER_OFFLINE_MS,
}));
}
diff --git a/xtts/bootstrap.sh b/xtts/bootstrap.sh
index d2dadae..f20915f 100755
--- a/xtts/bootstrap.sh
+++ b/xtts/bootstrap.sh
@@ -189,8 +189,17 @@ fi
# ── 3. NVIDIA-Treiber ──
step "NVIDIA-Treiber"
if nvidia-smi >/dev/null 2>&1; then
- ok "Treiber aktiv: $(nvidia-smi --query-gpu=name --format=csv,noheader | paste -sd', ')"
+ DRV="$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1 || true)"
+ ok "Treiber ${DRV} aktiv: $(nvidia-smi --query-gpu=name --format=csv,noheader | paste -sd', ')"
DRIVER_ACTIVE=1
+ # Fuer das lokale LLM zu alt? Dann laeuft es still auf der CPU — das soll man
+ # hier sehen, nicht erst an einer leeren Karte in nvidia-smi.
+ PROFILES="$(grep -E '^COMPOSE_PROFILES=' "$XTTS_DIR/.env" 2>/dev/null | cut -d= -f2- || true)"
+ if [[ "${DRV%%.*}" =~ ^[0-9]+$ && "${DRV%%.*}" -lt "$MIN_DRIVER_MAJOR" && ( -z "$PROFILES" || "$PROFILES" == *llm* ) ]]; then
+ warn "Treiber ${DRV} ist zu alt fuer das lokale LLM (braucht >= ${MIN_DRIVER_MAJOR})."
+ warn " Folge: llama.cpp findet keine Karte und rechnet auf der CPU."
+ warn " Abhilfe: sudo ./bootstrap.sh --upgrade-driver (zeigt erst den Plan, fragt nach)"
+ fi
else
if dpkg -l | grep -q '^ii nvidia-driver '; then
warn "Treiber installiert, aber nvidia-smi antwortet nicht → REBOOT noetig."
diff --git a/xtts/llm-adapter/adapter.py b/xtts/llm-adapter/adapter.py
index d6f92f9..6fb9719 100644
--- a/xtts/llm-adapter/adapter.py
+++ b/xtts/llm-adapter/adapter.py
@@ -444,6 +444,26 @@ def _model_ctx(key: str) -> int:
return int(m.group(1)) if m else 0
+async def _vram_used() -> dict:
+ """{index: belegte MB} — frisch bei jedem Aufruf (aendert sich laufend)."""
+ try:
+ proc = await asyncio.create_subprocess_exec(
+ "nvidia-smi", "--query-gpu=index,memory.used", "--format=csv,noheader,nounits",
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL)
+ out, _ = await asyncio.wait_for(proc.communicate(), 10)
+ return {a.strip(): int(float(b)) for a, b in
+ (l.split(",", 1) for l in out.decode().splitlines() if "," in l)}
+ except Exception:
+ return {}
+
+
+# Ein geladenes Modell belegt GB auf seiner Karte. Bleibt die Karte trotz
+# "ready" praktisch leer, rechnet llama.cpp auf der CPU — typisch, wenn der
+# Treiber zu alt fuer das CUDA im Image ist ("forward compatibility …").
+# llama.cpp meldet das nicht nach aussen; wir sehen es nur am Speicher.
+ON_CPU_BELOW_MB = 400
+
+
async def _slots_info() -> list:
"""Pro LLM-Karte: [{gpu, gpuName, model, state}]. state aus llama-swap
/running: ready | starting | stopping | stopped; "unknown", wenn llama-swap
@@ -452,12 +472,16 @@ async def _slots_info() -> list:
running = await _fetch_loaded()
rmap = {r["model"]: r["state"] for r in running} if running is not None else None
gpus = await _gpu_names()
+ used = await _vram_used()
out = []
for card in _llm_cards():
m = slots.get(card, "")
state = "" if not m else ("unknown" if rmap is None else rmap.get(m, "stopped"))
g = gpus.get(card) or {}
+ u = used.get(card)
+ on_cpu = bool(m and state == "ready" and u is not None and u < ON_CPU_BELOW_MB)
out.append({"gpu": card, "gpuName": g.get("name", ""), "vramMB": g.get("vramMB", 0),
+ "vramUsedMB": u if u is not None else -1, "onCpu": on_cpu,
"model": m, "state": state, "ctx": _model_ctx(m) if m else 0})
return out
@@ -504,7 +528,8 @@ async def _announce(ws) -> None:
"openaiKeySet": bool(_openai_key()),
"openaiEnvLocked": bool(_OPENAI_KEY_ENV),
})
- logger.info("worker_hello: %s", ", ".join(f"Karte {x['gpu']}={x['model'] or '-'}({x['state'] or 'leer'})" for x in info))
+ logger.info("worker_hello: %s", ", ".join(
+ f"Karte {x['gpu']}={x['model'] or '-'}({x['state'] or 'leer'}{', CPU!' if x.get('onCpu') else ''})" for x in info))
async def _worker_register(ws) -> None: