Feat: MTP-Speculative-Decoding-Support + Warm-Set-/Budget-Korrektur (Strix-Halo-Optimierung)
Backend: - MTP-Drafter-Support (Multi-Token-Prediction): erkennt MTP-Köpfe neben dem Modell (mtp-*.gguf / *-assistant, arch gemma4-assistant), schreibt --model-draft … --spec-type draft-mtp --spec-draft-n-max statt draft-simple. _parse_model erkennt --model-draft/-md; drafts_for/set_spec_draft/find_compatible_draft MTP-bewusst. Backward-kompatibel (klassische Drafts unverändert). (config.SPEC_DRAFT_N_MAX) - register_model: cache-reuse/-cram als Default (Drift-Fix — neue Installs wie der hand-getunte Box-Stand), --parallel 2 nur noch für coder (kein ctx-Halbierungs-Footgun), Spec-Auto-Attach für alle Rollen self-guarding. - budget.reserved_gb auf die VERIFIZIERTE llama-swap-Gruppen-Swap-Semantik angeglichen: on-demand-Modelle verdrängen die brains-Gruppe und laufen allein (reservieren 0, voller GTT); brains-Member reservieren nur die übrigen Member. Tote _persist_members entfernt. Frontend: - SpecDraftModal zeigt MTP-Drafter mit MTP-Badge (DraftInfo.mtp). Docs: - docs/OPTIMIZATION_PLAN.md: vollständiges Audit + Umsetzungs-Log (W1/W2 Warm-Set, gemma ctx/fa/cache-reuse/MTP 52→70,8 t/s, fast --parallel 1, scout=GLM-4.6V-Flash), alles live gegen die Box verifiziert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,11 @@ SPEC_DRAFT_MODEL_PATH = os.environ.get("MC_SPEC_DRAFT_MODEL", "")
|
|||||||
# Speculative-Decoding-Typ (llama.cpp dieser Generation braucht --spec-type zusätzlich
|
# Speculative-Decoding-Typ (llama.cpp dieser Generation braucht --spec-type zusätzlich
|
||||||
# zu --spec-draft-model, sonst ist Spec inaktiv).
|
# zu --spec-draft-model, sonst ist Spec inaktiv).
|
||||||
SPEC_TYPE = os.environ.get("MC_SPEC_TYPE", "draft-simple")
|
SPEC_TYPE = os.environ.get("MC_SPEC_TYPE", "draft-simple")
|
||||||
|
# MTP-Speculative-Decoding (Multi-Token-Prediction): manche Modelle bringen einen eigenen
|
||||||
|
# MTP-Kopf mit (z.B. gemma-4 → arch 'gemma4-assistant', Datei 'mtp-*.gguf'). Der wird mit
|
||||||
|
# `--model-draft <mtp.gguf> --spec-type draft-mtp --spec-draft-n-max N` geladen (NICHT
|
||||||
|
# --spec-draft-model/draft-simple). 1,5–2× Durchsatz bei null Qualitätsverlust.
|
||||||
|
SPEC_DRAFT_N_MAX = int(os.environ.get("MC_SPEC_DRAFT_N_MAX", "4"))
|
||||||
# Env für HuggingFace-Downloads: XET deaktivieren (Hänger bei ~6 MB, siehe v1-Gotcha).
|
# Env für HuggingFace-Downloads: XET deaktivieren (Hänger bei ~6 MB, siehe v1-Gotcha).
|
||||||
HF_DOWNLOAD_ENV = {"HF_HUB_DISABLE_XET": "1"}
|
HF_DOWNLOAD_ENV = {"HF_HUB_DISABLE_XET": "1"}
|
||||||
|
|
||||||
|
|||||||
+24
-22
@@ -75,46 +75,48 @@ def params_b_for(name: str) -> float:
|
|||||||
return extract_params_b(name)
|
return extract_params_b(name)
|
||||||
|
|
||||||
|
|
||||||
def _persist_members(groups: dict) -> set:
|
def _coresident_members(groups: dict) -> set:
|
||||||
|
"""Modelle, die GLEICHZEITIG warm sind: Mitglieder aller `swap:false`-Gruppen
|
||||||
|
(Ko-Residenz, z.B. brains = Hirn+embed+vision). Ein Modell AUSSERHALB dieser Gruppen
|
||||||
|
ist on-demand und verdrängt beim Laden die GANZE Gruppe — llama-swap swappt Gruppen
|
||||||
|
(live verifiziert: heavy laden → brains-Gruppe komplett raus, heavy läuft allein)."""
|
||||||
out: set = set()
|
out: set = set()
|
||||||
for g in (groups or {}).values():
|
for g in (groups or {}).values():
|
||||||
if isinstance(g, dict) and g.get("persist"):
|
if isinstance(g, dict) and g.get("swap") is False:
|
||||||
out.update(g.get("members") or [])
|
out.update(g.get("members") or [])
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def reserved_gb(role: str | None) -> dict:
|
def reserved_gb(role: str | None) -> dict:
|
||||||
"""Speicher, der NEBEN einem Zielmodell der gegebenen Rolle resident bleiben muss —
|
"""Speicher, der NEBEN einem Zielmodell der gegebenen Rolle resident bleibt — gemäß der
|
||||||
gemäß verifizierter Box-Residenz. Liest die aktuelle llama-swap-Config, passt sich
|
VERIFIZIERTEN llama-swap-Gruppen-Swap-Semantik (NICHT der früheren Annahme „Hirn bleibt
|
||||||
also der echten Gruppen-/persist-Konfiguration an (nicht hartkodiert).
|
immer"). Nur die ko-residente `swap:false`-Gruppe läuft gemeinsam; ein on-demand-Modell
|
||||||
|
verdrängt die Gruppe und läuft ALLEIN mit dem vollen GTT.
|
||||||
|
|
||||||
- Rolle `hermes` (das Hirn): muss mit dem GRÖSSTEN on-demand-Modell koexistieren.
|
- Modell IN der Ko-Residenz-Gruppe (Hirn/embed/vision): koexistiert mit den ÜBRIGEN
|
||||||
- Rolle, deren aktueller Träger im warmen (persist) Set liegt (z.B. fast/vision):
|
Gruppen-Mitgliedern → reserviert deren Summe.
|
||||||
koexistiert mit Hirn + den ÜBRIGEN warmen Mitgliedern.
|
- Modell AUSSERHALB (heavy/coder/coder-lite/fast/scout): läuft allein (Gruppe wird beim
|
||||||
- sonst (heavy/coder/scout/keine): on-demand → verdrängt fast/vision, nur das Hirn bleibt.
|
Laden rausgeswappt) → reserviert NICHTS, darf den vollen GTT für Kontext nutzen.
|
||||||
"""
|
"""
|
||||||
from services import llamaswap
|
from services import llamaswap
|
||||||
models = llamaswap.list_models()
|
models = llamaswap.list_models()
|
||||||
groups = llamaswap.list_groups()
|
groups = llamaswap.list_groups()
|
||||||
persist = _persist_members(groups)
|
cores = _coresident_members(groups)
|
||||||
brain = next((m for m in models if (m.get("role") == "hermes")), None)
|
brain = next((m for m in models if (m.get("role") == "hermes")), None)
|
||||||
brain_name = brain["name"] if brain else None
|
|
||||||
brain_gb = footprint_gb(brain) if brain else 0.0
|
brain_gb = footprint_gb(brain) if brain else 0.0
|
||||||
role = (role or "").strip().lower()
|
role = (role or "").strip().lower()
|
||||||
|
|
||||||
if role == "hermes":
|
|
||||||
reserved = max((footprint_gb(m) for m in models if m["name"] not in persist),
|
|
||||||
default=0.0)
|
|
||||||
return {"reserved_gb": reserved, "mode": "brain", "brain_gb": brain_gb}
|
|
||||||
|
|
||||||
holder = next((m for m in models if (m.get("role") == role)), None) if role else None
|
holder = next((m for m in models if (m.get("role") == role)), None) if role else None
|
||||||
warm = bool(holder and holder["name"] in persist)
|
holder_name = holder["name"] if holder else None
|
||||||
if warm:
|
# Hirn (hermes) ist per Definition Teil der Ko-Residenz-Gruppe; sonst Gruppen-Mitgliedschaft prüfen.
|
||||||
others = sum(footprint_gb(m) for m in models
|
in_group = role == "hermes" or bool(holder_name and holder_name in cores)
|
||||||
if m["name"] in persist and m["name"] not in {brain_name, holder["name"]})
|
|
||||||
return {"reserved_gb": brain_gb + others, "mode": "warm", "brain_gb": brain_gb}
|
|
||||||
|
|
||||||
return {"reserved_gb": brain_gb, "mode": "ondemand", "brain_gb": brain_gb}
|
if in_group:
|
||||||
|
others = sum(footprint_gb(m) for m in models
|
||||||
|
if m["name"] in cores and m["name"] != holder_name)
|
||||||
|
return {"reserved_gb": others, "mode": "co-resident", "brain_gb": brain_gb}
|
||||||
|
# on-demand: verdrängt die Ko-Residenz-Gruppe → läuft allein, voller GTT für Kontext.
|
||||||
|
return {"reserved_gb": 0.0, "mode": "ondemand-alone", "brain_gb": brain_gb}
|
||||||
|
|
||||||
|
|
||||||
def setup_aware_ctx(params_b: float, quant: str, role: str | None = None) -> dict:
|
def setup_aware_ctx(params_b: float, quant: str, role: str | None = None) -> dict:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from ruamel.yaml.scalarstring import LiteralScalarString
|
|||||||
|
|
||||||
from config import (
|
from config import (
|
||||||
CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, DRAFTS_DIR, LLAMA_SWAP_URL,
|
CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, DRAFTS_DIR, LLAMA_SWAP_URL,
|
||||||
SPEC_DRAFT_MODEL_PATH, SPEC_TYPE,
|
SPEC_DRAFT_MODEL_PATH, SPEC_DRAFT_N_MAX, SPEC_TYPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -84,15 +84,15 @@ def _parse_model(name: str, spec: dict) -> dict:
|
|||||||
role = aliases[0].lower() if aliases else (name.lower() if name.lower() in ROLE_IDS else None)
|
role = aliases[0].lower() if aliases else (name.lower() if name.lower() in ROLE_IDS else None)
|
||||||
|
|
||||||
prompt_cache = "--prompt-cache " in cmd or cmd.endswith("--prompt-cache") or "--prompt-cache-all" in cmd
|
prompt_cache = "--prompt-cache " in cmd or cmd.endswith("--prompt-cache") or "--prompt-cache-all" in cmd
|
||||||
|
# Draft-Modell erkennen — klassisch (--spec-draft-model) ODER MTP (--model-draft / -md).
|
||||||
spec_draft = None
|
spec_draft = None
|
||||||
if "--spec-draft-model" in cmd:
|
if (m_draft := re.search(r"--(?:spec-draft-model|model-draft)\s+([^\s]+)", cmd)) \
|
||||||
m_draft = re.search(r"--spec-draft-model\s+([^\s]+)", cmd)
|
or (m_draft := re.search(r"(?<![\w-])-md\s+([^\s]+)", cmd)):
|
||||||
if m_draft:
|
spec_draft = os.path.basename(m_draft.group(1).replace("'", "").replace('"', ""))
|
||||||
spec_draft = os.path.basename(m_draft.group(1).replace("'", "").replace('"', ""))
|
|
||||||
spec_type = None
|
spec_type = None
|
||||||
if (m_st := re.search(r"--spec-type\s+([^\s]+)", cmd)):
|
if (m_st := re.search(r"--spec-type\s+([^\s]+)", cmd)):
|
||||||
spec_type = m_st.group(1)
|
spec_type = m_st.group(1)
|
||||||
# Spec ist nur AKTIV, wenn BEIDES gesetzt ist (--spec-draft-model UND --spec-type).
|
# Spec ist nur AKTIV, wenn BEIDES gesetzt ist (Draft-Modell UND --spec-type).
|
||||||
spec_active = bool(spec_draft and spec_type)
|
spec_active = bool(spec_draft and spec_type)
|
||||||
parallel_match = re.search(r"--parallel\s+(\d+)", cmd)
|
parallel_match = re.search(r"--parallel\s+(\d+)", cmd)
|
||||||
parallel_slots = int(parallel_match.group(1)) if parallel_match else 1
|
parallel_slots = int(parallel_match.group(1)) if parallel_match else 1
|
||||||
@@ -218,13 +218,20 @@ def register_model(model_path: str, role: str | None = None, ctx: int = 8192,
|
|||||||
cmd += " --jinja"
|
cmd += " --jinja"
|
||||||
|
|
||||||
role_lower = (role or "").strip().lower()
|
role_lower = (role or "").strip().lower()
|
||||||
if role_lower in ("fast", "coder"):
|
# KV-Cache-Reuse über Turns (Prompt-Cache wiederverwenden) — hilft allen Chat-Modellen
|
||||||
if "--parallel" not in cmd:
|
# (Agent-Hirn, Coding, Multi-Turn). Spiegelt die auf der Box bewährten Flags wider, damit
|
||||||
cmd += " --parallel 2"
|
# neu installierte Modelle nicht hinter dem hand-getunten Stand zurückbleiben (Drift-Fix).
|
||||||
# Nur einen VOCAB-KOMPATIBLEN Draft anhängen (sonst scheitert llama.cpp).
|
if "--cache-reuse" not in cmd:
|
||||||
# Bei frischem Install existiert die GGUF noch nicht → kein Draft (später im UI setzbar).
|
cmd += " --cache-reuse 256 -cram 16384"
|
||||||
if "--spec-draft-model" not in cmd:
|
# IDE-Coding profitiert von Nebenläufigkeit; sonst Default 1 Slot = voller Kontext/Anfrage
|
||||||
cmd += spec_draft_flags(model_path)
|
# (--parallel teilt den Kontext HART auf die Slots auf, s. docs/OPTIMIZATION_PLAN.md §9.4 V6).
|
||||||
|
if role_lower == "coder" and "--parallel" not in cmd:
|
||||||
|
cmd += " --parallel 2"
|
||||||
|
# Vocab-kompatiblen Draft automatisch anhängen — klassisch (DRAFTS_DIR) ODER MTP-Kopf neben
|
||||||
|
# dem Modell. Self-guarding: ohne kompatiblen/vorhandenen Draft passiert nichts (später im UI
|
||||||
|
# setzbar). Bei frischem Install existiert die Modell-GGUF noch nicht → ebenfalls kein Draft.
|
||||||
|
if "--spec-draft-model" not in cmd and "--model-draft" not in cmd:
|
||||||
|
cmd += spec_draft_flags(model_path)
|
||||||
|
|
||||||
cfg.setdefault("models", {})[model_id] = {
|
cfg.setdefault("models", {})[model_id] = {
|
||||||
"cmd": LiteralScalarString(cmd + "\n"),
|
"cmd": LiteralScalarString(cmd + "\n"),
|
||||||
@@ -250,15 +257,54 @@ def list_drafts() -> list[dict]:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _is_mtp_draft(draft_path: str) -> bool:
|
||||||
|
"""Ist dieser Draft ein MTP-Kopf (Multi-Token-Prediction) statt eines klassischen
|
||||||
|
Draft-Modells? MTP-Köpfe (z.B. gemma-4) laden mit `--model-draft … --spec-type
|
||||||
|
draft-mtp` statt `--spec-draft-model … --spec-type draft-simple`. Erkennung am Arch
|
||||||
|
('…-assistant' / 'mtp') oder Dateinamen ('mtp-*', '*-MTP', '*-assistant')."""
|
||||||
|
base = os.path.basename(draft_path).lower()
|
||||||
|
if base.startswith("mtp-") or "-mtp" in base or "assistant" in base:
|
||||||
|
return True
|
||||||
|
from services import gguf_meta
|
||||||
|
arch = ((gguf_meta.fingerprint(draft_path) or {}).get("arch") or "").lower()
|
||||||
|
return arch.endswith("-assistant") or "mtp" in arch
|
||||||
|
|
||||||
|
|
||||||
|
def _spec_flags_for_draft(draft_path: str) -> str:
|
||||||
|
"""Korrekte llama-server-Spec-Flags für einen (vocab-kompatiblen) Draft. MTP-Kopf →
|
||||||
|
`--model-draft … --spec-type draft-mtp --spec-draft-n-max N`; klassischer Draft →
|
||||||
|
`--spec-draft-model … --spec-type draft-simple`. (Beides nötig, sonst Spec inaktiv.)"""
|
||||||
|
if _is_mtp_draft(draft_path):
|
||||||
|
return (f" --model-draft {draft_path} --spec-type draft-mtp"
|
||||||
|
f" --spec-draft-n-max {SPEC_DRAFT_N_MAX}")
|
||||||
|
return f" --spec-draft-model {draft_path} --spec-type {SPEC_TYPE}"
|
||||||
|
|
||||||
|
|
||||||
|
def _sibling_mtp_drafters(target_path: str) -> list[str]:
|
||||||
|
"""MTP-Kopf-GGUFs NEBEN dem Zielmodell (gleicher Ordner): 'mtp-*.gguf', '*-MTP.gguf',
|
||||||
|
'*-assistant*.gguf'. Per Konstruktion vocab-identisch zum Modell → idealer Draft."""
|
||||||
|
out: list[str] = []
|
||||||
|
d = os.path.dirname(target_path)
|
||||||
|
if os.path.isdir(d):
|
||||||
|
for f in sorted(os.listdir(d)):
|
||||||
|
fl = f.lower()
|
||||||
|
if fl.endswith(".gguf") and (fl.startswith("mtp-") or "-mtp" in fl or "assistant" in fl):
|
||||||
|
p = os.path.join(d, f)
|
||||||
|
if p != target_path:
|
||||||
|
out.append(p)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def find_compatible_draft(target_path: str) -> str | None:
|
def find_compatible_draft(target_path: str) -> str | None:
|
||||||
"""Pfad eines vocab-kompatiblen Drafts für target_path, oder None.
|
"""Pfad eines vocab-kompatiblen Drafts für target_path, oder None.
|
||||||
Bevorzugt MC_SPEC_DRAFT_MODEL (falls gesetzt+kompatibel), sonst der erste
|
Bevorzugt einen MTP-Kopf NEBEN dem Modell (höchste Qualität, by-construction),
|
||||||
kompatible Draft in DRAFTS_DIR. None auch, wenn target (noch) fehlt (dann
|
dann MC_SPEC_DRAFT_MODEL (falls gesetzt+kompatibel), sonst der erste kompatible
|
||||||
nicht verifizierbar → bewusst KEIN Draft anhängen)."""
|
Draft in DRAFTS_DIR. None auch, wenn target (noch) fehlt (nicht verifizierbar →
|
||||||
|
bewusst KEIN Draft anhängen)."""
|
||||||
if not target_path or not os.path.exists(target_path):
|
if not target_path or not os.path.exists(target_path):
|
||||||
return None
|
return None
|
||||||
from services import gguf_meta
|
from services import gguf_meta
|
||||||
candidates: list[str] = []
|
candidates: list[str] = list(_sibling_mtp_drafters(target_path))
|
||||||
if SPEC_DRAFT_MODEL_PATH and os.path.exists(SPEC_DRAFT_MODEL_PATH):
|
if SPEC_DRAFT_MODEL_PATH and os.path.exists(SPEC_DRAFT_MODEL_PATH):
|
||||||
candidates.append(SPEC_DRAFT_MODEL_PATH)
|
candidates.append(SPEC_DRAFT_MODEL_PATH)
|
||||||
for d in list_drafts():
|
for d in list_drafts():
|
||||||
@@ -272,19 +318,27 @@ def find_compatible_draft(target_path: str) -> str | None:
|
|||||||
|
|
||||||
def spec_draft_flags(target_path: str) -> str:
|
def spec_draft_flags(target_path: str) -> str:
|
||||||
"""llama-server-Flags für Speculative Decoding (Draft + --spec-type), oder ''
|
"""llama-server-Flags für Speculative Decoding (Draft + --spec-type), oder ''
|
||||||
wenn kein kompatibler Draft existiert. Beides nötig, sonst ist Spec inaktiv."""
|
wenn kein kompatibler Draft existiert. MTP-bewusst (s. _spec_flags_for_draft)."""
|
||||||
d = find_compatible_draft(target_path)
|
d = find_compatible_draft(target_path)
|
||||||
return f" --spec-draft-model {d} --spec-type {SPEC_TYPE}" if d else ""
|
return _spec_flags_for_draft(d) if d else ""
|
||||||
|
|
||||||
|
|
||||||
def drafts_for(target_path: str) -> dict:
|
def drafts_for(target_path: str) -> dict:
|
||||||
"""Für die UI: alle Drafts + ihre Kompatibilität zum Ziel-Modell.
|
"""Für die UI: alle Drafts + ihre Kompatibilität zum Ziel-Modell. Schließt MTP-Köpfe
|
||||||
|
NEBEN dem Zielmodell ein (DRAFTS_DIR kennt sie nicht). `mtp:true` markiert MTP-Drafts.
|
||||||
compatible=None heißt 'nicht prüfbar' (Ziel- oder Draft-GGUF fehlt)."""
|
compatible=None heißt 'nicht prüfbar' (Ziel- oder Draft-GGUF fehlt)."""
|
||||||
from services import gguf_meta
|
from services import gguf_meta
|
||||||
exists = bool(target_path and os.path.exists(target_path))
|
exists = bool(target_path and os.path.exists(target_path))
|
||||||
drafts = list_drafts()
|
drafts = list_drafts()
|
||||||
|
seen = {d["path"] for d in drafts}
|
||||||
|
for p in _sibling_mtp_drafters(target_path):
|
||||||
|
if p not in seen:
|
||||||
|
drafts.append({"path": p, "filename": os.path.basename(p),
|
||||||
|
"size_bytes": os.path.getsize(p) if os.path.exists(p) else None,
|
||||||
|
"vocab": gguf_meta.fingerprint(p)})
|
||||||
for d in drafts:
|
for d in drafts:
|
||||||
d["compatible"] = gguf_meta.compatible(target_path, d["path"]) if exists else None
|
d["compatible"] = gguf_meta.compatible(target_path, d["path"]) if exists else None
|
||||||
|
d["mtp"] = _is_mtp_draft(d["path"])
|
||||||
return {
|
return {
|
||||||
"target_path": target_path,
|
"target_path": target_path,
|
||||||
"target_exists": exists,
|
"target_exists": exists,
|
||||||
@@ -302,9 +356,11 @@ def set_spec_draft(model_id: str, draft_path: str | None) -> dict:
|
|||||||
if not isinstance(spec, dict):
|
if not isinstance(spec, dict):
|
||||||
return {"ok": False, "reason": "Modell nicht gefunden"}
|
return {"ok": False, "reason": "Modell nicht gefunden"}
|
||||||
cmd = str(spec.get("cmd", ""))
|
cmd = str(spec.get("cmd", ""))
|
||||||
# vorhandene Spec-Flags entfernen (idempotent)
|
# vorhandene Spec-Flags entfernen (idempotent) — klassisch UND MTP.
|
||||||
cmd = re.sub(r"\s+--spec-draft-model\s+\S+", "", cmd)
|
cmd = re.sub(r"\s+--(?:spec-draft-model|model-draft)\s+\S+", "", cmd)
|
||||||
|
cmd = re.sub(r"\s+-md\s+\S+", "", cmd)
|
||||||
cmd = re.sub(r"\s+--spec-type\s+\S+", "", cmd)
|
cmd = re.sub(r"\s+--spec-type\s+\S+", "", cmd)
|
||||||
|
cmd = re.sub(r"\s+--spec-draft-n-(?:max|min)\s+\S+", "", cmd)
|
||||||
|
|
||||||
if draft_path:
|
if draft_path:
|
||||||
# relative Angabe (nur Dateiname) gegen DRAFTS_DIR auflösen
|
# relative Angabe (nur Dateiname) gegen DRAFTS_DIR auflösen
|
||||||
@@ -323,7 +379,7 @@ def set_spec_draft(model_id: str, draft_path: str | None) -> dict:
|
|||||||
if comp is False else
|
if comp is False else
|
||||||
"Kompatibilität nicht prüfbar (Modell-GGUF fehlt) — Draft nicht gesetzt.")
|
"Kompatibilität nicht prüfbar (Modell-GGUF fehlt) — Draft nicht gesetzt.")
|
||||||
return {"ok": False, "reason": reason}
|
return {"ok": False, "reason": reason}
|
||||||
cmd = cmd.rstrip() + f" --spec-draft-model {draft_path} --spec-type {SPEC_TYPE}"
|
cmd = cmd.rstrip() + _spec_flags_for_draft(draft_path)
|
||||||
|
|
||||||
spec["cmd"] = LiteralScalarString(cmd.rstrip() + "\n")
|
spec["cmd"] = LiteralScalarString(cmd.rstrip() + "\n")
|
||||||
write_config(cfg)
|
write_config(cfg)
|
||||||
|
|||||||
@@ -0,0 +1,298 @@
|
|||||||
|
# Optimierungsplan MC2 — Rollen, Warm-Set & Durchsatz (Strix Halo)
|
||||||
|
|
||||||
|
> Audit-Ergebnis + Maßnahmenplan. **Erst Plan, dann Umsetzung nach Freigabe.** Alles reversibel
|
||||||
|
> (Config-Backups vor jeder Box-Änderung). Stand: 2026-06-30.
|
||||||
|
> Quellen: Code-Audit (`backend/`, `frontend/`) + **Live-Box-Verifikation per SSH** (`/running`, voller
|
||||||
|
> `config.yaml`, `free`, `du`, t/s-Probe) + `docs/AUDIT_KICKOFF.md` / `CLAUDE_CODE_BRIEF_lucy-brain-role.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. TL;DR (Priorität nach echtem Impact)
|
||||||
|
|
||||||
|
1. **✅ ERLEDIGT — Warm-Set korrigiert (W1/W2, 2026-06-30, live verifiziert):** `brains` jetzt = **gemma +
|
||||||
|
embedding + vision** (`swap:false, persist:true`, ~31 GB). **Wichtige Korrektur durch Live-Test:** das
|
||||||
|
ursprünglich vermutete OOM-Risiko existiert NICHT — llama-swap **swappt ganze Gruppen** statt sie zu
|
||||||
|
ko-laden (cross-group Load = Gruppe raus, neues Modell rein; kein Speicher-Überlauf). Der echte Punkt war
|
||||||
|
ein anderer: vorher pinnte `brains` auch `fast` (Chat-Lane-Hirn, kein Nutzen für Lucy → ~28 GB verschenkt),
|
||||||
|
und in der reinen `gemma+embed`-Variante hätte **Lucys Sehen ihr Hirn verdrängt**. Jetzt halten Hirn +
|
||||||
|
Gedächtnis + Sehen zusammen warm (verifiziert: gemma & vision ko-resident, 37 GB, kein Evict). **Reversibel.**
|
||||||
|
2. **MTP-Spec-Decoding für gemma:** 53 t/s → erwartet ~80–100 t/s (1,5–2×, 0 Qualitätsverlust). Drafter-GGUF
|
||||||
|
fehlt auf der Box **und MC2 kennt MTP-Drafter gar nicht** (nur klassisches `draft-simple`). **[mittel]**
|
||||||
|
3. **coder-Spec-Draft verifizieren:** `coder` (Qwen3-Coder-Next) hat `--spec-draft-model Qwen3-0.6B-Q8_0
|
||||||
|
--spec-type draft-simple` **aktiv** — ist dieser Draft wirklich vocab-kompatibel zu Qwen3-Coder-Next?
|
||||||
|
Wenn nein, bremst/bricht Spec still. **[Check]**
|
||||||
|
4. **Neues `scout`-Modell** (multimodaler Allrounder, Tools): HF-verifizierter Primärpick **GLM-4.6V-Flash (9B)**
|
||||||
|
— Q4 ~6 GB + mmproj, natives Tool-Calling, schlägt unser vision-Modell. Alt: Gemma-4-12B. (Qwen3.5-VL-MoE
|
||||||
|
verworfen — kein gepflegtes GGUF.) **[Recherche/Download]**
|
||||||
|
5. **gemma-ctx 128k → 64k:** *kleiner* Hebel (real nur ~2 GB, weil Gemmas KV winzig ist — s. §1.4),
|
||||||
|
trotzdem sinnvoll (freigegeben). Kein Funktions-Risiko für agentische Turns. **[risikoarm]**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. IST-Zustand (live verifiziert per SSH, 2026-06-30)
|
||||||
|
|
||||||
|
### 1.1 Rollen-Taxonomie — Code ist konsistent ✅
|
||||||
|
Der Brain-Rolle-Umbau aus `CLAUDE_CODE_BRIEF_lucy-brain-role.md` ist **bereits umgesetzt** (Commit `dd99401`).
|
||||||
|
`hermes` (= Lucys Hirn) ist als kanonische Rolle in ALLEN Quellen synchron: `llamaswap.ROLE_IDS`,
|
||||||
|
`sources.ROLE_IDS`+`CATEGORIES`, `roles.py` (`_capability_suit`/`_pref`/`_reason`), Frontend `ModelBadges.ROLES`.
|
||||||
|
Die „hermes-Altlast/Inkonsistenz" aus dem Brief existiert nicht mehr.
|
||||||
|
|
||||||
|
### 1.2 Brain-Warm-Flow — Code ist fertig ✅, Box bestätigt ✅
|
||||||
|
`POST /api/models/{id}/role role=hermes` → `agent.set_agent_brain()`: Alias + `brains:{swap:false,persist:true}`
|
||||||
|
+ ttl 0 (altes Hirn raus/entspannt) + Hermes `model.default` + Gateway-Restart + Budget-Warnung. `brain_status()`
|
||||||
|
prüft per `/running`.
|
||||||
|
**Box bestätigt das Akzeptanzkriterium:** `gemma-4-26B-A4B-it` ist `ready`, `ttl:0`, Alias `hermes`, und in
|
||||||
|
`groups.brains` (`swap:false, persist:true`). **Lucy bleibt warm — kein Kalt-Nachladen.** ✅
|
||||||
|
|
||||||
|
### 1.3 Live-Config aller Rollen (`/etc/llama-swap/config.yaml`, `globalTTL: 0`)
|
||||||
|
|
||||||
|
| Rolle (alias) | Modell | Gewichte (du) | ctx | ttl | Besonderheiten |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| fast | Qwen3.6-35B-A3B | 22 GB | 65536 | 0 | `--parallel 2`, mmproj, `--cache-reuse 256 -cram 16384`, **kein Spec**, **in `brains`** |
|
||||||
|
| heavy | Qwen3.5-122B-A10B | 73 GB | 32768 | 600 | split-GGUF, `--cache-reuse 256 -cram 16384`, kein Spec |
|
||||||
|
| coder | Qwen3-Coder-Next | 46 GB | 131072 | 600 | `--parallel 2`, **Spec aktiv** (Qwen3-0.6B-Q8 / draft-simple) |
|
||||||
|
| coder-lite | Qwen3-Coder-30B-A3B | 18 GB | 131072 | 300 | `--cache-reuse 256 -cram 16384` |
|
||||||
|
| vision | Qwen3-VL-8B-Instruct | 6 GB | 32768 | 300 | mmproj, **in `brains` (gepinnt!)** |
|
||||||
|
| embed | Qwen3-Embedding-0.6B | 1 GB | 8192 | 0 | `--embedding --pooling last`, **in `brains`** |
|
||||||
|
| **hermes** (Hirn) | **gemma-4-26B-A4B-it** | 17 GB (+1,2 mmproj) | **131072** | 0 | mmproj, `--jinja`, **kein Spec**, **in `brains`** |
|
||||||
|
|
||||||
|
`groups.brains = {swap:false, persist:true, members:[embedding, vision, fast, gemma]}`.
|
||||||
|
|
||||||
|
### 1.4 Speicher-Realität (GTT = 124 GB, `amdgpu.gttsize=126976`)
|
||||||
|
**Wichtige Korrektur ggü. der reinen `fit.py`-Schätzung:** mit NUR gemma@131072 geladen meldet die Box
|
||||||
|
**25,9 GB used** → gemma resident ≈ **~22 GB** (≈17 Gewichte + 1,2 mmproj + nur **~4 GB KV**). Gemma-4 nutzt
|
||||||
|
starke Sliding-Window-Attention → KV ist viel kleiner, als `fit.py` (kalibriert an Hermes-14B) vorhersagt.
|
||||||
|
**Folge:** gemma-ctx-Reduktion spart real nur ~2 GB; der echte Druck kommt vom **Warm-Set**, nicht vom Hirn-ctx.
|
||||||
|
|
||||||
|
**llama-swap-Gruppen-Semantik (live verifiziert, korrigiert frühere Annahme):** Eine `swap:false`-Gruppe hält
|
||||||
|
NUR ihre EIGENEN Member ko-resident. Ein Modell in einer ANDEREN Gruppe (heavy/coder = je eigene implizite
|
||||||
|
Gruppe) zu laden **swappt die gesamte aktive Gruppe raus** und lädt das neue Modell allein. `persist:true`
|
||||||
|
verhindert nur das Idle-TTL-Entladen, NICHT das Gruppen-Swapping. **Folge:** es gibt **kein** OOM durch
|
||||||
|
Ko-Laden (heavy lädt allein, 79 GB < 124 ✓) — aber das Hirn ist bei cross-group Loads (heavy/coder) **nicht**
|
||||||
|
geschützt: es wird mitgeswappt und lädt danach kalt nach (~Sekunden). Beweis: heavy laden → `/running` zeigte
|
||||||
|
nur heavy (78,7 GB), gemma weg trotz persist.
|
||||||
|
|
||||||
|
**Konsequenz fürs Design (umgesetzt):** Lucys zusammengehöriges Set (Hirn + Gedächtnis + Sehen) MUSS in DERSELBEN
|
||||||
|
`swap:false`-Gruppe stehen, sonst verdrängt schon Lucys eigenes Sehen ihr Hirn. → `brains = gemma + embed +
|
||||||
|
vision` (~31 GB). heavy/coder/coder-lite/fast = on-demand (swappen die brains-Gruppe beim Laden raus — ok, das
|
||||||
|
sind separate Aktivitäten IDE/Delegation; Lucy lädt danach kurz nach).
|
||||||
|
|
||||||
|
### 1.5 Durchsatz (live gemessen)
|
||||||
|
gemma (Hirn): **~53 t/s** Generierung, ~173 t/s Prompt (warm, kurzer Prompt). Solide Basis für ein 26B-A4B
|
||||||
|
(4B aktiv) auf der bandbreitenlimitierten APU; MTP-Ziel ~80–100 t/s.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Verifikations-Status (war §2 „offen" — jetzt aufgelöst)
|
||||||
|
- **V1 (gemma in `brains:{swap:false}`?)** → **JA** ✅ (Akzeptanzkriterium erfüllt).
|
||||||
|
- **V2 (t/s)** → gemma 53 t/s gemessen ✅. fast/heavy/coder noch nicht gemessen (Laden würde Warm-Set stören).
|
||||||
|
- **V3 (Voll-Config)** → komplett gelesen (§1.3) ✅.
|
||||||
|
- **V4 (GTT/Belegung)** → GTT 124 GB, gemma-only 25,9 GB used ✅. Cross-group-Load swappt Gruppen (kein OOM,
|
||||||
|
aber Hirn ungeschützt) — live verifiziert (§1.4). Behoben durch W1/W2.
|
||||||
|
|
||||||
|
- **V5 (coder-Spec-Draft)** → **AUFGELÖST ✅**: Qwen3-0.6B-Q8 ↔ Qwen3-Coder-Next = gleicher `tokens_sha
|
||||||
|
facf459…`, n_vocab 151936, `compatible: True`. coder-Spec ist gültig & aktiv. (Bonus-Opportunität §9.2 F4.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Maßnahmen je Rolle
|
||||||
|
|
||||||
|
### 3.1 ✅ Warm-Set / KO-Residenz — ERLEDIGT (2026-06-30, live verifiziert)
|
||||||
|
| # | Maßnahme | Status / Begründung |
|
||||||
|
|---|---|---|
|
||||||
|
| W1/W2 | **`brains:{swap:false,persist:true}` = gemma + embedding + vision** (~31 GB); fast raus (Chat-Lane-Hirn, kein Lucy-Nutzen, ~28 GB gespart) | ✅ angewendet via `PUT /api/groups` (Backup `config.yaml.bak-20260630-152911`). **Verifiziert:** gemma & vision ko-resident, kein Evict (37 GB used); gemma bleibt `ready`. |
|
||||||
|
| — | heavy/coder/coder-lite/fast = on-demand | Korrekt: kein OOM (Gruppen-Swap), Lucys Set bleibt zusammen warm. Trade-off: nach heavy-Delegation / IDE-Coding lädt Lucy kurz nach (~s). |
|
||||||
|
| Optional | fast wieder pinnen, falls Chat-Lane dauerwarm sein soll | +28 GB Pin, swappt aber bei jedem heavy/coder-Load eh raus → geringer Nutzen. Auf Wunsch nachrüstbar. |
|
||||||
|
|
||||||
|
### 3.2 `hermes` / Lucys Hirn — gemma-4-26B-A4B-it
|
||||||
|
| # | Maßnahme | Begründung / Zahlen | Risiko | Revert |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| H1 | **ctx 131072 → 65536** | Spart real nur ~2 GB (Gemma-KV winzig), aber konsistent mit `fast` ([[hermes-fast-context-blocker]]) und Agent-Turns brauchen selten >64k. | niedrig | ctx zurück |
|
||||||
|
| H2 | **MTP-Spec-Decoding** (s. §4) | 53 → ~80–100 t/s. Drafter ~0,4 B → Budget vernachlässigbar. | mittel | Spec-Flags raus |
|
||||||
|
| H3 | gemma sollte ggf. `--cache-reuse 256` bekommen (wie fast/heavy/coder) — fehlt aktuell | KV-Reuse über Turns → weniger Prompt-Reprocessing bei Agent-Ketten. | niedrig | Flag raus |
|
||||||
|
|
||||||
|
### 3.3 `fast` — Qwen3.6-35B-A3B (chat-Lane)
|
||||||
|
- ctx 65536, `--parallel 2`, **kein Spec** (gut — vermeidet den qwen2.5-Vocab-Bruch). **Belassen**, aber W2 (raus
|
||||||
|
aus dem harten Pin). Optional Qwen-Spec-Draft nur, wenn vocab-kompatibel zu Qwen3.6 (Check via `gguf_meta`).
|
||||||
|
|
||||||
|
### 3.4 `heavy` — Qwen3.5-122B-A10B
|
||||||
|
- 73 GB, ctx 32768, ttl 600, on-demand. **Belassen.** Profitiert direkt von W1/W2 (kann wieder laden). Kein Spec
|
||||||
|
(kein etablierter kompatibler Qwen3.5-Draft) — erst prüfen, sonst lassen.
|
||||||
|
|
||||||
|
### 3.5 `coder` / `coder-lite`
|
||||||
|
- **coder** (Qwen3-Coder-Next, 46 GB, ctx 131072): **Spec aktiv mit Qwen3-0.6B-Q8** → **V5: Vocab-Kompatibilität
|
||||||
|
verifizieren.** Falls inkompatibel → Draft entfernen (still kein Speed-Gewinn, evtl. Fehlerquelle).
|
||||||
|
- **coder-lite** (Qwen3-Coder-30B-A3B, 18 GB, ctx 131072): coding-Lane-Default, schnell (3B aktiv). **Belassen.**
|
||||||
|
Hinweis: Alias heißt `coder-lite` (Bindestrich) — die Lanes referenzieren `coder_lite`; sicherstellen, dass das
|
||||||
|
Routing (`router_logic.py`/`routing_policy.py`) auf den richtigen Alias zeigt (Konsistenz-Check, kein Box-Risiko).
|
||||||
|
|
||||||
|
### 3.6 `vision` — Qwen3-VL-8B-Instruct
|
||||||
|
- 6 GB, ctx 32768. **Belassen**, aber W2 (verdrängbar statt hart gepinnt). Sehen ist bursty → on-demand/warm-soft reicht.
|
||||||
|
|
||||||
|
### 3.7 `embed` — Qwen3-Embedding-0.6B
|
||||||
|
- 1 GB, ttl 0, **bleibt hart gepinnt** (W1). Unantastbar — Mem0/Gedächtnis hängt dran.
|
||||||
|
|
||||||
|
### 3.8 `scout` — VAKANT → §5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. MTP-Speculative-Decoding für gemma (Backend+UI-Erweiterung)
|
||||||
|
**Problem:** MC2 kennt nur **klassische** Drafts. `spec_draft_flags()` schreibt `--spec-draft-model <d>
|
||||||
|
--spec-type draft-simple`; `find_compatible_draft()` scannt nur `DRAFTS_DIR` mit stumpfem
|
||||||
|
`gguf_meta.compatible()`. Ein **MTP-Kopf** (`gemma-4-26B-A4B-it-assistant`, Arch `gemma4_assistant`) wird nie
|
||||||
|
angeboten. Engine 9843 kann `--spec-type draft-mtp` ✓; alte `--draft-max/-min` sind **entfernt** →
|
||||||
|
`--spec-draft-n-max/-n-min`.
|
||||||
|
|
||||||
|
**Schritte:**
|
||||||
|
1. **Drafter beschaffen** (Box, fehlt noch) — ⚠️ **Brief-Korrektur (HF-verifiziert 2026-06-30):** der Drafter
|
||||||
|
heißt NICHT `…-assistant`. In `unsloth/gemma-4-26B-A4B-it-GGUF` liegt er als **`mtp-gemma-4-26B-A4B-it.gguf`**
|
||||||
|
(Repo-Root, **0,46 GB**) bzw. **`MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf`** (0,46 GB; BF16/F16-Varianten 0,86 GB).
|
||||||
|
Eine Datei nach `/srv/models/gemma-4-26B-A4B-it-GGUF/` laden (Q8-MTP = kleinste, ~0,46 GB).
|
||||||
|
**Flags (laut unsloth MTP/README):** `--model-draft <mtp.gguf> --spec-type draft-mtp --spec-draft-n-max 4`
|
||||||
|
(kein `--spec-draft-n-min` nötig; neuere llama.cpp **auto-entdeckt** die Root-`mtp-*.gguf`). `-md` = Kurzform.
|
||||||
|
2. **Backend (`llamaswap.py`):** MTP-Pfad ergänzen — MTP-Drafter als gültigen, vocab-kompatiblen Draft erkennen
|
||||||
|
(Arch/Name `*-assistant*`); beim Aktivieren `-md <assistant.gguf> --spec-type draft-mtp
|
||||||
|
--spec-draft-n-max N --spec-draft-n-min M` schreiben (statt `--spec-draft-model`/`draft-simple`). `_parse_model`
|
||||||
|
um `draft-mtp`/`-md` erweitern (UI-Status). Flag-Namen mit `llama-server --help` (9843) gegenprüfen.
|
||||||
|
3. **Frontend:** Draft-Modal MTP-Drafter als empfohlene Option für gemma führen („MTP-Kopf, vocab-kompatibel ✓").
|
||||||
|
4. **Verifizieren:** t/s vor/nach (Basis 53 t/s) gegen die Box.
|
||||||
|
|
||||||
|
> Risiko mittel (neue Flag-Logik); voll reversibel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Neues `scout`-Modell (Empfehlung)
|
||||||
|
Profil: multimodaler Allrounder, klein/MoE (schnell auf bandbreitenlimitierter Box), Tool-Calling,
|
||||||
|
KO-Residenz-freundlich, **distinkt** von Hirn (gemma-4) und vision (Qwen3-VL-8B).
|
||||||
|
|
||||||
|
**HF-verifiziert 2026-06-30** (Verfügbarkeit + Dateigrößen real geprüft, nicht geraten):
|
||||||
|
|
||||||
|
| Pick | Modell | Verifizierte Fakten | Hinweis |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Primär** | **GLM-4.6V-Flash (9B)** | GGUF da: `unsloth/GLM-4.6V-Flash-GGUF` (29 Quants, 12k+ dl), `lmstudio-community`, `MaziyarPanahi`. **Q4_K_M = 6,17 GB + mmproj 1,84 GB ≈ 8 GB.** Natives multimodales Function-Calling, 128k ctx. Benchmarks: **schlägt Qwen3-VL-8B** (= unser aktuelles vision-Modell) in fast allen Kategorien. | **9B dense** (nicht MoE), aber bei 9B/6 GB trotzdem schnell. **on-demand** (nicht pinnen). Könnte perspektivisch sogar `vision` ablösen. |
|
||||||
|
| Alt A | **Gemma-4-12B-it (dense, multimodal)** | GGUF breit verfügbar: `unsloth/gemma-4-12b-it-GGUF` (1,4 Mio dl), `google/…-qat`, `lmstudio`. | dense → auf Bandbreiten-Box etwas langsamer; Familien-Dopplung mit dem Hirn (auch Gemma-4). |
|
||||||
|
| ~~Alt B~~ | ~~Qwen3.5-VL-MoE~~ | **VERWORFEN:** kein gut gepflegtes GGUF — nur EIN Nischen-Repo (`jc-builds/Qwen3.5-9B-VLM-Q4_K_M`, kein Trusted-Author). Meine frühere „familien-treu"-Empfehlung war ungedeckt. | nicht nehmen. |
|
||||||
|
|
||||||
|
**Vorgehen:** GLM-4.6V-Flash (Q4_K_M ~6 GB + mmproj) via „Modelle finden"/`/api/models/install` laden → Rolle
|
||||||
|
`scout`, **on-demand** → Mini-Benchmark (t/s + Vision+Tool-Prompt). Quellen (tagesaktuell, 2026-06):
|
||||||
|
[VentureBeat: GLM-4.6V native tool-calling](https://venturebeat.com/ai/z-ai-debuts-open-source-glm-4-6v-a-native-tool-calling-vision-model-for) ·
|
||||||
|
[zai-org/GLM-4.6V-Flash (HF)](https://huggingface.co/zai-org/GLM-4.6V-Flash) ·
|
||||||
|
[GLM-4.6V Flash 9B Review/Benchmarks](https://binaryverseai.com/glm-4-6v-review-benchmarks-pricing-local-install/) ·
|
||||||
|
[SiliconFlow: schnellste OSS-Multimodal 2026](https://www.siliconflow.com/articles/en/fastest-open-source-multimodal-models).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. KO-Residenz-Set (umgesetzt, live verifiziert)
|
||||||
|
**Lucys Warm-Set (`brains:{swap:false,persist:true}`):** gemma-Hirn + embedding + vision = **~31 GB.**
|
||||||
|
Diese drei MÜSSEN zusammen in einer Gruppe sein (sonst verdrängt Lucys Sehen ihr eigenes Hirn — Gruppen-Swap).
|
||||||
|
**Rein on-demand (swappen die brains-Gruppe beim Laden raus):** fast, heavy, coder, coder-lite, scout.
|
||||||
|
|
||||||
|
**Verifiziert:** gemma+vision ko-resident (37 GB, kein Evict); heavy-Load swappt die Gruppe (kein OOM, 79 GB).
|
||||||
|
**Hinweis `budget.reserved_gb`:** das Modul nimmt an, fast/vision seien verdrängbar persist-Member, die NEBEN
|
||||||
|
dem Hirn warm bleiben. Real swappt llama-swap aber die ganze Gruppe → die `reserved_gb`-Mathematik ist für
|
||||||
|
cross-group-Loads **zu konservativ** (rechnet Hirn+Rest gleichzeitig, was nie passiert). **Folge-Task (Code):**
|
||||||
|
`budget.reserved_gb` an die echte Gruppen-Swap-Semantik angleichen (nur das brains-Set zählt als resident,
|
||||||
|
on-demand-Modelle laufen allein). Kein Box-Risiko, nur genauere ctx-Empfehlungen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Reihenfolge / Risiko / Revert
|
||||||
|
| Schritt | Aktion | Risiko | Revert |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 | ✅ **Box-Backup** `config.yaml.bak-20260630-152911` | – | – |
|
||||||
|
| 1 | ✅ **W1+W2** brains = gemma+embed+vision (via `PUT /api/groups`), verifiziert | niedrig | `set_group` zurück |
|
||||||
|
| 2 | **H1** gemma ctx → 65536; **H3** `--cache-reuse 256` ergänzen | niedrig | ctx/Flag zurück |
|
||||||
|
| 3 | **V5** coder-Draft-Vocab prüfen; inaktive Spec entfernen | niedrig | – |
|
||||||
|
| 4 | **§4** MTP: Drafter laden → Backend/UI-Erweiterung → gemma-cmd → t/s-Vergleich | mittel | Spec-Flags raus |
|
||||||
|
| 5 | **§5** scout recherchieren/installieren/zuweisen (on-demand) | niedrig | Modell löschen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Offene Entscheidungen (für dich)
|
||||||
|
1. **Warm-Set-Fix (W1/W2) jetzt umsetzen?** — behebt die heavy-OOM-Gefahr, mein klare Empfehlung als Erstes.
|
||||||
|
2. **fast/vision verdrängbar** via eigener Gruppe `swap:true` (sie swappen sich gegenseitig) **oder** schlicht
|
||||||
|
`persist:false`? (Detail des Umbaus.)
|
||||||
|
3. **MTP jetzt oder als eigener Block?** (Code-Erweiterung Backend+UI + Drafter-Download.)
|
||||||
|
4. **scout-Pick:** GLM-4.6V (Primär) oder familien-treu Qwen3.5-VL-MoE?
|
||||||
|
5. gemma-ctx **65536** ist gesetzt (deine Wahl) — ok so.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Gesamtbild: Engine- (llama.cpp) & Router- (llama-swap) Flags — verifiziert gegen Build 9843
|
||||||
|
> Der Brief deckte das nicht ab. Alle Aussagen hier gegen `llama-server --help` (9843) + Live-Config geprüft.
|
||||||
|
|
||||||
|
### 9.1 Cross-cutting (alle Chat-Modelle): `-ngl 999 -fa 1 --no-mmap -c <ctx> --jinja`
|
||||||
|
- `-ngl 999` ✅ korrekt (volle Offload-Last in GTT auf der APU).
|
||||||
|
- `-fa 1` → funktioniert, ist aber **Legacy-Form**. Aktuell: `-fa [on|off|auto]` (Default `auto`). Auf `-fa on`
|
||||||
|
normalisieren (oder weglassen → auto), sonst Risiko bei künftigem Build. **[niedrig]**
|
||||||
|
- `--no-mmap` ✅ bewusst (volle RAM-Residenz statt file-backed Pages; sinnvoll für warm gehaltene Modelle auf
|
||||||
|
Unified Memory). Behalten.
|
||||||
|
|
||||||
|
### 9.2 Pro-Modell-Befunde & Hebel
|
||||||
|
| # | Befund | Hebel | Prio |
|
||||||
|
|---|---|---|---|
|
||||||
|
| F1 | **gemma (Hirn) fehlt `--cache-reuse 256 -cram 16384`** — der Agent macht die meiste Multi-Turn-/Tool-Arbeit, nutzt aber nur den 8 GB-Default-Cache & **kein KV-Reuse über Turns** | ergänzen → System-Prompt + History-KV werden wiederverwendet, weniger Prompt-Reprocessing, schnellere Folge-Antworten | **hoch (Quick-Win)** |
|
||||||
|
| F2 | **`--parallel 2` (fast/coder) ↔ Kontext:** unified KV ist „enabled if slots **auto**" — mit explizitem `--parallel 2` evtl. AUS → harte ctx-Teilung (fast 32k/Anfrage, coder 65k/Anfrage). `-cram` SOLL unified erzwingen, Help mehrdeutig | **V6:** `n_ctx_per_seq` aus `/props` verifizieren (beim nächsten Load). Dann: `--parallel 1`/auto (volle ctx, wenn keine Nebenläufigkeit) **oder** explizit `-kvu` setzen | mittel |
|
||||||
|
| F3 | **KV-Quant `-ctk q8_0 -ctv q8_0` nirgends** — v.a. coder/coder-lite @131072 tragen große KV | für die langen Coding-Kontexte testen → ~halbe KV bei min. Qualitätsverlust (FA-Interaktion prüfen) | mittel |
|
||||||
|
| F4 | **coder-Spec ✅ kompatibel** (verifiziert). Derselbe Draft (`tokens_sha facf459`) ist evtl. auch zu **heavy (122B-A10B)** & coder-lite kompatibel | Spec-Decoding für **heavy** prüfen → 10B aktiv, dense-artig → spürbarer Durchsatz möglich. Via `/api/models/drafts?target=<heavy.gguf>` verifizieren | mittel |
|
||||||
|
| F5 | **fast trägt `--mmproj`** (ist vision-fähig) — bewusst? +~1 GB | wenn die Chat-Lane nie Bilder bekommt: mmproj sparen | niedrig |
|
||||||
|
| F6 | `-b/-ub` (batch/ubatch), `-t` (threads) ungenutzt = Defaults | Prompt-Speed evtl. via `-ub` tunbar — **messen statt raten**, niedrige Prio | niedrig |
|
||||||
|
|
||||||
|
### 9.3 llama-swap (Router-Ebene)
|
||||||
|
- `globalTTL: 0` ✅ ok (Modelle haben überwiegend explizite ttl). `healthCheckTimeout: 300` ✅ reicht auch für
|
||||||
|
heavy (~60 s Load gemessen). Gruppen-Swap-Semantik verstanden (§1.4), brains gefixt (§3.1).
|
||||||
|
- **Systemischer Befund (Code↔Box-Drift):** MC2 `config._DEFAULT_CMD_TEMPLATE` =
|
||||||
|
`llama-server -m {model} --host … --port ${PORT} -c {ctx} -ngl 999 -fa 1 --no-mmap` — kennt **KEINE** der auf
|
||||||
|
der Box manuell ergänzten Optimierungen (`--cache-reuse`, `-cram`, `--parallel`, KV-Quant). **Folge: neu über
|
||||||
|
MC2 installierte Modelle bekommen diese Tunings NICHT automatisch** → die Box driftet von der Code-Quelle weg.
|
||||||
|
**Fix (Code):** sinnvolle Defaults ins Template / `register_model` (rollenabhängig), damit „Modelle finden"
|
||||||
|
schon optimiert registriert. Optional: llama-swap `macros` für wiederholte Flag-Blöcke (Wartbarkeit). **[mittel]**
|
||||||
|
|
||||||
|
### 9.4 Verifikationspunkte — aufgelöst (2026-06-30)
|
||||||
|
- **V6 ✅:** fast-Upstream `/props`: `total_slots:2`, **per-slot n_ctx 32768** → `--parallel 2` teilt den Kontext
|
||||||
|
HART (kein unified-Sharing trotz `-cram`). **Maßnahme F2 angewendet:** fast → `--parallel 1` = **per-slot
|
||||||
|
n_ctx 65536** (verifiziert). coder bleibt `--parallel 2` (IDE-Concurrency, 65k/Req).
|
||||||
|
- **V7 ✅:** Qwen3-0.6B-Draft (`tokens_sha facf459`) → **heavy INKOMPATIBEL** (`a5e1ccff`, kein Spec möglich),
|
||||||
|
**coder-lite KOMPATIBEL** (gleiche sha; aber MoE 3B-aktiv → Spec-Gewinn gering, vor Aktivierung benchmarken).
|
||||||
|
|
||||||
|
### Umsetzungs-Log (Box, 2026-06-30, Backups vorhanden)
|
||||||
|
- ✅ **W1/W2** brains = gemma+embed+vision (`config.yaml.bak-20260630-152911`).
|
||||||
|
- ✅ **gemma F1+H1+fa-on**: ctx 131072→65536, `-fa 1`→`-fa on`, `+ --cache-reuse 256 -cram 16384`
|
||||||
|
(`bak-20260630-154842`). Verifiziert: ready, ~52 t/s, 24,4 GB.
|
||||||
|
- ✅ **F2** fast `--parallel 2`→`1` (voller 65k-Kontext, verifiziert).
|
||||||
|
- ✅ **MTP für gemma** (`bak-20260630-155520`): Drafter `mtp-gemma-4-26B-A4B-it.gguf` (461 MB) geladen,
|
||||||
|
cmd `+ --model-draft … --spec-type draft-mtp --spec-draft-n-max 4`. **Verifiziert: 52 → 70,8 t/s (≈1,36×)**,
|
||||||
|
Draft-Akzeptanz ~50 %, kein Crash, ready.
|
||||||
|
- ✅ **scout** GLM-4.6V-Flash installiert (Q4 6,17 GB + mmproj 1,84 GB), role=scout, ttl 300, on-demand.
|
||||||
|
Verifiziert: lädt sauber (neue GLM-4.6V-Arch auf Engine 9843), 34,6 t/s, reasoniert korrekt
|
||||||
|
(`reasoning_content`). **Hinweis:** ist ein *Thinking*-Modell → höhere Latenz für Kurzantworten; Vision
|
||||||
|
(mmproj) geladen, aber Bild-Eingabe noch nicht getestet. Ggf. No-Think-Modus prüfen, falls als schneller
|
||||||
|
Allrounder gewünscht.
|
||||||
|
|
||||||
|
### Code-Änderungen (Dev-Repo F:\, **noch nicht deployt**)
|
||||||
|
- ✅ **MC2 MTP-Draft-Support** (Brief-Deliverable). Verifiziert: py_compile OK, Logik-Test gegen echte GGUF,
|
||||||
|
Frontend `tsc --noEmit` exit 0.
|
||||||
|
- `config.py`: `SPEC_DRAFT_N_MAX` (Default 4).
|
||||||
|
- `services/llamaswap.py`: `_is_mtp_draft`, `_spec_flags_for_draft`, `_sibling_mtp_drafters` (findet MTP-Köpfe
|
||||||
|
NEBEN dem Modell); `find_compatible_draft`/`spec_draft_flags`/`drafts_for`/`set_spec_draft` MTP-bewusst;
|
||||||
|
`_parse_model` erkennt `--model-draft`/`-md`. Backward-kompatibel (klassische Drafts unverändert).
|
||||||
|
- Frontend `api.ts` (`DraftInfo.mtp`), `SpecDraftModal.tsx` (MTP-Badge). → gemmas MTP-Drafter erscheint im
|
||||||
|
Spec-Modal automatisch als kompatibel + aktivierbar, schreibt die korrekten `draft-mtp`-Flags.
|
||||||
|
- ✅ **CMD-Template-Drift** (`register_model`): `--cache-reuse 256 -cram 16384` als Default für alle
|
||||||
|
Template-Modelle; `--parallel 2` nur noch für `coder` (kein ctx-Halbierungs-Footgun mehr); Spec-Auto-Attach
|
||||||
|
MTP-bewusst + für alle Rollen self-guarding. Verifiziert: py_compile OK.
|
||||||
|
- ✅ **`budget.reserved_gb`** an Gruppen-Swap-Semantik angeglichen (`_coresident_members` = `swap:false`-Set;
|
||||||
|
on-demand = läuft allein → reserviert 0, voller GTT; brains-Member = reserviert die übrigen Member).
|
||||||
|
Verifiziert gegen echte Config: hermes→16,2 GB, heavy/coder/scout→0 GB (ondemand-alone). Tote
|
||||||
|
`_persist_members` entfernt.
|
||||||
|
- ⬜ **Deploy:** Backend+Frontend müssen auf die Box (git push → box pull → `restart mission-control-2` +
|
||||||
|
Frontend-Build). Box-`config.yaml`-Tunings sind davon unabhängig und bereits live.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Leitplanken (eingehalten)
|
||||||
|
- Features (Sehen/Hören/Sprechen/Embedding/Gedächtnis) + IDE-Lanes (chat/coding) bleiben funktionsfähig —
|
||||||
|
W1/W2 macht sie sogar robuster (kein OOM mehr).
|
||||||
|
- Vor jeder Box-Config-Änderung Backup; llama-swap reloadt per `-watch-config`.
|
||||||
|
- **Keine destruktiven Aktionen ohne Freigabe dieses Plans.**
|
||||||
|
</content>
|
||||||
@@ -112,7 +112,17 @@ export function SpecDraftModal({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-[11px] font-mono font-semibold text-foreground truncate">{d.filename}</div>
|
<div className="text-[11px] font-mono font-semibold text-foreground truncate flex items-center gap-1.5">
|
||||||
|
{d.filename}
|
||||||
|
{d.mtp && (
|
||||||
|
<span
|
||||||
|
className="shrink-0 rounded bg-indigo-500/15 px-1.5 py-0.5 text-[8px] font-bold uppercase tracking-wider text-indigo-400 border border-indigo-500/25"
|
||||||
|
title="MTP-Kopf (Multi-Token-Prediction) — by-construction vocab-identisch, höchste Akzeptanzrate"
|
||||||
|
>
|
||||||
|
MTP
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="text-[9px] text-muted-foreground font-mono">
|
<div className="text-[9px] text-muted-foreground font-mono">
|
||||||
{fmtSize(d.size_bytes)} · Vocab: {vocabLabel(d.vocab)}
|
{fmtSize(d.size_bytes)} · Vocab: {vocabLabel(d.vocab)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export interface DraftInfo {
|
|||||||
size_bytes: number | null
|
size_bytes: number | null
|
||||||
vocab: VocabFingerprint | null
|
vocab: VocabFingerprint | null
|
||||||
compatible: boolean | null // null = nicht prüfbar (Ziel-GGUF fehlt)
|
compatible: boolean | null // null = nicht prüfbar (Ziel-GGUF fehlt)
|
||||||
|
mtp?: boolean // MTP-Kopf (Multi-Token-Prediction) statt klassischem Draft
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DraftsResp {
|
export interface DraftsResp {
|
||||||
|
|||||||
Reference in New Issue
Block a user