Feat: fit-aware Brain-Update (Speicher-Budget) + brains-Kontext gesenkt (Always-On-Footprint)

- Brain-Kontexte gesenkt (live config): hermes 128K->64K, fast/vision 128K->32K
  -> Always-Warm-Footprint 74GB->51GB, ~23GB frei; heavy/coder passen wieder daneben.
- /api/agent/brain liefert jetzt `budget`: projiziert den Always-On-Footprint mit dem
  empfohlenen Brain gegen das GTT-Budget (aus amdgpu.gttsize) und prueft, ob das groesste
  on-demand-Modell daneben passt (fit.estimate_memory_gb).
- Cockpit Agent-Hirn-Karte: Budget-Zeile (gruen/rot) + Warnung im Update-Confirm, wenn ein
  zu grosses Always-On-Brain das groesste on-demand-Modell verdraengen wuerde. Button wird rot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 02:56:32 +02:00
parent 510de69250
commit 5b699aaa79
6 changed files with 141 additions and 56 deletions
+52 -1
View File
@@ -25,6 +25,19 @@ def _hermes_version(name: str) -> float | None:
return float(m.group(1)) if m else None
def _gtt_budget_gb() -> float:
"""GPU-adressierbarer Speicher (GTT) in GB — die harte Obergrenze. Liest
amdgpu.gttsize aus /proc/cmdline, sonst RAM minus OS-Reserve."""
try:
with open("/proc/cmdline") as f:
m = re.search(r"amdgpu\.gttsize=(\d+)", f.read())
if m:
return round(int(m.group(1)) / 1024.0, 1)
except Exception:
pass
return round(psutil.virtual_memory().total / (1024 ** 3) - 6.0, 1)
def hermes_brain_info() -> dict:
"""Aktuelles Agent-Hirn (hermes-Rolle) + bestes verfügbares NousResearch-Hermes-Modell,
das auf diese Hardware passt. Für den Modell-Manager: Brain sichtbar + updatebar,
@@ -75,7 +88,45 @@ def hermes_brain_info() -> dict:
# gleiche Datei schon installiert? dann kein Update
if current and best["repo"].split("/")[-1].lower() in (current["name"] or "").lower():
update = False
return {"current": current, "recommended": best, "update_available": update}
# Fit-Check: passt das EMPFOHLENE Brain als Always-On noch ins Budget, sodass das
# größte on-demand-Modell daneben lädt? (Brain muss immer resident sein.)
budget = None
try:
from services.fit import estimate_memory_gb
groups = llamaswap.list_groups()
persist = set()
for g in groups.values():
if isinstance(g, dict) and g.get("persist"):
persist.update(g.get("members") or [])
def _foot(m: dict) -> float:
caps = m.get("capabilities") or {}
return estimate_memory_gb(float(caps.get("params_b") or 7.0),
m.get("quant") or "Q4_K_M", int(m.get("ctx") or 32768))
cur_name = cur["name"] if cur else None
brain_ctx = int((cur.get("ctx") if cur else None) or 32768)
# Always-Warm = persist-Modelle, das Brain durch die Empfehlung ersetzt
warm = sum(_foot(m) for m in models if m["name"] in persist and m["name"] != cur_name)
if best:
warm += estimate_memory_gb(float(best["params_b"]), "Q4_K_M", brain_ctx)
elif cur:
warm += _foot(cur)
largest_od = max((_foot(m) for m in models if m["name"] not in persist), default=0.0)
gtt = _gtt_budget_gb()
free_after = gtt - warm
budget = {
"gtt_gb": gtt,
"warm_projected_gb": round(warm, 1),
"free_after_gb": round(free_after, 1),
"largest_ondemand_gb": round(largest_od, 1),
"fits": free_after >= largest_od,
}
except Exception:
log.debug("hermes_brain_info: Budget-Berechnung fehlgeschlagen", exc_info=True)
return {"current": current, "recommended": best, "update_available": update, "budget": budget}
def _reach(url: str, path: str = "") -> bool: