Fix: Modell-Upgrades nur bei ECHTEM Upgrade (gleiche Familie + neuere Gen/groesser)
model_upgrades verletzte das Prinzip: schlug Generations-Downgrades (Qwen2.5-VL ueber Qwen3-VL), Groessen-Downgrades (Qwen3-Coder-Next ~84B -> 30B; Params aus Name als 7B fehlgeschaetzt) und Fremd-Familien-Swaps (gpt-oss als Qwen-"Upgrade") vor. - _params_of: groessen-bewusste Params (max aus Name + Dateigroesse). - _gen_key: Familie+Subtyp+Generation aus dem Namen (qwen-vl 3.0 vs 2.5 etc.). - Guard: Upgrade nur bei gleicher erkennbarer Familie UND (neuere Generation ODER deutlich groesser in gleicher Gen). Sonst keine "Bessere Version"-Anzeige. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -154,48 +154,77 @@ def _components_cached() -> list[dict]:
|
||||
return data
|
||||
|
||||
|
||||
def _params_of(m: dict) -> float:
|
||||
"""Größen-bewusste Parameterzahl eines installierten Modells: max aus Namens-Schätzung
|
||||
und Dateigröße (fängt namenlose wie 'Qwen3-Coder-Next' UND Split-GGUFs ab)."""
|
||||
from services.fit import QUANT_BYTES_PER_PARAM
|
||||
caps = m.get("capabilities") or {}
|
||||
bpp = QUANT_BYTES_PER_PARAM.get((m.get("quant") or "Q4_K_M").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
|
||||
return max(float(caps.get("params_b") or 0), pb_size, 0.0)
|
||||
|
||||
|
||||
# Familien-Subtyp + Generations-Version aus dem Modellnamen (für „echtes Upgrade?").
|
||||
_FAM_PATS = (("qwen", r"qwen(\d+(?:\.\d+)?)"), ("gemma", r"gemma[-_ ]?(\d+(?:\.\d+)?)"),
|
||||
("llama", r"llama[-_ ]?(\d+(?:\.\d+)?)"), ("phi", r"phi[-_ ]?(\d+(?:\.\d+)?)"),
|
||||
("mistral", r"mistral"), ("hermes", r"hermes[-_ ]?(\d+(?:\.\d+)?)"))
|
||||
|
||||
|
||||
def _gen_key(name: str):
|
||||
"""(Familie+Subtyp, Generations-Version) oder None. Z.B. 'Qwen3-VL-2B' → ('qwen-vl', 3.0),
|
||||
'Qwen2.5-VL-7B' → ('qwen-vl', 2.5). Nur gleiche Familie ist sinnvoll vergleichbar."""
|
||||
low = (name or "").lower()
|
||||
sub = "-vl" if any(k in low for k in ("-vl", "vl-", "vision", "llava", "pixtral")) else \
|
||||
"-coder" if ("coder" in low or "-code" in low) else ""
|
||||
for fam, pat in _FAM_PATS:
|
||||
m = re.search(pat, low)
|
||||
if m:
|
||||
ver = float(m.group(1)) if (m.groups() and m.group(1)) else 0.0
|
||||
return (fam + sub, ver)
|
||||
return None
|
||||
|
||||
|
||||
def model_upgrades() -> list[dict]:
|
||||
"""Dynamisch: je discover-Kategorie das empfohlene Modell, das NOCH NICHT installiert ist,
|
||||
aber NUR wenn für diese Rolle bereits irgendein Modell konfiguriert ist.
|
||||
→ Upgrade-Vorschlag für diese Rolle. Self-updating (kein Hardcode wie v1)."""
|
||||
"""Je Rolle ein ECHTES Upgrade-Vorschlag — nur wenn die Empfehlung wirklich besser ist:
|
||||
gleiche Familie UND (neuere Generation ODER größer in gleicher Generation). Verhindert
|
||||
Generations-Downgrades (Qwen2.5-VL über Qwen3-VL), Größen-Downgrades (Coder-Next → 30B)
|
||||
und Fremd-Familien-Swaps (gpt-oss als „Upgrade" von Qwen)."""
|
||||
disc = discover.safe_discover(_ram_gb())
|
||||
if not disc:
|
||||
return []
|
||||
|
||||
installed = llamaswap.list_models()
|
||||
active_roles = {m["role"] for m in installed if m.get("role")}
|
||||
# Größtes installiertes Modell je Rolle (params_b) — Basis für „kein Downgrade".
|
||||
inst_params: dict[str, float] = {}
|
||||
for m in installed:
|
||||
r, pb = m.get("role"), (m.get("capabilities") or {}).get("params_b")
|
||||
if r and pb:
|
||||
inst_params[r] = max(inst_params.get(r, 0.0), float(pb))
|
||||
|
||||
inst_by_role = {m["role"]: m for m in installed if m.get("role")}
|
||||
cmds = " ".join(str(s.get("cmd", "")).lower()
|
||||
for s in (llamaswap.read_config().get("models") or {}).values())
|
||||
out = []
|
||||
|
||||
# Discover-Rollen == Serving-Rollen (fast/heavy/coder/vision/scout) → kein Mapping mehr.
|
||||
for c in disc.get("categories", []):
|
||||
role = c["role"]
|
||||
if role not in active_roles: # nur Rollen, die bereits ein Modell haben
|
||||
im = inst_by_role.get(role)
|
||||
if im is None: # nur Rollen mit bereits installiertem Modell
|
||||
continue
|
||||
|
||||
rec = c.get("recommended")
|
||||
if not rec:
|
||||
continue
|
||||
# KEIN Downgrade: Empfehlung nur, wenn sie mind. so „groß"/fähig ist wie das
|
||||
# installierte Modell der Rolle (verhindert z.B. fast 35B-A3B → 4B).
|
||||
rec_model = next((x for x in c.get("models", []) if x.get("repo") == rec), None)
|
||||
rec_params = float((rec_model or {}).get("params_b") or 0.0)
|
||||
cur = inst_params.get(role, 0.0)
|
||||
if cur and rec_params and rec_params < cur * 0.95:
|
||||
continue
|
||||
inst_params = _params_of(im)
|
||||
|
||||
ig, rg = _gen_key(im["name"]), _gen_key(rec)
|
||||
# Upgrade nur bei GLEICHER, erkennbarer Familie:
|
||||
if not (ig and rg) or ig[0] != rg[0]:
|
||||
continue # Fremd-Familie / unbekannt → kein „Upgrade"
|
||||
if rg[1] < ig[1] - 1e-6:
|
||||
continue # ältere Generation → niemals
|
||||
if abs(rg[1] - ig[1]) < 1e-6 and rec_params and rec_params < inst_params * 1.05:
|
||||
continue # gleiche Gen, nicht (deutlich) größer
|
||||
|
||||
base = rec.split("/")[-1].lower()
|
||||
stem = base[:-5] if base.endswith("-gguf") else base
|
||||
if base in cmds or (stem and stem in cmds):
|
||||
continue
|
||||
continue # schon installiert
|
||||
out.append({"role": role, "title": c["title"], "repo": rec})
|
||||
return out
|
||||
|
||||
|
||||
Reference in New Issue
Block a user