Fix: KV-Schaetzung kalibriert + Brain-Fit-Check brain-spezifisch

- fit.estimate_memory_gb: KV-Cache jetzt sqrt-skaliert (nicht linear mit Gesamt-Params),
  kalibriert an Hermes-14B@128K ~19GB KV -> realistische Footprints (vorher massive Ueberschaetzung).
- agent.hermes_brain_info Budget: prueft jetzt Brain (immer resident) + groesstes on-demand-Modell
  <= GTT-Budget (fast/vision duerfen verdraengt werden) -> brain-spezifische, aussagekraeftige Warnung.
- Cockpit: Budget-Zeile + Confirm-Warnung entsprechend ("Brain ~X GB + groesstes on-demand ~Y GB").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 03:01:07 +02:00
parent 5b699aaa79
commit 43880b1965
6 changed files with 47 additions and 38 deletions
+12 -7
View File
@@ -107,21 +107,26 @@ def hermes_brain_info() -> dict:
cur_name = cur["name"] if cur else None cur_name = cur["name"] if cur else None
brain_ctx = int((cur.get("ctx") if cur else None) or 32768) 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: if best:
warm += estimate_memory_gb(float(best["params_b"]), "Q4_K_M", brain_ctx) brain_gb = estimate_memory_gb(float(best["params_b"]), "Q4_K_M", brain_ctx)
elif cur: elif cur:
warm += _foot(cur) brain_gb = _foot(cur)
else:
brain_gb = 0.0
# voller Always-Warm-Footprint (alle persist, Brain=Empfehlung) — nur Info
warm = brain_gb + sum(_foot(m) for m in models
if m["name"] in persist and m["name"] != cur_name)
largest_od = max((_foot(m) for m in models if m["name"] not in persist), default=0.0) largest_od = max((_foot(m) for m in models if m["name"] not in persist), default=0.0)
gtt = _gtt_budget_gb() gtt = _gtt_budget_gb()
free_after = gtt - warm # Brain muss immer resident sein → passt Brain + größtes on-demand zusammen?
# (fast/vision dürfen beim Laden eines großen Modells verdrängt werden.)
budget = { budget = {
"gtt_gb": gtt, "gtt_gb": gtt,
"brain_gb": round(brain_gb, 1),
"warm_projected_gb": round(warm, 1), "warm_projected_gb": round(warm, 1),
"free_after_gb": round(free_after, 1),
"largest_ondemand_gb": round(largest_od, 1), "largest_ondemand_gb": round(largest_od, 1),
"fits": free_after >= largest_od, "fits": (brain_gb + largest_od) <= gtt,
"free_after_gb": round(gtt - brain_gb - largest_od, 1),
} }
except Exception: except Exception:
log.debug("hermes_brain_info: Budget-Berechnung fehlgeschlagen", exc_info=True) log.debug("hermes_brain_info: Budget-Berechnung fehlgeschlagen", exc_info=True)
+5 -2
View File
@@ -15,10 +15,13 @@ QUANT_BYTES_PER_PARAM = {
def estimate_memory_gb(params_b: float, quant: str, ctx: int) -> float: def estimate_memory_gb(params_b: float, quant: str, ctx: int) -> float:
"""Geschätzter Speicherbedarf in GB (Gewichte + Kontext-KV).""" """Geschätzter Speicherbedarf in GB (Gewichte + Kontext-KV).
KV-Cache skaliert NICHT linear mit den Gesamt-Parametern (er hängt an
Layern × KV-Heads, gedämpft durch GQA) → sqrt-Skalierung, kalibriert am
gemessenen Punkt Hermes-4-14B @ 128K ≈ 19 GB KV."""
bpp = QUANT_BYTES_PER_PARAM.get(quant.upper(), 0.65) bpp = QUANT_BYTES_PER_PARAM.get(quant.upper(), 0.65)
weights = params_b * bpp weights = params_b * bpp
context_vram = (ctx / 8192) * (max(params_b, 7) / 7) * 0.8 context_vram = (ctx / 8192) * (max(params_b, 7) / 7) ** 0.5 * 0.84
return weights + context_vram return weights + context_vram
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title> <title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-eiqRvBvu.js"></script> <script type="module" crossorigin src="/assets/index-lvQamTQ3.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D4NZ6il2.css"> <link rel="stylesheet" crossorigin href="/assets/index-D4NZ6il2.css">
</head> </head>
<body> <body>
+5 -4
View File
@@ -317,10 +317,11 @@ export interface HermesBrainCandidate {
export interface HermesBrainBudget { export interface HermesBrainBudget {
gtt_gb: number gtt_gb: number
warm_projected_gb: number // Always-On-Footprint mit dem empfohlenen Brain brain_gb: number // Footprint des (empfohlenen) Brains, das immer resident bleibt
free_after_gb: number warm_projected_gb: number // alle persist-Modelle warm (Info)
largest_ondemand_gb: number // größtes on-demand-Modell (z.B. heavy) free_after_gb: number // frei nach Brain + größtem on-demand
fits: boolean // passt das größte on-demand daneben? largest_ondemand_gb: number // größtes on-demand-Modell (z.B. heavy/coder)
fits: boolean // passt Brain + größtes on-demand zusammen ins Budget?
} }
export interface HermesBrainResp { export interface HermesBrainResp {
+5 -5
View File
@@ -179,7 +179,7 @@ export function Cockpit() {
async function handleBrainUpdate(repo: string) { async function handleBrainUpdate(repo: string) {
const b = brain?.budget const b = brain?.budget
const warn = b && !b.fits const warn = b && !b.fits
? `\n\n⚠ Speicher-Warnung: Als Always-On würde dieses Brain ~${b.warm_projected_gb} GB belegen (Budget ${b.gtt_gb} GB) → nur ${b.free_after_gb} GB frei. Das größte on-demand-Modell (~${b.largest_ondemand_gb} GB) passt dann NICHT mehr gleichzeitig daneben. Erwäge ein kleineres Brain oder weniger Kontext.` ? `\n\n⚠ Speicher-Warnung: Dieses Brain (~${b.brain_gb} GB) muss immer resident sein. Zusammen mit dem größten on-demand-Modell (~${b.largest_ondemand_gb} GB) sprengt das das Budget (${b.gtt_gb} GB) → heavy/coder würden das Brain verdrängen. Erwäge ein kleineres Brain oder weniger Kontext.`
: "" : ""
showConfirm( showConfirm(
"Agent-Hirn aktualisieren?", "Agent-Hirn aktualisieren?",
@@ -709,11 +709,11 @@ export function Cockpit() {
brain.budget.fits ? "text-muted-foreground/80" : "text-red-400 font-semibold")}> brain.budget.fits ? "text-muted-foreground/80" : "text-red-400 font-semibold")}>
<HardDrive className="h-3 w-3 shrink-0 mt-0.5" /> <HardDrive className="h-3 w-3 shrink-0 mt-0.5" />
<span> <span>
{brain.update_available ? "Mit diesem Brain als Always-On: " : "Always-On-Budget: "} Always-On-Brain ~{brain.budget.brain_gb} GB + größtes on-demand (~{brain.budget.largest_ondemand_gb} GB)
~{brain.budget.warm_projected_gb} / {brain.budget.gtt_gb} GB belegt {brain.budget.free_after_gb} GB frei = {(brain.budget.brain_gb + brain.budget.largest_ondemand_gb).toFixed(1)} / {brain.budget.gtt_gb} GB
{brain.budget.fits {brain.budget.fits
? ` · größtes on-demand-Modell (~${brain.budget.largest_ondemand_gb} GB) passt daneben ✓` ? " · passt gleichzeitig ✓"
: ` · größtes on-demand-Modell (~${brain.budget.largest_ondemand_gb} GB) passt NICHT mehr daneben ⚠ — kleineres Brain/weniger Kontext`} : " · passt NICHT gleichzeitig ⚠ — heavy/coder würden das Brain verdrängen. Kleineres Brain oder weniger Kontext."}
</span> </span>
</div> </div>
)} )}