feat(llm): Tempo der Netzwerkkarte im Download-Fenster

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 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 15:33:18 +01:00
co-authored by Claude Opus 5.5
parent c511a6b5ae
commit 5783076e27
3 changed files with 43 additions and 1 deletions
+8
View File
@@ -9465,6 +9465,14 @@
? `${pct.toFixed(1)} % · ${_fmtBytes(j.bytes)} / ${_fmtBytes(j.total)}` ? `${pct.toFixed(1)} % · ${_fmtBytes(j.bytes)} / ${_fmtBytes(j.total)}`
: `${_fmtBytes(j.bytes)} heruntergeladen (Größe unbekannt)`; : `${_fmtBytes(j.bytes)} heruntergeladen (Größe unbekannt)`;
sub = (j.speedBps ? (j.speedBps / 1e6).toFixed(1) + ' MB/s' : '') + (j.etaSeconds != null ? ' · noch ' + _fmtEta(j.etaSeconds) : ''); 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 += ` · <span style="color:${slow ? '#FF9F0A' : '#6a6a88'};" title="Netzwerkkarte ${escapeHtml(l.iface)} der Box — mehr als ca. ${max.toFixed(0)} MB/s geht über diese Leitung nicht">`
+ `🔌 ${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)` : '') + '</span>';
}
} else if (j.phase === 'load') { } else if (j.phase === 'load') {
pct = 100; color = '#FFD60A'; 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 …'; line = j.cached ? 'Liegt schon auf der Box — lädt in die Karte …' : '100 % heruntergeladen — lädt jetzt in die Karte …';
+1
View File
@@ -181,6 +181,7 @@ services:
volumes: volumes:
- ./models:/models # generierte Config + Registry + Cache - ./models:/models # generierte Config + Registry + Cache
- ./llama-swap:/llamaswap:ro # Basis-Template (config.yaml) - ./llama-swap:/llamaswap:ro # Basis-Template (config.yaml)
- /sys:/hostsys:ro # nur lesen: Tempo der Netzwerkkarte (Download-Fenster)
environment: environment:
- NVIDIA_VISIBLE_DEVICES=all # alle Karten sichtbar (nur nvidia-smi) - NVIDIA_VISIBLE_DEVICES=all # alle Karten sichtbar (nur nvidia-smi)
- NVIDIA_DRIVER_CAPABILITIES=utility # utility = nvidia-smi, KEIN VRAM/Compute - NVIDIA_DRIVER_CAPABILITIES=utility # utility = nvidia-smi, KEIN VRAM/Compute
+34 -1
View File
@@ -1400,10 +1400,43 @@ async def _gpu_info(card: str) -> dict:
return {"gpu": card} 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: async def _emit_progress(ws, key: str, phase: str, **extra) -> None:
await _send(ws, "llm_provision_progress", await _send(ws, "llm_provision_progress",
{"instanceId": INSTANCE_ID, "node": NODE_NAME, "key": key, {"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: async def _watch_download(ws, key: str, files: list, stop: asyncio.Event, final_phase: bool = True) -> None: