Update llama runtime and isolate background reviews
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
# Generated as /etc/mike-ai/stack.env by install.sh. Never commit real values.
|
||||
AI_BIND_ADDRESS=10.77.0.2
|
||||
MODEL_DIR=/data/models
|
||||
REVIEW_MODEL_FILE=qwen3-4b-review/Qwen3-4B-Instruct-2507-Q4_K_M.gguf
|
||||
REVIEW_CONTEXT=32768
|
||||
REVIEW_GPU_DEVICE=1
|
||||
REVIEW_GPU_LAYERS=0
|
||||
ROUTER_API_KEY=GENERATED_BY_INSTALLER
|
||||
CONTROLLER_TOKEN=GENERATED_BY_INSTALLER
|
||||
FLUX_MODEL_DIR=/data/models/FLUX.2-klein-4B
|
||||
|
||||
+76
-8
@@ -32,6 +32,76 @@ x-llama-common: &llama-common
|
||||
start_period: 30s
|
||||
|
||||
services:
|
||||
# Kleines, dauerhaft geladenes Hilfsmodell fuer Hermes-Hintergrundaufgaben
|
||||
# (Memory-/Skill-Review). Es laeuft unabhaengig vom wechselnden Hauptmodell
|
||||
# und kann dessen Prompt-Cache daher nicht mehr
|
||||
# verdraengen. Athena reserviert die 3060 bereits fuer Qwen und XTTS; der
|
||||
# robuste Standard ist deshalb CPU/RAM. GPU-Layer koennen spaeter per Env
|
||||
# zugeschaltet werden, sobald dort garantiert Speicher frei ist.
|
||||
llama-review:
|
||||
image: ${LLAMA_IMAGE:-mike-ai/llama.cpp:local}
|
||||
container_name: mike-ai-llama-review
|
||||
restart: unless-stopped
|
||||
gpus: all
|
||||
ipc: host
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=512m,mode=1777
|
||||
volumes:
|
||||
- "${MODEL_DIR:-/srv/mike-ai/models}:/models:ro"
|
||||
environment:
|
||||
NVIDIA_VISIBLE_DEVICES: ${REVIEW_GPU_DEVICE:-1}
|
||||
NVIDIA_DRIVER_CAPABILITIES: compute,utility
|
||||
command:
|
||||
- --model
|
||||
- "/models/${REVIEW_MODEL_FILE:-qwen3-4b-review/Qwen3-4B-Instruct-2507-Q4_K_M.gguf}"
|
||||
- --alias
|
||||
- qwen-review
|
||||
- --ctx-size
|
||||
- "${REVIEW_CONTEXT:-32768}"
|
||||
- --flash-attn
|
||||
- "on"
|
||||
- --cache-type-k
|
||||
- q4_0
|
||||
- --cache-type-v
|
||||
- q4_0
|
||||
- --cache-prompt
|
||||
- --threads
|
||||
- "${LLAMA_THREADS:-6}"
|
||||
- --threads-batch
|
||||
- "${LLAMA_THREADS_BATCH:-6}"
|
||||
- --batch-size
|
||||
- "${REVIEW_BATCH_SIZE:-512}"
|
||||
- --ubatch-size
|
||||
- "${REVIEW_UBATCH_SIZE:-64}"
|
||||
- --parallel
|
||||
- "1"
|
||||
- --kv-unified
|
||||
- --jinja
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- "8080"
|
||||
- --metrics
|
||||
- --fit
|
||||
- "off"
|
||||
- --n-gpu-layers
|
||||
- "${REVIEW_GPU_LAYERS:-0}"
|
||||
- --no-kv-offload
|
||||
- --no-mmap
|
||||
- --no-ui
|
||||
- --temperature
|
||||
- "0.2"
|
||||
networks: [inference]
|
||||
security_opt: ["no-new-privileges:true"]
|
||||
cap_drop: [ALL]
|
||||
healthcheck:
|
||||
test: [CMD, curl, -fsS, "http://127.0.0.1:8080/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 60
|
||||
start_period: 30s
|
||||
|
||||
wireguard-gateway:
|
||||
build: ./platform/docker/wireguard-gateway
|
||||
image: mike-ai/wireguard-gateway:local
|
||||
@@ -153,17 +223,12 @@ services:
|
||||
environment:
|
||||
NVIDIA_VISIBLE_DEVICES: ${MEDIUM_GPU_DEVICES:-0,1}
|
||||
NVIDIA_DRIVER_CAPABILITIES: compute,utility
|
||||
# Keep the language model split unchanged while placing the complete
|
||||
# multimodal projector on the secondary RTX 3060.
|
||||
MTMD_BACKEND_DEVICE: CUDA1
|
||||
# Temporary llama.cpp cache workaround: keep Medium text-only. Current
|
||||
# multimodal builds can discard the complete KV state on a later turn.
|
||||
# The model split, context and MTP settings remain unchanged.
|
||||
command:
|
||||
- --model
|
||||
- "/models/${MEDIUM_MODEL_FILE:?MEDIUM_MODEL_FILE is required}"
|
||||
- --mmproj
|
||||
- "/models/${VISION_PROJECTOR_FILE:?VISION_PROJECTOR_FILE is required}"
|
||||
- --mmproj-offload
|
||||
- --mmproj-device
|
||||
- CUDA1
|
||||
- --alias
|
||||
- qwen-medium
|
||||
- --ctx-size
|
||||
@@ -568,6 +633,9 @@ services:
|
||||
ROUTER_STATE_FILE: /var/lib/mike-ai-profile-router/state.json
|
||||
ROUTER_MAX_CONCURRENT_REQUESTS: "16"
|
||||
UPSTREAM_URL: http://llama-upstream:8080
|
||||
REVIEW_UPSTREAM_URL: http://llama-review:8080
|
||||
REVIEW_MODEL_NAME: qwen-review
|
||||
REVIEW_CONTEXT_LENGTH: "${REVIEW_CONTEXT:-32768}"
|
||||
PROFILE_CONTROL_URL: http://profile-controller:8090
|
||||
PROFILE_CONTROL_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}"
|
||||
SWITCH_TIMEOUT: "600"
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
AI_HOSTNAME=ki-host
|
||||
ADMIN_USER=mike
|
||||
MODEL_DIR=/data/models
|
||||
REVIEW_MODEL_FILE=qwen3-4b-review/Qwen3-4B-Instruct-2507-Q4_K_M.gguf
|
||||
REVIEW_CONTEXT=32768
|
||||
# Eine einzelne GPU-ID oder stabile NVIDIA-GPU-UUID; auf Athena die RTX 3060.
|
||||
REVIEW_GPU_DEVICE=1
|
||||
REVIEW_GPU_LAYERS=0
|
||||
|
||||
# Installing a new NVIDIA driver can require one reboot. In that case this
|
||||
# installer exits with code 20 (NVIDIA) or 21 (stable NIC rename); rerun the
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Aktueller temporärer Laufzustand
|
||||
|
||||
Stand: 31. August 2026
|
||||
|
||||
## Produktive llama.cpp-Runtime
|
||||
|
||||
Alle Textprofile und das Review-Modell verwenden llama.cpp Build 10718,
|
||||
Commit `41ef91f7c8046087cdfbb276b79bff311ecf1c6d`. Der vorherige Build 10587
|
||||
wurde nach einem kontrollierten A/B-Test ersetzt. Der Wechsel verbessert den
|
||||
Durchsatz bei großen Werkzeuglisten, ohne im 70K-/120K-Kontext- und Cachetest
|
||||
eine Regression zu zeigen.
|
||||
|
||||
## Qwen Medium: vorübergehend ohne Vision-Projektor
|
||||
|
||||
`qwen-medium` läuft aktuell als reines Textprofil **ohne `--mmproj`**. Das ist
|
||||
ein vorläufiger Workaround für einen llama.cpp-Fehler, bei dem ein geladener
|
||||
Multimodal-/Vision-Projektor die Wiederverwendung des Prompt-/KV-Caches
|
||||
verhindern und dadurch eine vollständige erneute Prompt-Verarbeitung auslösen
|
||||
kann.
|
||||
|
||||
Dabei gilt ausdrücklich:
|
||||
|
||||
- Der Gesamtkontext bleibt bei **160.000 Tokens**.
|
||||
- **MTP / Speculative Decoding bleibt aktiviert**; MTP wurde nicht entfernt.
|
||||
- Modell, Quantisierung, GPU-Aufteilung und KV-Cache-Quantisierung bleiben
|
||||
unverändert.
|
||||
- Nur die Bildanalyse über den Qwen-Vision-Projektor ist im Medium-Profil
|
||||
vorübergehend nicht verfügbar.
|
||||
- Der Workaround ist noch nicht durch einen vollständig kontrollierten lokalen
|
||||
A/B-Test mit identischen Anfragen bestätigt. Er entspricht jedoch dem in
|
||||
llama.cpp Issue #19858 und verwandten Meldungen beschriebenen Workaround.
|
||||
|
||||
Referenz:
|
||||
|
||||
- https://github.com/ggml-org/llama.cpp/issues/19858
|
||||
- https://github.com/ggml-org/llama.cpp/issues/21133
|
||||
|
||||
## Separates bekanntes Problem
|
||||
|
||||
Automatische Hermes-Hintergrundanfragen können weiterhin den einzigen aktiven
|
||||
llama.cpp-Slot belegen und damit den Cache eines großen Chats verdrängen. Dieses
|
||||
Slot-Eviction-Problem ist unabhängig vom MMProj-Workaround und muss separat in
|
||||
der Hermes-Auxiliary-/Hintergrundverarbeitung geklärt werden.
|
||||
|
||||
## Nächster Entscheidungspunkt
|
||||
|
||||
Vor einer dauerhaften Übernahme kontrolliert vergleichen:
|
||||
|
||||
1. identischer Mehrturn-Textchat mit MMProj,
|
||||
2. identischer Mehrturn-Textchat ohne MMProj,
|
||||
3. jeweils keine parallelen Hermes-Hintergrundanfragen,
|
||||
4. gelesene beziehungsweise erneut verarbeitete Prompt-Tokens und Prefill-Zeit
|
||||
vergleichen.
|
||||
@@ -1 +1 @@
|
||||
3f545beccee69d9975f466ec7e45fd9aacd8ba90
|
||||
41ef91f7c8046087cdfbb276b79bff311ecf1c6d
|
||||
|
||||
@@ -21,11 +21,13 @@ nach Standardbenchmark, Tool-Calling-Test und Kontexttest übernommen.
|
||||
- Fast, Medium, Large und Uncensored: integrierte Vision; der jeweilige
|
||||
Projektor liegt vollständig auf der RTX 3060
|
||||
|
||||
Die produktive Runtime ist auf Commit
|
||||
`3f545beccee69d9975f466ec7e45fd9aacd8ba90` festgeschrieben. Gegen den
|
||||
vorherigen Commit waren Antworten und Geschwindigkeit praktisch identisch;
|
||||
übernommen wurde er wegen der Qwen-/MTP-/Multimodal-Korrekturen und des
|
||||
expliziten `--mmproj-device`.
|
||||
Die produktive Runtime ist auf llama.cpp Build 10718, Commit
|
||||
`41ef91f7c8046087cdfbb276b79bff311ecf1c6d`, festgeschrieben. Im lokalen
|
||||
A/B-Test blieben normaler Durchsatz, 70K-/120K-Kontext und Prompt-Cache
|
||||
unverändert. Mit 100 angebotenen Werkzeugen stieg der Ausgabedurchsatz von
|
||||
56,03 auf 60,67 Tokens/s. Der Build begrenzt insbesondere einen teuren
|
||||
Qwen3-Coder-Workaround wieder auf die betroffenen Modellfamilien, statt ihn
|
||||
auch auf Qwen3.8 anzuwenden.
|
||||
|
||||
Die Dateien selbst sind nicht Bestandteil des Repositories. Pfade und Hashes
|
||||
werden im lokalen Modellmanifest verwaltet.
|
||||
|
||||
@@ -94,6 +94,9 @@ from router_support import (
|
||||
HOST = os.environ.get("ROUTER_HOST", "0.0.0.0")
|
||||
PORT = int(os.environ.get("ROUTER_PORT", "8081"))
|
||||
UPSTREAM_URL = os.environ.get("UPSTREAM_URL", "http://127.0.0.1:8080").rstrip("/")
|
||||
REVIEW_UPSTREAM_URL = os.environ.get("REVIEW_UPSTREAM_URL", "").rstrip("/")
|
||||
REVIEW_MODEL_NAME = os.environ.get("REVIEW_MODEL_NAME", "qwen-review").strip()
|
||||
REVIEW_CONTEXT_LENGTH = int(os.environ.get("REVIEW_CONTEXT_LENGTH", "32768"))
|
||||
PROFILE_SCRIPT = os.environ.get("PROFILE_SCRIPT", "/usr/local/bin/llama-profile")
|
||||
PROFILE_DIR = os.environ.get(
|
||||
"PROFILE_DIR", "/etc/systemd/system/mike-ai-llama-ui.service.d")
|
||||
@@ -225,6 +228,11 @@ def _parse_upstream(url: str) -> tuple[str, int]:
|
||||
|
||||
|
||||
UPSTREAM_HOST, UPSTREAM_PORT = _parse_upstream(UPSTREAM_URL)
|
||||
if REVIEW_UPSTREAM_URL:
|
||||
REVIEW_UPSTREAM_HOST, REVIEW_UPSTREAM_PORT = _parse_upstream(
|
||||
REVIEW_UPSTREAM_URL)
|
||||
else:
|
||||
REVIEW_UPSTREAM_HOST, REVIEW_UPSTREAM_PORT = "", 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1590,9 +1598,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
@staticmethod
|
||||
def _models_payload() -> dict:
|
||||
return {
|
||||
"object": "list",
|
||||
"data": [
|
||||
models = [
|
||||
{
|
||||
"id": f"qwen-{name}",
|
||||
"object": "model",
|
||||
@@ -1602,7 +1608,19 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"context_window": ctx,
|
||||
}
|
||||
for name, ctx in PROFILES.items()
|
||||
],
|
||||
]
|
||||
if REVIEW_UPSTREAM_URL:
|
||||
models.append({
|
||||
"id": REVIEW_MODEL_NAME,
|
||||
"object": "model",
|
||||
"created": 0,
|
||||
"owned_by": "ai-profile-router",
|
||||
"context_length": REVIEW_CONTEXT_LENGTH,
|
||||
"context_window": REVIEW_CONTEXT_LENGTH,
|
||||
})
|
||||
return {
|
||||
"object": "list",
|
||||
"data": models,
|
||||
}
|
||||
|
||||
def _status_payload(self) -> dict:
|
||||
@@ -2165,6 +2183,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
data = None
|
||||
requested_profile: str | None = None
|
||||
requested_review = False
|
||||
# Virtuelles Modell erkennen. Umschalten und Chat-Lease werden weiter
|
||||
# unten atomar unter dem zentralen Orchestrierungs-Lock ausgeführt.
|
||||
if body is not None and self.path.startswith("/v1/"):
|
||||
@@ -2173,7 +2192,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
except ValueError:
|
||||
data = None
|
||||
model = data.get("model") if isinstance(data, dict) else None
|
||||
if isinstance(model, str) and model in VIRTUAL_MODELS:
|
||||
if (isinstance(model, str) and REVIEW_UPSTREAM_URL
|
||||
and model == REVIEW_MODEL_NAME):
|
||||
requested_review = True
|
||||
elif isinstance(model, str) and model in VIRTUAL_MODELS:
|
||||
requested_profile = VIRTUAL_MODELS[model]
|
||||
elif isinstance(model, str) and model.startswith("qwen-"):
|
||||
# qwen-* ist der Namensraum des Routers
|
||||
@@ -2185,6 +2207,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
# kann kein zweiter Client zwischen Profilwahl und Upstream-Request das
|
||||
# Modell austauschen. Vision-Vorbereitung gehört zur selben Transaktion.
|
||||
if isinstance(data, dict) and path == "/v1/chat/completions":
|
||||
if requested_review:
|
||||
self._review_chat_proxy(data)
|
||||
return
|
||||
self._chat_proxy(body, data, requested_profile)
|
||||
return
|
||||
if requested_profile is not None:
|
||||
@@ -2262,6 +2287,28 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if lease_acquired:
|
||||
self._release_model_lease()
|
||||
|
||||
def _review_chat_proxy(self, data: dict) -> None:
|
||||
"""Leitet kompakte Hermes-Hintergrundreviews an das Hilfsmodell.
|
||||
|
||||
Absichtlich ohne Hauptmodell-Lease und Profilwechsel: Der Review darf
|
||||
den aktiven Qwen-Chat weder anhalten noch dessen Prompt-Cache ersetzen.
|
||||
"""
|
||||
try:
|
||||
data = _normalize_llamacpp_reasoning(data)
|
||||
data = _cap_chat_generation(data)
|
||||
if _request_has_image(data):
|
||||
raise ValueError("qwen-review unterstützt keine Bilder")
|
||||
data["model"] = REVIEW_MODEL_NAME
|
||||
self._proxy_to(
|
||||
json.dumps(data).encode(),
|
||||
REVIEW_UPSTREAM_HOST,
|
||||
REVIEW_UPSTREAM_PORT,
|
||||
"Review-Modell",
|
||||
)
|
||||
except ValueError as exc:
|
||||
self._send_error(400, str(exc), "invalid_request_error",
|
||||
"invalid_review_request")
|
||||
|
||||
def _proxy_with_wait(self, body: bytes | None) -> None:
|
||||
"""Leitet an llama.cpp weiter, wartet aber erst, bis Qwen verfügbar ist.
|
||||
|
||||
@@ -2295,9 +2342,13 @@ class Handler(BaseHTTPRequestHandler):
|
||||
STATE.active_chats -= 1
|
||||
|
||||
def _proxy(self, body: bytes | None) -> None:
|
||||
self._proxy_to(body, UPSTREAM_HOST, UPSTREAM_PORT, "llama.cpp")
|
||||
|
||||
def _proxy_to(self, body: bytes | None, host: str, port: int,
|
||||
upstream_name: str) -> None:
|
||||
# An llama.cpp weiterleiten (Streaming bleibt erhalten).
|
||||
try:
|
||||
conn = http.client.HTTPConnection(UPSTREAM_HOST, UPSTREAM_PORT,
|
||||
conn = http.client.HTTPConnection(host, port,
|
||||
timeout=CONNECT_TIMEOUT)
|
||||
conn.connect()
|
||||
conn.sock.settimeout(REQUEST_TIMEOUT)
|
||||
@@ -2306,7 +2357,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
conn.request(self.command, self.path, body=body, headers=headers)
|
||||
resp = conn.getresponse()
|
||||
except (OSError, http.client.HTTPException) as e:
|
||||
self._send_error(502, f"llama.cpp nicht erreichbar: {e}",
|
||||
self._send_error(502, f"{upstream_name} nicht erreichbar: {e}",
|
||||
"server_error", "upstream_unavailable")
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user