feat(llm): Kontextfenster pro Karte einstellbar + Fehlerart context_exceeded
Anlass: die Software-Schmiede bekam "request (8274 tokens) exceeds the available context size (8192 tokens)". Sie holt ctx vom Cluster zwar ab, kuerzt aber nach einem eigenen, von Hand eingetragenen Wert (32768). ARIA-Seite: - Neue Fehlerart context_exceeded (statt model_error) mit promptTokens und ctx — Programme koennen automatisch kuerzen und nochmal senden, statt Text auszuwerten. llm-adapter liest es aus llama.cpps exceed_context_size_error, der Cluster-Router reicht es durch und wiederholt es nicht anderswo. - Diagnostic → LLM-Karten: Kontextfenster pro Karte (4k … 128k). Laedt das Modell mit neuem -c; Programme sehen den neuen Wert ueber ctx. Passt es nicht in den Grafikspeicher, setzt die Box zurueck — jetzt inkl. Registry (vorher blieb bei einem Fehlschlag der zu grosse ctx in aria_models.json stehen und waere beim naechsten Start wieder gescheitert). - Doku: context_exceeded, ctx kann sich aendern, "danach kuerzen, nicht nur anzeigen". Getestet gegen llama-swap: Kontext 512 → 1024 (gleiche Karte), Fehlschlag bei 65536 → Registry + Config wieder 1024, zu langer Verlauf → context_exceeded mit promptTokens=6030, ctx=1024. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -211,7 +211,8 @@ class LlmCluster:
|
|||||||
res = await self.route(payload)
|
res = await self.route(payload)
|
||||||
out = {"requestId": rid, "ok": bool(res.get("ok"))}
|
out = {"requestId": rid, "ok": bool(res.get("ok"))}
|
||||||
for k in ("content", "tool_calls", "model", "node", "gpu", "instanceId", "usage",
|
for k in ("content", "tool_calls", "model", "node", "gpu", "instanceId", "usage",
|
||||||
"elapsedMs", "attempts", "error", "errorCode", "available"):
|
"elapsedMs", "attempts", "error", "errorCode", "available",
|
||||||
|
"promptTokens", "ctx"):
|
||||||
if res.get(k) is not None:
|
if res.get(k) is not None:
|
||||||
out[k] = res[k]
|
out[k] = res[k]
|
||||||
await self.b._send_to_rvs({"type": "llm_cluster_response", "payload": out,
|
await self.b._send_to_rvs({"type": "llm_cluster_response", "payload": out,
|
||||||
|
|||||||
+31
-1
@@ -9175,13 +9175,25 @@
|
|||||||
const job = Object.values(_llmJobs || {}).find(j => j.instanceId === b.instanceId && String(j.gpu) === String(sl.gpu)
|
const job = Object.values(_llmJobs || {}).find(j => j.instanceId === b.instanceId && String(j.gpu) === String(sl.gpu)
|
||||||
&& !['ready', 'error'].includes(j.phase));
|
&& !['ready', 'error'].includes(j.phase));
|
||||||
const jobTxt = job ? (job.phase === 'download' && job.percent != null ? `lädt ${job.percent.toFixed(0)} %` : 'lädt …') : '';
|
const jobTxt = job ? (job.phase === 'download' && job.percent != null ? `lädt ${job.percent.toFixed(0)} %` : 'lädt …') : '';
|
||||||
rows.push(`<div style="display:grid;grid-template-columns:minmax(220px,1fr) minmax(260px,2fr) 160px;gap:10px;align-items:center;">
|
// Kontextfenster dieser Karte. Mehr Kontext = mehr VRAM; passt es
|
||||||
|
// nicht, setzt die Box die Karte von selbst zurueck.
|
||||||
|
const CTX = [4096, 8192, 16384, 32768, 65536, 131072];
|
||||||
|
const curCtx = sl.ctx || 0;
|
||||||
|
const ctxOpts = (curCtx && !CTX.includes(curCtx) ? [curCtx, ...CTX] : CTX)
|
||||||
|
.map(n => `<option value="${n}"${n === curCtx ? ' selected' : ''}>${n >= 1024 ? (n / 1024) + 'k' : n} Tokens</option>`).join('');
|
||||||
|
const ctxSel = sl.model
|
||||||
|
? `<select data-box="${escapeHtml(b.instanceId)}" data-gpu="${escapeHtml(sl.gpu)}" data-model="${escapeHtml(sl.model)}" data-current="${curCtx}"
|
||||||
|
onchange="onLlmCardCtx(this)" ${job ? 'disabled' : ''} title="Kontextfenster (Eingabe + Ausgabe). Mehr braucht mehr Grafikspeicher."
|
||||||
|
style="background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:5px 6px;color:#E0E0F0;font-size:12px;">${ctxOpts}</select>`
|
||||||
|
: '<span></span>';
|
||||||
|
rows.push(`<div style="display:grid;grid-template-columns:minmax(220px,1fr) minmax(260px,2fr) 130px 160px;gap:10px;align-items:center;">
|
||||||
<div><b style="color:#E0E0F0;">${escapeHtml(b.node)}</b> · Karte ${escapeHtml(sl.gpu)}
|
<div><b style="color:#E0E0F0;">${escapeHtml(b.node)}</b> · Karte ${escapeHtml(sl.gpu)}
|
||||||
<div style="color:#555570;font-size:11px;">${escapeHtml(sl.gpuName || '')}${vramGB ? ' · ' + vramGB.toFixed(0) + ' GB' : ''}${sl.vramUsedMB >= 0 ? ' · belegt ' + (sl.vramUsedMB / 1024).toFixed(1) + ' GB' : ''}${sl.ctx ? ' · Kontext ' + sl.ctx : ''}</div>
|
<div style="color:#555570;font-size:11px;">${escapeHtml(sl.gpuName || '')}${vramGB ? ' · ' + vramGB.toFixed(0) + ' GB' : ''}${sl.vramUsedMB >= 0 ? ' · belegt ' + (sl.vramUsedMB / 1024).toFixed(1) + ' GB' : ''}${sl.ctx ? ' · Kontext ' + sl.ctx : ''}</div>
|
||||||
${sl.onCpu ? '<div style="color:#FF9F0A;font-size:11px;" title="llama.cpp findet die Karte nicht und rechnet langsam auf der CPU. Ursache meist: Treiber zu alt fuer das CUDA im llama-swap-Image. Abhilfe: auf der Box sudo ./bootstrap.sh --upgrade-driver">⚠ Modell läuft auf der CPU, Karte leer — Treiber zu alt? (bootstrap.sh --upgrade-driver)</div>' : ''}</div>
|
${sl.onCpu ? '<div style="color:#FF9F0A;font-size:11px;" title="llama.cpp findet die Karte nicht und rechnet langsam auf der CPU. Ursache meist: Treiber zu alt fuer das CUDA im llama-swap-Image. Abhilfe: auf der Box sudo ./bootstrap.sh --upgrade-driver">⚠ Modell läuft auf der CPU, Karte leer — Treiber zu alt? (bootstrap.sh --upgrade-driver)</div>' : ''}</div>
|
||||||
<select data-box="${escapeHtml(b.instanceId)}" data-gpu="${escapeHtml(sl.gpu)}" data-current="${escapeHtml(sl.model || '')}"
|
<select data-box="${escapeHtml(b.instanceId)}" data-gpu="${escapeHtml(sl.gpu)}" data-current="${escapeHtml(sl.model || '')}"
|
||||||
onchange="onLlmCardSelect(this)" ${job ? 'disabled' : ''}
|
onchange="onLlmCardSelect(this)" ${job ? 'disabled' : ''}
|
||||||
style="background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:5px 8px;color:#E0E0F0;font-size:12px;">${opts.join('')}</select>
|
style="background:#1E1E2E;border:1px solid #333;border-radius:4px;padding:5px 8px;color:#E0E0F0;font-size:12px;">${opts.join('')}</select>
|
||||||
|
${ctxSel}
|
||||||
<span style="color:${job ? '#FFD60A' : c};font-size:12px;">${jobTxt || st}</span>
|
<span style="color:${job ? '#FFD60A' : c};font-size:12px;">${jobTxt || st}</span>
|
||||||
</div>`);
|
</div>`);
|
||||||
}
|
}
|
||||||
@@ -9189,6 +9201,24 @@
|
|||||||
box.innerHTML = rows.join('');
|
box.innerHTML = rows.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kontextfenster einer Karte aendern = dasselbe Modell mit anderem -c neu
|
||||||
|
// laden. Programme bekommen den neuen Wert automatisch (ctx in
|
||||||
|
// llm_cluster_models). Passt es nicht in den Grafikspeicher, setzt die Box
|
||||||
|
// die Karte auf den alten Stand zurueck.
|
||||||
|
function onLlmCardCtx(sel) {
|
||||||
|
const target = sel.dataset.box, gpu = sel.dataset.gpu, model = sel.dataset.model;
|
||||||
|
const cur = Number(sel.dataset.current || 0), next = Number(sel.value);
|
||||||
|
if (next === cur) return;
|
||||||
|
const m = _llmCatalog.find(x => x.id === model);
|
||||||
|
if (!m || !m.hfRepo) { alert(`${model} steht nicht im Modell-Katalog — Kontext so nicht änderbar.`); sel.value = cur; return; }
|
||||||
|
const more = next > cur;
|
||||||
|
if (!confirm(`${model} auf ${_boxName(target)} · Karte ${gpu} mit ${next} statt ${cur || '?'} Tokens Kontext neu laden?\n\n`
|
||||||
|
+ (more ? 'Mehr Kontext braucht mehr Grafikspeicher. Passt es nicht, setzt die Box die Karte von selbst auf den alten Stand zurück.\n\n' : '')
|
||||||
|
+ 'Die Box startet ihre Modelle dabei kurz neu. Programme bekommen den neuen Wert automatisch.')) { sel.value = cur; return; }
|
||||||
|
send({ action: 'llm_provision_model', targetInstance: target, gpu, key: m.id, hfRepo: m.hfRepo, quant: m.quant, ctx: next });
|
||||||
|
openLlmDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
function onLlmCardSelect(sel) {
|
function onLlmCardSelect(sel) {
|
||||||
const target = sel.dataset.box, gpu = sel.dataset.gpu, current = sel.dataset.current, next = sel.value;
|
const target = sel.dataset.box, gpu = sel.dataset.gpu, current = sel.dataset.current, next = sel.value;
|
||||||
const node = _boxName(target);
|
const node = _boxName(target);
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ Antwort:
|
|||||||
| Feld | Bedeutung |
|
| Feld | Bedeutung |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `model` | Name — genau so in der Anfrage angeben. **Das** zeigst du dem Nutzer zur Auswahl. |
|
| `model` | Name — genau so in der Anfrage angeben. **Das** zeigst du dem Nutzer zur Auswahl. |
|
||||||
| `ctx` | **Kontextfenster in Tokens** (Eingabe + Ausgabe zusammen). Das kleinste unter allen Karten mit diesem Modell — darauf kannst du dich verlassen, egal wohin verteilt wird. Nicht mehr annehmen. `0` = unbekannt. |
|
| `ctx` | **Kontextfenster in Tokens** (Eingabe + Ausgabe zusammen). Das kleinste unter allen Karten mit diesem Modell — darauf kannst du dich verlassen, egal wohin verteilt wird. Nicht mehr annehmen. `0` = unbekannt. **Kann sich ändern** (der Betreiber stellt es pro Karte ein) — vor längeren Aufträgen neu holen. |
|
||||||
| `available` | auf wie vielen Karten es gerade liegt |
|
| `available` | auf wie vielen Karten es gerade liegt |
|
||||||
| `cards` | nur zur Info (Anzeige, Fehlersuche) — für die Anfrage nicht nötig |
|
| `cards` | nur zur Info (Anzeige, Fehlersuche) — für die Anfrage nicht nötig |
|
||||||
|
|
||||||
@@ -165,7 +165,8 @@ Fehler — `ok: false`, dazu `error` (Text für Menschen) und `errorCode`:
|
|||||||
| `bad_request` | Anfrage unvollständig, z.B. `messages` leer oder `model` fehlt bei mehreren Modellen | Anfrage korrigieren |
|
| `bad_request` | Anfrage unvollständig, z.B. `messages` leer oder `model` fehlt bei mehreren Modellen | Anfrage korrigieren |
|
||||||
| `timeout` | Keine Antwort innerhalb der Frist (Standard 600 s) — auch nach Ersatzversuch | später nochmal |
|
| `timeout` | Keine Antwort innerhalb der Frist (Standard 600 s) — auch nach Ersatzversuch | später nochmal |
|
||||||
| `box_offline` | Box ist während der Anfrage weggefallen, und es gab keine Ersatz-Box | später nochmal |
|
| `box_offline` | Box ist während der Anfrage weggefallen, und es gab keine Ersatz-Box | später nochmal |
|
||||||
| `model_error` | Das Modell selbst hat einen Fehler gemeldet, z.B. Kontext zu lang. Wird nicht anderswo wiederholt — wäre dort genauso. | `error` lesen |
|
| `context_exceeded` | Der Verlauf ist länger als das Kontextfenster. Dazu `promptTokens` (so lang war die Anfrage) und `ctx` (so viel geht). | Verlauf kürzen und nochmal senden — z.B. auf ~80 % von `ctx` abzüglich der gewünschten Antwortlänge. |
|
||||||
|
| `model_error` | Das Modell selbst hat einen anderen Fehler gemeldet. Wird nicht anderswo wiederholt — wäre dort genauso. | `error` lesen |
|
||||||
|
|
||||||
**Fehler nie als Antwort der KI behandeln.** Zeig `error` dem Nutzer als
|
**Fehler nie als Antwort der KI behandeln.** Zeig `error` dem Nutzer als
|
||||||
Fehlermeldung an, aber schreib ihn nicht als Assistant-Nachricht in den
|
Fehlermeldung an, aber schreib ihn nicht als Assistant-Nachricht in den
|
||||||
@@ -247,8 +248,12 @@ Wer schon gegen Abschnitt C gebaut hat (`llm_models_request` /
|
|||||||
2. **Auswahl:** Dem Nutzer nur den **Modellnamen** zeigen — nicht mehr
|
2. **Auswahl:** Dem Nutzer nur den **Modellnamen** zeigen — nicht mehr
|
||||||
„Modell · Karte · Box". Wo es läuft, entscheidet der Cluster.
|
„Modell · Karte · Box". Wo es läuft, entscheidet der Cluster.
|
||||||
3. **Kontextfenster:** `ctx` aus der Liste übernehmen statt einen eigenen Wert
|
3. **Kontextfenster:** `ctx` aus der Liste übernehmen statt einen eigenen Wert
|
||||||
einzustellen. Die Box gibt nur so viel her, wie dort steht — rechnet ein
|
einzustellen — und **danach kürzen**, nicht nur anzeigen. Die Box gibt nur
|
||||||
Programm mit mehr, scheitern lange Aufträge oder werden falsch gekürzt.
|
so viel her, wie dort steht; rechnet ein Programm mit mehr, kommt
|
||||||
|
`context_exceeded`. Den Fehler zusätzlich abfangen: kürzen und nochmal
|
||||||
|
senden (`promptTokens`/`ctx` stehen dabei). Tokens grob schätzen reicht
|
||||||
|
nicht immer — Code und Deutsch brauchen mehr Tokens pro Zeichen als
|
||||||
|
englischer Fließtext.
|
||||||
4. **Anfrage:** `llm_cluster_request` statt `llm_request`; **kein**
|
4. **Anfrage:** `llm_cluster_request` statt `llm_request`; **kein**
|
||||||
`targetInstance` mehr. Die Antwort kommt als `llm_cluster_response`.
|
`targetInstance` mehr. Die Antwort kommt als `llm_cluster_response`.
|
||||||
5. **Fehler:** `ok: false` als Fehler anzeigen, nicht als Antwort in den
|
5. **Fehler:** `ok: false` als Fehler anzeigen, nicht als Antwort in den
|
||||||
|
|||||||
@@ -356,6 +356,24 @@ def _sanitize_messages(messages: list) -> list:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _llama_error_info(exc: Exception) -> dict:
|
||||||
|
"""Maschinenlesbare Fehlerart. Wichtigster Fall: der Verlauf ist laenger
|
||||||
|
als das Kontextfenster — dann soll ein Programm automatisch kuerzen und
|
||||||
|
nochmal fragen koennen, statt einen Text auszuwerten."""
|
||||||
|
resp = getattr(exc, "response", None)
|
||||||
|
if resp is None:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
e = (resp.json() or {}).get("error") or {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
if isinstance(e, dict) and e.get("type") == "exceed_context_size_error":
|
||||||
|
return {"errorCode": "context_exceeded",
|
||||||
|
"promptTokens": int(e.get("n_prompt_tokens") or 0),
|
||||||
|
"ctx": int(e.get("n_ctx") or 0)}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _llama_error_text(exc: Exception) -> str:
|
def _llama_error_text(exc: Exception) -> str:
|
||||||
"""Bei HTTP-Fehlern den GRUND von llama.cpp mitnehmen. Bisher kam nur
|
"""Bei HTTP-Fehlern den GRUND von llama.cpp mitnehmen. Bisher kam nur
|
||||||
"Server error '500 …'" an — die eigentliche Erklaerung steht im Body."""
|
"Server error '500 …'" an — die eigentliche Erklaerung steht im Body."""
|
||||||
@@ -419,7 +437,7 @@ async def _call_llama(messages: list, *, max_tokens: int, temperature: float,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = _llama_error_text(e)
|
msg = _llama_error_text(e)
|
||||||
logger.warning("llama.cpp-Call fehlgeschlagen: %s", msg[:300])
|
logger.warning("llama.cpp-Call fehlgeschlagen: %s", msg[:300])
|
||||||
return {"ok": False, "content": "", "error": msg[:700]}
|
return {"ok": False, "content": "", "error": msg[:700], **_llama_error_info(e)}
|
||||||
|
|
||||||
|
|
||||||
# B0.5-2: Lade-Status ans Diagnostic (service_status, service="llm"). Wir kennen
|
# B0.5-2: Lade-Status ans Diagnostic (service_status, service="llm"). Wir kennen
|
||||||
@@ -678,6 +696,8 @@ async def _do_llm_request(ws, payload: dict) -> None:
|
|||||||
"tool_calls": tc,
|
"tool_calls": tc,
|
||||||
"error": res.get("error"),
|
"error": res.get("error"),
|
||||||
"model": model,
|
"model": model,
|
||||||
|
# Fehlerart fuer Programme (z.B. context_exceeded + promptTokens + ctx)
|
||||||
|
**{k: res[k] for k in ("errorCode", "promptTokens", "ctx") if res.get(k)},
|
||||||
# Wer hat geantwortet: Box + Karte. Hilft Clients mit mehreren Boxen.
|
# Wer hat geantwortet: Box + Karte. Hilft Clients mit mehreren Boxen.
|
||||||
"instanceId": INSTANCE_ID, "node": NODE_NAME, "gpu": _card_of(model),
|
"instanceId": INSTANCE_ID, "node": NODE_NAME, "gpu": _card_of(model),
|
||||||
"usage": res.get("usage") or {},
|
"usage": res.get("usage") or {},
|
||||||
@@ -927,6 +947,10 @@ async def _handle_provision(ws, payload: dict) -> None:
|
|||||||
error=f"{key} liegt schon auf Karte {elsewhere} dieser Box — ein Modell pro Box nur einmal.")
|
error=f"{key} liegt schon auf Karte {elsewhere} dieser Box — ein Modell pro Box nur einmal.")
|
||||||
quant_note = ""
|
quant_note = ""
|
||||||
repo_files = None
|
repo_files = None
|
||||||
|
# Fuer ein Zuruecksetzen: der bisherige Registry-Eintrag dieses Modells.
|
||||||
|
# Noetig, wenn nur das Kontextfenster geaendert wird (gleiches Modell, gleiche
|
||||||
|
# Karte) — sonst bliebe nach einem Fehlschlag der zu grosse Wert stehen.
|
||||||
|
old_entry = next((dict(e) for e in _load_registry() if e.get("key") == key), None)
|
||||||
if repo:
|
if repo:
|
||||||
wanted = (payload.get("quant") or "Q4_K_M").strip()
|
wanted = (payload.get("quant") or "Q4_K_M").strip()
|
||||||
quant = wanted
|
quant = wanted
|
||||||
@@ -1012,6 +1036,10 @@ async def _handle_provision(ws, payload: dict) -> None:
|
|||||||
else:
|
else:
|
||||||
slots.pop(card, None)
|
slots.pop(card, None)
|
||||||
_save_slots(slots)
|
_save_slots(slots)
|
||||||
|
reg = [e for e in _load_registry() if e.get("key") != key]
|
||||||
|
if old_entry:
|
||||||
|
reg.append(old_entry)
|
||||||
|
_save_registry(reg)
|
||||||
_generate_config()
|
_generate_config()
|
||||||
await _reload_llama()
|
await _reload_llama()
|
||||||
await _announce(ws)
|
await _announce(ws)
|
||||||
|
|||||||
Reference in New Issue
Block a user