From 5783076e2719c978fe4e5a2b9df4ca71ae9de6a4 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 31 Oct 2026 15:33:18 +0100 Subject: [PATCH] feat(llm): Tempo der Netzwerkkarte im Download-Fenster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Box meldet bei jedem Download-Fortschritt ihre Netzwerkkarten (echte Karten, die "up" sind) mit Tempo. Der Container sieht nur sein eigenes eth0, darum liest er das /sys des Rechners (neu: /sys:/hostsys:ro im llm-adapter). Das Download-Fenster zeigt pro Download z.B. "🔌 enp5s0 100 Mbit/s (max. ~12 MB/s — Kabel/Port prüfen)" — orange unter 1 Gbit/s. Anlass: ki-box hing an einem 100-Mbit-Port, 8 Verbindungen brachten darum nichts. Co-Authored-By: Claude Opus 5.5 --- diagnostic/index.html | 8 ++++++++ xtts/docker-compose.yml | 1 + xtts/llm-adapter/adapter.py | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/diagnostic/index.html b/diagnostic/index.html index 8c240ea..144afad 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -9465,6 +9465,14 @@ ? `${pct.toFixed(1)} % · ${_fmtBytes(j.bytes)} / ${_fmtBytes(j.total)}` : `${_fmtBytes(j.bytes)} heruntergeladen (Größe unbekannt)`; sub = (j.speedBps ? (j.speedBps / 1e6).toFixed(1) + ' MB/s' : '') + (j.etaSeconds != null ? ' · noch ' + _fmtEta(j.etaSeconds) : ''); + // Netzwerkkarte der Box: unter 1 Gbit/s ist sie oft die Bremse (Kabel, Port, Green-Mode) + for (const l of (j.links || [])) { + const max = l.mbit / 8 * 0.92; + const slow = l.mbit < 1000; + sub += ` · ` + + `🔌 ${escapeHtml(l.iface)} ${l.mbit >= 1000 ? (l.mbit / 1000) + ' Gbit/s' : l.mbit + ' Mbit/s'}` + + (slow ? ` (max. ~${max.toFixed(0)} MB/s — Kabel/Port prüfen)` : '') + ''; + } } else if (j.phase === 'load') { pct = 100; color = '#FFD60A'; line = j.cached ? 'Liegt schon auf der Box — lädt in die Karte …' : '100 % heruntergeladen — lädt jetzt in die Karte …'; diff --git a/xtts/docker-compose.yml b/xtts/docker-compose.yml index b67e0d8..69079e5 100644 --- a/xtts/docker-compose.yml +++ b/xtts/docker-compose.yml @@ -181,6 +181,7 @@ services: volumes: - ./models:/models # generierte Config + Registry + Cache - ./llama-swap:/llamaswap:ro # Basis-Template (config.yaml) + - /sys:/hostsys:ro # nur lesen: Tempo der Netzwerkkarte (Download-Fenster) environment: - NVIDIA_VISIBLE_DEVICES=all # alle Karten sichtbar (nur nvidia-smi) - NVIDIA_DRIVER_CAPABILITIES=utility # utility = nvidia-smi, KEIN VRAM/Compute diff --git a/xtts/llm-adapter/adapter.py b/xtts/llm-adapter/adapter.py index 92f5216..d0a98c7 100644 --- a/xtts/llm-adapter/adapter.py +++ b/xtts/llm-adapter/adapter.py @@ -1400,10 +1400,43 @@ async def _gpu_info(card: str) -> dict: return {"gpu": card} +# Tempo der Netzwerkkarte(n) der Box — der Container sieht nur sein eigenes +# eth0, darum liest er das /sys des Rechners (docker-compose: /sys:/hostsys:ro). +# Nur echte Karten (mit device-Link), die "up" sind. Fehlt der Mount: []. +HOST_SYS = os.getenv("HOST_SYS", "/hostsys") +_link_cache: dict = {"ts": 0.0, "links": []} + + +def _host_links() -> list: + if time.time() - _link_cache["ts"] < 60: + return _link_cache["links"] + links = [] + base = os.path.join(HOST_SYS, "class", "net") + try: + for name in sorted(os.listdir(base)): + d = os.path.join(base, name) + try: + if not os.path.exists(os.path.join(d, "device")): + continue + with open(os.path.join(d, "operstate")) as fh: + if fh.read().strip() != "up": + continue + with open(os.path.join(d, "speed")) as fh: + mbit = int(fh.read().strip()) + except (OSError, ValueError): + continue + if mbit > 0: + links.append({"iface": name, "mbit": mbit}) + except OSError: + pass + _link_cache.update(ts=time.time(), links=links) + return links + + async def _emit_progress(ws, key: str, phase: str, **extra) -> None: await _send(ws, "llm_provision_progress", {"instanceId": INSTANCE_ID, "node": NODE_NAME, "key": key, - "phase": phase, "ts": int(time.time() * 1000), **extra}) + "phase": phase, "ts": int(time.time() * 1000), "links": _host_links(), **extra}) async def _watch_download(ws, key: str, files: list, stop: asyncio.Event, final_phase: bool = True) -> None: