[Sync] Lua Hyprland chain, licence cleanup
This commit is contained in:
1 parent
93b4f695ce
commit
47166d877f
183 files changed
+5618
-5839
No files matched your search
@@ -1,5 +1,6 @@
|
||||
# WM HOST OVERLAY — full-file copy of common/.config/aichat/config.yaml with the default model
|
||||
# flipped to the duskadiy client: wm is not on the home LAN, so the `local` endpoint
|
||||
# WM HOST OVERLAY — full-file copy of common/.config/aichat/config.yaml plus the `wm` client
|
||||
# (this box's own llama.cpp router at 127.0.0.1:11343, see wm/.config/llamacpp/README.md) and the
|
||||
# default model flipped to the duskadiy client: wm is not on the home LAN, so the `local` endpoint
|
||||
# (192.168.0.204) is unreachable here (kept below for VPN use). Stowed with --override so it
|
||||
# shadows the common file on wm only. When the common config changes, mirror the change here.
|
||||
# see https://github.com/sigoden/aichat/blob/main/config.example.yaml
|
||||
@@ -21,6 +22,34 @@ right_prompt: '{color.purple}{?session {consume_tokens}/{max_input_tokens} }{col
|
||||
|
||||
# max_input_tokens = real ctx-size (from the router) minus output headroom.
|
||||
clients:
|
||||
# This box's own GTX 1080 Ti server (`llamaserver` abbr → 127.0.0.1:11343; presets + benched
|
||||
# speeds in wm/.config/llamacpp/README.md). Model id = config.ini section name.
|
||||
- type: openai-compatible
|
||||
name: wm
|
||||
api_base: http://127.0.0.1:11343/v1
|
||||
models:
|
||||
- name: Qwen3.5-9B-Q4_K_M
|
||||
max_input_tokens: 14000 # ctx 16384 · 33 t/s — daily driver, instant answers
|
||||
supports_vision: true
|
||||
- name: Qwen3.5-9B-Thinking
|
||||
max_input_tokens: 14000 # ctx 16384 · same file, reasoning on (budget 8k)
|
||||
supports_vision: true
|
||||
- name: Qwen3.5-9B-MTP
|
||||
max_input_tokens: 7000 # ctx 8192 · 57 t/s (MTP, +0.75 GB VRAM) — loads on a QUIET desktop only
|
||||
supports_vision: true
|
||||
- name: gemma-4-E4B-it-UD-Q4_K_XL
|
||||
max_input_tokens: 30000 # ctx 32768 · 85 t/s (MTP) — fastest; small vision model, long docs
|
||||
supports_vision: true
|
||||
- name: Mellum2.1-12B-A2.5B-Thinking-IQ4_XS
|
||||
max_input_tokens: 14000 # ctx 16384 · 63 t/s (87 quiet) — fast reasoning coder, prefill > 1k t/s
|
||||
- name: Ornith-1.5-35B-A3B-IQ4_XS
|
||||
max_input_tokens: 14000 # ctx 16384 · ~10 t/s (MTP, 47% acc) — agentic coder, thinking
|
||||
- name: Ornith-1.5-35B-A3B-instant
|
||||
max_input_tokens: 14000 # ctx 16384 · 14–21 t/s busy / 31 quiet (MTP) — same file, thinking off: agent loops
|
||||
- name: gpt-oss-20b
|
||||
max_input_tokens: 30000 # ctx 32768 · 17 t/s — reasoning + tools (TTFT = thinking)
|
||||
- name: gpt-oss-20b-low
|
||||
max_input_tokens: 30000 # ctx 32768 · reasoning effort low, snappy
|
||||
# LAN access (fast; only reachable on the home network)
|
||||
- type: openai-compatible
|
||||
name: local
|
||||
@@ -54,7 +83,7 @@ clients:
|
||||
- name: GLM-4.7-Flash-UD-Q3_K_XL
|
||||
max_input_tokens: 22000 # ctx 24576 · ~28 t/s — fast coder (3-bit)
|
||||
- name: Ornith-1.5-35B-A3B-IQ3_XXS
|
||||
max_input_tokens: 22000 # ctx 24576 · ~32 t/s — agentic coder, thinking
|
||||
max_input_tokens: 30000 # ctx 32768 · ~32 t/s — agentic coder, thinking
|
||||
supports_vision: true
|
||||
- name: gpt-oss-20b
|
||||
max_input_tokens: 62000 # ctx 65536 · ~38 t/s — fast reasoning + tools
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../gui/.config/hypr/.luarc.json
|
||||
@@ -1,15 +0,0 @@
|
||||
# wm host extras (sourced last by ~/.config/hypr/hyprland.conf)
|
||||
source = ~/.config/hypr/nvidia.conf
|
||||
|
||||
# THEME OVERRIDES — personal deltas on top of wm's themes/theme.conf (tracked in this
|
||||
# overlay while wm's HyDE is old enough to hard-source it; that file is generated, so
|
||||
# anything edited in there is lost on the next theme switch — keep the deltas here,
|
||||
# where they are sourced after every theme file and therefore win).
|
||||
general {
|
||||
gaps_in = 2
|
||||
gaps_out = 4
|
||||
}
|
||||
|
||||
decoration {
|
||||
rounding = 8
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
-- wm host layer — loaded by gui/.config/hypr/hyprland.lua via check_require("host").
|
||||
-- Ports wm/.config/hypr/{monitors,host,nvidia}.conf to the Lua chain.
|
||||
|
||||
-- ── Monitors ─────────────────────────────────────────────────────────────────
|
||||
-- DP-2 (the LG 4K) is the only fixed screen. Anything else works when plugged in,
|
||||
-- without editing this file: the 1080 Ti has four other connectors (DP-1, DVI-D-1,
|
||||
-- HDMI-A-1, HDMI-A-2), so extras are read from hl.get_monitors() in connection
|
||||
-- order instead of being named here. The first extra sits right of DP-2 and owns
|
||||
-- workspaces 11-20, a second sits left and owns 21-30.
|
||||
local mainScreen = "DP-2"
|
||||
|
||||
local mainW, mainH, mainScale = 3840, 2160, 1.25
|
||||
hl.monitor({ output = mainScreen, mode = mainW .. "x" .. mainH .. "@60", position = "0x0", scale = mainScale })
|
||||
-- Catch-all for a screen with no rule of its own yet (the empty output name is
|
||||
-- the default rule) — covers the moment between plug-in and the re-pin reload.
|
||||
-- Not scale "auto": that picked 2 for a 1080p screen.
|
||||
hl.monitor({ output = "", mode = "preferred", position = "auto-right", scale = 1 })
|
||||
|
||||
local function extra_screens()
|
||||
local ok, monitors = pcall(hl.get_monitors)
|
||||
local found = {}
|
||||
for _, m in ipairs(ok and monitors or {}) do
|
||||
if m.name ~= mainScreen and not m.is_mirror then
|
||||
found[#found + 1] = m
|
||||
end
|
||||
end
|
||||
table.sort(found, function(a, b)
|
||||
return a.id < b.id
|
||||
end)
|
||||
return found
|
||||
end
|
||||
|
||||
-- A 4K extra gets DP-2's scale, anything smaller 1; placed beside DP-2 and
|
||||
-- vertically centred on it (what the old fixed 3072x350 rule approximated).
|
||||
local function place(m, side)
|
||||
local scale = m.width >= 3840 and mainScale or 1
|
||||
local w, h = math.floor(m.width / scale), math.floor(m.height / scale)
|
||||
local x = side == "right" and math.floor(mainW / mainScale) or -w
|
||||
local y = math.max(0, math.floor((mainH / mainScale - h) / 2))
|
||||
hl.monitor({ output = m.name, mode = "preferred", position = x .. "x" .. y, scale = scale })
|
||||
end
|
||||
|
||||
local extras = extra_screens()
|
||||
local rightScreen, leftScreen
|
||||
if extras[1] then
|
||||
rightScreen = extras[1].name
|
||||
place(extras[1], "right")
|
||||
end
|
||||
if extras[2] then
|
||||
leftScreen = extras[2].name
|
||||
place(extras[2], "left")
|
||||
end
|
||||
|
||||
-- ── Workspace pins (1-10 main, 11-20 right, 21-30 left) ──────────────────────
|
||||
-- These are what make the r~N binds land on the focused monitor's set.
|
||||
for i = 1, 10 do
|
||||
hl.workspace_rule({ workspace = tostring(i), monitor = mainScreen, default = (i == 1) })
|
||||
if rightScreen then
|
||||
hl.workspace_rule({ workspace = tostring(i + 10), monitor = rightScreen, default = (i == 1) })
|
||||
end
|
||||
if leftScreen then
|
||||
hl.workspace_rule({ workspace = tostring(i + 20), monitor = leftScreen, default = (i == 1) })
|
||||
end
|
||||
end
|
||||
|
||||
-- Pins are only computed at load, so a hotplug that changes the set of extras
|
||||
-- reloads once to re-pin. DP-2's own link drop/return (it sleeps as a hotplug)
|
||||
-- leaves the extras unchanged and does not reload.
|
||||
local function names(list)
|
||||
local out = {}
|
||||
for i, m in ipairs(list) do
|
||||
out[i] = m.name
|
||||
end
|
||||
return table.concat(out, ",")
|
||||
end
|
||||
local pinned = names(extras)
|
||||
local function repin_if_changed()
|
||||
if names(extra_screens()) ~= pinned then
|
||||
hl.exec_cmd("hyprctl reload")
|
||||
end
|
||||
end
|
||||
hl.on("monitor.added", repin_if_changed)
|
||||
hl.on("monitor.removed", repin_if_changed)
|
||||
|
||||
-- ── Theme overrides (was host.conf) ──────────────────────────────────────────
|
||||
-- Loads after dynamic.lua's theme state, so these still win over the theme.
|
||||
hl.config({
|
||||
general = { gaps_in = 2, gaps_out = 4 },
|
||||
decoration = { rounding = 8 },
|
||||
})
|
||||
|
||||
-- ── NVIDIA extras (was nvidia.conf) ──────────────────────────────────────────
|
||||
-- LIBVA_DRIVER_NAME / __GLX_VENDOR_LIBRARY_NAME / GBM_BACKEND are already set
|
||||
-- by HyDE's env.lua when the driver probe passes; only the two extras remain.
|
||||
hl.env("NVD_BACKEND", "direct") -- va-api via libva-nvidia-driver
|
||||
hl.config({ cursor = { no_hardware_cursors = true } }) -- avoid cursor hitches
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
# █▀▄▀█ █▀█ █▄░█ █ ▀█▀ █▀█ █▀█ █▀
|
||||
# █░▀░█ █▄█ █░▀█ █ ░█░ █▄█ █▀▄ ▄█
|
||||
|
||||
# Set your monitor configuration here
|
||||
# See https://wiki.hyprland.org/Configuring/Monitors/
|
||||
# For a sample file, please refer to https://github.com/prasanthrangan/hyprdots/blob/main/Configs/.config/hypr/monitors.t2
|
||||
|
||||
$mainScreen = DP-2
|
||||
$leftScreen = eDP-1 # FIXME: eDP is a laptop panel (leftover from lw) — set the real connector when wm's third monitor exists; harmless meanwhile
|
||||
$rightScreen = HDMI-A-1
|
||||
|
||||
monitor = $mainScreen, 3840x2160@60.00, 0x0, 1.25 # main screen
|
||||
monitor = $rightScreen, 1920x1080@60, 3072x350, 1 # right
|
||||
|
||||
# monitor = $mainScreen, 1920x1080@59.99, 0x0, 2 # main screen
|
||||
# monitor = $leftScreen, 1920x1080@60, -1920x0, 1 # left
|
||||
|
||||
#Main Monitor
|
||||
workspace = 1, monitor:$mainScreen, default:true
|
||||
workspace = 2, monitor:$mainScreen
|
||||
workspace = 3, monitor:$mainScreen
|
||||
workspace = 4, monitor:$mainScreen
|
||||
workspace = 5, monitor:$mainScreen
|
||||
workspace = 6, monitor:$mainScreen
|
||||
workspace = 7, monitor:$mainScreen
|
||||
workspace = 8, monitor:$mainScreen
|
||||
workspace = 9, monitor:$mainScreen
|
||||
workspace = 10,monitor:$mainScreen
|
||||
|
||||
# Second Monitor
|
||||
workspace = 11, monitor:$rightScreen, default:true
|
||||
workspace = 12, monitor:$rightScreen
|
||||
workspace = 13, monitor:$rightScreen
|
||||
workspace = 14, monitor:$rightScreen
|
||||
workspace = 15, monitor:$rightScreen
|
||||
workspace = 16, monitor:$rightScreen
|
||||
workspace = 17, monitor:$rightScreen
|
||||
workspace = 18, monitor:$rightScreen
|
||||
workspace = 19, monitor:$rightScreen
|
||||
workspace = 20, monitor:$rightScreen
|
||||
|
||||
# Third Monitor
|
||||
workspace = 21, monitor:$leftScreen, default:true
|
||||
workspace = 22, monitor:$leftScreen
|
||||
workspace = 23, monitor:$leftScreen
|
||||
workspace = 24, monitor:$leftScreen
|
||||
workspace = 25, monitor:$leftScreen
|
||||
workspace = 26, monitor:$leftScreen
|
||||
workspace = 27, monitor:$leftScreen
|
||||
workspace = 28, monitor:$leftScreen
|
||||
workspace = 29, monitor:$leftScreen
|
||||
workspace = 30, monitor:$leftScreen
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
# █▄░█ █░█ █ █▀▄ █ ▄▀█
|
||||
# █░▀█ ▀▄▀ █ █▄▀ █ █▀█
|
||||
|
||||
# Hyprland Nvidia Configuration
|
||||
# See https://wiki.hyprland.org/Nvidia/
|
||||
env = LIBVA_DRIVER_NAME,nvidia
|
||||
env = __GLX_VENDOR_LIBRARY_NAME,nvidia # Disable this if you have issues with screensharing
|
||||
|
||||
|
||||
# If you want to try hardware cursors,
|
||||
# you can enable them by setting `cursor:no_hardware_cursors = false` ,
|
||||
# but it will require also enabling `cursor:allow_dumb_copy`
|
||||
# which may cause small to major hitches whenever the cursor shape changes.
|
||||
# If this is a problem on your system, keep hardware cursors disabled.
|
||||
cursor:no_hardware_cursors = true # Set to true to avoid hitches
|
||||
# cursor:allow_dumb_copy = true
|
||||
|
||||
# https://wiki.hyprland.org/Nvidia/#va-api-hardware-video-acceleration
|
||||
# Hardware video acceleration on Nvidia and Wayland is
|
||||
# possible with the nvidia-vaapi-driver.
|
||||
# This may solve specific issues in Electron apps.
|
||||
env = NVD_BACKEND,direct # Requires 'libva-nvidia-driver' package
|
||||
|
||||
# https://wiki.hyprland.org/Nvidia/#regarding-environment-variables
|
||||
# If you encounter crashes in Firefox, remove this line
|
||||
env = GBM_BACKEND,nvidia-drm
|
||||
|
||||
# If you have a multi-GPU setup and you are facing lag in external monitor.
|
||||
# See https://wiki.hyprland.org/Configuring/Multi-GPU/
|
||||
@@ -0,0 +1,222 @@
|
||||
# Local LLM server — wm (GTX 1080 Ti), setup & usage
|
||||
|
||||
llama.cpp in **router mode** on the Wingman work desktop. This is the **NVIDIA** counterpart of
|
||||
fl (`fl/.config/llamacpp/`, RX 7600 XT 16 GB) and lw (`lw/…`, CPU laptop). Same recipe as fl —
|
||||
MoE experts in RAM, quantized KV, MTP — on a smaller, older card.
|
||||
|
||||
- **Box**: GTX 1080 Ti **11 GB** (Pascal, compute 6.1: int8 MMQ path, no tensor cores) on the
|
||||
legacy `nvidia-580xx` driver · i7-8700 (6 cores / 12 threads) · **31 GiB DDR4** (~35 GB/s =
|
||||
the ceiling for experts kept in RAM).
|
||||
- **Backend**: upstream prebuilt **CUDA 12.8** tarball (`~/.local/opt/llama.cpp-cuda/current`,
|
||||
build b11227) through the `~/.local/bin/llama-server-cuda` wrapper. CUDA 13.x cannot target
|
||||
Pascal, so the Arch `ggml-cuda` / `llama.cpp-cuda-git` packages are useless on this box; the
|
||||
prebuilt carries Pascal as PTX and the driver JITs it (first start of a new build ≈ 50 s, then
|
||||
cached). A Vulkan prebuilt sits beside it (`llama-server-vulkan`) as the no-CUDA fallback —
|
||||
same build, ~half the prompt speed, same decode.
|
||||
- **Endpoint**: `http://127.0.0.1:11343/v1` on the box, `http://192.168.50.24:11343/v1` from the LAN
|
||||
(wired IP, DHCP — may change). Bound to `0.0.0.0` and **keyless**: anyone on the office network
|
||||
(and ZeroTier) can use it. IPv4 only — use `127.0.0.1`, not `localhost` (which also resolves to
|
||||
`::1`, where nothing listens). The chat endpoint is POST-only: a browser GET gets 404.
|
||||
- **Files here** (`~/.config/llamacpp/`, per-file stow links from the repo's `wm/.config/llamacpp/`):
|
||||
`config.ini` (presets — section names ARE the API model ids), `llama.service` (user unit), `bench.py` (nvidia-smi benchmark),
|
||||
`bench-results.md` / `bench-history.md` (ledgers, generated next to the script, not tracked).
|
||||
The GGUFs live in `~/Documents/models/` (`llama-server -hf` drops downloads into
|
||||
`~/.cache/huggingface/hub/` — move them out).
|
||||
|
||||
## How it runs
|
||||
|
||||
A **user** systemd unit, `llama.service` (this dir; linked into `~/.config/systemd/user/` and enabled,
|
||||
starts with the login session — `loginctl enable-linger coja` makes it start at boot without a login).
|
||||
Its `ExecStart` is the command the old abbr ran:
|
||||
|
||||
```
|
||||
nice -n 19 llama-server-cuda --host 0.0.0.0 --port 11343 --cors-origins localhost \
|
||||
--models-max 1 --models-preset ~/.config/llamacpp/config.ini
|
||||
```
|
||||
|
||||
The fish abbr **`llamaserver`** now means `systemctl --user restart llama; and journalctl --user -fu llama`.
|
||||
Install once after stowing (a symlinked unit can't be linked directly, hence `readlink -f`):
|
||||
|
||||
```
|
||||
systemctl --user link (readlink -f ~/.config/llamacpp/llama.service); systemctl --user enable --now llama
|
||||
```
|
||||
|
||||
- Router mode: each requested model loads on demand in a child process; `/v1/models` lists the presets.
|
||||
- **`--models-max 1`**: one model resident; a new request LRU-evicts the previous. Two presets
|
||||
never fit together in the ~6.5–7.5 GB the desktop leaves free. `sleep-idle-seconds` per
|
||||
preset frees idle models either way.
|
||||
- **`nice -n 19`**: the MoE presets run their expert matmuls on all 6 cores — niceness keeps the
|
||||
desktop responsive; the GPU part is unaffected.
|
||||
- **`--cors-origins localhost`**: same-machine profile (llama.cpp PR 25655) — blocks browser pages
|
||||
from calling the keyless API. Non-browser clients unaffected.
|
||||
- Presets are read once at startup: edit `config.ini` → `systemctl --user restart llama` → `./bench.py -m <ids>`.
|
||||
- Logs: `journalctl --user -u llama -f` (each model child logs under the router).
|
||||
- "couldn't bind HTTP server socket … 11343" = a router is already running (the service, another
|
||||
shell, or a bench run) — `systemctl --user status llama`, `pgrep -af llama-server`.
|
||||
|
||||
## VRAM (the golden rule)
|
||||
|
||||
11,264 MiB total, but Hyprland + Xorg + electron apps hold **3.5–4.5 GB**, so a preset gets
|
||||
**~6.5–7.5 GB** and must be sized for the low end. Unlike fl's amdgpu, an NVIDIA overcommit does
|
||||
**not** freeze the PC — `cudaMalloc` fails and the load errors out cleanly. So the guard is
|
||||
"loads reliably on a busy desktop": keep every benched **VRAM free ≥ ~1 GB** (`bench.py` flags
|
||||
< 0.8). If a preset is tight: raise `n-cpu-moe` (MoE), lower `ctx-size`, or use a smaller quant.
|
||||
|
||||
Dense models are the trap here: anything that doesn't fit entirely (> ~6 GB file) streams the
|
||||
leftover layers from DDR4 and collapses (14B Q3 = 7.6 t/s, 8B Q8 = 3.4, 24B IQ3 = 1.8 — see the
|
||||
tombstones in `config.ini`). MoE with the experts in RAM is 2–5× faster at the same size.
|
||||
|
||||
## Model roster (router benches 2026-09-28 → 10-19, `bench.py` through the service, desktop baseline 3.3–4.6 GB, gen 256)
|
||||
|
||||
Three conditions appear below: **quiet** = ~3.3 GB desktop, nothing else on the CPU · **busy** = 4.2–4.6 GB
|
||||
desktop · **sim** = the 2026-10-19 sweep with the AirSim/PX4 simulation stack running (details after the table).
|
||||
|
||||
| model id | decode t/s | ctx | VRAM free | extras | role |
|
||||
|---|---|---|---|---|---|
|
||||
| `gemma-4-E4B-it-UD-Q4_K_XL` | **85–101** (MTP 64–84%; 91 sim) | 32k | 3.6–4.8 GB | vision (projector on CPU), MTP draft, thinking OFF | ⭐ **fastest + instant tier** — quick generalist, images, long documents; pi's default local model |
|
||||
| `Mellum2.1-12B-A2.5B-Thinking-IQ4_XS` | **63** @moe4 / 87 @moe0 quiet · 42 sim | 16k | 1.6–2.3 GB | code MoE 2.5B active, thinking ON, prefill 1.1–1.5k t/s | ⭐ **fast reasoning coder** (added 10-12) |
|
||||
| `Qwen3.5-9B-MTP` | **51–61** (MTP 90%) | 8k | **0.4–1.8 GB** | vision, MTP | ⚠ quiet-desktop preferred (loaded at 1.0 GB free on a ~4.5 GB desktop 10-19) — the daily driver at +75% |
|
||||
| `Qwen3.5-9B-Thinking` | 34–38 | 16k | 1.0–2.5 GB | vision, reasoning budget 8k | hard problems (TTFT ≈ think block) |
|
||||
| `Qwen3.5-9B-Q4_K_M` | 31–33 (one unexplained 24 on 10-12) | 16k | 1.0–2.8 GB | vision, thinking OFF | ⭐ **daily driver** — best quality that always fits |
|
||||
| `gpt-oss-20b` / `-low` | 21 quiet · 17 busy · 10–12 sim | 32k | 2.1–3.7 GB | MoE @moe18, reasoning + tools | reasoning and tool use; `-low` cuts the 8.8 s think TTFT to 1.8 s |
|
||||
| `Ornith-1.5-35B-A3B-instant` | **14–31** (MTP 59–67%; 15 sim) | 16k | 1.4–3.5 GB | MoE @moe36, MTP, thinking OFF | ⭐ **agentic coder** for agent loops; 31.5 and 15.1 t/s on two quiet-desktop runs 16 min apart (prefill 63 → 9) — CPU contention, the server runs at nice 19 |
|
||||
| `Ornith-1.5-35B-A3B-IQ4_XS` | 10–26 (MTP 47–61%; 15 sim) | 16k | 1.8–3.3 GB | same file, thinking ON | coder with reasoning; TTFT ~20 s is the think block |
|
||||
| `Ornith-1.5-35B-A3B-long` | 10 busy · 16.9 @35k prompt (MTP 59–65%) | **128k** | 1.8 GB | same file, @moe40, thinking OFF, prefill ~250 t/s | **long-context tier** (added 10-28) — 35k prompt = 2.4 min to prefill, 100k ≈ 7 min |
|
||||
|
||||
Read across: everything that fits **entirely on the GPU** runs 31–101 t/s; everything
|
||||
that keeps experts in RAM runs 10–31 and tracks the CPU's idle share. The bench guard (`--min-free-gb 0.8`) skipped four presets
|
||||
in the first sweep; they were re-tuned (coder moe 28→34, gpt-oss moe 14→18, 9B draft split into
|
||||
its own alias) and re-benched green. Expected spread: MTP rows swing with draft acceptance
|
||||
(content-dependent); every MoE row dips when the desktop is busy (the experts share the 6 cores).
|
||||
|
||||
**The sim condition (2026-10-19 12:55 sweep).** Unreal `Blocks` (≈ 3 cores at nice 5, 0.56 GB VRAM, GPU 41%
|
||||
busy) plus the PX4/planner nodes at realtime priority had been running since 12:08; load average ~9 on the
|
||||
6-core box. Every CPU-expert preset sat at its floor (Ornith 15, gpt-oss 10–12, Mellum 42) and the GPU-only
|
||||
presets lost 5–15% to the shared GPU. Nothing in the presets caused it and nothing in them fixes it: the
|
||||
service runs at nice 19 and yields by design. Judge local-model speed with the sim stopped. Two things
|
||||
the run did establish: all nine presets loaded with **≥ 1.0 GB free at a ~4–4.5 GB desktop** (the
|
||||
busy-desktop sizing holds), and the run's "baseline 7.5 GB" header was pi's resident gemma, not the desktop
|
||||
— `bench.py` now drains before it records the baseline.
|
||||
|
||||
## Which model, when
|
||||
|
||||
- **Quick questions, images, long one-shot documents**: `gemma-4-E4B` — 85 t/s, 32k, vision.
|
||||
- **Best always-available quality**: `Qwen3.5-9B-Q4_K_M` (33 t/s). On a quiet desktop pick
|
||||
`Qwen3.5-9B-MTP` instead — same model, 57 t/s; if it fails to load, the desktop is holding too
|
||||
much VRAM (electron apps), fall back to the plain preset.
|
||||
- **Autocomplete-class / cheapest agent steps**: `gemma-4-E4B` again — LFM2.5 was faster (119 t/s) but
|
||||
its output was unusable in practice and it was retired 2026-10-12.
|
||||
- **Reasoning coder at speed**: `Mellum2.1-12B-A2.5B-Thinking` — 63 t/s (87 on a quiet desktop) with a
|
||||
think block and >1k t/s prefill; the first stop for a bug that needs thought.
|
||||
- **Agent coding loops** (opencode/pi edit-test cycles): `Ornith-1.5-35B-A3B-instant` — 14–31 t/s
|
||||
depending on what else is using the CPU (nice 19), no think preamble. Switch to `…-IQ4_XS` (thinking) for the bug that needs reasoning.
|
||||
- **Reasoning + tool use at long context**: `gpt-oss-20b` (32k); `-low` for interactive use.
|
||||
- **Really long context (to 128k)**: `Ornith-1.5-35B-A3B-long` — the 35B with every expert in RAM so
|
||||
the KV fits; prefill ~250 t/s (100k ≈ 7 min). fl's Qwen3-Coder-Next 80B needs ≥ 28 GB of RAM for
|
||||
its experts and does not fit next to this desktop.
|
||||
- No separate fast coder: the 2-bit Qwen3-Coder was deleted 2026-10-12 (dominated by Ornith-instant);
|
||||
a K-quant Qwen3-Coder stays listed in `config.ini` candidates if a second coder is ever wanted.
|
||||
- Anything bigger/denser (14B+ dense, 27B, 80B-A3B) does **not** belong on this card — see the
|
||||
candidates list in `config.ini` for what does.
|
||||
|
||||
## Client wiring
|
||||
|
||||
These clients run on this box (other machines use `http://192.168.50.24:11343/v1`). Rule: client model **id = config.ini section
|
||||
name**, client context ≤ server `ctx-size`.
|
||||
|
||||
| tool | overlay file | provider / client id |
|
||||
|---|---|---|
|
||||
| aichat | `wm/.config/aichat/config.yaml` | `wm:` (e.g. `wm:Ornith-1.5-35B-A3B-instant`) — default model still `duskadiy:`; `aichat -m wm:…` or flip `model:` |
|
||||
| opencode | `wm/.config/opencode/opencode.json` | `wmcpp/` (e.g. `wmcpp/Ornith-1.5-35B-A3B-instant`); defaults + agent models point at `duskadiy/` (the common file's `llama.cpp/` LAN provider is unreachable here) |
|
||||
| pi | `wm/.pi/agent/models.json` + `settings.json` | `wmcpp`; default model `wmcpp/gemma-4-E4B-it-UD-Q4_K_XL` since 10-12 (was `duskadiy/Ornith…`) |
|
||||
|
||||
**Two ways a model "doesn't respond" in pi** (both seen 2026-10-12):
|
||||
|
||||
1. The router was started before the preset existed — it reads `config.ini` once, so pi's request fails
|
||||
as an unknown model. `systemctl --user restart llama` after every preset change.
|
||||
2. A small coder (Mellum 2.1, and AesCode-8B before its removal) run with pi's full tool set **from `~`** keeps calling
|
||||
read/bash to explore the home directory and never finishes (router log: the same conversation
|
||||
re-served with a growing context, 2.3k → 5.4k tokens). Run pi inside a project directory, or
|
||||
`pi --no-tools` for plain questions; the same prompt from an empty directory finishes in four rounds.
|
||||
|
||||
When adding/removing a preset, update all three wm overlays (aichat, opencode, pi). They are full-file
|
||||
copies of the common configs plus the `wm`/`wmcpp` provider — when a common client config changes,
|
||||
mirror the change here (same rule as lw).
|
||||
|
||||
## Tuning cheat-sheet (Pascal + CPU experts)
|
||||
|
||||
- **`n-cpu-moe N`** (MoE only): experts of the first N layers stay in RAM. The VRAM/speed dial;
|
||||
each 4-bit expert layer on the GPU ≈ 0.4 GB (35B-A3B). Measured: gpt-oss @moe12 = 29.8 vs
|
||||
@moe24 = 18.6 t/s; Coder-30B @moe24 = 29.9 vs @moe48 = 20.2. Tune in steps of 2–4, re-bench.
|
||||
**Except Ornith**: moe30 vs moe36 on the same quiet desktop = 32.1 vs 31.5 t/s (10-12) — its experts
|
||||
on the GPU buy nothing; give the VRAM to the desktop instead.
|
||||
- **Quant type for CPU experts**: I-quants (IQ4_XS/IQ3_XXS) dequantize slowly on CPU and 4-bit
|
||||
experts stream 2× the bytes of 2-bit → Ornith IQ4_XS with 80% of experts in RAM = 11–15 t/s
|
||||
base. Prefer **K-quants** (Q4_K_M / UD-Q4_K_XL / UD-Q3_K_XL) and the smallest quant the
|
||||
quality allows when the experts live in RAM.
|
||||
- **MTP / spec decode** (`spec-type = draft-mtp`) **works here**: Qwen3.5-9B 32.6 → 57.3 t/s at
|
||||
90% acceptance; Ornith 11–15 → 14–21 at 60%; gemma-4-E4B 84.7 with its draft. Cost: a flat
|
||||
~0.75–1.1 GB VRAM per model — which is why the 9B draft lives in its own quiet-desktop alias.
|
||||
Thinking mode drafts worse (Ornith 47–53%, 10 t/s). Qwen3.5/3.6 heads
|
||||
live in the `-MTP` repo files; gemma-4 needs a separate `mtp-*.gguf` draft; gpt-oss and
|
||||
Qwen3-Coder-30B have no head.
|
||||
- **`threads = 6`** = physical cores. **Never 12**: decode collapses 14 → 1.3 t/s (HT contention).
|
||||
- **KV cache**: `q8_0` K and V everywhere except gpt-oss (attention sinks — defaults). `flash-attn`
|
||||
on everywhere (required for quantized V). Dense Qwens pay ~0.6–0.8 GB per 16k; MoE 48-layer
|
||||
Qwen3-Coder ~1.6 GB per 16k (why it stays at 16k).
|
||||
- **First value after a download**: `llama-fit-params -m <gguf> -c <ctx> -fa on` prints the split
|
||||
that fits the VRAM free *right now*; take it, then add margin (moe +2, or ngl −4).
|
||||
- **CPU governor** is `powersave` (intel_pstate still boosts); `performance` might buy a little
|
||||
on the CPU-expert presets — unmeasured.
|
||||
- **Vision**: keep the projector on the CPU (`no-mmproj-offload = true`) — ~0.9 GB VRAM back.
|
||||
|
||||
## Benchmarking
|
||||
|
||||
Run on this box against the running `llamaserver` (it evicts whatever is resident — close chat clients first):
|
||||
|
||||
```
|
||||
./bench.py # full sweep of everything the router lists
|
||||
./bench.py -m id1,id2 # just the changed presets
|
||||
./bench.py -n 512 --ctx 12000 # longer gen + a long-context decode column
|
||||
```
|
||||
|
||||
Safety-first like fl's: force-unloads resident models via the router API, waits for the card
|
||||
to drain (`--drain-gb 5.5` = the desktop's own usage plus slack — raise it if your desktop
|
||||
alone sits higher), records a ⚠ row instead of generating when a model leaves < 0.8 GB free.
|
||||
The MTP acceptance % lands in the spec column. Ledgers: `bench-results.md` (latest) +
|
||||
`bench-history.md` (append-only) next to the script.
|
||||
|
||||
Reading the columns (changed 2026-10-19):
|
||||
|
||||
- **baseline used** is measured *after* draining anything of ours; the header adds "(was X GB with a
|
||||
resident model, unloaded first)" when it had to. A high baseline now really is the desktop.
|
||||
- **TTFT** = time to the first *answer* token — for a thinking model that is the think block.
|
||||
`all-think` = the whole gen budget (256 tokens) was reasoning and no answer arrived; decode t/s on that
|
||||
row is still valid (server timings count every token). `-n 512` or the `-low`/`-instant` alias gets an answer.
|
||||
- **prefill t/s** at the default ~30-token prompt is fixed-overhead noise (Mellum showed 15 and 100 on two
|
||||
runs; a 1.7k-token probe gave 1,160). Use `--ctx` for a real prefill number.
|
||||
- fl's and lw's `bench.py` still carry the content-only TTFT (0.00 on an all-think row); port the two
|
||||
changes when their ledgers next matter.
|
||||
|
||||
## Watch list
|
||||
|
||||
- **Free VRAM is the whole game**: the desktop's 4.2–4.6 GB (Hyprland ~0.4, Xorg, Ferdium/electron
|
||||
~0.6+) costs the 9B its MTP and every MoE two or three expert layers. Closing electron apps before
|
||||
a session is worth more than any preset knob.
|
||||
- **Qwen3.5-9B ctx 32768** for the plain preset (KV is cheap on the hybrid arch) — bench first.
|
||||
- **Qwen3.6-35B-A3B-MTP UD-IQ3_XXS** (14 GB) — the one "bigger" model worth a test: 28% fewer bytes per
|
||||
token than Ornith's file and a draft that accepts well on non-thinking Qwen; likely ≥ Ornith's 31 t/s quiet.
|
||||
- 2026-10-12 candidate round: AesCode-8B and Mellum 2.1 kept; FrogNano-4B (87.9 t/s with draft, vision)
|
||||
retired — gemma-4-E4B and LFM2.5 cover that slot faster.
|
||||
- 2026-10-19: AesCode-8B removed (preset and the aichat/opencode/pi entries); tombstone in `config.ini`.
|
||||
- **gemma `sleep-idle-seconds = 30` vs pi** (journal 2026-10-19 12:44–12:56): a pi session slept and woke
|
||||
its default model 3× in 12 min, each wake a full reload ≈ 6 s before the first token (cold load 17 s).
|
||||
Resident, gemma holds 3.7 GB and still leaves ~3 GB beside a 4.5 GB desktop; 120–300 s (fl uses 300 on its
|
||||
coders and 30 on this same gemma) would remove the stall. Not changed — the VRAM-vs-latency trade is the user's call.
|
||||
- **The sim stack is the busy floor** (10-19): with Blocks/PX4 running, CPU-expert presets drop 40–55% and
|
||||
GPU-only ones 5–15%. Unmeasured: `threads 3`–`4` on the CPU-expert presets *while the sim holds 3 cores*
|
||||
— 6 threads then share physical cores with it, and HT sharing is exactly what collapsed decode at 12.
|
||||
- gemma logs `Gemma4Assistant requires ctx_other … (this warning is normal during memory fitting)` and
|
||||
`failed to measure the memory of the extra model` at every load, including each wake: the auto-fitter
|
||||
probing the MTP draft. Harmless (ngl 99, everything on the GPU); noted in the preset so nobody chases it.
|
||||
- Dead files were deleted 2026-10-12 (66 GB): ~/Documents/models now holds exactly the nine presets' files (46 GB).
|
||||
Executable
+348
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
llama.cpp router benchmark — WM (GTX 1080 Ti) edition → writes a results ledger for tuning.
|
||||
|
||||
This is the NVIDIA port of the fl (RX 7600 XT) bench.py. Same flow, different failure mode:
|
||||
an NVIDIA overcommit does NOT spill to system RAM and freeze the desktop — cudaMalloc fails
|
||||
and the load errors out cleanly. So the guards here protect the NUMBERS, not the PC:
|
||||
- records the BASELINE VRAM after unloading anything of ours (the desktop alone holds 3.5–4.5 GB;
|
||||
a browser/game on top skews every margin — the header says when a resident model was unloaded
|
||||
first, so "7.5 GB baseline" can't masquerade as the desktop again)
|
||||
- ensures a CLEAN card before EVERY load: resident models are force-unloaded via the
|
||||
router API (POST /models/unload), then VRAM is verified with nvidia-smi — the poll covers
|
||||
driver reclaim lag, older builds without the endpoint (sleep-idle fallback) and desktop
|
||||
apps the router doesn't own. Aborts if the card never drains.
|
||||
- checks free VRAM right after a model mounts; too tight (--min-free-gb) → records a ⚠ row
|
||||
and skips generating (a preset that barely loads on a clean bench will fail on a busy
|
||||
desktop); the sweep continues after the drain-wait
|
||||
- refuses to sweep multiple models blind (no nvidia-smi) unless --no-vram-ok
|
||||
(There is no GTT column: that is an amdgpu concept. The gtt hooks are kept as no-ops so the
|
||||
fl script and this one stay diffable.)
|
||||
|
||||
Per model it records decode/prefill t/s, TTFT, VRAM used/free, and the preset's knobs
|
||||
(ctx, n-cpu-moe, spec-type) parsed from config.ini. For spec/MTP presets the draft
|
||||
acceptance % is appended to the spec column, read from the response's `timings` object.
|
||||
|
||||
Output: bench-results.md (latest run, overwritten) + bench-history.md (every run, appended)
|
||||
— both next to this script. Run ON this box against its own localhost router. Stdlib only.
|
||||
|
||||
./bench.py # all models the server lists
|
||||
./bench.py -m id1,id2 # only these
|
||||
./bench.py -x some-id # skip some (e.g. not downloaded yet)
|
||||
./bench.py -n 512 --ctx 12000 # longer gen + a long-context decode column
|
||||
Compare a change: edit config.ini → restart `llamaserver` → re-run → diff runs in bench-history.md.
|
||||
"""
|
||||
import argparse, json, os, re, subprocess, sys, time, urllib.request, urllib.error
|
||||
from datetime import datetime
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_smi_warned = False
|
||||
|
||||
|
||||
def gpu_mem(kind="vram"):
|
||||
"""(used_bytes, total_bytes) from nvidia-smi for 'vram'; (None, None) for 'gtt' (no such
|
||||
thing on NVIDIA — kept so the fl flow stays identical) or if nvidia-smi is unavailable."""
|
||||
global _smi_warned
|
||||
if kind != "vram":
|
||||
return None, None
|
||||
try:
|
||||
out = subprocess.run(["nvidia-smi", "--query-gpu=memory.used,memory.total",
|
||||
"--format=csv,noheader,nounits"],
|
||||
capture_output=True, text=True, timeout=15).stdout
|
||||
used_mib, total_mib = (int(x.strip()) for x in out.strip().splitlines()[0].split(","))
|
||||
return used_mib * 1024 * 1024, total_mib * 1024 * 1024
|
||||
except Exception:
|
||||
if not _smi_warned:
|
||||
_smi_warned = True
|
||||
print(" (nvidia-smi not readable here — VRAM columns and guards are OFF)", file=sys.stderr)
|
||||
return None, None
|
||||
|
||||
|
||||
def with_retries(fn, retries, wait, label=""):
|
||||
"""Retry on 5xx/connection errors (model still mounting). A deterministic 'failed to
|
||||
load' from the router is NOT retried — that's a broken preset, not a slow mount."""
|
||||
last = None
|
||||
for i in range(retries + 1):
|
||||
try:
|
||||
return fn()
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code < 500: # 4xx = real client error
|
||||
raise
|
||||
body = ""
|
||||
try:
|
||||
body = e.read().decode("utf-8", "ignore")
|
||||
except Exception:
|
||||
pass
|
||||
if "failed to load" in body: # deterministic → don't burn retries
|
||||
raise RuntimeError("model failed to load — check the llamaserver terminal (VRAM? path?)") from None
|
||||
last = e
|
||||
except (urllib.error.URLError, ConnectionError, TimeoutError) as e:
|
||||
last = e
|
||||
if i < retries:
|
||||
print(f" … {label} not ready ({last}); waiting {wait}s (try {i + 1}/{retries})", file=sys.stderr)
|
||||
time.sleep(wait)
|
||||
raise last
|
||||
|
||||
|
||||
def _open(url, key, payload=None, timeout=600):
|
||||
data = json.dumps(payload).encode() if payload is not None else None
|
||||
hdrs = {"Authorization": f"Bearer {key}"}
|
||||
if data:
|
||||
hdrs["Content-Type"] = "application/json"
|
||||
return urllib.request.urlopen(urllib.request.Request(url, data=data, headers=hdrs), timeout=timeout)
|
||||
|
||||
|
||||
def list_models(base, key):
|
||||
d = json.load(_open(base.rstrip("/") + "/models", key))
|
||||
return [m["id"] for m in d.get("data", [])]
|
||||
|
||||
|
||||
def force_unload(base, key):
|
||||
"""Ask the router to unload every resident model (POST /models/unload on the ROOT api,
|
||||
not /v1). Harmless no-op on builds without the endpoint — callers fall back to polling."""
|
||||
root = re.sub(r"/v1/?$", "", base.rstrip("/"))
|
||||
try:
|
||||
d = json.load(_open(root + "/models", key, timeout=30))
|
||||
except Exception as e:
|
||||
print(f" (router /models not readable: {e} — relying on sleep-idle drain)", file=sys.stderr)
|
||||
return
|
||||
for m in d.get("data", []):
|
||||
if m.get("status", "loaded") != "loaded": # builds without 'status': try them all
|
||||
continue
|
||||
try:
|
||||
_open(root + "/models/unload", key, {"model": m["id"]}, timeout=30).read()
|
||||
except Exception:
|
||||
pass # not loaded / endpoint missing — poll covers it
|
||||
|
||||
|
||||
def parse_config(path):
|
||||
"""{preset: {ctx, ncpumoe, spec}} from the router config.ini."""
|
||||
sections, sec = {}, None
|
||||
try:
|
||||
lines = open(path, encoding="utf-8").read().splitlines()
|
||||
except OSError:
|
||||
return {}
|
||||
for ln in lines:
|
||||
s = ln.strip()
|
||||
if s.startswith("[") and s.endswith("]"):
|
||||
sec = s[1:-1]; sections[sec] = {}
|
||||
elif sec and "=" in s and not s.startswith("#"):
|
||||
k, v = s.split("=", 1)
|
||||
sections[sec][k.strip()] = v.split("#", 1)[0].strip()
|
||||
return {sec: {"ctx": kv.get("ctx-size", "-"),
|
||||
"ncpumoe": kv.get("n-cpu-moe", "-"),
|
||||
"spec": kv.get("spec-type", "-") or "-"}
|
||||
for sec, kv in sections.items()}
|
||||
|
||||
|
||||
def run(base, key, model, prompt, n, temp, timeout):
|
||||
payload = {"model": model, "messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": n, "temperature": temp, "stream": True,
|
||||
"stream_options": {"include_usage": True},
|
||||
# llama.cpp extension: makes the server embed its own timings in the stream →
|
||||
# the table shows the SAME predicted_per_second as the llama.cpp web UI
|
||||
# (without it we fall back to wall-clock estimates, which read lower).
|
||||
"timings_per_token": True}
|
||||
# ttft = first ANSWER token (delta.content) — for a thinking model that is the end of the think
|
||||
# block, which is what the user waits for. t_first = first token of any kind (reasoning_content
|
||||
# counts): the end of prefill, used for the wall-clock fallbacks. A thinking model that spends the
|
||||
# whole gen budget reasoning never sets ttft → returned as None, rendered 'all-think' (NOT 0.00).
|
||||
t0 = time.perf_counter(); ttft = None; t_first = None; ntok = 0; usage = None; timings = None
|
||||
for raw in _open(base.rstrip("/") + "/chat/completions", key, payload, timeout):
|
||||
line = raw.decode("utf-8", "ignore").strip()
|
||||
if not line.startswith("data:"):
|
||||
continue
|
||||
body = line[5:].strip()
|
||||
if body == "[DONE]":
|
||||
break
|
||||
try:
|
||||
c = json.loads(body)
|
||||
except ValueError:
|
||||
continue
|
||||
ch = c.get("choices") or [{}]
|
||||
delta = ch[0].get("delta", {}) if ch else {}
|
||||
if delta.get("content") or delta.get("reasoning_content"):
|
||||
if t_first is None:
|
||||
t_first = time.perf_counter() - t0
|
||||
if delta.get("content") and ttft is None:
|
||||
ttft = time.perf_counter() - t0
|
||||
ntok += 1
|
||||
if c.get("usage"):
|
||||
usage = c["usage"]
|
||||
if c.get("timings"):
|
||||
timings = c["timings"]
|
||||
total = time.perf_counter() - t0
|
||||
comp = (usage or {}).get("completion_tokens") or ntok
|
||||
ptok = (usage or {}).get("prompt_tokens")
|
||||
gen_s = total - (t_first or total)
|
||||
tg = comp / gen_s if gen_s > 0 else 0.0
|
||||
pp = (ptok / t_first) if (ptok and t_first) else None
|
||||
acc = None
|
||||
if timings: # llama.cpp's own numbers are authoritative when the router forwards them
|
||||
tg = timings.get("predicted_per_second", tg)
|
||||
pp = timings.get("prompt_per_second", pp)
|
||||
ptok = timings.get("prompt_n", ptok)
|
||||
dn, da = timings.get("draft_n"), timings.get("draft_n_accepted")
|
||||
if dn:
|
||||
acc = 100.0 * (da or 0) / dn
|
||||
return {"tg": tg, "pp": pp, "ttft": ttft, "ptok": ptok or 0, "acc": acc}
|
||||
|
||||
|
||||
def wait_drain(drain_gb, window_s, label="", base=None, key=None):
|
||||
"""Force-unload resident models via the router API, then poll until VRAM used drops
|
||||
below drain_gb (card is clean) or the window expires."""
|
||||
used, _ = gpu_mem()
|
||||
if base and used is not None and used > drain_gb * 1e9:
|
||||
force_unload(base, key)
|
||||
end = time.time() + window_s
|
||||
announced = False
|
||||
while used is not None and used > drain_gb * 1e9 and time.time() < end:
|
||||
if not announced:
|
||||
print(f" … waiting for VRAM to drain before {label} ({gb(used)} used)", file=sys.stderr)
|
||||
announced = True
|
||||
time.sleep(5)
|
||||
used, _ = gpu_mem()
|
||||
return used
|
||||
|
||||
|
||||
def gb(b):
|
||||
return f"{b / 1e9:.1f} GB" if b else "-"
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("-u", "--url", default="http://127.0.0.1:11343/v1")
|
||||
ap.add_argument("-k", "--key", default="no-key-required")
|
||||
ap.add_argument("-m", "--models", default="all", help="comma-separated ids, or 'all'")
|
||||
ap.add_argument("-x", "--skip", default="", help="comma-separated ids to skip")
|
||||
ap.add_argument("-n", "--tokens", type=int, default=256)
|
||||
ap.add_argument("-t", "--temp", type=float, default=0.3)
|
||||
ap.add_argument("--ctx", type=int, default=0, help="also record decode at ~this many prompt tokens")
|
||||
ap.add_argument("--timeout", type=int, default=600, help="per-request stall timeout (s)")
|
||||
ap.add_argument("--config", default=os.path.join(HERE, "config.ini"))
|
||||
ap.add_argument("--out", default=os.path.join(HERE, "bench-results.md"))
|
||||
ap.add_argument("--history", default=os.path.join(HERE, "bench-history.md"))
|
||||
ap.add_argument("--retries", type=int, default=4, help="retries while a model is still mounting")
|
||||
ap.add_argument("--retry-wait", type=int, default=15, help="seconds between retries")
|
||||
ap.add_argument("--drain-gb", type=float, default=5.5,
|
||||
help="VRAM considered 'clean' below this (the desktop alone uses 3.5–4.5 GB here)")
|
||||
ap.add_argument("--min-free-gb", type=float, default=0.8,
|
||||
help="flag a model that leaves less VRAM free than this after loading (busy-desktop risk)")
|
||||
ap.add_argument("--max-gtt-gb", type=float, default=2.0,
|
||||
help="(inert on NVIDIA — kept for fl parity)")
|
||||
ap.add_argument("--unload-wait", type=int, default=360,
|
||||
help="after a too-tight model, wait up to this long for it to unload before continuing "
|
||||
"(sleep-idle-seconds keeps models resident up to 120s here)")
|
||||
ap.add_argument("--no-vram-ok", action="store_true",
|
||||
help="allow a multi-model sweep even though nvidia-smi (and thus the guards) is unavailable")
|
||||
a = ap.parse_args()
|
||||
|
||||
models = list_models(a.url, a.key) if a.models == "all" else [m.strip() for m in a.models.split(",")]
|
||||
skip = {s.strip() for s in a.skip.split(",") if s.strip()}
|
||||
models = [m for m in models if m not in skip]
|
||||
cfg = parse_config(a.config)
|
||||
|
||||
used0, total_vram = gpu_mem("vram")
|
||||
if total_vram is None and len(models) > 1 and not a.no_vram_ok:
|
||||
sys.exit("ABORT: nvidia-smi is not readable here, so the VRAM guards can't work.\n"
|
||||
"Run this ON the GPU box, bench a single model with -m <id>, or pass --no-vram-ok to accept the risk.")
|
||||
# The BASELINE must be the desktop, not a resident model: the 2026-10-19 sweep started while pi's
|
||||
# gemma was still mounted and recorded "baseline 7.5 GB" for a ~4 GB desktop. Drain first (force-
|
||||
# unload + poll, same as before every model), record the result; keep the pre-drain figure so the
|
||||
# header says which it was.
|
||||
resident0 = None
|
||||
if used0 is not None and used0 > a.drain_gb * 1e9:
|
||||
resident0 = used0
|
||||
used0 = wait_drain(a.drain_gb, a.unload_wait, "the baseline", a.url, a.key)
|
||||
if used0 is not None and used0 > a.drain_gb * 1e9:
|
||||
print(f"⚠ baseline VRAM used is {gb(used0)} with nothing of ours resident — desktop apps "
|
||||
f"(browser/electron/game) hold it. Margins below are for THIS desktop; ideally close "
|
||||
f"GPU apps and re-run (or raise --drain-gb if this is normal).", file=sys.stderr)
|
||||
|
||||
task = "Write a Python function that merges two sorted lists, with a short docstring and one example."
|
||||
filler = ("The quick brown fox jumps over the lazy dog. " * max(1, a.ctx // 9)) if a.ctx else None
|
||||
|
||||
cols = ["model", "cfg ctx", "n-cpu-moe", "spec", "decode t/s", "prefill t/s", "TTFT s", "VRAM used", "VRAM free"]
|
||||
if a.ctx:
|
||||
cols.append(f"decode@{a.ctx // 1000}k")
|
||||
|
||||
latest = open(a.out, "w", encoding="utf-8")
|
||||
hist = open(a.history, "a", encoding="utf-8")
|
||||
baseline = gb(used0)
|
||||
if resident0 is not None and used0 is not None and resident0 - used0 > 0.5e9:
|
||||
baseline += f" (was {gb(resident0)} with a resident model, unloaded first)"
|
||||
meta = (f"_Run {datetime.now():%Y-%m-%d %H:%M} · server `{a.url}` · VRAM total {gb(total_vram)} · "
|
||||
f"baseline used {baseline} · gen {a.tokens} tok · guard: free≥{a.min_free_gb} GB_")
|
||||
|
||||
def emit(line, both=True):
|
||||
print(line)
|
||||
latest.write(line + "\n"); latest.flush()
|
||||
if both:
|
||||
hist.write(line + "\n"); hist.flush()
|
||||
|
||||
latest.write("# llama.cpp benchmark results (latest run)\n\n")
|
||||
hist.write(f"\n## run {datetime.now():%Y-%m-%d %H:%M}\n\n")
|
||||
emit(meta + "\n")
|
||||
emit("| " + " | ".join(cols) + " |")
|
||||
emit("|" + "|".join(["---"] * len(cols)) + "|")
|
||||
|
||||
for m in models:
|
||||
c = cfg.get(m, {})
|
||||
base_cells = [m, c.get("ctx", "-"), c.get("ncpumoe", "-"), c.get("spec", "-")]
|
||||
# A CLEAN card before EVERY load (incl. the first): force-unload via the router API,
|
||||
# then verify with nvidia-smi — double residency = bogus numbers (or a failed load).
|
||||
u = wait_drain(a.drain_gb, a.unload_wait, m, a.url, a.key)
|
||||
if u is not None and u > a.drain_gb * 1e9:
|
||||
emit("")
|
||||
emit(f"_⚠ aborted before {m}: VRAM still {gb(u)} used after {a.unload_wait}s — something "
|
||||
f"won't unload (leftover model / GPU apps). If that's your normal desktop, raise --drain-gb._")
|
||||
print(f"\n⚠ aborted: card not clean ({gb(u)} used).", file=sys.stderr)
|
||||
break
|
||||
try:
|
||||
# warmup — retries wait out the mount; 'failed to load' aborts immediately
|
||||
with_retries(lambda: run(a.url, a.key, m, "hi", 8, a.temp, a.timeout), a.retries, a.retry_wait, m)
|
||||
used, _ = gpu_mem("vram")
|
||||
free = (total_vram - used) if (total_vram and used) else None
|
||||
reason = None
|
||||
if free is not None and free < a.min_free_gb * 1e9:
|
||||
reason = f"only {gb(free)} VRAM free"
|
||||
if reason:
|
||||
# Too tight — do NOT generate (allocates more). Record the row; the sweep continues
|
||||
# once this model has fully unloaded (see below).
|
||||
hint = ("raise n-cpu-moe" if c.get("ncpumoe", "-") != "-"
|
||||
else "lower ctx / n-gpu-layers / smaller quant")
|
||||
cells = base_cells + [f"⚠ {reason} — SKIPPED ({hint})", "", "", gb(used), gb(free)]
|
||||
if a.ctx:
|
||||
cells.append("")
|
||||
else:
|
||||
r = with_retries(lambda: run(a.url, a.key, m, task, a.tokens, a.temp, a.timeout),
|
||||
a.retries, a.retry_wait, m)
|
||||
if r.get("acc") is not None:
|
||||
base_cells[3] = f"{base_cells[3]} ({r['acc']:.0f}% acc)"
|
||||
cells = base_cells + [f"{r['tg']:.1f}", (f"{r['pp']:.0f}" if r['pp'] else "-"),
|
||||
(f"{r['ttft']:.2f}" if r['ttft'] is not None else "all-think"),
|
||||
gb(used), gb(free)]
|
||||
if a.ctx:
|
||||
rc = with_retries(lambda: run(a.url, a.key, m, filler + "\n\n" + task, a.tokens, a.temp, a.timeout),
|
||||
a.retries, a.retry_wait, m)
|
||||
cells.append(f"{rc['tg']:.1f}")
|
||||
except Exception as e:
|
||||
err = f"HTTP {e.code} (check server log)" if isinstance(e, urllib.error.HTTPError) else f"ERROR {e}"
|
||||
cells = base_cells + [err] + [""] * (len(cols) - len(base_cells) - 1)
|
||||
emit("| " + " | ".join(str(x) for x in cells) + " |")
|
||||
# (no post-row wait needed: the pre-load drain above protects the next iteration,
|
||||
# including after ⚠-tight rows where generation was skipped)
|
||||
|
||||
latest.write("\n_Tuning hints: **VRAM free** = headroom to raise `ctx-size` or lower `n-cpu-moe` "
|
||||
"(more experts on GPU → faster). Low decode t/s on an offloaded MoE → lower `n-cpu-moe` "
|
||||
"if free allows, or pick a smaller/K-quant file (CPU experts are bandwidth-bound). "
|
||||
"High **baseline used** invalidates the run. For spec/MTP models the acceptance % is in the spec column. "
|
||||
"**TTFT** = time to the first ANSWER token (for thinking models: the think block); `all-think` = the whole "
|
||||
"gen budget was reasoning and no answer arrived (decode t/s is still valid — raise `-n` or use the "
|
||||
"-low/-instant alias). Prefill t/s at the ~30-token prompt is fixed-overhead noise; use `--ctx` for a real number._\n")
|
||||
latest.close(); hist.close()
|
||||
print(f"\nwrote {a.out} (+ appended {a.history})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,489 @@
|
||||
# llama.cpp model config — WM (Wingman work desktop) · NVIDIA GTX 1080 Ti 11 GB (Pascal, legacy)
|
||||
# backend = upstream prebuilt CUDA 12.8 build (~/.local/opt/llama.cpp-cuda, launched via the
|
||||
# `llama-server-cuda` wrapper in ~/.local/bin) — NOT the Arch package, NOT a local build.
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# HARDWARE: i7-8700 (6 cores / 12 threads, AVX2, no AVX-512) · 31 GiB DDR4 dual-channel
|
||||
# (~35 GB/s effective = the CPU-expert ceiling) · GTX 1080 Ti 11,264 MiB, compute
|
||||
# capability 6.1 (Pascal): no tensor cores, no usable fp16 → CUDA runs its int8 MMQ
|
||||
# path, which is what llama.cpp is best at on this card. Driver nvidia-580xx (AUR):
|
||||
# the LAST branch for Pascal; CUDA 13.x toolkits can no longer target this GPU, so
|
||||
# the repo `ggml-cuda`/`llama.cpp-cuda-git` builds are USELESS here — the prebuilt
|
||||
# CUDA 12.8 tarball carries Pascal as PTX and the driver JITs it (first run of a new
|
||||
# build ≈ 50 s, then cached in ~/.nv/ComputeCache; the wrapper raises the cache cap).
|
||||
#
|
||||
# This is the NVIDIA analog of fl/.config/llamacpp/config.ini (RX 7600 XT 16 GB). Same
|
||||
# recipe (MoE experts on CPU, quantized KV, MTP), different limits:
|
||||
#
|
||||
# • VRAM BUDGET: 11.2 GB card, but the desktop (Hyprland + Xorg + electron apps) holds
|
||||
# 3.5–4.5 GB → ~6.5–7.5 GB usable. Every preset below is sized for ~6.5 free.
|
||||
# ⚠ Unlike fl's amdgpu, an NVIDIA overcommit does NOT freeze the PC — cudaMalloc fails and
|
||||
# the load errors out cleanly ("failed to allocate buffer for kv cache"). The guard here is
|
||||
# therefore "loads reliably on a busy desktop", target ≥ ~1 GB free after load (bench.py
|
||||
# records it). Still keep `--models-max 1`: two presets never fit together.
|
||||
# • MoE IS THE BOX. Dense models that don't fit VRAM fall off a cliff (every layer left on
|
||||
# the CPU streams from DDR4): Qwen3-14B Q3 = 7.6 t/s @ngl34, Qwen3-8B Q8 = 3.4, Ministral-14B
|
||||
# = 3.8, Devstral-24B = 1.8 (measured 2026-09-28, tombstones below). A 20–35B MoE with its
|
||||
# experts in RAM decodes at 15–30 t/s because only ~3B params are read per token.
|
||||
# • `n-cpu-moe N` = experts of the first N layers stay in RAM. LOWER = more VRAM, faster.
|
||||
# Measured 2026-09-28 (llama-bench, ~9 GB free at the time): gpt-oss-20b @moe12 = 29.8 t/s,
|
||||
# @moe24 (all) = 18.6; Qwen3-Coder-30B IQ2_XXS @moe24 = 29.9, @moe48 (all) = 20.2.
|
||||
# • QUANT TYPE MATTERS FOR CPU EXPERTS: I-quants (IQ4_XS/IQ3_XXS) dequantize slowly on CPU and
|
||||
# 4-bit experts stream twice the bytes of 2-bit ones — Ornith IQ4_XS with 80% of experts in
|
||||
# RAM = 11–15 t/s base. When choosing a file for the experts-in-RAM pattern prefer K-quants
|
||||
# (Q4_K_M/UD-Q4_K_XL/UD-Q3_K_XL) and the smallest quant the quality allows.
|
||||
# • `threads = 6` = PHYSICAL cores. NEVER 12: measured 2026-09-28, tg collapses 14.3 → 1.3 t/s
|
||||
# (HT contention on the expert matmuls). The default (6) is right; the key is pinned anyway.
|
||||
# • MTP / speculative decode WORKS here (`spec-type = draft-mtp`, Qwen3.5-family heads):
|
||||
# Ornith @moe34: 11–15 → 18.5–19.0 t/s at 61% acceptance for ~1.1 GB extra VRAM (09-28).
|
||||
# Unlike lw (2 cores) the 6 cores have compute to spare for the batched verify.
|
||||
# • KV cache: `cache-type-k/v = q8_0` everywhere except gpt-oss (attention sinks — keep the
|
||||
# defaults there, same as fl). flash-attn = on everywhere (required for quantized V).
|
||||
# • CPU governor is `powersave` (intel_pstate still boosts). The service runs at Nice=19 so the
|
||||
# 6-core expert matmuls never lag the desktop; GPU work is unaffected by niceness. THE FLIP SIDE
|
||||
# (full sweep 2026-10-19 with the AirSim/PX4 sim stack up: Unreal Blocks ≈ 3 cores at nice 5,
|
||||
# root nodes at realtime priority, load avg ~9, GPU 41% busy): every CPU-expert preset sat at
|
||||
# its floor — Ornith 15 t/s (31 quiet), gpt-oss 10–12 (21), Mellum 42 (69) — and the GPU-only
|
||||
# presets lost 5–15% to the shared GPU (9B-MTP 51 vs 57–61). Nothing to tune: the server yields
|
||||
# by design; stop the sim (or accept the floor) rather than touch the presets. All nine still
|
||||
# loaded with ≥ 1.0 GB free at that ~4–4.5 GB desktop — the busy-desktop sizing holds.
|
||||
#
|
||||
# Router: user systemd unit llama.service (this dir, Nice=19) =
|
||||
# llama-server-cuda --host 0.0.0.0 --port 11343 --cors-origins localhost \
|
||||
# --models-max 1 --models-preset ~/.config/llamacpp/config.ini
|
||||
# abbr `llamaserver` = restart it + tail the journal. Presets are read ONCE → restart after edits.
|
||||
# LAN-reachable (http://192.168.50.24:11343/v1, DHCP) and keyless: anyone on the office network can use it.
|
||||
# This file stows to ~/.config/llamacpp/ (like fl/lw); the GGUFs live in ~/Documents/models/.
|
||||
# Section names ARE the API model ids. Paths must stay absolute (no ~ expansion in presets).
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Fast — the only class that fits ENTIRELY on the GPU (≤ ~6 GB file): dense 9B, tiny-active MoE, E4B
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Qwen3.5-9B-Q4_K_M]
|
||||
# ⭐ DAILY DRIVER — Qwen3.5-9B, Q4_K_M (5.87 GB, the -MTP repo file; identical weights to the plain
|
||||
# repo's Q4_K_M plus an MTP head that is only used by the [Qwen3.5-9B-MTP] alias). Fully on GPU.
|
||||
# llama-bench 2026-09-28: pp512 828 t/s / tg128 38.3 t/s — vision via the mmproj (projector on
|
||||
# CPU: no-mmproj-offload). Thinking hard-OFF for instant answers; -Thinking alias below.
|
||||
# VRAM PROBES 2026-09-28 (process VRAM, nvidia-smi, 4.6 GB desktop baseline):
|
||||
# no draft @16k (this preset) 5.63 GB → ~1.0 GB free ← fits
|
||||
# draft-mtp n=3 @8k (±mmproj) 6.38 GB → ~0.3 GB free ← MTP alias: quiet desktop only
|
||||
# draft-mtp @8k/16k, ub 256 6.29 GB (ctx and the CPU-side projector cost ~nothing:
|
||||
# hybrid attention → tiny KV; MTP is a flat +0.75 GB)
|
||||
# ✓ ROUTER 2026-10-19 (sim stack up, ~4–4.5 GB desktop): 30.7 t/s / 197 pp / 10.1 used / 1.7 free — GPU-only,
|
||||
# so only the shared GPU (Unreal at 41%) shows: −6% vs 09-28.
|
||||
# ⬇ SOURCE unsloth/Qwen3.5-9B-MTP-GGUF → Qwen3.5-9B-Q4_K_M.gguf saved as *-MTP-Q4_K_M.gguf (sha256
|
||||
# verified); mmproj = the plain repo's mmproj-BF16 (same encoder). The plain Qwen3.5-9B-Q4_K_M.gguf
|
||||
# (5.68 GB, fetched by `llama-server -hf` earlier) was redundant → DELETED 2026-10-12.
|
||||
model = /home/coja/Documents/models/Qwen3.5-9B-MTP-Q4_K_M.gguf
|
||||
mmproj = /home/coja/Documents/models/Qwen3.5-9B-mmproj-BF16.gguf
|
||||
no-mmproj-offload = true # vision encoder on CPU
|
||||
reasoning-budget = 0 # instant answers; delete to allow /think (or use the alias)
|
||||
ctx-size = 16384 # ✓ ROUTER 09-28: 32.6 t/s / 184 pp / 10.8 used / 1.0 free @4.6 desktop.
|
||||
# 5.63 GB process VRAM @16k (probe); n_ctx_train 262144, KV is
|
||||
# cheap on this hybrid-attention arch — 32768 likely fits, bench first
|
||||
n-gpu-layers = 99
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 0.7 # Qwen non-thinking card: 0.7 / 0.8 / 20 / 0, rep 1.05
|
||||
top-p = 0.8
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
repeat-penalty = 1.05
|
||||
jinja = on
|
||||
sleep-idle-seconds = 60
|
||||
|
||||
[Qwen3.5-9B-Thinking]
|
||||
# Same GGUF, reasoning ON with a medium budget (fl's pattern) — zero disk cost. Slower per
|
||||
# answer (thinking preamble streams first as reasoning_content); use for hard problems.
|
||||
# No MTP here on purpose: thinking tokens draft poorly (Ornith: 60% → 47% acceptance) and the
|
||||
# draft's 0.75 GB does not fit beside the desktop anyway. ✓ ROUTER 09-28: 37.8 t/s / 219 pp / TTFT
|
||||
# 2.3 s (the think block) / 1.0 free · 10-19 (sim stack up): 33.7 / 195 pp / TTFT 2.6 s / 1.7 free.
|
||||
model = /home/coja/Documents/models/Qwen3.5-9B-MTP-Q4_K_M.gguf
|
||||
mmproj = /home/coja/Documents/models/Qwen3.5-9B-mmproj-BF16.gguf
|
||||
no-mmproj-offload = true
|
||||
reasoning-budget = 8192 # caps runaway think loops; leaves half the ctx for prompt+answer
|
||||
ctx-size = 16384
|
||||
n-gpu-layers = 99
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 0.6 # Qwen thinking card: 0.6 / 0.95 / 20 / 0, no rep-penalty
|
||||
top-p = 0.95
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 60
|
||||
|
||||
[Qwen3.5-9B-MTP]
|
||||
# ⚠ QUIET-DESKTOP SPECIALTY (fl's Qwen3.8-27B pattern) — the daily driver + its MTP draft. The draft
|
||||
# is a flat +0.75 GB (6.29 GB process VRAM @ub 256, probe 09-28): needs a desktop ≤ ~4.5 GB
|
||||
# (1.0 GB free there on 10-19, 0.4 free at 4.8 — close the electron apps on a fat desktop),
|
||||
# otherwise the load fails cleanly and the router reports it. Worth it when it fits: fl
|
||||
# measured nearly 2× decode on this model.
|
||||
# ✓ ROUTER 2026-09-28 (guard lowered to 0.2 for the measurement): 57.3 t/s / 164 pp / 90% draft
|
||||
# acceptance / 11.4 used / 0.4 free @4.6 desktop — +75% over the plain preset's 32.6.
|
||||
# ✓ ROUTER 2026-10-19 (sim stack up, ~4–4.5 GB desktop, default guard): 51.3 t/s / 140 pp / 90% / 10.8 used /
|
||||
# 1.0 free — loaded at the guard on a normal busy desktop, so the "≤ ~3.9 GB" rule above is conservative;
|
||||
# the −10% vs 57–61 is the GPU shared with Unreal (the MTP verify is GPU work too).
|
||||
model = /home/coja/Documents/models/Qwen3.5-9B-MTP-Q4_K_M.gguf
|
||||
mmproj = /home/coja/Documents/models/Qwen3.5-9B-mmproj-BF16.gguf
|
||||
no-mmproj-offload = true
|
||||
spec-type = draft-mtp
|
||||
spec-draft-n-max = 3 # fl: n=3 beat n=2 by ~1 t/s on this model; drop to 2 if acceptance is low
|
||||
reasoning-budget = 0
|
||||
ctx-size = 8192 # ctx barely moves the footprint here; kept small out of caution
|
||||
n-gpu-layers = 99
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 512
|
||||
ubatch-size = 256 # −0.09 GB vs 512 (probe) — every bit counts for this one
|
||||
cache-reuse = 256
|
||||
temp = 0.7
|
||||
top-p = 0.8
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
repeat-penalty = 1.05
|
||||
jinja = on
|
||||
sleep-idle-seconds = 60
|
||||
|
||||
# ─── RETIRED 2026-10-12: [LFM2.5-8B-A1B-UD-Q4_K_XL] ───────────────────────────────────────────
|
||||
# Liquid LFM2.5-8B-A1B UD-Q4_K_XL (5.35 GB): fastest decode on the box (119 t/s quiet / 69 busy) but
|
||||
# the user found its OUTPUT QUALITY unusable in pi → retired the same day it topped the chart. The
|
||||
# instant tier is gemma-4-E4B (101 t/s quiet / 85 busy, vision, MTP). File deleted.
|
||||
|
||||
[gemma-4-E4B-it-UD-Q4_K_XL]
|
||||
# Small VISION model + MTP draft — gemma-4 E4B (~4B effective), QAT UD-Q4_K_XL 4.22 GB, fully on
|
||||
# GPU. ✓ ROUTER BENCH 2026-09-28: 84.7 t/s WITH the draft (64% acceptance) / 297 pp / TTFT 0.15 s /
|
||||
# 8.2 used / 3.6 free @32k — the FASTEST model on the box (fl's Q8 does 57). Roles: images (projector on CPU
|
||||
# to save ~1 GB VRAM), long one-shot documents (prefill is fast), a second quick generalist.
|
||||
# reasoning-budget 0: since the Jul-17 template gemma-4 deliberates on hard prompts and can
|
||||
# return an empty answer under a small max_tokens (fl note) — fast models answer NOW.
|
||||
# (id drops "qat" like lw's convention; the file IS the qat build.)
|
||||
# ⬇ SOURCE unsloth/gemma-4-E4B-it-qat-GGUF (all sha256 verified 09-28), local ← upstream:
|
||||
# gemma-4-E4B-it-qat-UD-Q4_K_XL.gguf ← same name (4.22 GB)
|
||||
# gemma-4-E4B-it-mmproj-F16.gguf ← mmproj-F16.gguf (0.99 GB)
|
||||
# gemma-4-E4B-it-mtp-Q8_0.gguf ← MTP/mtp-gemma-4-E4B-it-Q8_0.gguf (0.10 GB)
|
||||
# Main and drafter from the SAME repo (fl's cross-repo drafter mismatch = "Invalid input batch").
|
||||
# ✓ ROUTER 2026-10-19 (sim stack up): 91.0 t/s / 84% acceptance / 292 pp / TTFT 0.10 s / 7.6 used / 4.2 free —
|
||||
# the t/s follows the acceptance (64–84% across runs, content-dependent); GPU-only, unbothered by the CPU load.
|
||||
# ℹ LOG LINES AT EVERY LOAD (incl. each wake from sleep) — harmless, do not chase:
|
||||
# "E llama_init_from_model: failed to initialize the context: Gemma4Assistant requires ctx_other to be
|
||||
# set (this warning is normal during memory fitting)" + "W failed to measure the memory of the extra
|
||||
# model, fitting without it" = the auto-fitter probing the MTP draft and giving up; everything is on the
|
||||
# GPU anyway (ngl 99) so the fit result is irrelevant. The model serves fine right after.
|
||||
# ⚠ sleep-idle 30 s vs pi (journal 2026-10-19 12:44–12:56): a pi session slept/woke this preset 3× in 12 min;
|
||||
# each wake = full reload ≈ 6 s before the first token (cold load 17 s). Resident it holds 3.7 GB and still
|
||||
# leaves ~3 GB beside a 4.5 GB desktop → 120–300 s (fl runs 300 on its coders, 30 on this same gemma) would
|
||||
# remove that stall for pi's default model. Left at 30 pending the user's call.
|
||||
model = /home/coja/Documents/models/gemma-4-E4B-it-qat-UD-Q4_K_XL.gguf
|
||||
mmproj = /home/coja/Documents/models/gemma-4-E4B-it-mmproj-F16.gguf
|
||||
no-mmproj-offload = true
|
||||
model-draft = /home/coja/Documents/models/gemma-4-E4B-it-mtp-Q8_0.gguf
|
||||
spec-type = draft-mtp
|
||||
spec-draft-n-max = 2
|
||||
reasoning-budget = 0 # ⚠ remove if startup says the option is not recognized / not honored
|
||||
ctx-size = 32768 # n_ctx_train 131072; fl runs 98304 with room — raise after a bench
|
||||
n-gpu-layers = 99
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256 # inert under mmproj ("not supported by multimodal") — kept for parity
|
||||
temp = 1.0 # Gemma defaults: 1.0 / 0.95 / 64
|
||||
top-p = 0.95
|
||||
top-k = 64
|
||||
jinja = on
|
||||
sleep-idle-seconds = 30
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Coding — MoE, experts in RAM
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Ornith-1.5-35B-A3B-IQ4_XS]
|
||||
# ⭐ AGENTIC CODER — Ornith-1.5-35B-A3B (ornith-ai, MIT): Qwen3.5-MoE arch (40 layers, 256
|
||||
# experts / 8 active + shared, ~3B active, MTP head embedded, vision, n_ctx_train 262144).
|
||||
# Card claims it beats Qwen3.6-35B-A3B on coding/agent benches (SWE-bench Verified 79,
|
||||
# Terminal-Bench 68.5) — unverified, judge it in use. Thinking ON by default (coder role);
|
||||
# the -instant alias below turns it off.
|
||||
# MEASURED 2026-09-28 (server, ctx 16384, q8/q8 KV, 6.8 GB free baseline):
|
||||
# @moe34 no MTP : 11.2–15.2 t/s decode, 4.6 GB VRAM
|
||||
# @moe34 MTP n=2 : 18.5–19.0 t/s, acceptance 0.61 (mean len 2.2), 5.7 GB VRAM ← +25–60%
|
||||
# llama-bench @moe40 (all experts CPU) = 13.7 / @moe32 = 12.7 (VRAM-starved) → the wall is
|
||||
# DDR4 streaming 4-bit I-quant experts; MTP is the lever, not moe.
|
||||
# moe36 chosen for a busy-desktop margin (~5.2 GB predicted); moe34 = +~0.5 GB for ~+1 t/s.
|
||||
# ✓ ROUTER BENCH 2026-09-28 15:20 (bench.py, 4.5 GB desktop baseline, gen 256):
|
||||
# [instant] 21.2 t/s decode / 36 pp / TTFT 1.0 s / 60% MTP acceptance / 10.4 used / 1.4 free
|
||||
# [thinking] 10.0 t/s / 20 pp / TTFT 21.5 s (the think block) / 47% acceptance / 10.1 / 1.8
|
||||
# → thinking mode halves MTP's payoff (reasoning tokens draft worse); the instant alias is
|
||||
# the agent-loop pick, thinking for hard problems only. 1.4 GB free = at the margin; if a
|
||||
# load ever fails on a busy desktop, step moe 36→38 (≈ −0.4 GB, ≈ −0.5 t/s).
|
||||
# 2nd sweep 15:5x (4.2 GB baseline): instant 14.0 t/s (60%) / 2.0 free · thinking 10.5 (53%) / 2.2
|
||||
# free → the instant alias ranges 14–21 t/s with desktop CPU load (the experts share the 6 cores).
|
||||
# ✓ QUIET DESKTOP 2026-10-12 15:36 (3.3 GB baseline, user run): instant 31.5 t/s / 63 pp / TTFT 0.6 s /
|
||||
# 67% acceptance / 8.3 used / 3.5 free. Same preset — the 1 GB the desktop gave back is worth
|
||||
# +10–17 t/s here.
|
||||
# ✗ moe30 TESTED 2026-10-12 (same quiet desktop, 3.1 GB baseline, one-off load): 32.1 / 31.7 t/s at
|
||||
# 59% acceptance, 7.35 GB process VRAM, 0.7 GB free on the card → +0.5 t/s for 2.4 GB of margin.
|
||||
# GPU-resident experts barely move this model (fl saw the same on Coder-Next); the wall is the
|
||||
# MTP verify + CPU-side work, not expert bandwidth. A -fast alias was added and REMOVED the same
|
||||
# day on this evidence — moe36 stays; lower moe is not the lever here, free VRAM for the
|
||||
# desktop is.
|
||||
# ⚠ VARIANCE 2026-10-12 15:52 (user run, SAME quiet desktop: 3.4 GB baseline / 3.5 free): 15.1 t/s /
|
||||
# 9 pp / TTFT 3.4 s / 59% acceptance — vs 31.5 / 63 pp sixteen minutes earlier. Prefill is pure
|
||||
# compute, so a 7× prefill drop = CPU contention, not VRAM: the abbr runs the server at nice 19 and
|
||||
# it yields to any other CPU load (browser/electron bursts, a build, a cold mmap re-read of the
|
||||
# 19 GB file). Expect 14–31 t/s on this preset and judge it over several runs (`top` during one).
|
||||
# ✓ ROUTER 2026-10-19 (sim stack up — header; ~4–4.5 GB desktop): instant 15.0 t/s / 37 pp / 60% / 9.0 used /
|
||||
# 2.9 free · thinking 14.7 / 34 pp / 57% / 2.9 free (TTFT = all-think: 256 tokens of reasoning, no answer
|
||||
# yet). The busy floor again, this time with a named cause: Unreal Blocks + PX4 on 3+ of the 6 cores.
|
||||
# ⬇ SOURCE bartowski/Ornith-1.5-35B-A3B-GGUF (imatrix): Ornith-1.5-35B-A3B-IQ4_XS.gguf
|
||||
# 19,278,554,784 B, sha256 verified 09-28. fl runs the IQ3_XXS (15.34 GB) of the same repo
|
||||
# at 32 t/s with only 16 expert layers in RAM — here it would also stream fewer bytes per
|
||||
# token; swap to it if speed matters more than the 4-bit quality.
|
||||
# mmproj-Ornith-1.5-35B-A3B-f16.gguf (0.9 GB) is available in the repo — not fetched (add
|
||||
# `mmproj = …` + `no-mmproj-offload = true` if vision is wanted).
|
||||
model = /home/coja/Documents/models/Ornith-1.5-35B-A3B-IQ4_XS.gguf
|
||||
spec-type = draft-mtp
|
||||
spec-draft-n-max = 2
|
||||
ctx-size = 16384 # KV ≈ 1.3 GB @16k q8/q8 (measured OOM at 8k with ngl 99 and NO
|
||||
# moe on the 14B — with experts in RAM the KV is the only GPU cost
|
||||
# besides attention). 24576 needs a bench first.
|
||||
n-gpu-layers = 99
|
||||
n-cpu-moe = 36 # 40 MoE layers total. ↓ toward 34 on a quiet desktop (bench free ≥ 1 GB)
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 0.6 # model card, general use: 0.6 / 0.95 / 20 / 0
|
||||
top-p = 0.95
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 120
|
||||
|
||||
[Ornith-1.5-35B-A3B-instant]
|
||||
# Same GGUF with thinking hard-OFF (reasoning-budget 0) for agent loops that can't afford the
|
||||
# thinking TTFT. Otherwise identical to the preset above.
|
||||
model = /home/coja/Documents/models/Ornith-1.5-35B-A3B-IQ4_XS.gguf
|
||||
spec-type = draft-mtp
|
||||
spec-draft-n-max = 2
|
||||
reasoning-budget = 0
|
||||
ctx-size = 16384
|
||||
n-gpu-layers = 99
|
||||
n-cpu-moe = 36
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 0.6
|
||||
top-p = 0.95
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 120
|
||||
|
||||
[Ornith-1.5-35B-A3B-long]
|
||||
# The long-context tier (added 2026-10-28): the same GGUF at 128k (n_ctx_train 262144) — wm's answer
|
||||
# to fl's Qwen3-Coder-Next 80B, which is NOT viable here (RAM, see candidates). Thinking OFF like
|
||||
# -instant and like Coder-Next: a think block would eat the context this preset exists for; set
|
||||
# reasoning-budget = -1 to bring it back.
|
||||
# All 40 expert layers in RAM: llama-bench measured moe40 ≈ moe32 on this model (13.7 vs 12.7 t/s), so
|
||||
# the ~1.6 GB moe36→40 frees pays for the KV. Qwen3.5-MoE is hybrid — only every 4th layer holds KV —
|
||||
# so 128k costs ~1.2 GB over 16k.
|
||||
# llama-fit-params 2026-10-28 (moe40, q8/q8, fa on, no MTP; CUDA0 MiB model / KV / compute):
|
||||
# 16k 1342/232/530 · 64k 1342/742/530 · 128k 1342/1422/569 · 256k 1342/2782/953 (+1.7 GB: no room)
|
||||
# ✓ ROUTER BENCH 2026-10-28 16:13 (bench.py --ctx 32000, 4.7 GB baseline, load avg ~11 = busy desktop):
|
||||
# 10.0 t/s short-prompt decode / 59% MTP / 10.0 used / 1.8 free · @35.6k prompt: prefill 249 t/s
|
||||
# (142.7 s), decode 16.9 t/s at 65% acceptance. → a 100k prompt ≈ 7 min to prefill: long context
|
||||
# here is for patient one-shots and growing agent sessions (cache-reuse), not dumping 100k at once.
|
||||
# The 19 GB mmap pushed the desktop ~3.5 GB further into swap (9.5 → 13 GiB) — RAM is the limit.
|
||||
model = /home/coja/Documents/models/Ornith-1.5-35B-A3B-IQ4_XS.gguf
|
||||
spec-type = draft-mtp
|
||||
spec-draft-n-max = 2
|
||||
reasoning-budget = 0
|
||||
ctx-size = 131072
|
||||
n-gpu-layers = 99
|
||||
n-cpu-moe = 40 # = all 40 MoE layers (see header)
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 0.6
|
||||
top-p = 0.95
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 120
|
||||
|
||||
# ─── RETIRED 2026-10-12: [Qwen3-Coder-30B-Instruct-UD-IQ2_XXS] ──────────────────────────────
|
||||
# Qwen3-Coder-30B-A3B, UD-IQ2_XXS (10.33 GB, 2-bit). Router bench 09-28 @moe34/16k: 14.0 t/s, 1.9 GB
|
||||
# free (moe28 left 0.6 GB). Dominated by Ornith-instant (same speed, 4-bit, MTP) → removed from the
|
||||
# three wm client overlays and DELETED from ~/Documents/models/ 2026-10-12. The K-quant upgrade
|
||||
# (UD-Q3_K_XL 13.8 GB) stays in the candidates list if a second coder is ever wanted.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Reasoning + tools — gpt-oss
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[gpt-oss-20b]
|
||||
# OpenAI gpt-oss-20b — MoE 21B / 3.6B active, 24 layers, harmony template (jinja required).
|
||||
# File is the Q4_K_M requant (11.62 GB); the experts are MXFP4 in every quant, so this is
|
||||
# effectively the native file (fl uses ggml-org's mxfp4.gguf, 12.1 GB — same thing).
|
||||
# llama-bench 2026-09-28 (~9 GB free): @moe12 = 648 pp / 29.8 tg · @moe24 (all CPU) = 455 / 18.6.
|
||||
# The fitter picked ~10 expert layers on CPU @16k with 7.5 free; moe14 = margin for a busy
|
||||
# desktop. Sliding-window attention → KV is cheap, so ctx is generous.
|
||||
# KV types left at default on purpose (attention sinks + quantized KV = known trouble, fl note).
|
||||
# ⬇ On disk since before this config (user download). TTFT of several seconds = the model
|
||||
# THINKING (harmony reasoning streams first) — use the -low alias for snappy answers.
|
||||
# ✓ ROUTER 2026-10-19 (sim stack up): 11.7 t/s / 24 pp / 8.8 used / 3.0 free (TTFT all-think) · -low 9.8 /
|
||||
# 23 pp / TTFT 3.1 s / 2.6 free — the lowest yet (21 quiet 10-12, 17.4 busy 09-28): all 18 expert layers
|
||||
# stream from RAM on a CPU the sim already half-owns. Not a preset problem; see the header.
|
||||
model = /home/coja/Documents/models/gpt-oss-20b-Q4_K_M.gguf
|
||||
ctx-size = 32768 # ⚗ unbenched at 32k — fl measured 32k→64k = +1.2 GB on this arch;
|
||||
# here 16k is the safe fallback if bench.py shows < 1 GB free
|
||||
n-gpu-layers = 99
|
||||
n-cpu-moe = 18 # 24 MoE layers. ✗ moe14 @32k = 0.3 GB free (router sweep 09-28) → 18 (+4 layers
|
||||
# ≈ +1.8 GB). ✓ moe18 @32k: 17.4 t/s / 30 pp / TTFT 8.8 s (= thinking) /
|
||||
# 9.7 used / 2.1 free (router 09-28). moe12 = 29.8 needs ~9 GB free
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 1.0
|
||||
top-p = 1.0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 120
|
||||
|
||||
[gpt-oss-20b-low]
|
||||
# Same GGUF, reasoning effort LOW — zero disk cost. Cuts the thinking preamble for interactive
|
||||
# use; keep [gpt-oss-20b] (medium) for hard problems.
|
||||
model = /home/coja/Documents/models/gpt-oss-20b-Q4_K_M.gguf
|
||||
chat-template-kwargs = {"reasoning_effort":"low"}
|
||||
ctx-size = 32768
|
||||
n-gpu-layers = 99
|
||||
n-cpu-moe = 18 # = the main preset. ✓ 17.5 t/s / TTFT 1.8 s / 2.1 free (router 09-28)
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 1.0
|
||||
top-p = 1.0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 60
|
||||
|
||||
# ─── RETIRED 2026-09-28: [Qwen3-14B-UD-Q3_K_XL] ──────────────────────────────────────────
|
||||
# Dense 14B, UD-Q3_K_XL (7.59 GB, 40 layers): does not fit beside the desktop. ngl 34 @16k was
|
||||
# the fitter's split at 7.5 GB free and gave 7.6 t/s (llama-bench); through the router at a
|
||||
# 4.5 GB desktop baseline it FAILED TO LOAD (context creation, 15:2x sweep). A fitting split
|
||||
# (~ngl 28 @8k) lands ≈ 5 t/s — Qwen3.5-9B covers dense at 38 t/s. Not wired to clients.
|
||||
# DELETED from ~/Documents/models/ 2026-10-12.
|
||||
|
||||
# ─── RETIRED 2026-09-28 (never wired) — more dense files that cannot fit this card ───────
|
||||
# All measured with llama-bench at the fitter's ngl for 16k ctx (p128/n64), 6.8 GB free:
|
||||
# Qwen3-8B-Q8_0.gguf (8.71 GB) ngl 19 → 3.4 t/s
|
||||
# → a Q4_K_M (~5 GB, unsloth/Qwen3-8B-GGUF) WOULD fit fully and run ~40 t/s; but
|
||||
# Qwen3.5-9B already covers that slot. Delete or re-quant.
|
||||
# Ministral-3-14B-Instruct-2512-Q4_K_M.gguf (8.24 GB) ngl 21 → 3.8 t/s
|
||||
# Devstral-Small-2-24B-Instruct-2512-UD-IQ3_XXS.gguf (9.41) ngl 28 → 1.8 t/s
|
||||
# Lexi-Llama-3-8B-Uncensored_F16.gguf (16.07 GB, F16!) ngl 9 → 1.0 t/s
|
||||
# → an 8B at F16 is 2× the bytes of Q8 for no quality; a Q4_K_M of it would fit fully.
|
||||
# All four DELETED from ~/Documents/models/ 2026-10-12 (66 GB freed together with the 14B, the plain 9B
|
||||
# and the 2-bit coder).
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Coding — fits ENTIRELY on the GPU (added 2026-10-12 after passing its gate bench)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Mellum2.1-12B-A2.5B-Thinking-IQ4_XS]
|
||||
# ⚗ JetBrains Mellum 2.1 — code MoE, 12B total / 2.5B active, 28 layers × 64 experts, THINKING on.
|
||||
# IQ4_XS 6.97 GB: fits entirely on a quiet desktop (fitter @16k: no offload at 8.3 GB free);
|
||||
# n-cpu-moe 4 keeps ~0.85 GB in RAM as busy-desktop insurance (each layer's experts ≈ 0.2 GB) —
|
||||
# drop to 0 on a quiet desktop (see probe).
|
||||
# ✓ ROUTER BENCH 2026-10-12 @moe4: 68.7 t/s decode / 9.5 used / 2.3 free. The bench's "15 pp" is the
|
||||
# short-prompt artifact — PROBE with a 1,713-token prompt: moe4 = 1,160 pp / 62.8–63.7 t/s / 6.2 GB
|
||||
# process VRAM; moe0 (all on GPU) = 1,549 pp / 86.7–87.4 t/s / 6.9 GB. KEPT at moe4 (loads beside a
|
||||
# 4.5 GB desktop: ~1.1 GB free); moe0 is +24 t/s when the desktop holds ≤ ~3.9 GB. Role: the fast
|
||||
# REASONING coder — 63–87 t/s with a think block, prefill > 1k t/s. Wired into aichat/opencode/pi.
|
||||
# Note n_ctx_train is 131072 via RoPE scaling over an 8192 base — keep ctx modest until tested.
|
||||
# ✓ ROUTER 2026-10-19 (sim stack up): 41.9 t/s / TTFT 5.2 s (= the think block) / 10.2 used / 1.6 free — −40% vs
|
||||
# quiet: even 4 CPU-expert layers hurt once the cores are taken. moe0 would dodge it, on a quiet desktop only.
|
||||
# ⬇ SOURCE bartowski/Mellum2.1-12B-A2.5B-Thinking-GGUF: Mellum2.1-12B-A2.5B-Thinking-IQ4_XS.gguf (sha verified).
|
||||
model = /home/coja/Documents/models/Mellum2.1-12B-A2.5B-Thinking-IQ4_XS.gguf
|
||||
ctx-size = 16384
|
||||
n-gpu-layers = 99
|
||||
n-cpu-moe = 4 # 28 MoE layers; 0 = all on GPU (quiet desktop only)
|
||||
threads = 6
|
||||
flash-attn = on
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
batch-size = 2048
|
||||
ubatch-size = 512
|
||||
cache-reuse = 256
|
||||
temp = 0.6 # thinking-model defaults (0.6 / 0.95 / 20)
|
||||
top-p = 0.95
|
||||
top-k = 20
|
||||
min-p = 0
|
||||
jinja = on
|
||||
sleep-idle-seconds = 60
|
||||
|
||||
# ─── RETIRED 2026-10-19 (user's call): [AesCode-8B-Q4_K_M] ─────────────────────────────────────
|
||||
# Microsoft AesCode-8B (qwen3vl, 36 layers, vision), bartowski Q4_K_M 5.21 GB + mmproj f16 1.16 GB.
|
||||
# Router bench 2026-10-12: 36.4 t/s / 386 pp / TTFT 0.07 s / 9.4 used / 2.4 free, fully on the GPU at
|
||||
# ctx 16384 (mmproj on CPU). Was the quick-edit / screenshot-aware coder; removed from aichat/opencode/pi.
|
||||
|
||||
# ─── RETIRED 2026-10-12 (same day as its gate bench): [FrogNano-4B-2609-Q4_K_M] ──────────────────
|
||||
# Microsoft FrogNano-4B-2609 (qwen35, MTP head, vision), Q4_K_M 2.80 GB + mmproj 0.67. Router bench:
|
||||
# 87.9 t/s with the draft (84% acceptance) / 244 pp / 7.9 used / 3.9 free — good, but no slot: on the
|
||||
# same quiet desktop gemma-4-E4B does 101 t/s WITH vision and LFM2.5-8B-A1B 119 without. Both files
|
||||
# deleted from ~/Documents/models/ 2026-10-12.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Candidates — ⬇ NOT ON DISK. Sized for this box (≤ ~20 GB file, 2–4B active). Add a section
|
||||
# when downloaded; measured expectations are extrapolations from the presets above.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# (2026-09-28: user dropped Qwen3.6-35B-A3B, GLM-4.7-Flash, LFM2-24B-A2B, the coder upgrades and the
|
||||
# embedder from the shortlist; LFM2.5-8B-A1B and gemma-4-E4B were downloaded and have presets above.)
|
||||
# unsloth/Qwen3.6-35B-A3B-MTP-GGUF UD-IQ3_XXS 14.1 GB — newest general Qwen MoE + MTP; est. ~25 t/s here.
|
||||
# unsloth/Qwen3-Coder-30B-A3B-Instruct UD-Q3_K_XL 13.8 GB — K-quant coder (est. ~15–18 t/s); the 2-bit file was deleted 10-12.
|
||||
# unsloth/GLM-4.7-Flash-GGUF UD-Q3_K_XL 13.8 GB — K-quant coder; deepseek2/MLA → KV k/v types must match.
|
||||
# LiquidAI/LFM2-24B-A2B-GGUF Q4_K_M 14.4 GB — 2B active; est. ~20–25 t/s, unproven arch.
|
||||
# unsloth/gemma-4-26B-A4B-it-qat-GGUF UD-Q4_K_XL 14.2 GB — 4B active in RAM = slower than the 3B-active Qwens.
|
||||
# NOT viable (RAM): Qwen3-Coder-Next 80B (≥ 28 GB), Qwen3.8-Flash-Next, GLM-5.3-Flash, Kimi-Linear-48B
|
||||
# (≥ 23 GB), Qwen3.6/3.8-27B and Muse-Glimmer-30B (dense — see the tombstones above).
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Notes
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# n-cpu-moe N : experts of the first N MoE layers stay in system RAM. Higher = less VRAM
|
||||
# (loads on a busier desktop) but slower; each layer on the GPU ≈ 0.4 GB for a
|
||||
# 35B-A3B at 4-bit. Dense models ignore it — use n-gpu-layers / ctx instead.
|
||||
# Sizing rule : file GB × (1 − N/layers) + KV + ~0.7 GB compute ≤ VRAM free at load time.
|
||||
# Fitter : `llama-fit-params -m <gguf> -c <ctx> -fa on` prints the split that fits RIGHT
|
||||
# NOW (it reads current free VRAM) — a good first value after a download.
|
||||
# After ANY change: restart the router (presets are read once) and run ./bench.py — keep
|
||||
# every "VRAM free" ≥ ~1 GB; a load failure here is a clean error, not a freeze.
|
||||
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=llama.cpp router (wm, GTX 1080 Ti) — OpenAI-compatible API on 0.0.0.0:11343
|
||||
Documentation=file:///home/coja/.config/llamacpp/README.md
|
||||
# A USER unit (fl's llama.service is a system unit under a service user): it runs as coja, so the
|
||||
# prebuilt binary in ~/.local/opt, the presets in ~/.config/llamacpp and the GGUFs in ~/Documents/models
|
||||
# need no extra permissions. Starts with the login session (default.target); for a true at-boot start
|
||||
# without logging in: `loginctl enable-linger coja`.
|
||||
After=graphical-session-pre.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Same command as the old `llamaserver` abbr. The wrapper execs the CUDA 12.8 prebuilt and raises the
|
||||
# PTX JIT cache cap (Pascal is JIT-compiled from PTX; first start after a build update ≈ 50 s).
|
||||
# 0.0.0.0 = every IPv4 interface (wired + wifi office LAN, ZeroTier, docker0) and still 127.0.0.1;
|
||||
# keyless, by choice. IPv4 only: `localhost` → ::1 is refused, so clients use 127.0.0.1.
|
||||
ExecStart=/home/coja/.local/bin/llama-server-cuda --host 0.0.0.0 --port 11343 --cors-origins localhost --models-max 1 --models-preset /home/coja/.config/llamacpp/config.ini
|
||||
# Presets are read once at startup → `systemctl --user restart llama` after editing config.ini.
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
# The MoE presets run their CPU experts on all 6 cores: yield to interactive apps (GPU work unaffected).
|
||||
Nice=19
|
||||
# Idle router holds no model (sleep-idle-seconds per preset), so keeping it up costs nothing.
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,331 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"model": "duskadiy/Qwen3.6-35B-A3B-Thinking",
|
||||
"default_agent": "auto",
|
||||
"instructions": ["CLAUDE.md", "AGENTS.md"],
|
||||
"share": "disabled",
|
||||
"autoupdate": "notify",
|
||||
"tool_output": { "max_lines": 200, "max_bytes": 16384 },
|
||||
"snapshot": false,
|
||||
"disabled_providers": [
|
||||
"gemini",
|
||||
"anthropic",
|
||||
"groq",
|
||||
"openrouter",
|
||||
"opencode"
|
||||
],
|
||||
"permission": {
|
||||
"edit": "deny",
|
||||
"webfetch": "ask",
|
||||
"websearch": "ask",
|
||||
"external_directory": "ask",
|
||||
"question": "allow",
|
||||
"read": {
|
||||
"*": "allow",
|
||||
"**/.env*": "deny",
|
||||
"**/*.env": "deny",
|
||||
"**/*.pem": "deny",
|
||||
"**/*.key": "deny",
|
||||
"**/*.p12": "deny",
|
||||
"**/*.pfx": "deny",
|
||||
"**/*.keystore": "deny",
|
||||
"**/id_rsa*": "deny",
|
||||
"**/.ssh/**": "deny",
|
||||
"**/.aws/**": "deny",
|
||||
"**/.npmrc": "deny",
|
||||
"**/.netrc": "deny"
|
||||
},
|
||||
"bash": {
|
||||
"*": "ask",
|
||||
|
||||
"ls *": "allow",
|
||||
"cat *": "allow",
|
||||
"grep *": "allow",
|
||||
"rg *": "allow",
|
||||
"find *": "allow",
|
||||
"fd *": "allow",
|
||||
"which *": "allow",
|
||||
"file *": "allow",
|
||||
"stat *": "allow",
|
||||
"wc *": "allow",
|
||||
"tree *": "allow",
|
||||
"diff *": "allow",
|
||||
"sort *": "allow",
|
||||
"uniq *": "allow",
|
||||
"echo *": "ask",
|
||||
"pwd": "allow",
|
||||
"date": "allow",
|
||||
"whoami": "allow",
|
||||
"jq *": "allow",
|
||||
"tsc *": "allow",
|
||||
"mkdir *": "ask",
|
||||
"touch *": "ask",
|
||||
|
||||
"git *": "ask",
|
||||
"git status": "allow",
|
||||
"git status *": "allow",
|
||||
"git diff": "allow",
|
||||
"git diff *": "allow",
|
||||
"git log": "allow",
|
||||
"git log *": "allow",
|
||||
"git show *": "allow",
|
||||
"git branch": "allow",
|
||||
"git remote -v": "allow",
|
||||
|
||||
"npm *": "ask",
|
||||
"pnpm *": "ask",
|
||||
"npx *": "ask",
|
||||
"npm list": "allow",
|
||||
"npm help": "allow",
|
||||
"npm version": "allow",
|
||||
"pnpm list": "allow",
|
||||
"pnpm help": "allow",
|
||||
"npx --version": "allow",
|
||||
|
||||
"curl *": "ask",
|
||||
"wget *": "ask",
|
||||
|
||||
"rm *": "deny",
|
||||
"rmdir *": "deny",
|
||||
"dd *": "deny",
|
||||
"mkfs*": "deny",
|
||||
"shred *": "deny",
|
||||
"chmod -R *": "deny",
|
||||
"sudo *": "deny",
|
||||
"doas *": "deny",
|
||||
|
||||
"npm install*": "deny",
|
||||
"npm i": "deny",
|
||||
"npm i *": "deny",
|
||||
"npm ci*": "deny",
|
||||
"pnpm install*": "deny",
|
||||
"pnpm i": "deny",
|
||||
"pnpm i *": "deny",
|
||||
"pnpm add*": "deny",
|
||||
"yarn add*": "deny",
|
||||
"yarn install*": "deny",
|
||||
"pip install*": "deny",
|
||||
"pip3 install*": "deny",
|
||||
"pipx install*": "deny",
|
||||
"cargo install*": "deny",
|
||||
"go install*": "deny",
|
||||
"gem install*": "deny",
|
||||
"brew install*": "deny",
|
||||
|
||||
"pacman *": "deny",
|
||||
"yay *": "deny",
|
||||
"apt *": "deny",
|
||||
"apt-get *": "deny",
|
||||
"dnf *": "deny",
|
||||
|
||||
"cat .env*": "deny",
|
||||
"cat *.env": "deny",
|
||||
"cat *.pem": "deny",
|
||||
"cat *.key": "deny",
|
||||
"cat *id_rsa*": "deny",
|
||||
"cat *secret*": "deny",
|
||||
"cat *credential*": "deny",
|
||||
"cat ~/.ssh/*": "deny",
|
||||
"cat ~/.aws/*": "deny",
|
||||
"grep .env*": "deny",
|
||||
"grep * .env*": "deny"
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"wmcpp": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "llamacpp (wm local, GTX 1080 Ti)",
|
||||
"options": {
|
||||
"baseURL": "http://127.0.0.1:11343/v1"
|
||||
},
|
||||
"models": {
|
||||
"Qwen3.5-9B-Q4_K_M": { "name": "Qwen3.5 9B · 16k · vision — daily driver (33 t/s)", "attachment": true, "limit": { "context": 16384, "output": 4096 } },
|
||||
"Qwen3.5-9B-Thinking": { "name": "Qwen3.5 9B Thinking · 16k · vision — hard problems (38 t/s)", "attachment": true, "limit": { "context": 16384, "output": 4096 } },
|
||||
"Qwen3.5-9B-MTP": { "name": "Qwen3.5 9B MTP · 8k · vision — quiet desktop only (57 t/s)", "attachment": true, "limit": { "context": 8192, "output": 2048 } },
|
||||
"gemma-4-E4B-it-UD-Q4_K_XL": { "name": "Gemma 4 E4B · 32k · vision — fastest, long docs (85 t/s)", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
|
||||
"Mellum2.1-12B-A2.5B-Thinking-IQ4_XS": { "name": "Mellum 2.1 12B A2.5B · 16k — fast reasoning coder (63–87 t/s)", "limit": { "context": 16384, "output": 4096 } },
|
||||
"Ornith-1.5-35B-A3B-instant": { "name": "Ornith 35B A3B instant · 16k — agentic coder (14–21 t/s busy, 31 quiet)", "limit": { "context": 16384, "output": 4096 } },
|
||||
"Ornith-1.5-35B-A3B-IQ4_XS": { "name": "Ornith 35B A3B thinking · 16k — coder with reasoning (10 t/s)", "limit": { "context": 16384, "output": 4096 } },
|
||||
"gpt-oss-20b": { "name": "gpt-oss 20B · 32k — reasoning + tools (17 t/s)", "limit": { "context": 32768, "output": 8192 } },
|
||||
"gpt-oss-20b-low": { "name": "gpt-oss 20B low · 32k — snappy answers (17 t/s)", "limit": { "context": 32768, "output": 8192 } }
|
||||
}
|
||||
},
|
||||
"duskadiy": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "DuskaDIY",
|
||||
"options": {
|
||||
"baseURL": "https://llm.duskadiy.com/api/v1",
|
||||
"apiKey": "{env:DUSKADIY_API_KEY}",
|
||||
"timeout": 3600000
|
||||
},
|
||||
"models": {
|
||||
"Qwen3-Coder-30B-Instruct-UD-Q3_K_XL": { "name": "Qwen3 Coder 30B · 32k — main agent coder (~30 t/s, remote)", "limit": { "context": 32768, "output": 4096 } },
|
||||
"Qwen3-Coder-Next-UD-IQ3_XXS": { "name": "Qwen3 Coder Next 80B · 128k — long sessions (~16 t/s, remote)", "limit": { "context": 131072, "output": 16384 } },
|
||||
"Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS": { "name": "Qwen3.6 35B · 24k · vision — daily driver (~36 t/s, remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"Qwen3.6-35B-A3B-Thinking": { "reasoning": true, "name": "Qwen3.6 35B Thinking · 24k · vision — hard problems (~39 t/s, remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"Qwen3.5-9B-UD-Q6_K_XL": { "reasoning": true, "name": "Qwen3.5 9B · 32k · vision — quick tasks (~32 t/s, remote)", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
|
||||
"Qwen3.8-27B-UD-IQ3_XXS": { "reasoning": true, "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner, quiet desktop only (~15 t/s, remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"gemma-4-26B-A4B-it-UD-IQ4_XS": { "name": "Gemma 4 26B · 24k · vision — quality generalist (~34 t/s, remote)", "attachment": true, "limit": { "context": 24576, "output": 4096 } },
|
||||
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (~57 t/s, remote)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
|
||||
"GLM-4.7-Flash-UD-Q4_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash · 24k — quality coder (~21 t/s, remote)", "limit": { "context": 24576, "output": 8192 } },
|
||||
"GLM-4.7-Flash-UD-Q3_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash Q3 · 24k — fast coder (~28 t/s, remote)", "limit": { "context": 24576, "output": 8192 } },
|
||||
"Ornith-1.5-35B-A3B-IQ3_XXS": { "reasoning": true, "name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s, remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"gpt-oss-20b": { "reasoning": true, "name": "gpt-oss 20B · 64k — fast reasoning + tools (~38 t/s, remote)", "limit": { "context": 65536, "output": 8192 } },
|
||||
"gpt-oss-20b-low": { "reasoning": true, "name": "gpt-oss 20B low · 64k — snappy answers (~37 t/s, remote)", "limit": { "context": 65536, "output": 8192 } }
|
||||
}
|
||||
},
|
||||
"llama.cpp": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "llamacpp",
|
||||
"options": {
|
||||
"baseURL": "http://192.168.0.204:11343/v1",
|
||||
"timeout": 3600000
|
||||
},
|
||||
"models": {
|
||||
"Qwen3-Coder-Next-UD-IQ3_XXS": { "name": "Qwen3 Coder Next 80B · 128k — long sessions (~16 t/s)", "limit": { "context": 131072, "output": 16384 } },
|
||||
"Qwen3-Coder-30B-Instruct-UD-Q3_K_XL": { "name": "Qwen3 Coder 30B · 32k — main agent coder (~30 t/s)", "limit": { "context": 32768, "output": 4096 } },
|
||||
"Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS": { "name": "Qwen3.6 35B · 24k · vision — daily driver (~36 t/s)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"Qwen3.6-35B-A3B-Thinking": { "reasoning": true, "name": "Qwen3.6 35B Thinking · 24k · vision — hard problems (~39 t/s)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"Qwen3.5-9B-UD-Q6_K_XL": { "reasoning": true, "name": "Qwen3.5 9B · 32k · vision — quick tasks (~32 t/s)", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
|
||||
"Qwen3.8-27B-UD-IQ3_XXS": { "reasoning": true, "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner, quiet desktop only (~15 t/s)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"gemma-4-26B-A4B-it-UD-IQ4_XS": { "name": "Gemma 4 26B · 24k · vision — quality generalist (~34 t/s)", "attachment": true, "limit": { "context": 24576, "output": 4096 } },
|
||||
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (~57 t/s)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
|
||||
"GLM-4.7-Flash-UD-Q4_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash · 24k — quality coder (~21 t/s)", "limit": { "context": 24576, "output": 8192 } },
|
||||
"GLM-4.7-Flash-UD-Q3_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash Q3 · 24k — fast coder (~28 t/s)", "limit": { "context": 24576, "output": 8192 } },
|
||||
"Ornith-1.5-35B-A3B-IQ3_XXS": { "reasoning": true, "name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
||||
"gpt-oss-20b": { "reasoning": true, "name": "gpt-oss 20B · 64k — fast reasoning + tools (~38 t/s)", "limit": { "context": 65536, "output": 8192 } },
|
||||
"gpt-oss-20b-low": { "reasoning": true, "name": "gpt-oss 20B low · 64k — snappy answers (~37 t/s)", "limit": { "context": 65536, "output": 8192 } }
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"plan": {
|
||||
"mode": "primary",
|
||||
"color": "info",
|
||||
"steps": 30,
|
||||
"model": "duskadiy/Qwen3.6-35B-A3B-Thinking",
|
||||
"permission": {
|
||||
"edit": "deny"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"mode": "primary",
|
||||
"color": "primary",
|
||||
"steps": 30,
|
||||
"model": "duskadiy/Qwen3.6-35B-A3B-Thinking",
|
||||
"permission": {
|
||||
"edit": {
|
||||
"*": "allow",
|
||||
"**/.env*": "deny",
|
||||
"**/*.env": "deny",
|
||||
"**/*.pem": "deny",
|
||||
"**/*.key": "deny",
|
||||
"**/id_rsa*": "deny",
|
||||
"**/.ssh/**": "deny",
|
||||
"**/.aws/**": "deny",
|
||||
"**/.git/**": "deny",
|
||||
"**/.npmrc": "deny",
|
||||
"**/.netrc": "deny"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auto": {
|
||||
"description": "Auto mode: acts without asking; only irreversible or outward-facing actions ask",
|
||||
"mode": "primary",
|
||||
"color": "#f9e2af",
|
||||
"steps": 30,
|
||||
"model": "duskadiy/Qwen3.6-35B-A3B-Thinking",
|
||||
"permission": {
|
||||
"edit": {
|
||||
"*": "allow",
|
||||
"**/.env*": "deny",
|
||||
"**/*.env": "deny",
|
||||
"**/*.pem": "deny",
|
||||
"**/*.key": "deny",
|
||||
"**/id_rsa*": "deny",
|
||||
"**/.ssh/**": "deny",
|
||||
"**/.aws/**": "deny",
|
||||
"**/.git/**": "deny",
|
||||
"**/.npmrc": "deny",
|
||||
"**/.netrc": "deny"
|
||||
},
|
||||
"webfetch": "allow",
|
||||
"websearch": "allow",
|
||||
"external_directory": "ask",
|
||||
"question": "allow",
|
||||
"bash": {
|
||||
"*": "allow",
|
||||
|
||||
"git *": "allow",
|
||||
"git commit*": "ask",
|
||||
"git push*": "ask",
|
||||
"git reset*": "ask",
|
||||
"git checkout*": "ask",
|
||||
"git switch*": "ask",
|
||||
"git restore*": "ask",
|
||||
"git rebase*": "ask",
|
||||
"git merge*": "ask",
|
||||
"git cherry-pick*": "ask",
|
||||
"git stash*": "ask",
|
||||
"git clean*": "ask",
|
||||
"git branch -*": "ask",
|
||||
"git tag*": "ask",
|
||||
"git remote add*": "ask",
|
||||
"git remote remove*": "ask",
|
||||
"git remote set-url*": "ask",
|
||||
|
||||
"rm *": "ask",
|
||||
"rmdir *": "ask",
|
||||
"kill *": "ask",
|
||||
"pkill *": "ask",
|
||||
"killall *": "ask",
|
||||
"systemctl *": "ask",
|
||||
"curl *": "ask",
|
||||
"wget *": "ask",
|
||||
"ssh *": "ask",
|
||||
"scp *": "ask",
|
||||
"rsync *": "ask",
|
||||
|
||||
"npm install*": "ask",
|
||||
"npm i": "ask",
|
||||
"npm i *": "ask",
|
||||
"npm ci*": "ask",
|
||||
"pnpm install*": "ask",
|
||||
"pnpm i": "ask",
|
||||
"pnpm i *": "ask",
|
||||
"pnpm add*": "ask",
|
||||
"yarn add*": "ask",
|
||||
"yarn install*": "ask",
|
||||
"pip install*": "ask",
|
||||
"pip3 install*": "ask",
|
||||
"pipx install*": "ask",
|
||||
"cargo install*": "ask",
|
||||
"go install*": "ask",
|
||||
"gem install*": "ask",
|
||||
"yay *": "ask",
|
||||
|
||||
"sudo *": "deny",
|
||||
"doas *": "deny",
|
||||
"dd *": "deny",
|
||||
"mkfs*": "deny",
|
||||
"shred *": "deny",
|
||||
"chmod -R *": "deny",
|
||||
"rm -rf /*": "deny",
|
||||
"rm -rf ~*": "deny",
|
||||
|
||||
"cat .env*": "deny",
|
||||
"cat *.env": "deny",
|
||||
"cat *.pem": "deny",
|
||||
"cat *.key": "deny",
|
||||
"cat *id_rsa*": "deny",
|
||||
"cat *secret*": "deny",
|
||||
"cat *credential*": "deny",
|
||||
"cat ~/.ssh/*": "deny",
|
||||
"cat ~/.aws/*": "deny",
|
||||
"grep .env*": "deny",
|
||||
"grep * .env*": "deny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user