feat(llm): ein Modell pro Karte, Clients waehlen nur aus — und Doku fuer Drittprogramme

Bisher konnte jeder Client per Modellname llama-swap zum Wechseln bringen —
ein Fremdprogramm haette ARIA das Modell unter den Fuessen weggetauscht.
Jetzt:

- Jede LLM-Karte einer Box (LLM_GPUS=1,2, alt LLM_GPU=1) haelt GENAU ein
  Modell, dauerhaft. Welches, legt nur die Diagnostic fest. Karten, die nicht
  fuers LLM vorgesehen sind, tauchen beim LLM nicht auf und werden als Ziel
  abgelehnt.
- Clients (ARIA, Fremdprogramme ueber RVS, OpenHands ueber den OpenAI-Port)
  sehen nur die geladenen Modelle und laden nie: Anfrage fuer ein anderes →
  model_not_loaded mit Liste des Geladenen (RVS) bzw. HTTP 404 (OpenAI-Port).
  Nichts wird verdraengt.
- Mehrere Clients koennen dasselbe Modell gleichzeitig nutzen.

llm-adapter: Belegung in /models/aria_slots.json. Die llama-swap-Config
enthaelt nur belegte Karten: pro Modell CUDA_VISIBLE_DEVICES=<Karte>, ttl 0,
groups.swap=false, hooks.on_startup.preload. Alles gegen llama-swap
ausprobiert (zwei Modelle gleichzeitig ready, keins verdraengt, Unbekanntes
404, env kommt beim jeweiligen llama-server an). llama-swap laeuft mit
--watch-config; die frueher aufgerufenen Reload-Endpunkte (/reload,
/api/config/reload) gibt es nicht (404). Neu: llm_unload_model (Karte leeren),
llm_models_request → llm_models (Erkundung, jede Box antwortet einzeln).
Fehlgeschlagenes Laden setzt die Karte auf ihr vorheriges Modell zurueck,
sonst risse jedes Neuladen die anderen Karten mit. Uebernahme: ohne
Belegungsdatei landet LLM_MODEL auf der ersten LLM-Karte.

Compose: llama-swap sieht alle Karten (NVIDIA_VISIBLE_DEVICES=all) und
zaehlt sie wie nvidia-smi (CUDA_DEVICE_ORDER=PCI_BUS_ID); das Pinnen pro
Modell macht der Adapter.

Bridge: uebernimmt models/slots auch aus worker_ping (nach "Entladen" sonst
30 s veraltet) und sagt sofort ab, wenn das Modell auf keiner Box liegt,
statt per Broadcast in den Timeout zu laufen.

Diagnostic: Flotte zeigt pro LLM-Karte Modell + Zustand + "Entladen";
Katalog waehlt eine Ziel-KARTE (leere vorausgewaehlt, Ersetzen mit
Rueckfrage); Download-Fenster nennt Karte und was ersetzt wird.

RVS: llm_unload_model, llm_models_request, llm_models freigegeben.

docs/LLM-SCHNITTSTELLE.md: Schnittstelle fuer Drittprogramme (RVS + OpenAI-
Port), Fehlercodes, Gleichzeitigkeit, Python-Beispiel.

Tests: 23 gegen llama-swap (Adapter direkt) + Ende-zu-Ende ueber den echten
RVS mit dem Adapter als Prozess: Laden per RVS mit Fortschritt, das
Doku-Beispiel unveraendert (nur Adresse) findet beide Karten und bekommt eine
Antwort, Absage/Schweigen wie dokumentiert, zwei Clients parallel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 16:04:24 +02:00
co-authored by Claude Opus 5.5
parent 89cb3a765b
commit 23feb3ba2d
9 changed files with 805 additions and 170 deletions
+4
View File
@@ -129,6 +129,10 @@ const ALLOWED_TYPES = new Set([
// Liste, die Box hat nie davon erfahren. _progress meldet den Download in %.
"llm_provision_model", "llm_provision_result", "llm_provision_progress",
"llm_remove_model",
// Karten: Diagnostic leert eine LLM-Karte; Clients fragen, welche Modelle
// auf welchen Boxen/Karten geladen sind (jede Box antwortet mit llm_models).
// Schnittstelle fuer Drittprogramme: docs/LLM-SCHNITTSTELLE.md
"llm_unload_model", "llm_models_request", "llm_models",
"node_stats", "node_stats_stream_start", "node_stats_stream_stop",
"node_stats_history_request", "node_stats_history",
"node_stats_reset", "node_stats_reset_done",