Feat: setup-bewusste ctx-Vergabe - eine Quelle der Wahrheit (services/budget.py)

Bisher rechnete nur der Hirn-Wechsel setup-bewusst; die allgemeine ctx-Auto-Groesse
nahm den Gesamt-RAM in ISOLATION (ignorierte Hirn/warmes Set/Ko-Residenz) -> ctx
konnte zu gross gewaehlt werden.

Neu: services/budget.py buendelt GTT-Budget, Modell-Footprint und reservierten Speicher
gemaess VERIFIZIERTER Box-Residenz (Hirn immer resident; fast/vision duerfen weichen,
wenn grosses on-demand-Modell laedt). setup_aware_ctx() bemisst den groessten ctx, der
NEBEN dem bestehenden Setup passt - rollen-/gruppen-bewusst aus der echten Config.

- fit.py: max_ctx_in_budget() als budget-basierter Kern; max_ctx_for() delegiert
- models.py: install nutzt setup_aware_ctx; /api/fit liefert assigned_ctx + Budget-Herleitung
- agent.py: nutzt die gemeinsamen Helfer (entfernt Duplikate _gtt_budget_gb/_foot)
- AddModel: Ampel zeigt den setup-bewussten ctx ('ctx -> Nk') inkl. Budget-Tooltip;
  Rollen-Wechsel laedt die Vorschau neu (Rolle bestimmt das Budget)

Effekt: heavy-122B bekommt z.B. 16k statt 131072 (passt neben dem Hirn), waehrend
warme Kleinmodelle weiter grossen Kontext erhalten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 14:13:40 +02:00
parent 14325e7690
commit bfa6844124
9 changed files with 577 additions and 454 deletions
+6 -34
View File
@@ -25,19 +25,6 @@ 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,
@@ -93,42 +80,27 @@ def hermes_brain_info() -> dict:
# größte on-demand-Modell daneben lädt? (Brain muss immer resident sein.)
budget = None
try:
from services.fit import QUANT_BYTES_PER_PARAM, estimate_memory_gb
from services.budget import footprint_gb, gtt_budget_gb
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:
"""Loaded-Footprint = Gewichte + kalibrierter KV-Anteil. Params robust aus dem
MAXIMUM von Namens-Schätzung und Dateigröße (deckt beides ab: 'Coder-Next' ohne
Größe im Namen → aus Datei; Split-GGUFs wie heavy → aus Namen, da size_bytes nur
den ersten Teil zählt)."""
caps = m.get("capabilities") or {}
quant = m.get("quant") or "Q4_K_M"
ctx = int(m.get("ctx") or 32768)
bpp = QUANT_BYTES_PER_PARAM.get(quant.upper(), 0.55)
size_gb = (m.get("size_bytes") or 0) / (1024 ** 3)
pb_size = (size_gb / bpp) if size_gb > 1.0 else 0.0 # Split-Teil → ignoriert
pb = max(float(caps.get("params_b") or 0), pb_size, 7.0)
weights = max(pb * bpp, size_gb)
kv = estimate_memory_gb(pb, quant, ctx) - pb * bpp
return weights + max(kv, 0.0)
cur_name = cur["name"] if cur else None
brain_ctx = int((cur.get("ctx") if cur else None) or 32768)
if best:
brain_gb = estimate_memory_gb(float(best["params_b"]), "Q4_K_M", brain_ctx)
elif cur:
brain_gb = _foot(cur)
brain_gb = footprint_gb(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
warm = brain_gb + sum(footprint_gb(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)
gtt = _gtt_budget_gb()
largest_od = max((footprint_gb(m) for m in models if m["name"] not in persist), default=0.0)
gtt = gtt_budget_gb()
# 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 = {