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).
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
# Annahme: Bytes per Parameter für GGUF Quants
|
||||
QUANT_BYTES_PER_PARAM = {
|
||||
"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),
|
||||
"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
|
||||
|
||||
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)])
|
||||
|
||||
@@ -79,6 +79,7 @@ async def analyze_repo(req: AnalyzeRequest):
|
||||
"filename": f,
|
||||
"quant": quant,
|
||||
"fit": fit,
|
||||
"optimal_ctx": max_ctx_for(params_b, quant, ram_gb),
|
||||
"priority": priority
|
||||
})
|
||||
|
||||
@@ -96,5 +97,6 @@ async def analyze_repo(req: AnalyzeRequest):
|
||||
def evaluate_single(req: EvaluateRequest):
|
||||
ram_gb = psutil.virtual_memory().total / (1024**3)
|
||||
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
|
||||
|
||||
|
||||
+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 jobengine import JOBS, start_job
|
||||
from llamaswap import _swap_get, read_config, write_config
|
||||
from hw_math import extract_params_b, max_ctx_for
|
||||
import re
|
||||
import os
|
||||
import psutil
|
||||
|
||||
router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
|
||||
|
||||
@@ -54,6 +56,10 @@ class UpdateReq(BaseModel):
|
||||
@router.get("/status")
|
||||
def status():
|
||||
cfg = read_config()
|
||||
try:
|
||||
ram_gb = psutil.virtual_memory().total / (1024 ** 3)
|
||||
except Exception: # noqa: BLE001
|
||||
ram_gb = 0
|
||||
configured = {}
|
||||
for name, spec in (cfg.get("models") or {}).items():
|
||||
spec = spec or {}
|
||||
@@ -94,7 +100,9 @@ def status():
|
||||
"size_bytes": size_bytes,
|
||||
"quant": quant,
|
||||
"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
|
||||
|
||||
@@ -74,6 +74,7 @@ function mount() {
|
||||
<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>
|
||||
<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><div style="font-size:13px">Ressourcen-Check</div>
|
||||
@@ -94,6 +95,7 @@ function mount() {
|
||||
$("#cb-m-download").addEventListener("click", doDownload);
|
||||
$("#cb-m-files").addEventListener("change", updateLiveFit);
|
||||
$("#cb-m-ctx").addEventListener("change", reanalyzeCtx);
|
||||
window.cbSetCtx = v => { $("#cb-m-ctx").value = v; reanalyzeCtx(); };
|
||||
|
||||
renderHwChip();
|
||||
renderCurated();
|
||||
@@ -207,6 +209,10 @@ function showFit() {
|
||||
$("#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-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");
|
||||
if (f.fit.level === "too_tight") { btn.className = "primary warn"; btn.textContent = "Trotzdem holen (zu groß)"; }
|
||||
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;
|
||||
try {
|
||||
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();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ function mount() {
|
||||
<label>Kontext-Größe (Tokens)</label>
|
||||
<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="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>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -97,6 +102,14 @@ function openConfig(alias) {
|
||||
const m = ALL.find(x => x.name === alias); if (!m) return;
|
||||
$("#cfg-model-name").textContent = m.name;
|
||||
$("#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";
|
||||
}
|
||||
async function saveConfig() {
|
||||
|
||||
Reference in New Issue
Block a user