diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md index a912809..653d960 100644 --- a/BEDIENUNGSANLEITUNG.md +++ b/BEDIENUNGSANLEITUNG.md @@ -57,7 +57,8 @@ Ausgabe bei Erfolg: [✓] Judge (:8002) bereit ``` -Dauer: 1–3 Minuten (Modell wird in GPU-VRAM geladen). +Dauer: bis zu 5 Minuten (Modell wird in GPU-VRAM geladen; neuere llama.cpp-Versionen +prüfen beim Start die VRAM-Verfügbarkeit, was zusätzliche Zeit kostet). ### Status prüfen @@ -324,8 +325,18 @@ Fehler schnell. Erst ab Runde 2 (oder bei `--continue`) kommt der vollständige Bei klarem `PASS` entfällt die ShipIt-Runde — es wird direkt SHIP ausgelöst. Mit `--approve-concerns` gilt das auch für `PASS WITH CONCERNS`. -Während des Ablaufs zeigt die Statuszeile immer die aktuelle Aktivität: -`Coder implementiert…` → `Quick-Check…` → `Coder fixt Blocker…` → `Judge reviewt (Runde 2/2)…` +Während des Ablaufs zeigt die Statuszeile die aktuelle Phase — mit laufendem Timer, +der beweist, dass die LLM tatsächlich arbeitet (und nicht hängt): + +``` +◉ Coder implementiert: Login-Flow mit JWT [01:23] +◉○ Runde 1/2: Quick-Check [00:47] +●◉ Runde 2/2: Coder fixt — fehlendes Null-Check [01:05] +●● ✓ PASS nach Runde 2/2 — ShipIt… +🚀 SHIP – produktionsreif +``` + +Steht der Timer still, hängt der Prozess — dann `/cancel` verwenden. ### Beispiel: mehr Runden diff --git a/README.md b/README.md index 721afcd..d5755b7 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ den Endpunkten wenn du ein `/judge`-, `/fix`- oder `/coder`-Kommando aufrufst. ## Modelle -| Rolle | Modell | Port | Container | Alias | -|--------|---------------------------------------------------|------|------------------|----------------| -| Coder | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8001 | qwen36-27b-coder | qwen3.5-coder | -| Judge | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8002 | qwen36-27b-judge | qwen3.5-judge | +| Rolle | Modell | Port | Container | Alias | GPU | +|--------|---------------------------------------------------|------|------------------|----------------|----------| +| Coder | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8001 | qwen36-27b-coder | qwen3.5-coder | device=1 | +| Judge | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8002 | qwen36-27b-judge | qwen3.5-judge | device=2 | Beide Container verwenden dasselbe GGUF-Datei, aber mit unterschiedlichen Serverparametern (Kontext, Temperatur, Parallelität). @@ -58,7 +58,8 @@ Serverparametern (Kontext, Temperatur, Parallelität). sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker ``` -- Mindestens eine NVIDIA-GPU (empfohlen: zwei GPUs mit je ≥ 16 GB VRAM) +- NVIDIA-GPUs: empfohlen 2 × RTX 3090 (je 24 GB VRAM) für Coder und Judge parallel. + Bei 3 GPUs: GPU 0 für Display reservieren, GPU 1 → Coder, GPU 2 → Judge. - GGUF-Modell vorhanden unter: `$HF_HOME/models/qwen3/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS.gguf` - Standard-Pfad: `HF_HOME=/home/dschlueter/nvme2n1p7_home/huggingface` @@ -110,7 +111,9 @@ Nach späteren Änderungen an `pi-coder-judge-extension.ts` oder `models.json`: ``` `start-servers.sh` startet beide Container gleichzeitig und wartet bis beide -HTTP-ready sind. Logs werden getrennt gesammelt und nur bei Fehler ausgegeben. +HTTP-ready sind (max. 5 Minuten — die neue llama.cpp-Version prüft beim Start, ob +Modell + KV-Cache in den VRAM passen, was zusätzliche Zeit kostet). Logs werden +getrennt gesammelt und nur bei Fehler ausgegeben. Wenn Server bereits laufen und du `start-servers.sh` (oder ein Einzelskript) aufrufst, werden die laufenden Container zuerst per `docker rm -f` gestoppt @@ -125,19 +128,20 @@ und dann neu gestartet — ein laufender Inference-Request wird dabei abgebroche | Parameter | Wert | Bedeutung | |---|---|---| | `--jinja` | — | Verwendet das im GGUF eingebettete Jinja-Chat-Template (Qwen-Format). Notwendig für korrekte `<\|im_start\|>`-Tokens. | +| `--reasoning on` | — | Aktiviert das interne Thinking-Budget des Modells (Qwen3-spezifisch). Ersetzt das frühere `--chat-template-kwargs '{"enable_thinking":true}'`. | | `--no-context-shift` | — | Kontextfenster wird **nicht** verschoben wenn es voll ist — stattdessen Fehler. Verhindert stille Datenverluste. | | `--repeat-penalty 1.05` | — | Leichte Penalty für Wiederholungen. Wert > 1.0 unterdrückt Loops. | -| `--top-k 40` | — | Nur die 40 wahrscheinlichsten nächsten Tokens werden berücksichtigt. | +| `--top-k 20` | — | Nur die 20 wahrscheinlichsten nächsten Tokens werden berücksichtigt. | | `--min-p 0.01` | — | Tokens mit Wahrscheinlichkeit < 1 % des wahrscheinlichsten Tokens werden ausgeschlossen. | | `-ngl 999` | — | Alle Layer auf die GPU laden (999 = „alle"). Bei zu wenig VRAM reduzieren. | | `-fa on` | — | Flash Attention — schnellere Attention-Berechnung, weniger VRAM für den Attention-Pass. | | `--kv-unified` | — | Einheitlicher KV-Cache über alle Schichten. Effizienter bei langen Kontexten. | -| `--cache-type-k q4_0` | — | KV-Cache Keys in 4-Bit quantisiert. Spart ~75 % VRAM gegenüber fp16 — nötig für 256K Kontext auf 2× 24 GB. | +| `--cache-type-k q4_0` | — | KV-Cache Keys in 4-Bit quantisiert. Spart ~75 % VRAM gegenüber fp16 — nötig für 256K Kontext auf einer 24-GB-GPU. | | `--cache-type-v q4_0` | — | KV-Cache Values ebenfalls 4-Bit quantisiert. | | `--cont-batching` | — | Continuous Batching: neue Anfragen werden in laufende Batches eingefügt — höherer Durchsatz bei mehreren parallelen Anfragen. | -| `--main-gpu 0` | — | GPU-Index (0 = erste der übergebenen GPUs) für Nicht-Tensor-Operationen. | -| `--tensor-split 0.5,0.5` | — | Modell-Gewichte 50/50 auf zwei GPUs aufteilen. | -| `--gpus '"device=1,2"'` | — | Docker-Argument: GPU 1 und GPU 2 dem Container übergeben. | +| `--main-gpu 0` | — | GPU-Index (0 = erste des Containers) für Nicht-Tensor-Operationen. | +| `--gpus '"device=1"'` (Coder) | — | Docker: nur GPU 1 dem Coder-Container zuweisen. | +| `--gpus '"device=2"'` (Judge) | — | Docker: nur GPU 2 dem Judge-Container zuweisen. | ### Coder-Server (Port 8001) — optimiert für Coding-Aufgaben @@ -165,58 +169,60 @@ und dann neu gestartet — ein laufender Inference-Request wird dabei abgebroche --- -## Anpassung für eine einzelne GPU +## GPU-Konfiguration und VRAM -Mit einer GPU läuft das Modell vollständig auf dieser GPU statt verteilt. -Anpassungen in `start-coder.sh` und `start-judge.sh`: +### Aktuelles Setup (1 GPU pro Server) -```bash -# Vorher (2 GPUs, device 1 und 2): - --gpus '"device=1,2"' \ - --main-gpu 0 \ - --tensor-split 0.5,0.5 \ +Jeder Server bekommt eine dedizierte GPU — dadurch kein VRAM-Konflikt: -# Nachher (1 GPU, z.B. device 0): - --gpus '"device=0"' \ - --main-gpu 0 \ -# --tensor-split ← diese Zeile komplett entfernen +``` +GPU 0 NVIDIA T600 → Display / Monitor (nicht für KI genutzt) +GPU 1 RTX 3090 (24 GB) → qwen36-27b-coder (Port 8001) +GPU 2 RTX 3090 (24 GB) → qwen36-27b-judge (Port 8002) ``` -### VRAM-Abschätzung für das 27B IQ4_XS-Modell +Die Skripte verwenden `--gpus '"device=1"'` (Coder) bzw. `'"device=2"'` (Judge). +Innerhalb des Containers ist dies stets `CUDA:0`, daher `--main-gpu 0` ohne `--tensor-split`. + +### VRAM-Abschätzung für das 27B IQ4_XS-Modell (eine GPU) | Komponente | Größe (ca.) | |---|---| -| Modell-Gewichte (IQ4_XS, 27B) | ~14,5 GB | -| KV-Cache bei 128K Kontext (q8_0) | ~14 GB | -| KV-Cache bei 64K Kontext (q8_0) | ~7 GB | -| KV-Cache bei 32K Kontext (q8_0) | ~3,5 GB | +| Modell-Gewichte (IQ4_XS, 27B) | ~14 GB | +| KV-Cache bei 262 144 Tokens (q4_0) | ~8–10 GB | +| KV-Cache bei 131 072 Tokens (q4_0) | ~4–5 GB | +| KV-Cache bei 65 536 Tokens (q4_0) | ~2–3 GB | +| **Summe (262k Kontext)** | **~22–24 GB** | -Bei einer **24-GB-GPU** ist nur ein Server gleichzeitig sinnvoll betreibbar: -- Modell-Gewichte: ~14,5 GB -- KV-Cache bei 32K Kontext: ~3,5 GB -- Summe: ~18 GB → passt mit Puffer - -**Empfehlung für eine 24-GB-GPU:** +Bei einer **24-GB-GPU** ist 262k Kontext knapp kalkuliert. Wichtig: keine anderen Prozesse +dürfen nennenswert VRAM belegen (z. B. TTS-Dienste, Ollama). Prüfen mit: ```bash -# Coder — Kontext reduzieren --c 32768 # statt 131072 --n 8192 # statt 16384 +nvidia-smi --query-gpu=index,memory.used,memory.free --format=csv +``` -# Judge — Kontext reduzieren --c 32768 # statt 131072 +Falls der VRAM nicht reicht: Kontext in den Startskripten auf `131072` oder `65536` reduzieren. + +### Anpassung für andere GPU-Konfigurationen + +```bash +# 2 GPUs (beide für KI, keine Display-GPU): + --gpus '"device=0"' # Coder + --gpus '"device=1"' # Judge + +# 1 GPU (beide Server auf gleicher GPU — nicht empfohlen, VRAM-Konflikt): + --gpus '"device=0"' # beide Server + --tensor-split 0.5,0.5 # Modell hälftig aufteilen + -c 65536 # Kontext stark reduzieren + +# 2 GPUs für einen Server (wenn sehr großer Kontext nötig): + --gpus '"device=0,1"' \ + --tensor-split 0.5,0.5 \ + --main-gpu 0 \ + -c 262144 ``` Bei einer **16-GB-GPU** ist die Modellgröße allein schon grenzwertig. -Entweder ein kleineres Modell verwenden oder die Quantisierung weiter erhöhen (IQ3_XS, Q4_K_M). - -### Beide Server auf einer GPU betreiben - -Technisch möglich, aber beide Server laden das Modell gleichzeitig → doppelter VRAM-Bedarf. -Auf einer 24-GB-GPU daher **nicht empfohlen**. Alternativen: - -- Nur einen Server gleichzeitig starten (manuell umschalten) -- Kleinere Quantisierung wählen (IQ3_XS: ~11 GB) -- `ollama` als Alternative — lädt Modelle bei Bedarf und entlädt sie wieder +Entweder ein kleineres Modell verwenden oder die Quantisierung erhöhen (IQ3_XS, Q4_K_M). --- @@ -243,7 +249,7 @@ KV-Cache ≈ `context_size × 28 × 128 × 32 × 2 × 1 Byte ≈ context_size × | 32 768 | ~1,9 GB | 1 × 16-GB-GPU | | 65 536 | ~3,7 GB | 1 × 24-GB-GPU | | 131 072 | ~7,5 GB | 2 × 16-GB-GPU | -| 262 144 | ~15 GB | 2 × 24-GB-GPU — **aktuell gesetzt** | +| 262 144 | ~8–10 GB | 1 × 24-GB-GPU (q4_0) — **aktuell gesetzt** | ### KV-Cache-Quantisierung @@ -306,16 +312,19 @@ Ausführliche Beschreibung aller Kommandos mit Beispielen: siehe **BEDIENUNGSANL ## Live-Aktivitätsstatus -Während der Ausführung zeigt pi_coder in der Statuszeile, was gerade passiert: +Während der Ausführung zeigt pi_coder in der Statuszeile, was gerade passiert — +inklusive eines laufenden `[MM:SS]`-Timers während jeder LLM-Inference-Phase: | Situation | Anzeige | |---|---| -| Coder implementiert | `Coder implementiert…` | +| Coder implementiert | `◉ Coder implementiert: Login-Flow mit JWT [01:23]` | | edit-Tool aktiv | `Editiere src/main.py…` | | git commit | `Git-Commit…` | -| Judge reviewt (Runde 2/2) | `Judge reviewt (Runde 2/2)…` | -| Tests laufen | `Tests laufen…` | -| Fix-Phase | `Coder fixt Blocker…` | +| Judge (Quick-Check, Runde 1) | `◉○ Runde 1/2: Quick-Check [00:47]` | +| Judge (voller Review, Runde 2) | `●◉ Runde 2/2: Judge — TASK.md + letzter Commit + Tests [02:15]` | +| Tests laufen | `●○ Runde 1/2: Tests laufen (pytest, max. 120s)…` | +| Fix-Phase | `●◉ Runde 2/2: Coder fixt — fehlendes Null-Check [01:05]` | +| ShipIt | `●●◉ ShipIt — finale Freigabe [00:33]` | | Interactive-Pause (--interactive) | `⏸ PASS – warte auf /continue…` | -So ist jederzeit erkennbar, in welcher Phase sich der automatische Loop befindet. +Der Timer beweist, dass die LLM tatsächlich arbeitet. Steht er still, hängt der Prozess. diff --git a/pi-coder-judge-extension.ts b/pi-coder-judge-extension.ts index 950949c..fcb2dac 100644 --- a/pi-coder-judge-extension.ts +++ b/pi-coder-judge-extension.ts @@ -888,6 +888,28 @@ let interactivePauseActive = false; let interactiveContinueRequested = false; let interactivePauseTask = ""; let currentActivity = ""; // Working-Message für den aktuellen Command-Kontext +let phaseTimerHandle: ReturnType | null = null; + +// Startet einen Sekundentakt, der [MM:SS] in die Statuszeile schreibt — zeigt dem Nutzer, +// dass die LLM-Inference läuft und nicht hängt. Muss mit stopPhaseTimer() beendet werden. +function startPhaseTimer(ctx: ExtensionCommandContext, statusKey: string, label: string): void { + stopPhaseTimer(); + const start = Date.now(); + ctx.ui.setStatus(statusKey, `${label} [00:00]`); + phaseTimerHandle = setInterval(() => { + const s = Math.round((Date.now() - start) / 1000); + const mm = String(Math.floor(s / 60)).padStart(2, "0"); + const ss = String(s % 60).padStart(2, "0"); + ctx.ui.setStatus(statusKey, `${label} [${mm}:${ss}]`); + }, 1000); +} + +function stopPhaseTimer(): void { + if (phaseTimerHandle !== null) { + clearInterval(phaseTimerHandle); + phaseTimerHandle = null; + } +} // Erzeugt eine knappe Statuszeile aus Tool-Name und Argumenten. function toolExecutionLabel(toolName: string, args: Record): string { @@ -1126,7 +1148,9 @@ export default function (pi: ExtensionAPI) { return; } currentActivity = "Coder implementiert…"; + startPhaseTimer(ctx, "optimize", `◉ Coder implementiert: ${taskPreview}`); await sendAndWait(pi, ctx, coderKickoff(task)); + stopPhaseTimer(); await tickTaskMdStatus(pi, ctx, "Implementierung"); if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", "Nach Implementierung"); return; } @@ -1187,16 +1211,18 @@ export default function (pi: ExtensionAPI) { ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label}, max. ${testTimeout}s)…`); const testOutput = await runTestsParallel(pi, ctx, autoTestCmds, testTimeout); const judgeLabel = useQuickJudge ? "Quick-Check" : "Judge analysiert"; - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: ${judgeLabel} Test-Ergebnis…`); - currentActivity = `Judge reviewt (Runde ${round}/${maxRounds})…`; + currentActivity = `Judge Runde ${round}/${maxRounds}${useQuickJudge ? " (Quick)" : ""}…`; + startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: ${judgeLabel} Test-Ergebnis`); await sendAndWait(pi, ctx, useQuickJudge ? quickJudgeWithTestsPrompt(testOutput, "") : judgeWithTestsPrompt(testOutput, "")); + stopPhaseTimer(); } else { const judgeLabel = useQuickJudge ? "Quick-Check" : "Judge — TASK.md + letzter Commit + Tests"; - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: ${judgeLabel}…`); - currentActivity = `Judge reviewt (Runde ${round}/${maxRounds})…`; + currentActivity = `Judge Runde ${round}/${maxRounds}${useQuickJudge ? " (Quick)" : ""}…`; + startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: ${judgeLabel}`); await sendAndWait(pi, ctx, useQuickJudge ? quickJudgePrompt("") : judgePrompt("")); + stopPhaseTimer(); } if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; } @@ -1234,13 +1260,14 @@ export default function (pi: ExtensionAPI) { const blockerHint = currentBlockers ? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers) : "Kritikpunkte aus Judge-Bericht"; - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Coder fixt — ${blockerHint}`); if (!await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder")) { finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell (llama-cpp-coder) nicht verfügbar"); return; } - currentActivity = "Coder fixt Blocker…"; + currentActivity = `Coder fixt: ${blockerHint}`; + startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: Coder fixt — ${blockerHint}`); await sendAndWait(pi, ctx, fixPrompt("")); + stopPhaseTimer(); if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Fix Runde ${round}`); return; } } @@ -1282,7 +1309,9 @@ export default function (pi: ExtensionAPI) { return; } currentActivity = "Coder implementiert Zusatzauftrag…"; + startPhaseTimer(ctx, "optimize", `◉ Coder implementiert Zusatzauftrag: ${addPreview}`); await sendAndWait(pi, ctx, coderKickoff(interactivePauseTask)); + stopPhaseTimer(); await tickTaskMdStatus(pi, ctx, "Implementierung"); if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", "Nach Zusatz-Implementierung"); return; } keepGoing = true; @@ -1313,7 +1342,9 @@ export default function (pi: ExtensionAPI) { return; } currentActivity = "Judge: finale Freigabe…"; + startPhaseTimer(ctx, "optimize", `${"●".repeat(maxRounds)}◉ ShipIt — finale Freigabe`); await sendAndWait(pi, ctx, shipitPrompt("")); + stopPhaseTimer(); const shipText = getLastAssistantText(ctx); const shipVerdict = shipText.match(/Urteil:\s*(SHIP|NO-SHIP)/i)?.[1]?.toUpperCase() ?? ""; @@ -1340,6 +1371,7 @@ export default function (pi: ExtensionAPI) { } catch (e: any) { finalNotify(ctx, "⛔ Fehler", String(e?.message ?? e)); } finally { + stopPhaseTimer(); // Sicherstellen dass keine Zustandsvariable in späteren /optimize-Aufruf leckt cancelRequested = false; currentModelKey = ""; diff --git a/start-coder.sh b/start-coder.sh index 0032fc4..f707fb9 100755 --- a/start-coder.sh +++ b/start-coder.sh @@ -22,7 +22,7 @@ fi echo "[*] Starte llama.cpp-Server für Coder ..." docker run -d \ - --gpus '"device=1,2"' \ + --gpus '"device=1"' \ --name "$CONTAINER_NAME" \ --restart unless-stopped \ -e HF_HOME="/hf_home" \ @@ -34,7 +34,7 @@ docker run -d \ -c 262144 \ -n 16384 \ --jinja \ - --chat-template-kwargs '{"enable_thinking":true}' \ + --reasoning on \ --no-context-shift \ --temp 0.6 \ --top-p 0.80 \ @@ -42,7 +42,6 @@ docker run -d \ --min-p 0.01 \ --repeat-penalty 1.05 \ --main-gpu 0 \ - --tensor-split 0.5,0.5 \ -ngl 999 \ -fa on \ --kv-unified \ @@ -57,7 +56,7 @@ docker run -d \ echo "[*] Warte auf Modell-Bereitschaft (Completion-Check, max. 180 s) ..." MODEL_READY=0 -for i in {1..90}; do +for i in {1..150}; do HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -X POST "http://localhost:${HOST_PORT}/v1/chat/completions" \ -H "Content-Type: application/json" \ diff --git a/start-judge.sh b/start-judge.sh index 40c138e..ef60324 100755 --- a/start-judge.sh +++ b/start-judge.sh @@ -22,7 +22,7 @@ fi echo "[*] Starte llama.cpp-Server für Judge ..." docker run -d \ - --gpus '"device=1,2"' \ + --gpus '"device=2"' \ --name "$CONTAINER_NAME" \ --restart unless-stopped \ -e HF_HOME="/hf_home" \ @@ -34,7 +34,7 @@ docker run -d \ -c 262144 \ -n 16384 \ --jinja \ - --chat-template-kwargs '{"enable_thinking":true}' \ + --reasoning on \ --no-context-shift \ --temp 0.7 \ --top-p 0.80 \ @@ -42,7 +42,6 @@ docker run -d \ --min-p 0.01 \ --repeat-penalty 1.05 \ --main-gpu 0 \ - --tensor-split 0.5,0.5 \ -ngl 999 \ -fa on \ --kv-unified \ @@ -57,7 +56,7 @@ docker run -d \ echo "[*] Warte auf Modell-Bereitschaft (Completion-Check, max. 180 s) ..." MODEL_READY=0 -for i in {1..90}; do +for i in {1..150}; do HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -X POST "http://localhost:${HOST_PORT}/v1/chat/completions" \ -H "Content-Type: application/json" \