Document unified Medium slots and dashboard capacity

This commit is contained in:
Mikei386
2026-09-20 18:52:07 +02:00
parent 9af719674b
commit 53320fa6c3
9 changed files with 94 additions and 57 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Athena – Betriebsanleitung
Stand: **16. September 2026**, auf Athena geprüft. Die fünf Textprofil-Images
Stand: **20. September 2026**, auf Athena geprüft. Die fünf Textprofil-Images
tragen **llama.cpp b29c606** (0.4.1).
Der [geprüfte Live-Stand](docs/LIVE_STATE.md) beschreibt Profile, GPUs und die
Abweichung zwischen dem bereitgestellten Stack und dem Git-Checkout.
+18 -13
View File
@@ -1,6 +1,6 @@
# Athena AI
Stand: **16. September 2026**, auf Athena geprüft. Die Textprofil-Images verwenden
Stand: **20. September 2026**, auf Athena geprüft. Die Textprofil-Images verwenden
**llama.cpp 0.4.1** (`b29c606`).
Der [geprüfte Live-Stand](docs/LIVE_STATE.md) beschreibt Profile, GPUs und die
Abweichung zwischen dem bereitgestellten Stack und dem Git-Checkout.
@@ -95,28 +95,33 @@ von llama.cpp übersteuern würde. Clients, die direkt
### Ein oder zwei Modell-Slots
Produktiv laufen alle Profile mit einem Slot. Damit erhält ein einzelner Chat
den vollständigen Profilkontext und die bewährte Ausgabegeschwindigkeit. Die
Einstellung liegt auf Athena in `/etc/mike-ai/stack.env`:
Fast, Large, Ultra und Uncensored laufen mit einem Slot. Medium läuft seit dem
19. September in einem kontrollierten OpenClaw-Praxistest mit zwei Slots. Beide
Medium-Slots teilen sich durch `--kv-unified` **einen** 160.000-Token-KV-Pool;
es entstehen keine zwei unabhängigen 160K-Kontextfenster. Belegt ein Slot
beispielsweise 30.000 Token, stehen dem zweiten höchstens noch etwa 130.000
Token aus diesem Pool zur Verfügung. Das Dashboard zeigt diese aktuell
erreichbare Kapazität und den freien gemeinsamen Pool dynamisch an.
Die Live-Einstellung liegt auf Athena in `/etc/mike-ai/stack.env`:
```bash
MEDIUM_PARALLEL_SLOTS=1
MEDIUM_PARALLEL_SLOTS=2
```
Für einen späteren erneuten Paralleltest genügt es, den Wert auf `2` zu setzen
und ausschließlich das aktuell betroffene Profil neu zu erstellen:
Zum Umschalten wird nur Medium neu erstellt:
```bash
sed -i 's/^MEDIUM_PARALLEL_SLOTS=.*/MEDIUM_PARALLEL_SLOTS=2/' /etc/mike-ai/stack.env
sed -i 's/^MEDIUM_PARALLEL_SLOTS=.*/MEDIUM_PARALLEL_SLOTS=1/' /etc/mike-ai/stack.env
cd /opt/mike-ai/stack
docker compose --env-file /etc/mike-ai/stack.env up -d --no-deps --force-recreate llama-medium
```
Zurück zum stabilen Ein-Slot-Betrieb geht es mit denselben zwei Befehlen und
`MEDIUM_PARALLEL_SLOTS=1`. `--kv-unified` ist bereits im Compose-Stack gesetzt.
Zwei Slots wurden direkt am Router erfolgreich getestet; Hermes verwaltete zwei
gleichzeitig aktive Chats jedoch nicht zuverlässig. Deshalb bleibt ein Slot der
Standard, bis Hermes' Sitzungsfehler behoben ist.
Für zwei Slots wird im ersten Befehl wieder `2` gesetzt. Der Compose- und
Installationsstandard bleibt bewusst `1`; damit wird der Versuchsstand bei
einer Neuinstallation nicht unbemerkt zur Vorgabe. Der aktuelle Live-Test mit
OpenClaw kann zwei gleichzeitige Anforderungen annehmen. Sehr große parallele
Prefills konkurrieren weiterhin um Rechenleistung und den gemeinsamen KV-Pool.
## Endpunkte
+8
View File
@@ -152,6 +152,14 @@ class DashboardModeTests(unittest.TestCase):
self.assertIn('id="slotHistoryRanges"', self.dashboard.HTML)
self.assertIn("drawSlotHistory(d.slot_points||[])", self.dashboard.HISTORY_JS)
def test_unified_slots_show_current_shared_capacity(self):
script = self.dashboard.FULL_JS
self.assertIn("function sharedSlotPool(slots,unified)", script)
self.assertIn("used+pool.free", script)
self.assertIn("lr.kv_unified===true", script)
self.assertIn("Gemeinsamer Pool:", script)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -32,7 +32,7 @@ flowchart LR
K[Backup alle 5 Stunden] --> DATA[/data und /etc/mike-ai]
```
Stand: 16. September 2026. Versionen und Abweichungen: [Live-Stand](LIVE_STATE.md).
Stand: 20. September 2026. Versionen und Abweichungen: [Live-Stand](LIVE_STATE.md).
FLUX nutzt beide GPUs und pausiert dafür LLM und Qwen3-TTS. Dashboard und
Portainer besitzen eigene Netzwerk-Namespaces in `mike-ai_frontend`; der Router
läuft in `mike-ai_control`. Der Gateway vermittelt den privaten Zugriff.
+11 -9
View File
@@ -1,8 +1,8 @@
# Container-Inventar auf Athena
Stand: 16. September 2026 (lesender Docker-Abgleich)
Stand: 20. September 2026 (lesender Docker-Abgleich)
Athena hatte beim lesenden Abgleich 27 Docker-Container. Nicht jeder Container enthält
Athena hatte beim lesenden Abgleich 30 Docker-Container. Nicht jeder Container enthält
ein KI-Modell: Router, Oberflächen, Netzwerk, Steuerung und Sicherung sind
gewöhnliche Dienste. Die rechenintensiven GPU-Worker werden absichtlich nur bei
Bedarf gestartet. Ein Container im Zustand `Created` oder `Exited (0)` ist daher
@@ -11,20 +11,23 @@ nicht automatisch ein ungenutzter Rest.
| Container | Modell oder wesentliche Komponente | Aufgabe |
|---|---|---|
| `mike-ai-backup` | kein Modell; Offen Docker Volume Backup `v2` (Digest vom 15. September 2026) | Sichert `/data`, `/etc/mike-ai`, den Stack und die persistenten Docker-Volumes im Fünf-Stunden-Takt. |
| `mike-ai-bonsai2-ab` | Bonsai-2-Vergleichsmodell | Gestoppter, reproduzierbar dokumentierter A/B-Testcontainer; kein Produktivprofil. |
| `mike-ai-applio-studio` | Applio/RVC; Stimmenmodelle werden nutzerseitig ergänzt | Vollständige RVC-Oberfläche für Inferenz, Modellverwaltung und Training auf der RTX 5080. Für eine Konvertierung ist ein importiertes oder trainiertes `.pth`-Modell nötig; eine Referenzaufnahme allein reicht nicht. |
| `mike-ai-image-worker` | FLUX.2 Klein 9B FP8, Qwen3-8B NF4 Textencoder und VAE | Erzeugt und bearbeitet Bilder transaktional; nutzt während eines Auftrags RTX 5080 und RTX 3060. |
| `mike-ai-llama-dashboard` | kein Modell | Zeigt Telemetrie, Profile, GPU-Nutzung, Slot-Kontextbelegung mit Verlauf und Betriebsarten an und bietet die Modusumschaltung. |
| `mike-ai-llama-fast` | Qwen3.8-27B `IQ4-MIX`, Qwen-MMProj BF16 | Schnelles Q4-Text-/Vision-Profil mit 76.800 Token Kontext. |
| `mike-ai-llama-large` | Qwen3.8-27B `IQ4_XS-pure`, Qwen-MMProj BF16 | Q4-Text-/Vision-Profil mit 192.000 Token Kontext und Verteilung auf beide GPUs. |
| `mike-ai-llama-medium` | Qwen3.8-27B `IQ4_XS-pure`, Qwen-MMProj BF16 | Standard-Q4-Text-/Vision-Profil mit 160.000 Token Kontext und Verteilung auf beide GPUs. |
| `mike-ai-llama-medium` | Qwen3.8-27B `IQ4_XS-pure`, Qwen-MMProj BF16 | Standard-Q4-Text-/Vision-Profil mit gemeinsamem 160.000-Token-KV-Pool, aktuell zwei Slots und Verteilung auf beide GPUs. |
| `mike-ai-llama-ultra` | Qwen3.8-27B `IQ4_XS-pure`, ohne Vision-Projektor | Maximales Langkontextprofil mit 262.144 Token Kontext und Verteilung auf beide GPUs. |
| `mike-ai-llama-uncensored` | Qwen3.8-27B Abliterated `Q4_K_M`, eigener MMProj F16 | Spezialprofil mit 80.000 Token Kontext und gelockerten Modellgrenzen. |
| `mike-ai-ltx2-studio` | LTX-Video-Backend | GPU-Worker für lokale Videogenerierung; beim Abgleich gestoppt. |
| `mike-ai-mcp-athena-operator` | kein Modell | Stellt Hermes begrenzte Werkzeuge zum Prüfen, Ändern, Testen, Sichern und Versionieren von Athena bereit. |
| `mike-ai-music-acestep-test` | ACE-Step 1.5 XL-SFT und `acestep-5Hz-lm-1.7B` | Generiert Musik im exklusiven Musikmodus auf der RTX 5080. |
| `mike-ai-music-ui` | kein Modell; `fspecii/ace-step-ui` | Community-Oberfläche für ACE-Step; bleibt als leichte UI verfügbar, während der GPU-Worker bedarfsgesteuert läuft. |
| `mike-ai-ornith15-ab` | Ornith 1.5 35B-A3B | Gestoppter, reproduzierbar dokumentierter A/B-Testcontainer; kein Produktivprofil. |
| `mike-ai-portainer` | kein Modell; Portainer CE | Optionale Docker-Verwaltungsoberfläche. |
| `mike-ai-profile-controller` | kein Modell | Startet und stoppt ausschließlich freigegebene Modellprofile und Spezialworker in einer sicheren Reihenfolge. |
| `mike-ai-qwen-cron-test` | kleines Qwen-Testmodell | Gestoppter CPU-/Cron-Worker-Versuch; nicht produktiv eingesetzt. |
| `mike-ai-realtime-voice` | kein Modell | Laufende, gesunde WebRTC-Brücke für OpenClaw Talk; verbindet über den privaten WireGuard-Pfad Athena Whisper, OpenClaw-Agent und Qwen3-TTS ohne Profilwechsel. |
| `mike-ai-qwen3-tts` | `Qwen/Qwen3-TTS-12Hz-1.7B-Base`, Stimme Serena | Hochwertige deutsche Sprachausgabe auf der RTX 3060 im LLM-Betrieb. |
| `mike-ai-router` | kein eigenes Modell | Einzige OpenAI-kompatible Modelladresse; koordiniert Profile, Bildaufträge, Sprache und Betriebsarten. |
@@ -38,12 +41,11 @@ nicht automatisch ein ungenutzter Rest.
| `mike-ai-trellis-studio` | Image `mike-ai/trellis-studio:0.6.0-q8` | Vorhandener, gestoppter 3D-Worker; in diesem Abgleich nicht funktional getestet. |
| `mike-ai-mikes-applio-ui` | Image `mike-ai/mikes-applio-ui:latest` | Laufende zusätzliche Applio-Oberfläche. |
Alle fünf llama-Container verwenden llama.cpp 0.4.1 (`b29c606`). Medium
lief beim ersten Abgleich, war später aber mit Exit-Code 139 beendet; die
anderen vier waren nicht aktiv. Dieser Laufzeitfehler wurde im Zuge der
Dokumentationsarbeit nicht behoben. Der Image-Worker war ebenfalls gestoppt.
Die Infrastruktur und die Musik-/Applio-Oberflächen liefen. Diese Zustände
ändern sich mit der Nutzung.
Alle fünf llama-Container verwenden llama.cpp 0.4.1 (`b29c606`). Medium lief
beim Abgleich gesund mit zwei Slots; die anderen vier Textprofile waren
planmäßig nicht aktiv. Der Image-Worker war ebenfalls gestoppt. Die
Infrastruktur und die Musik-/Applio-Oberflächen liefen. Diese Zustände ändern
sich mit der Nutzung.
Die Detailbeschreibungen der Spezialmodelle stammen aus der bestehenden
Betriebsdokumentation; dieses Update prüfte deren Containerbestand, nicht
sämtliche Modellgewichte oder Funktionen neu.
+6 -1
View File
@@ -1,12 +1,17 @@
# Aktuelle Laufzeitnotizen
Stand: 15. September 2026.
Stand: 20. September 2026.
Der verbindliche Abgleich steht im [geprüften Live-Stand](LIVE_STATE.md).
Produktiv läuft **llama.cpp 0.4.1** auf Commit `b29c606`, zuvor b10930.
Alle fünf installierten Profile verwenden dasselbe CUDA-Image. Die Startoption
bleibt `--load-mode none`.
Medium läuft im aktuellen OpenClaw-Praxistest mit zwei Slots. `--kv-unified`
teilt den 160.000-Token-KV-Pool dynamisch zwischen beiden Slots; das Dashboard
zeigt je Slot die aktuell noch erreichbare Kapazität. Alle anderen Profile und
der Installationsstandard bleiben bei einem Slot.
[B10930-Updatebericht](LLAMA_B10930_UPDATE_20260912.md): Version, Image-ID,
Funktionsproben, Vergleichsmessungen und gesicherter Rückfallstand.
+27 -24
View File
@@ -1,6 +1,6 @@
# Geprüfter Live-Stand auf Athena
Stand: **16. September 2026**. Quelle: lesender SSH-Abgleich von Docker,
Stand: **20. September 2026**. Quelle: lesender SSH-Abgleich von Docker,
Compose-Dateien, Git-Inhalten, Images, Health-Endpunkten und Backup-Timern.
Containerzustände sind Momentaufnahmen; der Controller darf Profile danach
umschalten. Es wurde für diesen Abgleich kein Dienst neu gestartet.
@@ -9,28 +9,30 @@ umschalten. Es wurde für diesen Abgleich kein Dienst neu gestartet.
Athena: Debian 13, RTX 5080 (16 GB) und RTX 3060 (12 GB). Die fünf
Textprofil-Images tragen alle `com.mike-ai.llama-cpp-commit=b29c606`
(llama.cpp 0.4.1). Sie verwenden `--load-mode none`, `--parallel 1` und
`--batch-size 2048`. Die `ubatch-size` wurde pro Profil auf einen getesteten
Wert gesetzt; sie ist **nicht** überall identisch.
(llama.cpp 0.4.1). Sie verwenden `--load-mode none` und `--batch-size 2048`.
Fast, Large, Ultra und Uncensored verwenden einen Slot. Medium läuft seit dem
19. September kontrolliert mit `--parallel 2` und `--kv-unified`. Seine beiden
Slots teilen sich den einzigen 160.000-Token-KV-Pool. Die `ubatch-size` wurde
pro Profil auf einen getesteten Wert gesetzt; sie ist **nicht** überall
identisch.
| Profil | Qwen3.8-27B-Variante | Kontext | Tensor-Split 5080:3060 | Vision-Projektor | MTP Draft | ubatch |
|---|---|---:|---|---|---:|---:|
| Fast | IQ4-MIX | 76.800 | nur 5080 | 3060 | 2 | 64 |
| Medium | IQ4_XS Pure | 160.000 | 85:15 | 3060 | 3 | 512 |
| Large | IQ4_XS Pure | 192.000 | 86:14 | 3060 | 3 | 256 |
| Ultra | IQ4_XS Pure | 262.144 | 80:20 | keiner | 2 | 128 |
| Uncensored | Abliterated Q4_K_M | 80.000 | 90:10 | 3060 | 2 | 256 |
| Profil | Qwen3.8-27B-Variante | Kontext | Slots | Tensor-Split 5080:3060 | Vision-Projektor | MTP Draft | ubatch |
|---|---|---:|---:|---|---|---:|---:|
| Fast | IQ4-MIX | 76.800 | 1 | nur 5080 | 3060 | 2 | 64 |
| Medium | IQ4_XS Pure | 160.000 gemeinsam | 2 | 85:15 | 3060 | 3 | 512 |
| Large | IQ4_XS Pure | 192.000 | 1 | 86:14 | 3060 | 3 | 256 |
| Ultra | IQ4_XS Pure | 262.144 | 1 | 80:20 | keiner | 2 | 128 |
| Uncensored | Abliterated Q4_K_M | 80.000 | 1 | 90:10 | 3060 | 2 | 256 |
Tensor-Splits sind Startparameter, keine gemessenen VRAM-Anteile. Der
Vision-Projektor liegt bei den vier Vision-Profilen auf GPU 1 (3060).
`beta1` steht in der Repository-Matrix, ist aber nicht installiert. Der
Router bietet nur die fünf installierten Profile an. Beim ersten Abgleich
war Medium aktiv und der Router im LLM-Modus; beim späteren Containerabgleich
war `mike-ai-llama-medium` mit Exit-Code 139 beendet. Das Log endet mit
einem CUDA-Stacktrace im Vision-/CLIP-Encoding (`libmtmd`/`libggml-cuda`);
eine eindeutige Ursache wurde nicht ermittelt. Dieser Laufzeitfehler wurde
bei der Dokumentationsarbeit nicht behoben oder durch einen Neustart
verdeckt. Der aktuelle Zustand ist über Dashboard/Controller zu prüfen.
Router bietet nur die fünf installierten Profile an. Beim Abgleich am
20. September lief Medium seit rund 21 Stunden gesund mit zwei Slots; die vier
anderen Textprofile waren planmäßig gestoppt. Dashboard, Router, Controller,
Whisper, TTS und Realtime-Voice waren ebenfalls gesund. Der frühere
Vision-/CLIP-Absturz ist als historischer Befund erhalten, beschreibt aber
nicht mehr den aktuellen Containerzustand.
## Weitere Dienste
@@ -54,17 +56,18 @@ verdeckt. Der aktuelle Zustand ist über Dashboard/Controller zu prüfen.
trotzdem Status, Modelle und vorbereitende Aufgaben anzeigen; eine echte
Konvertierung verlangt den Applio-Modus.
Der vollständige Bestand der **27** angelegten Docker-Container steht im
Der vollständige Bestand der **30** angelegten Docker-Container steht im
[Container-Inventar](CONTAINER_INVENTORY.md). `Created` oder `Exited` bei
Spezialworkern bedeutet nicht automatisch einen zu entfernenden Testrest.
## Git und reproduzierbarer Stand
Der laufende Stack liegt unter `/opt/mike-ai/stack`. Sein Git-HEAD war
`276df0e` und damit älter als der aktuelle Git-Stand; `compose.yaml` und
`services/athena-realtime-voice/` sind im Live-Checkout nicht committed.
**Die geprüften Nutzdaten dieser Dateien stimmen aber bytegenau mit dem
neueren Git-Stand überein.** Ein frischer Clone liefert damit den Quellcode
Der laufende Stack liegt unter `/opt/mike-ai/stack`. Sein Git-HEAD ist weiterhin
`276df0e` und damit älter als Doggo; `compose.yaml`, Dashboard und
`services/athena-realtime-voice/` liegen dort als Live-Änderungen. Die am
20. September geprüften Hashes von Compose, Dashboard, Dashboard-Test und
Voice-Bridge stimmen bytegenau mit diesem veröffentlichten Repository-Stand
überein. Ein frischer Clone liefert damit den Quellcode
und die Compose-Definitionen; er liefert weder lokale Secrets noch
Modellgewichte oder persistente Nutzerdaten. Vor einem Deploy ist die
Live-Konfiguration zu sichern und mit dem Checkout abzugleichen, statt den
+6 -4
View File
@@ -23,10 +23,12 @@ The larger logical batches 3072 and 4096 did not improve Medium at ubatch 128. T
## Historischer Medium-Zwei-Slot-Test
This was an A/B candidate, not the current production configuration. Production
was returned to **one slot** because concurrent Hermes requests did not behave
reliably enough. The 85:15 GPU split and batch / ubatch 2048 / 128 remain in
production because they also work with the single-slot profile.
This began as an A/B candidate and was initially returned to **one slot**
because concurrent Hermes requests did not behave reliably enough. On
19 September 2026, Medium was enabled again with two unified-KV slots for a
controlled OpenClaw live trial. The figures below remain the historical
benchmark rather than a new performance claim. The current Medium ubatch is
512; the 85:15 GPU split is unchanged.
Identical fresh 100,297-token prompt with a deterministic 256-token completion:
+16 -4
View File
@@ -270,6 +270,7 @@ def llama_runtime() -> dict[str, Any]:
flags = {
"--flash-attn": "flash_attention",
"--cache-prompt": "prompt_cache",
"--kv-unified": "kv_unified",
"--mmproj-offload": "vision_offload",
}
try:
@@ -843,10 +844,20 @@ function stableLiveRate(kind,tokensTotal,secondsTotal,gauge,active){
if(state.value!=null&&(active||now-state.seen<8000)) return {text:`${deNum(state.value)} tok/s`,fresh:now-state.seen<2500};
return {text:active?'misst …':'–',fresh:false};
}
function slotHtml(s){
let used=s.context_used||0,total=s.n_ctx||0,p=total?Math.min(100,used/total*100):0;
function sharedSlotPool(slots,unified){
if(!unified||!Array.isArray(slots)||slots.length<2)return null;
const total=Math.max(0,...slots.map(s=>Number(s.n_ctx)||0));
const used=slots.reduce((sum,s)=>sum+Math.max(0,Number(s.context_used)||0),0);
return total?{total,used,free:Math.max(0,total-used)}:null;
}
function slotHtml(s,pool){
let used=Math.max(0,Number(s.context_used)||0),total=Math.max(0,Number(s.n_ctx)||0);
let available=pool?Math.min(total,used+pool.free):total;
let p=available?Math.min(100,used/available*100):0;
let state=s.processing?'arbeitet':'frei';
return `<div class="slot"><div class="slot-head"><b>Slot ${s.id??'–'} · ${state}</b><span class="badge">${s.speculative?'MTP aktiv':'Standard'}</span></div><div class="bar-label"><span>${deNum(used)} / ${deNum(total)} Token</span><span>${p.toFixed(1)} %</span></div><div class="bar"><div class="fill" style="width:${p}%"></div></div><div class="metrics">${metric(deNum(s.prompt_tokens),'Prompt')}${metric(deNum(s.prompt_cached),'Cache-Token')}${metric(deNum(s.decoded_tokens),'generiert')}${metric(deNum(s.remaining_generation),'Ausgabe übrig')}${metric(s.task_id??'–','Task-ID')}${metric(s.temperature??'–','Temperatur')}${metric(boolLabel(s.stream),'Streaming')}${metric(deNum(s.max_tokens),'Ausgabelimit')}</div></div>`;
let capacity=pool?`${deNum(used)} / ${deNum(available)} Token aktuell möglich`:`${deNum(used)} / ${deNum(total)} Token`;
let shared=pool?`<span>Gemeinsamer Pool: ${deNum(pool.free)} Token frei</span>`:'';
return `<div class="slot"><div class="slot-head"><b>Slot ${s.id??'–'} · ${state}</b><span class="badge">${s.speculative?'MTP aktiv':'Standard'}</span></div><div class="bar-label"><span>${capacity}</span><span>${p.toFixed(1)} %</span></div><div class="bar"><div class="fill" style="width:${p}%"></div></div>${shared?`<div class="sub">${shared}</div>`:''}<div class="metrics">${metric(deNum(s.prompt_tokens),'Prompt')}${metric(deNum(s.prompt_cached),'Cache-Token')}${metric(deNum(s.decoded_tokens),'generiert')}${metric(deNum(s.remaining_generation),'Ausgabe übrig')}${metric(s.task_id??'–','Task-ID')}${metric(s.temperature??'–','Temperatur')}${metric(boolLabel(s.stream),'Streaming')}${metric(deNum(s.max_tokens),'Ausgabelimit')}</div></div>`;
}
async function refreshFull(){
try{
@@ -866,7 +877,8 @@ async function refreshFull(){
$('cacheSub').textContent=hit==null?'Keine Cache-Metrik':`${deNum(cached)} wiederverwendet · ${deNum(processed)} neu`;
$('requestState').textContent=`${running} aktiv · ${waiting} wartet`;
$('requestSub').textContent=`${(lt.slots||[]).filter(s=>!s.processing).length} freie Slots`;
$('slotCards').innerHTML=(lt.slots||[]).map(slotHtml).join('')||'<div class="sub">Slot-Telemetrie momentan nicht verfügbar</div>';
let slots=lt.slots||[],slotPool=sharedSlotPool(slots,lr.kv_unified===true);
$('slotCards').innerHTML=slots.map(s=>slotHtml(s,slotPool)).join('')||'<div class="sub">Slot-Telemetrie momentan nicht verfügbar</div>';
let drafted=Number(met.spec_decode_num_draft_tokens_total||0),accepted=Number(met.spec_decode_num_accepted_tokens_total||0),acceptance=drafted?accepted/drafted*100:null;
$('mtpAcceptance').textContent=acceptance==null?'–':`${acceptance.toFixed(1)} % akzeptiert`;$('mtpBar').style.width=`${acceptance||0}%`;
$('mtpDrafted').textContent=deNum(drafted);$('mtpAccepted').textContent=deNum(accepted);$('mtpSteps').textContent=deNum(met.spec_decode_num_drafts_total);$('mtpDepth').textContent=lr.mtp_draft_tokens??'–';