--- kind: responsibility name: gpu-fleet description: > Manages the GPU inference fleet across all hosts. Handles model deployment, registration, health checks, LiteLLM sync, agent key management, GPU saturation watchdog, Prometheus/Grafana monitoring, and self-healing. Current as of 2026-07-08: context reduced to 128K on NVIDIA GPUs, parallel 2 on all GPUs, LiteLLM timeouts tuned (gemma 25→120s, qwen 40→90s), router fully deprecated — nginx routes /v1 → LiteLLM directly. agent: abiba triggers: - on model add/remove - on GPU health degradation - on agent key rotation - on router restart (roster must be loaded) --- ## Maintains - gpu_roster: { models: map, hosts: map } — Single source of truth for all GPU models - router: { status: "healthy", roster_loaded: bool, models: array } - litellm: { status: "healthy", keys: array, models: array } - agent_keys: { agent: api_key } — All agent API keys registered in LiteLLM DB - health: { gpus: array, circuit_breakers: array } — Fleet-wide health state - monitor: { status: "running", version: "2.0.0" } — GPU monitor server on pi (:9100) - watchdog: { status: "running" } — GPU saturation watchdog (restarts stuck llama-server) - benchmarks: { tok_per_sec: map, baseline: map, history: array } — Inference speed benchmarks tracked over time - grafana: { status: "running", dashboards: ["gpu-fleet"] } — Grafana on CT 116 (:3001) - prometheus: { status: "running", targets: 5 } — Scrapes GPU :9400 exporters + LiteLLM - port_conflict_detection: { status: "active" } — All 3 GPU wrappers detect ghost processes before binding ## Fleet Topology (Current — June 2026) ``` ┌──────────────────────────────────────────────────────────────────┐ │ CT 116 (192.168.68.116) — Inference Harness Host │ │ │ │ nginx:80 (entrypoint) │ │ ├─ /v1/* → harness-litellm:4000 (API requests) │ │ ├─ /admin/* → harness-litellm:4000 (admin endpoints) │ │ ├─ /dashboard/ → harness-dashboard:3000 (harness UI) │ │ ├─ /litellm/* → harness-litellm:4000 (LiteLLM UI + API) │ │ ├─ /health/* → harness-litellm:4000 (health probes) │ │ └─ /gpu/* → 192.168.68.24:9100 (fleet monitor) │ │ │ │ Containers: │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ LiteLLM │ │ Router │ │Dashboard │ │ Grafana │ │ │ │ :4000 │─▶│ :9000 │ │ :3000 │ │ :3000 │ │ │ │ keys+sync│ │internal │ │ harness │ │ Prometheus│ │ │ │ fallback │ │only! │ │ UI │ │ data src │ │ │ └──────────┘ └───┬──────┘ └──────────┘ └──────────┘ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │PostgreSQL│ │ Redis │ │Prometheus│ │ │ │ :5432 │ │ :6379 │ │ :9090 │ │ │ └──────────┘ └──────────┘ └──────────┘ │ └────────────────────┼─────────────────────────────────────────────┘ │ ┌───────────────┼───────────────┬──────────────────┐ │ │ │ │ ┌────▼─────┐ ┌──────▼──────┐ ┌────▼──────┐ ┌───────▼──────┐ │ CT 8 │ │ CT 110 │ │ CT 15 │ │ pi (.24) │ │ RTX 3090 │ │ RTX 5070 │ │ Strix Halo│ │ GPU Monitor │ │ 24GB │ │ 12GB │ │ 64GB UMA │ │ :9100 │ │ 128K ctx │ │ 128K ctx │ │ 256K ctx │ │ Watchdog │ │ qwen3.6 │ │ gemma-4-12b │ │ ornith35B │ │ Prometheus │ │ 27B-code │ │ :8080 │ │ :8080 │ │ exporter │ │ :8080 │ │ :9400 (exp) │ │ :9400(exp)│ │ :9401 │ │ :9400 │ └─────────────┘ └───────────┘ └──────────────┘ └──────────┘ ``` ## Current Model Assignments (2026-07-08) | Model | GPU | Host | VRAM | Ctx | KV Cache | Parallel | Batch/Ubatch | Status | |-------|-----|------|------|-----|----------|----------|-------------|--------| | qwen3.6-27B-code | RTX 3090 | .8 (llm-gpu) | 20.3/24GB (83%) | 128K | turbo4 | 2 | 512/512 | ✅ healthy | | gemma-4-12b | RTX 5070 | .110 (ocu-llm) | 9.4/12.2GB (77%) | 128K | q4_0 | 2 | 2048/512 | ✅ healthy | | ornith-1.0-35b | Strix Halo Vulkan | .15 (amdpve) | 24.4/64GB (35%) | 256K | q8_0 | 2 | 2048/512 | ✅ healthy | ## Operations ### add-model 1. Download model file from Hugging Face or source 2. Check disk space + GPU VRAM compatibility 3. Start llama-server via systemd service on GPU host 4. Add to `/opt/inference-harness/gpu_roster.yaml` (or router env vars) 5. Hot-reload or restart router 6. Add model to LiteLLM config (`model_list` + `fallbacks`) 7. Generate agent keys for new model access via `/key/generate` 8. Add Prometheus scrape target for the new GPU exporter 9. Verify end-to-end: LiteLLM → Router → Model ### remove-model 1. Drain active requests (wait for active=0) 2. Remove from LiteLLM config 3. Remove from router config 4. Stop llama-server (systemd) 5. Remove Prometheus scrape target 6. Cleanup model files (optional) ### heal 1. Check all GPUs via router internal `:9000/health/unified` 2. Check LiteLLM health via nginx `:80/litellm/health/liveliness` 3. Reset stuck circuit breakers if idle (Redis) 4. Restart dead llama-server instances via SSH 5. Flush Redis active counters if stale 6. Verify GPU monitor server is running on pi (:9100) 7. Verify watchdog is running on pi 8. Restart router if roster not loaded (check logs for STARTUP ROSTER) 9. Reload roster via `POST :9000/admin/roster/reload` if available ### sync-keys 1. List all agent keys in LiteLLM DB via `GET /key/list` 2. Compare against expected agent list: [tanko, mumuni, abiba, tdunna, baggy, kagenz0] 3. Generate missing keys via `POST /key/generate` with unlimited budget 4. Update agent configs — `/etc/environment` LITELLM_API_KEY 5. Send Zulip DM to agents that can't be reached via SSH 6. Verify each key with test request through full chain 7. Document keys in knowledge graph ### list Show full fleet status: GPUs, models, VRAM, context windows, parallel slots, active requests, circuit breakers, keys ### health-check 1. Check GPU hardware: nvidia-smi (.8, .110) + amdgpu sysfs (.15 via /sys/class/drm/card0/device/) 2. Check llama-server processes: `ps aux | grep llama-server` on all 3 hosts 3. Check LiteLLM: `curl http://192.168.68.116/health` (expect "I'm alive!") 4. Check LiteLLM models: `curl -H "Authorization: Bearer $MASTER_KEY" http://192.168.68.116/v1/models` 5. Check LiteLLM timeouts: `grep -n 'timeout:' /opt/inference-harness/litellm_config.yaml` - gemma-4-12b: 120s, qwen3.6-27B: 90s, ornith-1.0-35b: 120s - global request_timeout: 300s, nginx proxy_read_timeout: 600s 6. Check AMD metrics: `curl http://192.168.68.15:9400/metrics` (Radeon 8060S, util%, VRAM, temp, power) 7. Check port conflicts: verify only one llama-server on :8080 per host 8. Verify agent keys: 9 keys in LiteLLM DB (`GET /key/list`) ## Agent Keys (LiteLLM DB — Current 2026-06-30) | Agent | CT | IP | Key | Access | |-------|-----|-----|-----|--------| | Tanko | 112 | .122 | `sk-CggiHWlamQyShxWC3Hx6uw` | SSH jerome | | Mumuni | 114 | .123 | `sk-VrqCNlwUgzoNGOpikJ7nwQ` | SSH root | | Abiba | 100 | .24 | `sk-Qvzi4uYQBhlSK_XstEhcyQ` | local (pi agent) | | Tdunna | 111 | ? | `sk-6sbCNjz2T6lTVDBdlNHXsA` | Zulip DM | | Baggy | 113 | ? | `sk-krnw_zGBwvvL5b7l2t-s-A` | no SSH | | Kagenz0 | 105 | ? | `sk-Dh4CDkaHebMLEp8qqq20qA` | no SSH | **Key update procedure**: Update `/etc/environment` → `LITELLM_API_KEY=sk-...` → restart Hermes. If no SSH access, send Zulip DM via abiba-bot. ## Configuration Files | File | Host | Purpose | |------|------|---------| | `/opt/inference-harness/docker-compose.yml` | CT 116 | All containers (router, litellm, nginx, postgres, redis, dashboard) | | `/opt/inference-harness/litellm_config.yaml` | CT 116 | LiteLLM proxy config (models, fallbacks, timeouts) | | `/opt/inference-harness/router/router.py` | CT 116 | Router source (builds via compose) | | `/etc/nginx/nginx.conf` | CT 116 (nginx container) | Routes /v1→LiteLLM, /dashboard/, /litellm/, /health | | `/opt/monitoring/prometheus.yml` | CT 116 | Prometheus scrape config (5 targets) | | `/root/scripts/gpu-monitor-server.py` | pi (.24) | GPU fleet monitor v2.1.0 (with benchmarks) | | `/root/scripts/gpu_benchmark.py` | pi (.24) | GPU inference benchmark module (tok/s tracking) | | `/root/scripts/gpu-saturation-watchdog.py` | pi (.24) | Auto-restart stuck llama-server | | `/root/dashboard/gpu-fleet.html` | pi (.24) | Live HTML dashboard | | `/etc/systemd/system/llama-server.service` | .8, .110 | llama-server daemons (Nvidia GPUs) | | `/etc/systemd/system/ornith-server.service` | .15 (amdpve) | llama-server daemon (Vulkan, Strix Halo). Note: `llama-server.service` and `llama-server@.service` are **masked** on .15 to prevent port 8080 collisions. | ## Prometheus & Grafana | Component | URL | Details | |-----------|-----|---------| | Grafana | `http://192.168.68.116:3001/` | admin / syslog-grafana-2026 | | GPU Dashboard | `http://192.168.68.116:3001/d/gpu-fleet` | Gauges + time series | | Prometheus | `http://192.168.68.116:9090/` (internal) | 5 scrape targets | | GPU Exporters | `:9400/metrics` on .8, .110, .15 | NVIDIA/AMD GPU metrics | | Router Exporter | `:9401/metrics` on .24 | Router + LiteLLM metrics | ## Known Issues & Watch Points - **Router startup race**: Compose router.py doesn't call load_roster(). Reload thread sleeps 30s first. Fix: trigger roster reload via SSH after restart, or rebuild image with startup load_roster(). - **LiteLLM /metrics**: Requires auth. Prometheus uses `/health/liveliness` as workaround. - **VRAM (2026-07-08)**: RTX 3090 at 20.3/24GB (83%), RTX 5070 at 9.4/12.2GB (77%), Strix Halo at 24.4/64GB (35%). Context reduced from 256K→128K on NVIDIA GPUs freed ~3.3GB (.8) and ~1.5GB (.110). - **All GPUs at `--parallel 2` (2026-07-08)**: Fleet serves 6 concurrent requests (was 3). 2× throughput. - **RTX 3090 config**: `-c 131072 -ctk turbo4 -ctv turbo4 --parallel 2`. No explicit batch flags (512/512 default). Service: `/home/llmuser/llama-wrapper.sh`. - **RTX 5070 config**: `--ctx-size 131072 --cache-type-k q4_0 --cache-type-v q4_0 --batch-size 2048 --ubatch-size 512 --parallel 2`. Ubatch fixed 4096→512 (was inverted — ubatch > batch killed prompt throughput). Service: `/home/llmuser/llama-wrapper.sh`. - **LiteLLM timeout tuning (2026-07-08)**: gemma-4-12b 25→120s, qwen3.6-27B-code 40→90s, syslog-auto (qwen route) 40→90s. Nginx proxy_read_timeout: 600s. Global request_timeout: 300s. Config at `/opt/inference-harness/litellm_config.yaml`. - **Strix Halo GPU**: Vulkan is the working backend (ROCm/HIP path abandoned — HSA runtime blocked on Debian 13). Build at `/root/llama.cpp/build-vk/`, commit `4fc4ec5` (2026-07-01), ggml 0.15.3 shared-lib arch. Mesa RADV 25.0.7, KHR_coopmat fast path active. ~70 tok/s gen, 532 tok/s prompt. Service: `ornith-server.service` on port 8080, 256K context, flash-attn + q8 KV. - **Port conflict detection (2026-07-05)**: All 3 GPU wrappers now detect ghost processes squatting port 8080 before starting. `.8` and `.110` use inline pre-start check in `llama-wrapper.sh`; `.15` uses `/usr/local/bin/port-cleanup.sh` ExecStartPre. Replaces the blanket `pkill -9 -x llama-server` on .15 which would kill ALL llama-server instances regardless of port. Ghost detection was the root cause of .8 crash-looping for 27+ restarts (stale pid 25836 squatting 8080 after OOM kill). - **Strix Halo thermal safeguard (2026-07-02)**: `ornith-server.service` has `-n 8192` (hard generation cap per request). Without it, `--predict` defaults to -1 (infinity) — a runaway request from .123 (Mumuni) decoded 39,868 tokens over 24 min, pushing Tctl to 98°C (crit 89.8°C) and throttling 70→29 t/s. The cap bounds worst-case generation to ~5 min. Do NOT remove `-n` without a replacement ceiling. Sustained load hits ~84°C even at 92s; the APU is fanless/low-flow. Clients MUST also set `max_tokens`. - **Port 8080 firewall**: amdpve iptables restricts 8080 to 192.168.68.116 (LiteLLM/router host) only. All inbound connections are from .116 (LiteLLM proxied via nginx). Localhost curls hang (SYN dropped). Always test from .116. - **Router sidecar fallback**: `router.py` `check_gpu_health()` now probes GPU `/health` directly when sidecar at :8090 is absent. Sidecar JSON exporters not deployed on any GPU host — router relies on GPU-direct fallback. - **Router GPU_MOE_URL bug (fixed 2026-07-01)**: docker-compose had `GPU_MOE_URL=.110:8080` (gemma host) instead of `.15:8080` (amdpve). Corrected. - **Alert migration**: All alerts now go to `#agent-hub` topics (`alerts-gpu`, `alerts-pm2`, `alerts-infra`) instead of DMs. Cross-agent visibility enabled. - **tok/s benchmarks**: Measured every 5 min via LiteLLM proxy. Baselines tracked with 30%/50% degradation thresholds. - **NetBird 502**: Tanko routes through NetBird for litellm.sysloggh.net. Use direct IP if NetBird down. ## GPU Inference Benchmarks (Current) | GPU | Model | Gen tok/s | Prompt tok/s | Baseline | Samples | |-----|-------|-----------|--------------|----------|---------| | RTX 3090 (.8) | qwen3.6-27B-code | 75 | 305 | 74 | 6 | | RTX 5070 (.110) | gemma-4-12b | 75 | 323 | 75 | 6 | | Strix Halo (.15) | ornith-1.0-35b | 70 | 532 | 70 | 6 | Benchmarks run through LiteLLM proxy (192.168.68.116:4001) every 5 minutes. Degradation alerts fire at 30% (warning) and 50% (critical) below baseline. History stored at `/root/data/toks-history.json` with 7-day rolling window. **Note (2026-07-01)**: Strix Halo prompt tok/s jumped 209→532 after Vulkan rebuild (cooperative-matrix fast path now active on GFX1151). Baseline may need re-calibration. ## Agent Config Implications (2026-07-08) With NVIDIA GPUs at 128K context: - Agents using `syslog-auto` (50/50 qwen+ornith): keep `context_length: 262144` — ornith supports it, Litellm fallbacks handle qwen overflow - Agents using `qwen3.6-27B-code` directly: set `context_length: 131072` and `max_tokens: 4096` per thermal safety rule - Agents using `gemma-4-12b` directly (auxiliary tasks): set `context_length: 131072` - Compression threshold at 0.65: fires at ~170K for syslog-auto (262K ctx), ~85K for direct qwen/gemma (128K ctx) - All Hermes clients MUST set `max_tokens: 4096` — first line of defense before server-side `-n 8192` cap - Port 8080 is used on all 3 GPU hosts (not 8090 as previously documented)