v4 Schritt 1: optimale Kontextfenster automatisch ermitteln
- hw_math: max_ctx_for() (Umkehrung der ctx-Heuristik) + extract_params_b + recommend_ctx. - cookbook.py: optimal_ctx pro Datei in analyze + evaluate. - models.py status: params_b + optimal_ctx pro Modell (psutil-RAM). - models.js Konfig-Modal: Empfehlung + 'Optimal uebernehmen'. - cookbook.js Modal: 'Empfohlener Kontext ... uebernehmen'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+43
@@ -3,6 +3,8 @@ Extrahierte Mathematik aus dem Odysseus Projekt zur VRAM/RAM Berechnung.
|
|||||||
Abgestimmt auf APUs mit Unified Memory (Bosgame M5 / Strix Halo).
|
Abgestimmt auf APUs mit Unified Memory (Bosgame M5 / Strix Halo).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
# Annahme: Bytes per Parameter für GGUF Quants
|
# Annahme: Bytes per Parameter für GGUF Quants
|
||||||
QUANT_BYTES_PER_PARAM = {
|
QUANT_BYTES_PER_PARAM = {
|
||||||
"Q2_K": 0.35,
|
"Q2_K": 0.35,
|
||||||
@@ -69,3 +71,44 @@ def evaluate_fit(params_b: float, quant: str, ctx: int, sys_ram_gb: float) -> di
|
|||||||
"req_gb": round(req_gb, 1),
|
"req_gb": round(req_gb, 1),
|
||||||
"tps": round(tps, 0)
|
"tps": round(tps, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_params_b(name: str) -> float:
|
||||||
|
"""Parametergröße (Mrd.) aus Repo-/Dateiname. 8x7B (MoE) -> 56."""
|
||||||
|
moe = re.search(r"(\d+)x(\d+(?:\.\d+)?)[bB]", name)
|
||||||
|
if moe:
|
||||||
|
return float(moe.group(1)) * float(moe.group(2))
|
||||||
|
m = re.search(r"(\d+(?:\.\d+)?)[bB](?![a-zA-Z])", name)
|
||||||
|
if m:
|
||||||
|
return float(m.group(1))
|
||||||
|
return 7.0
|
||||||
|
|
||||||
|
|
||||||
|
# "Schöne" Kontextstufen, die UIs/Modelle gern mögen.
|
||||||
|
_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:
|
||||||
|
"""Größter 'schöner' Kontext, der komfortabel passt (80% des nutzbaren RAM, 4 GB OS-Puffer).
|
||||||
|
Umkehrung von estimate_memory_gb: löst die Kontext-Heuristik nach ctx auf."""
|
||||||
|
bpp = QUANT_BYTES_PER_PARAM.get(quant.upper(), 0.65)
|
||||||
|
weights = params_b * bpp
|
||||||
|
usable = max(sys_ram_gb - 4.0, 0) * 0.8 # gleicher Komfort wie evaluate_fit "perfect"
|
||||||
|
ctx_budget = usable - weights # GB, die für den KV-Cache übrig sind
|
||||||
|
if ctx_budget <= 0:
|
||||||
|
return 2048 # Modell selbst schon knapp -> Minimal-Kontext
|
||||||
|
per_8k = (max(params_b, 7) / 7) * 0.8 # GB pro 8k Kontext (aus der Heuristik)
|
||||||
|
raw_ctx = (ctx_budget / per_8k) * 8192
|
||||||
|
best = _NICE_CTX[0]
|
||||||
|
for c in _NICE_CTX:
|
||||||
|
if c <= raw_ctx:
|
||||||
|
best = c
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def recommend_ctx(params_b: float, quant: str, sys_ram_gb: float) -> dict:
|
||||||
|
"""Empfohlener Kontext + Klartext-Begründung (für die UI)."""
|
||||||
|
ctx = max_ctx_for(params_b, quant, sys_ram_gb)
|
||||||
|
k = ctx // 1024
|
||||||
|
return {"ctx": ctx, "k": k,
|
||||||
|
"note": f"Bis ~{k}k Kontext passt komfortabel auf deine Hardware ({round(sys_ram_gb)} GB)."}
|
||||||
|
|||||||
+3
-1
@@ -9,7 +9,7 @@ from pydantic import BaseModel
|
|||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from auth import auth
|
from auth import auth
|
||||||
from hw_math import evaluate_fit
|
from hw_math import evaluate_fit, max_ctx_for
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/cookbook", dependencies=[Depends(auth)])
|
router = APIRouter(prefix="/api/cookbook", dependencies=[Depends(auth)])
|
||||||
|
|
||||||
@@ -79,6 +79,7 @@ async def analyze_repo(req: AnalyzeRequest):
|
|||||||
"filename": f,
|
"filename": f,
|
||||||
"quant": quant,
|
"quant": quant,
|
||||||
"fit": fit,
|
"fit": fit,
|
||||||
|
"optimal_ctx": max_ctx_for(params_b, quant, ram_gb),
|
||||||
"priority": priority
|
"priority": priority
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,5 +97,6 @@ async def analyze_repo(req: AnalyzeRequest):
|
|||||||
def evaluate_single(req: EvaluateRequest):
|
def evaluate_single(req: EvaluateRequest):
|
||||||
ram_gb = psutil.virtual_memory().total / (1024**3)
|
ram_gb = psutil.virtual_memory().total / (1024**3)
|
||||||
fit = evaluate_fit(req.params_b, req.quant, req.ctx, ram_gb)
|
fit = evaluate_fit(req.params_b, req.quant, req.ctx, ram_gb)
|
||||||
|
fit["optimal_ctx"] = max_ctx_for(req.params_b, req.quant, ram_gb)
|
||||||
return fit
|
return fit
|
||||||
|
|
||||||
|
|||||||
+9
-1
@@ -16,8 +16,10 @@ from auth import auth
|
|||||||
from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, MODELS_DIR, TOKEN
|
from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, MODELS_DIR, TOKEN
|
||||||
from jobengine import JOBS, start_job
|
from jobengine import JOBS, start_job
|
||||||
from llamaswap import _swap_get, read_config, write_config
|
from llamaswap import _swap_get, read_config, write_config
|
||||||
|
from hw_math import extract_params_b, max_ctx_for
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import psutil
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
|
router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
|
||||||
|
|
||||||
@@ -54,6 +56,10 @@ class UpdateReq(BaseModel):
|
|||||||
@router.get("/status")
|
@router.get("/status")
|
||||||
def status():
|
def status():
|
||||||
cfg = read_config()
|
cfg = read_config()
|
||||||
|
try:
|
||||||
|
ram_gb = psutil.virtual_memory().total / (1024 ** 3)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
ram_gb = 0
|
||||||
configured = {}
|
configured = {}
|
||||||
for name, spec in (cfg.get("models") or {}).items():
|
for name, spec in (cfg.get("models") or {}).items():
|
||||||
spec = spec or {}
|
spec = spec or {}
|
||||||
@@ -94,7 +100,9 @@ def status():
|
|||||||
"size_bytes": size_bytes,
|
"size_bytes": size_bytes,
|
||||||
"quant": quant,
|
"quant": quant,
|
||||||
"caps": caps,
|
"caps": caps,
|
||||||
"filename": filename
|
"filename": filename,
|
||||||
|
"params_b": extract_params_b(filename or name),
|
||||||
|
"optimal_ctx": (max_ctx_for(extract_params_b(filename or name), quant or "Q4_K_M", ram_gb) if ram_gb else None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
swap_ok = True
|
swap_ok = True
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ function mount() {
|
|||||||
<div><label>Alias (Rolle)</label><input id="cb-m-alias" placeholder="z.B. coder"></div>
|
<div><label>Alias (Rolle)</label><input id="cb-m-alias" placeholder="z.B. coder"></div>
|
||||||
<div><label>Kontext-Größe</label><input id="cb-m-ctx" type="number" value="8192"></div>
|
<div><label>Kontext-Größe</label><input id="cb-m-ctx" type="number" value="8192"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hint" id="cb-m-ctx-rec" style="margin:-6px 0 14px"></div>
|
||||||
|
|
||||||
<div id="cb-m-fit" class="tile" style="display:flex;justify-content:space-between;align-items:center;margin:8px 0 18px">
|
<div id="cb-m-fit" class="tile" style="display:flex;justify-content:space-between;align-items:center;margin:8px 0 18px">
|
||||||
<div><div style="font-size:13px">Ressourcen-Check</div>
|
<div><div style="font-size:13px">Ressourcen-Check</div>
|
||||||
@@ -94,6 +95,7 @@ function mount() {
|
|||||||
$("#cb-m-download").addEventListener("click", doDownload);
|
$("#cb-m-download").addEventListener("click", doDownload);
|
||||||
$("#cb-m-files").addEventListener("change", updateLiveFit);
|
$("#cb-m-files").addEventListener("change", updateLiveFit);
|
||||||
$("#cb-m-ctx").addEventListener("change", reanalyzeCtx);
|
$("#cb-m-ctx").addEventListener("change", reanalyzeCtx);
|
||||||
|
window.cbSetCtx = v => { $("#cb-m-ctx").value = v; reanalyzeCtx(); };
|
||||||
|
|
||||||
renderHwChip();
|
renderHwChip();
|
||||||
renderCurated();
|
renderCurated();
|
||||||
@@ -207,6 +209,10 @@ function showFit() {
|
|||||||
$("#cb-m-fit").style.display = "flex";
|
$("#cb-m-fit").style.display = "flex";
|
||||||
$("#cb-m-fit-text").innerHTML = `Bedarf: <b>~${f.fit.req_gb.toFixed(1)} GB</b> · ${currentAnalysis.params_b}B · ${esc(f.quant)} · ~${Math.round(f.fit.tps)} Tok/s`;
|
$("#cb-m-fit-text").innerHTML = `Bedarf: <b>~${f.fit.req_gb.toFixed(1)} GB</b> · ${currentAnalysis.params_b}B · ${esc(f.quant)} · ~${Math.round(f.fit.tps)} Tok/s`;
|
||||||
$("#cb-m-fit-badge").innerHTML = `<span class="fit-badge ${fitCls(f.fit.level)}">${esc(f.fit.text)}</span>`;
|
$("#cb-m-fit-badge").innerHTML = `<span class="fit-badge ${fitCls(f.fit.level)}">${esc(f.fit.text)}</span>`;
|
||||||
|
const opt = f.optimal_ctx, recEl = $("#cb-m-ctx-rec");
|
||||||
|
if (recEl) recEl.innerHTML = opt
|
||||||
|
? `Empfohlener Kontext für deine Hardware: <b>~${Math.round(opt / 1024)}k</b> — <a href="#" onclick="event.preventDefault();window.cbSetCtx(${opt})">übernehmen</a>`
|
||||||
|
: "";
|
||||||
const btn = $("#cb-m-download");
|
const btn = $("#cb-m-download");
|
||||||
if (f.fit.level === "too_tight") { btn.className = "primary warn"; btn.textContent = "Trotzdem holen (zu groß)"; }
|
if (f.fit.level === "too_tight") { btn.className = "primary warn"; btn.textContent = "Trotzdem holen (zu groß)"; }
|
||||||
else { btn.className = "primary"; btn.textContent = "Herunterladen & Einpflegen"; }
|
else { btn.className = "primary"; btn.textContent = "Herunterladen & Einpflegen"; }
|
||||||
@@ -255,7 +261,7 @@ async function openCurated(i) {
|
|||||||
$("#cb-m-alias").value = m.alias; $("#cb-m-ctx").value = m.ctx; $("#cb-m-download").disabled = false;
|
$("#cb-m-alias").value = m.alias; $("#cb-m-ctx").value = m.ctx; $("#cb-m-download").disabled = false;
|
||||||
try {
|
try {
|
||||||
const fit = await api("/api/cookbook/evaluate", { method: "POST", body: JSON.stringify({ params_b: m.params_b, quant: m.quant, ctx: m.ctx }) });
|
const fit = await api("/api/cookbook/evaluate", { method: "POST", body: JSON.stringify({ params_b: m.params_b, quant: m.quant, ctx: m.ctx }) });
|
||||||
currentAnalysis = { repo: m.repo, params_b: m.params_b, files: [{ filename: m.file, quant: m.quant, fit }] };
|
currentAnalysis = { repo: m.repo, params_b: m.params_b, files: [{ filename: m.file, quant: m.quant, fit, optimal_ctx: fit.optimal_ctx }] };
|
||||||
showFit();
|
showFit();
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ function mount() {
|
|||||||
<label>Kontext-Größe (Tokens)</label>
|
<label>Kontext-Größe (Tokens)</label>
|
||||||
<input id="cfg-ctx" type="number" value="8192">
|
<input id="cfg-ctx" type="number" value="8192">
|
||||||
<div class="hint">Höhere Werte erlauben längere Texte, brauchen aber mehr Grafikspeicher.</div>
|
<div class="hint">Höhere Werte erlauben längere Texte, brauchen aber mehr Grafikspeicher.</div>
|
||||||
|
<div class="tile" id="cfg-rec" style="display:none;margin:0 0 14px;justify-content:space-between;align-items:center" >
|
||||||
|
<div style="flex:1"><div style="font-size:13px">Empfohlen für deine Hardware</div>
|
||||||
|
<div class="hint" id="cfg-rec-note" style="margin:4px 0 0"></div></div>
|
||||||
|
<button class="ghost" id="cfg-rec-apply">Optimal übernehmen</button>
|
||||||
|
</div>
|
||||||
<button class="primary" id="cfg-save" style="width:100%;margin-top:6px">Speichern</button>
|
<button class="primary" id="cfg-save" style="width:100%;margin-top:6px">Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -97,6 +102,14 @@ function openConfig(alias) {
|
|||||||
const m = ALL.find(x => x.name === alias); if (!m) return;
|
const m = ALL.find(x => x.name === alias); if (!m) return;
|
||||||
$("#cfg-model-name").textContent = m.name;
|
$("#cfg-model-name").textContent = m.name;
|
||||||
$("#cfg-ctx").value = m.meta?.ctx || 8192;
|
$("#cfg-ctx").value = m.meta?.ctx || 8192;
|
||||||
|
const opt = m.meta?.optimal_ctx, rec = $("#cfg-rec");
|
||||||
|
if (opt) {
|
||||||
|
const cur = m.meta?.ctx || 0;
|
||||||
|
$("#cfg-rec-note").textContent =
|
||||||
|
`Bis ~${Math.round(opt / 1024)}k passt komfortabel. Aktuell: ~${Math.round(cur / 1024)}k.`;
|
||||||
|
$("#cfg-rec-apply").onclick = () => { $("#cfg-ctx").value = opt; };
|
||||||
|
rec.style.display = "flex";
|
||||||
|
} else { rec.style.display = "none"; }
|
||||||
$("#cfg-modal").style.display = "flex";
|
$("#cfg-modal").style.display = "flex";
|
||||||
}
|
}
|
||||||
async function saveConfig() {
|
async function saveConfig() {
|
||||||
|
|||||||
Reference in New Issue
Block a user