feat(llm): Diagnostic zeigt, welches Modell gerade in der Karte liegt
Die Flotte listete "qwen3-4b, qwen3-8b" — das sind die Modelle, die die Box
fahren KANN (llama-swap /v1/models), nicht die geladenen. Ohne groups-Config
liegt immer nur eins in der Karte; ein anderes anzufragen entlaedt es
(gegen llama-swap ausprobiert: A laden, B anfragen → A unloaded).
Der Adapter fragt jetzt llama-swap /running und schickt das Ergebnis in jedem
worker_ping (10 s) und im worker_hello mit; nach einem Modellwechsel sofort.
None = llama-swap nicht erreichbar, [] = nichts geladen — die Anzeige
unterscheidet das ("geladen: ?" vs. "keins geladen").
Diagnostic: das geladene Modell gruen mit "● geladen", die anderen grau mit
Hinweis, dass sie beim ersten Aufruf laden und das geladene verdraengen.
"GPU void": die LLM-Zeile zeigt jetzt LLM_GPU (die Karte von llama-swap) statt
der NVIDIA_VISIBLE_DEVICES des Adapters, die mit der Modell-Karte nichts zu
tun hat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -315,14 +315,43 @@ async def _fetch_available_models() -> list:
|
||||
return [LLM_MODEL]
|
||||
|
||||
|
||||
async def _fetch_loaded() -> list:
|
||||
"""Welche Modelle liegen GERADE in der Karte? llama-swap /running →
|
||||
[{"model", "state"}]. Ohne groups-Config ist das hoechstens eins — ein
|
||||
anderes anzufragen entlaedt es. None, wenn llama-swap nicht antwortet
|
||||
("unbekannt" ist etwas anderes als "nichts geladen")."""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5) as client:
|
||||
r = await client.get(f"{LLAMA_URL}/running")
|
||||
r.raise_for_status()
|
||||
return [{"model": m.get("model", ""), "state": m.get("state", "")}
|
||||
for m in (r.json().get("running") or []) if m.get("model")]
|
||||
except Exception as e:
|
||||
logger.debug("llama-swap /running nicht abfragbar: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
async def _ping(ws) -> None:
|
||||
"""Lebenszeichen mit busy + geladenem Modell. loaded=None heisst "konnte
|
||||
llama-swap nicht fragen" — das ist etwas anderes als "nichts geladen"."""
|
||||
await _send(ws, "worker_ping",
|
||||
{"instanceId": INSTANCE_ID, "busy": _inflight > 0,
|
||||
"loaded": await _fetch_loaded()})
|
||||
|
||||
|
||||
async def _announce(ws) -> None:
|
||||
"""Sendet ein frisches worker_hello mit der aktuellen Modell-Liste (nach
|
||||
Provision/Remove aufrufen, damit Bridge+Diagnostic das neue Modell lernen)."""
|
||||
models = await _fetch_available_models()
|
||||
await _send(ws, "worker_hello", {
|
||||
"instanceId": INSTANCE_ID, "service": WORKER_SERVICE,
|
||||
"node": NODE_NAME, "gpus": GPU_IDS, "model": LLM_MODEL,
|
||||
# Die LLM-Karte ist die von llama-swap (LLM_GPU). Die eigene
|
||||
# NVIDIA_VISIBLE_DEVICES des Adapters ist "all" bzw. wird vom
|
||||
# Container-Toolkit ersetzt (Anzeige "GPU void") — sagt nichts darueber,
|
||||
# wo das Modell laeuft.
|
||||
"node": NODE_NAME, "gpus": LLM_GPU or GPU_IDS, "model": LLM_MODEL,
|
||||
"models": models, # welche Modelle diese Box fahren kann (llama-swap-Keys)
|
||||
"loaded": await _fetch_loaded(), # welches davon GERADE in der Karte liegt
|
||||
# OpenAI-Endpunkt fuer Fremd-Tools (OpenHands & Co.). Der Key selbst wird
|
||||
# NICHT gesendet (Broadcast!) — nur ob einer gesetzt ist und ob die .env
|
||||
# ihn vorgibt (dann ist das Feld in der Diagnostic schreibgeschuetzt).
|
||||
@@ -343,8 +372,7 @@ async def _worker_register(ws) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(WORKER_PING_INTERVAL_S)
|
||||
n += 1
|
||||
await _send(ws, "worker_ping",
|
||||
{"instanceId": INSTANCE_ID, "busy": _inflight > 0})
|
||||
await _ping(ws)
|
||||
if n % 3 == 0: # ~30s worker_hello wiederholen (wie der Satellit) →
|
||||
await _announce(ws) # auch neu gestartetes Diagnostic/Bridge lernt uns
|
||||
except asyncio.CancelledError:
|
||||
@@ -396,6 +424,7 @@ async def _do_llm_request(ws, payload: dict) -> None:
|
||||
_last_model = eff_model
|
||||
await _emit_llm_status(ws, "ready", eff_model,
|
||||
loadSeconds=round(dt, 1), freshlyDownloaded=fresh)
|
||||
await _ping(ws) # Diagnostic zeigt den Wechsel sofort, nicht erst in 10 s
|
||||
else:
|
||||
# bei Fehler _last_model NICHT setzen → naechster Versuch meldet erneut loading
|
||||
await _emit_llm_status(ws, "error", eff_model,
|
||||
@@ -637,6 +666,7 @@ async def _handle_provision(ws, payload: dict) -> None:
|
||||
freshlyDownloaded=dt > 25)
|
||||
await _emit_progress(ws, key, "ready", percent=100, loadSeconds=round(dt, 1),
|
||||
**(await _gpu_info()))
|
||||
await _ping(ws)
|
||||
else:
|
||||
await _emit_llm_status(ws, "error", key, error=(res.get("error") or "")[:160])
|
||||
await _emit_progress(ws, key, "error", error=(res.get("error") or "")[:200])
|
||||
|
||||
Reference in New Issue
Block a user