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:
+15
-12
@@ -5,8 +5,8 @@ from fastapi import APIRouter, HTTPException
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from config import HF_DOWNLOAD_ENV, MODELS_DIR
|
from config import HF_DOWNLOAD_ENV, MODELS_DIR
|
||||||
from services import catalog, discover, hf, jobengine, llamaswap
|
from services import budget, discover, hf, jobengine, llamaswap
|
||||||
from services.fit import evaluate_fit, extract_params_b, max_ctx_for
|
from services.fit import evaluate_fit, max_ctx_for
|
||||||
|
|
||||||
router = APIRouter(prefix="/api")
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
@@ -31,19 +31,22 @@ def discover_models(force: bool = False) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/fit")
|
@router.get("/fit")
|
||||||
def fit(params_b: float = 0, quant: str = "Q4_K_M", ctx: int = 8192, name: str = "") -> dict:
|
def fit(params_b: float = 0, quant: str = "Q4_K_M", ctx: int = 8192,
|
||||||
|
name: str = "", role: str = "") -> dict:
|
||||||
"""Hardware-Fit-Vorschau. params_b<=0 → aus KATALOG (echte Metadaten, MoE-bewusst)
|
"""Hardware-Fit-Vorschau. params_b<=0 → aus KATALOG (echte Metadaten, MoE-bewusst)
|
||||||
oder sonst aus dem Namen geschätzt. So liefert die 'Erweiterte Ansicht' eine
|
oder sonst aus dem Namen geschätzt. assigned_ctx = der ctx, der TATSÄCHLICH vergeben
|
||||||
Ampel/t-s-Schätzung für beliebige HF-Repos, bevor heruntergeladen wird."""
|
würde: SETUP-BEWUSST (neben Hirn/warmem Set), nicht nur gegen den Gesamt-RAM.
|
||||||
|
So sieht die 'Erweiterte Ansicht' vor dem Download Ampel + echten ctx."""
|
||||||
ram = _ram_gb()
|
ram = _ram_gb()
|
||||||
pb = params_b
|
pb = params_b if params_b > 0 else budget.params_b_for(name)
|
||||||
if pb <= 0:
|
saw = budget.setup_aware_ctx(pb, quant, role=role or None)
|
||||||
meta = catalog.meta_for_name(name) if name else None
|
|
||||||
pb = float(meta["total_params_b"]) if (meta and meta.get("total_params_b")) else extract_params_b(name)
|
|
||||||
return {
|
return {
|
||||||
"params_b": round(pb, 1),
|
"params_b": round(pb, 1),
|
||||||
"fit": evaluate_fit(pb, quant, ctx, ram, name=name),
|
"fit": evaluate_fit(pb, quant, ctx, ram, name=name),
|
||||||
"optimal_ctx": max_ctx_for(pb, quant, ram),
|
"optimal_ctx": max_ctx_for(pb, quant, ram), # Roh-Obergrenze (Modell allein)
|
||||||
|
"assigned_ctx": saw["ctx"], # setup-bewusst vergeben
|
||||||
|
"budget": {"gtt_gb": saw["gtt_gb"], "reserved_gb": saw["reserved_gb"],
|
||||||
|
"budget_gb": saw["budget_gb"], "mode": saw["mode"]},
|
||||||
"sys_ram_gb": round(ram, 1),
|
"sys_ram_gb": round(ram, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +111,8 @@ def install(req: InstallReq) -> dict:
|
|||||||
|
|
||||||
ctx = req.ctx
|
ctx = req.ctx
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
ram = _ram_gb()
|
# SETUP-BEWUSST: größter ctx, der neben Hirn/warmem Set passt (nicht nur Modell allein).
|
||||||
ctx = max_ctx_for(extract_params_b(repo), req.quant, ram)
|
ctx = budget.setup_aware_ctx(budget.params_b_for(repo), req.quant, role=req.role)["ctx"]
|
||||||
|
|
||||||
# Sofort registrieren (Datei kommt gleich) — robust gegen -watch-config.
|
# Sofort registrieren (Datei kommt gleich) — robust gegen -watch-config.
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -25,19 +25,6 @@ def _hermes_version(name: str) -> float | None:
|
|||||||
return float(m.group(1)) if m else 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:
|
def hermes_brain_info() -> dict:
|
||||||
"""Aktuelles Agent-Hirn (hermes-Rolle) + bestes verfügbares NousResearch-Hermes-Modell,
|
"""Aktuelles Agent-Hirn (hermes-Rolle) + bestes verfügbares NousResearch-Hermes-Modell,
|
||||||
das auf diese Hardware passt. Für den Modell-Manager: Brain sichtbar + updatebar,
|
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.)
|
# größte on-demand-Modell daneben lädt? (Brain muss immer resident sein.)
|
||||||
budget = None
|
budget = None
|
||||||
try:
|
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()
|
groups = llamaswap.list_groups()
|
||||||
persist = set()
|
persist = set()
|
||||||
for g in groups.values():
|
for g in groups.values():
|
||||||
if isinstance(g, dict) and g.get("persist"):
|
if isinstance(g, dict) and g.get("persist"):
|
||||||
persist.update(g.get("members") or [])
|
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
|
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)
|
||||||
if best:
|
if best:
|
||||||
brain_gb = 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:
|
||||||
brain_gb = _foot(cur)
|
brain_gb = footprint_gb(cur)
|
||||||
else:
|
else:
|
||||||
brain_gb = 0.0
|
brain_gb = 0.0
|
||||||
# voller Always-Warm-Footprint (alle persist, Brain=Empfehlung) — nur Info
|
# 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)
|
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((footprint_gb(m) for m in models if m["name"] not in persist), default=0.0)
|
||||||
gtt = _gtt_budget_gb()
|
gtt = gtt_budget_gb()
|
||||||
# Brain muss immer resident sein → passt Brain + größtes on-demand zusammen?
|
# 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.)
|
# (fast/vision dürfen beim Laden eines großen Modells verdrängt werden.)
|
||||||
budget = {
|
budget = {
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
"""
|
||||||
|
Speicher-Budget & SETUP-BEWUSSTE ctx-Vergabe — EINE Quelle der Wahrheit.
|
||||||
|
|
||||||
|
Modelliert die auf der Box VERIFIZIERTE Residenz-Realität (llama-swap, Ein-Gruppen-
|
||||||
|
Residenz, GTT ~124 GB):
|
||||||
|
• Das Agent-Hirn (Rolle `hermes`) ist IMMER resident.
|
||||||
|
• Weitere persist-Mitglieder (fast/vision) dürfen verdrängt werden, wenn ein großes
|
||||||
|
on-demand-Modell lädt.
|
||||||
|
Daraus folgt, wie viel Speicher NEBEN einem Zielmodell reserviert bleiben muss —
|
||||||
|
und damit der größte Kontext, der wirklich passt (nicht nur für das Modell allein).
|
||||||
|
|
||||||
|
Vorher rechnete nur der Hirn-Wechsel (agent.py) setup-bewusst; die allgemeine
|
||||||
|
ctx-Vergabe nahm den Gesamt-RAM in Isolation. Dieses Modul vereint beides.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
from services.fit import (
|
||||||
|
QUANT_BYTES_PER_PARAM,
|
||||||
|
estimate_memory_gb,
|
||||||
|
extract_params_b,
|
||||||
|
max_ctx_in_budget,
|
||||||
|
)
|
||||||
|
|
||||||
|
HEADROOM_GB = 4.0 # OS/Treiber/Fragmentierung
|
||||||
|
|
||||||
|
|
||||||
|
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 footprint_gb(model: dict) -> float:
|
||||||
|
"""Loaded-Footprint eines Modells = Gewichte + kalibrierter KV-Anteil. Params robust
|
||||||
|
aus dem MAXIMUM von Namens-Schätzung und Dateigröße (deckt 'Coder-Next' ohne Größe im
|
||||||
|
Namen sowie Split-GGUFs ab, deren size_bytes nur den ersten Teil zählt)."""
|
||||||
|
caps = model.get("capabilities") or {}
|
||||||
|
quant = model.get("quant") or "Q4_K_M"
|
||||||
|
ctx = int(model.get("ctx") or 32768)
|
||||||
|
bpp = QUANT_BYTES_PER_PARAM.get(quant.upper(), 0.55)
|
||||||
|
size_gb = (model.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)
|
||||||
|
|
||||||
|
|
||||||
|
def params_b_for(name: str) -> float:
|
||||||
|
"""Parameter (Mrd.) für einen Modell-/Repo-Namen: KATALOG (echte Metadaten) zuerst,
|
||||||
|
sonst Namens-Schätzung. Gemeinsam für Fit-Vorschau und ctx-Vergabe."""
|
||||||
|
from services import catalog
|
||||||
|
meta = catalog.meta_for_name(name) if name else None
|
||||||
|
if meta and meta.get("total_params_b"):
|
||||||
|
return float(meta["total_params_b"])
|
||||||
|
return extract_params_b(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _persist_members(groups: dict) -> set:
|
||||||
|
out: set = set()
|
||||||
|
for g in (groups or {}).values():
|
||||||
|
if isinstance(g, dict) and g.get("persist"):
|
||||||
|
out.update(g.get("members") or [])
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def reserved_gb(role: str | None) -> dict:
|
||||||
|
"""Speicher, der NEBEN einem Zielmodell der gegebenen Rolle resident bleiben muss —
|
||||||
|
gemäß verifizierter Box-Residenz. Liest die aktuelle llama-swap-Config, passt sich
|
||||||
|
also der echten Gruppen-/persist-Konfiguration an (nicht hartkodiert).
|
||||||
|
|
||||||
|
- Rolle `hermes` (das Hirn): muss mit dem GRÖSSTEN on-demand-Modell koexistieren.
|
||||||
|
- Rolle, deren aktueller Träger im warmen (persist) Set liegt (z.B. fast/vision):
|
||||||
|
koexistiert mit Hirn + den ÜBRIGEN warmen Mitgliedern.
|
||||||
|
- sonst (heavy/coder/scout/keine): on-demand → verdrängt fast/vision, nur das Hirn bleibt.
|
||||||
|
"""
|
||||||
|
from services import llamaswap
|
||||||
|
models = llamaswap.list_models()
|
||||||
|
groups = llamaswap.list_groups()
|
||||||
|
persist = _persist_members(groups)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
warm = bool(holder and holder["name"] in persist)
|
||||||
|
if warm:
|
||||||
|
others = sum(footprint_gb(m) for m in models
|
||||||
|
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}
|
||||||
|
|
||||||
|
|
||||||
|
def setup_aware_ctx(params_b: float, quant: str, role: str | None = None) -> dict:
|
||||||
|
"""Größter Kontext, der für ein Modell (params_b/quant) der gegebenen Rolle NEBEN dem
|
||||||
|
bestehenden Setup passt. Gibt ctx + die Budget-Herleitung zurück (für UI/Transparenz)."""
|
||||||
|
gtt = gtt_budget_gb()
|
||||||
|
r = reserved_gb(role)
|
||||||
|
budget = max(gtt - r["reserved_gb"] - HEADROOM_GB, 0.0)
|
||||||
|
ctx = max_ctx_in_budget(params_b, quant, budget)
|
||||||
|
return {
|
||||||
|
"ctx": ctx,
|
||||||
|
"gtt_gb": gtt,
|
||||||
|
"reserved_gb": round(r["reserved_gb"], 1),
|
||||||
|
"budget_gb": round(budget, 1),
|
||||||
|
"mode": r["mode"],
|
||||||
|
}
|
||||||
+12
-4
@@ -72,12 +72,13 @@ def extract_params_b(name: str) -> float:
|
|||||||
_NICE_CTX = [2048, 4096, 8192, 16384, 32768, 49152, 65536, 98304, 131072]
|
_NICE_CTX = [2048, 4096, 8192, 16384, 32768, 49152, 65536, 98304, 131072]
|
||||||
|
|
||||||
|
|
||||||
def max_ctx_for(params_b: float, quant: str, sys_ram_gb: float) -> int:
|
def max_ctx_in_budget(params_b: float, quant: str, budget_gb: float) -> int:
|
||||||
"""Größter 'schöner' Kontext, der komfortabel passt (80 % des nutzbaren RAM)."""
|
"""Größter 'schöner' Kontext, dessen Gewichte + KV in budget_gb passen.
|
||||||
|
Budget-basierter Kern → wird von der setup-bewussten ctx-Vergabe
|
||||||
|
(services.budget) mit dem ECHTEN freien Budget gefüttert."""
|
||||||
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
|
||||||
usable = max(sys_ram_gb - 4.0, 0) * 0.8
|
ctx_budget = budget_gb - weights
|
||||||
ctx_budget = usable - weights
|
|
||||||
if ctx_budget <= 0:
|
if ctx_budget <= 0:
|
||||||
return 2048
|
return 2048
|
||||||
per_8k = (max(params_b, 7) / 7) * 0.8
|
per_8k = (max(params_b, 7) / 7) * 0.8
|
||||||
@@ -89,6 +90,13 @@ def max_ctx_for(params_b: float, quant: str, sys_ram_gb: float) -> int:
|
|||||||
return best
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def max_ctx_for(params_b: float, quant: str, sys_ram_gb: float) -> int:
|
||||||
|
"""Roh-Obergrenze: größter Kontext für dieses Modell ALLEIN gegen den
|
||||||
|
Gesamt-RAM (80 % nutzbar). Ignoriert bewusst das übrige Setup —
|
||||||
|
setup-bewusst rechnet services.budget.setup_aware_ctx."""
|
||||||
|
return max_ctx_in_budget(params_b, quant, max(sys_ram_gb - 4.0, 0) * 0.8)
|
||||||
|
|
||||||
|
|
||||||
def recommend_ctx(params_b: float, quant: str, sys_ram_gb: float) -> dict:
|
def recommend_ctx(params_b: float, quant: str, sys_ram_gb: float) -> dict:
|
||||||
ctx = max_ctx_for(params_b, quant, sys_ram_gb)
|
ctx = max_ctx_for(params_b, quant, sys_ram_gb)
|
||||||
k = ctx // 1024
|
k = ctx // 1024
|
||||||
|
|||||||
-397
File diff suppressed because one or more lines are too long
+397
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -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-CXYyTK29.js"></script>
|
<script type="module" crossorigin src="/assets/index-DGRyEXwM.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CqYu-pXY.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CqYu-pXY.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ export interface FitResp {
|
|||||||
params_b: number
|
params_b: number
|
||||||
fit: Fit
|
fit: Fit
|
||||||
optimal_ctx: number
|
optimal_ctx: number
|
||||||
|
assigned_ctx: number
|
||||||
|
budget: { gtt_gb: number; reserved_gb: number; budget_gb: number; mode: string }
|
||||||
sys_ram_gb: number
|
sys_ram_gb: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ export function AddModel() {
|
|||||||
? modelsData?.models.find((m) => (m.role || "").toLowerCase() === role)
|
? modelsData?.models.find((m) => (m.role || "").toLowerCase() === role)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
async function refreshFit(r: string, qq: string) {
|
async function refreshFit(r: string, qq: string, rl: string) {
|
||||||
setOomArmed(false)
|
setOomArmed(false)
|
||||||
if (!r.trim()) { setFit(null); return }
|
if (!r.trim()) { setFit(null); return }
|
||||||
try {
|
try {
|
||||||
const d = await api<FitResp>(
|
const d = await api<FitResp>(
|
||||||
`/api/fit?params_b=0&quant=${encodeURIComponent(qq)}&ctx=8192&name=${encodeURIComponent(r)}`
|
`/api/fit?params_b=0&quant=${encodeURIComponent(qq)}&ctx=8192` +
|
||||||
|
`&name=${encodeURIComponent(r)}&role=${encodeURIComponent(rl)}`
|
||||||
)
|
)
|
||||||
setFit(d)
|
setFit(d)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -46,7 +47,7 @@ export function AddModel() {
|
|||||||
const pick = d.quants.length ? (d.quants.includes("Q4_K_M") ? "Q4_K_M" : d.quants[0]) : quant
|
const pick = d.quants.length ? (d.quants.includes("Q4_K_M") ? "Q4_K_M" : d.quants[0]) : quant
|
||||||
if (d.quants.length) setQuant(pick)
|
if (d.quants.length) setQuant(pick)
|
||||||
setMsg(d.quants.length ? "" : "Keine GGUF-Dateien in diesem Repository gefunden.")
|
setMsg(d.quants.length ? "" : "Keine GGUF-Dateien in diesem Repository gefunden.")
|
||||||
if (d.quants.length) refreshFit(d.repo, pick)
|
if (d.quants.length) refreshFit(d.repo, pick, role)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg(`Fehler: ${e}`)
|
setMsg(`Fehler: ${e}`)
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,12 @@ export function AddModel() {
|
|||||||
|
|
||||||
function onQuantChange(qq: string) {
|
function onQuantChange(qq: string) {
|
||||||
setQuant(qq)
|
setQuant(qq)
|
||||||
refreshFit(repo, qq)
|
refreshFit(repo, qq, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRoleChange(rl: string) {
|
||||||
|
setRole(rl)
|
||||||
|
if (quants.length) refreshFit(repo, quant, rl)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function search() {
|
async function search() {
|
||||||
@@ -130,8 +136,8 @@ export function AddModel() {
|
|||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
value={role}
|
value={role}
|
||||||
onChange={(e) => setRole(e.target.value)}
|
onChange={(e) => onRoleChange(e.target.value)}
|
||||||
title="Rolle (optional) — bestimmt Auto-Konfiguration wie parallele Slots"
|
title="Rolle (optional) — bestimmt Auto-Konfiguration + setup-bewussten Kontext"
|
||||||
className="h-9 rounded-lg border border-border/60 bg-background/40 px-3 text-xs outline-none text-foreground font-semibold"
|
className="h-9 rounded-lg border border-border/60 bg-background/40 px-3 text-xs outline-none text-foreground font-semibold"
|
||||||
>
|
>
|
||||||
<option value="" className="bg-popover text-foreground">Rolle…</option>
|
<option value="" className="bg-popover text-foreground">Rolle…</option>
|
||||||
@@ -161,6 +167,14 @@ export function AddModel() {
|
|||||||
<span className="font-mono opacity-90">
|
<span className="font-mono opacity-90">
|
||||||
~{fit.params_b}B · ~{fit.fit.req_gb} GB / {fit.sys_ram_gb} GB RAM · ~{fit.fit.tps} t/s
|
~{fit.params_b}B · ~{fit.fit.req_gb} GB / {fit.sys_ram_gb} GB RAM · ~{fit.fit.tps} t/s
|
||||||
</span>
|
</span>
|
||||||
|
{fit.fit.level !== "too_tight" && (
|
||||||
|
<span
|
||||||
|
className="font-mono opacity-80"
|
||||||
|
title={`Setup-bewusst: GTT ${fit.budget.gtt_gb} GB − reserviert ${fit.budget.reserved_gb} GB (${fit.budget.mode}) → ${fit.budget.budget_gb} GB frei`}
|
||||||
|
>
|
||||||
|
ctx → {(fit.assigned_ctx / 1024).toFixed(0)}k
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{fit.fit.level === "too_tight" && (
|
{fit.fit.level === "too_tight" && (
|
||||||
<span className="opacity-90">— passt nicht in den Speicher, würde beim Laden abstürzen (OOM).</span>
|
<span className="opacity-90">— passt nicht in den Speicher, würde beim Laden abstürzen (OOM).</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user