The host
Read live 2026-09-18 01:13 IST, uptime 2 h 37 m, Flash-Next serving; re-read rather than trust it.
| GPU | NVIDIA RTX PRO 6000 Blackwell Workstation Edition, 97,887 MiB, compute capability 12.0 (SM120) |
| Driver | 595.84 (nvidia-driver-595-open 595.84-0ubuntu0.26.04.1), CUDA 13.2 driver-side |
| Power cap | 375 W live; card range 150-600 W, factory default 600 W |
| Host RAM | 182 GiB, no swap device; /dev/shm and /tmp are each a 92 G tmpfs |
| Disk | /dev/nvme0n1p2 915 G, 746 G used, 123 G free (86%) |
| Board, kernel | Gigabyte X870E AORUS ELITE X3D, 32 cores, Ubuntu, kernel 7.0.0-31-generic |
GPU
One card, shared by everything: at 01:13 on 2026-09-18, 94,435 of 97,887 MiB were in use by the
one serving model. models.toml [gpu] records total_mib = 97887 and margin_mib = 1024, and
that margin is never handed to a model. gnome-shell is on this GPU as a type G process
(8 MiB), so the card is not headless — nvidia-smi --query-compute-apps never lists a graphics
process, which is where the "no display attached to this card" line in
vllm-qwen38next/SETUP.md:260 came from; it is wrong. The cost is single-digit MiB, but a
utilisation value with zero slack has the compositor inside it.
SM120 is the expensive part: kernels the ecosystem ships for SM90 either fail to select or select
and then assert here — see models/glm53.md for the attention-backend case and
BUILDS.md for what had to be compiled. There is no system CUDA toolkit: no
nvcc on PATH, no /usr/local/cuda. ptxas comes from pip wheels, which is why every
launcher exports CUDA_HOME into its own venv's nvidia/cu13 and why
VLLM_USE_FLASHINFER_SAMPLER=0 is in [defaults.env] — FlashInfer's JIT sampler cannot compile
without one.
Driver upgrades are a hazard, and they are unattended. APT::Periodic::Unattended-Upgrade
"1" is set and no NVIDIA package is held (apt-mark showhold is empty), while three serving
venvs are built against this driver's stack, all on torch 2.13.0+cu130: .venv-llm-029 (vLLM
0.29.0, stock wheel), .venv-next and .venv-glm53 (editable forks). A driver bump replaces the
kernel module under running engines and can move the ABI away from the cu13 wheels those forks
were compiled against; recovering means a rebuild, not a restart. Check the driver after an
upgrade window before blaming servedeck for a model that stops booting.
The power cap — an open decision
/etc/systemd/system/nvidia-power-limit.service (oneshot, RemainAfterExit=yes, after and
requiring nvidia-persistenced.service, enabled and active) runs nvidia-smi -pl 375 at every
boot, so a cap set by hand does not survive a reboot. Nothing in models.toml pins an expected
value, so servedeck doctor's power-cap row reports the live number and says it has nothing to
compare against. Older notes claim this unit sets 485 W and other docs say 450 W or 500 W — the
unit file says 375 W and the live cap is 375 W.
The card has fallen off the bus three times
| When | Xids | Cap in force | What was running |
|---|---|---|---|
| 2026-08-24 20:12:26 | 79, then 154 | not recorded | qwen-vllm.service (27B NVFP4), 22 min into its run |
| 2026-09-11 21:24:41 | 79, then 154 | 600 W, raised from 550 W 3 min 52 s earlier | 27B in a heavy prefill burst: 16 running, 1 waiting, prompt 9,228 tok/s, KV 73-83% |
| 2026-09-12 14:25:44 | 79 (pid=nvidia-smi), then 154 |
450 W, unchanged for 90 min | 27B at ordinary load, ~1,000 tok/s prompt, KV 29% |
Xid 79 is "GPU has fallen off the bus", Xid 154 is "recovery action changed ... to 0x2 (Node
Reboot Required)". Each time nvidia-smi reported "No devices were found", the rest of the
machine stayed up, only a reboot brought the card back, and no PCIe AER or thermal message
preceded it.
The "600 W did it, 485 W is the validated stable cap" conclusion is not supported. It was
written after the 2026-09-11 event alone, where a cap change four minutes earlier made a power
transition the obvious suspect. The other two kill it: 2026-09-12 happened at a cap that had not
moved in 90 minutes, under light load, with the fault attributed to a plain nvidia-smi query,
and 2026-08-24 happened while the 27B merely served. The owner-supplied external report
(dbirks/home-k8s#46) describes the same card class (GB202) and the same Xid 79 + 154 pair,
reproduced across motherboards and drivers 570-595, as a confirmed NVIDIA GSP firmware/driver bug
with affected cards treated as RMA candidates. On that reading a lower cap lowers the odds and
does not remove them.
The cap is therefore an open owner decision. Mitigations named in that report are not applied:
NVreg_DynamicPowerManagement=0x00 is unset and persistence mode is Disabled (Ubuntu runs
nvidia-persistenced --no-persistence-mode). What a cap costs is measured below; what it buys in
fault rate is unknown.
What a cap costs, measured
Both sweeps, 2026-09-11: idle box, one frozen 39,760-token prompt with a per-cap cache-busting
salt (so every cold prefill misses the whole prefix chain), 45 s decode windows timed from the
first token, power sampled every 0.5 s, idle_warn=0 on every row kept. Raw CSVs in
docs/imported-2026-09/local_llm/power-cap-exp/; the protocol handoff beside them contains a
credential, and a credential was redacted from this source.
27B = Qwen3.8-27B-NVFP4, vLLM 0.29.0 stock wheel, util 0.95, ctx 262,144, --max-num-seqs 16,
port 8004, batched at 16. FN = Flash-Next on port 8001, batched at 6.
| Model | Cap (W) | Cold prefill TTFT | Prefill tok/s | Decode-1 | Batched decode | Temp max |
|---|---|---|---|---|---|---|
| 27B | 585 | 4.48 s | 8,875 | 140 | 1,467 | 86 C |
| 27B | 502 | 5.06 s | 7,858 | 145 | 1,352 | 87 C |
| 27B | 388 | 6.01 s | 6,616 | 136 | 1,221 | 80 C |
| 27B | 300 | 7.44 s | 5,344 | 141 | 1,024 | 72 C |
| FN | 585 | 2.78 s | 14,302 | 134 | 538 | 81 C |
| FN | 474 | 3.19 s | 12,464 | 136 | 553 | 83 C |
| FN | 384 | 3.71 s | 10,717 | 146 | 523 | 77 C |
| FN | 227 | 6.03 s | 6,594 | 106 | 368 | 64 C |
Batched decode and prefill pay; single-stream decode does not. 27B decode-1 stays in 124-145 tok/s from 300 W to 585 W and Flash-Next decode-1 in 106-146 tok/s from 227 W to 585 W, inside run-to-run scatter. What costs power is concurrency and prefill.
Neither 375 W nor 485 W was measured directly. Interpolating the bracketing rows, 375 W against 485 W costs the 27B about 12% of batched decode (~1,187 vs ~1,356 tok/s) and about 20% more cold-prefill time (~6.2 s vs ~5.2 s), and costs Flash-Next about 7% of batched decode (~516 vs ~552 tok/s) and about 20% more cold prefill (~3.8 s vs ~3.2 s). Against 585 W the 27B figures are about -19% and +38%. Derived, not measured.
A cap is a ceiling, not a target. At 585 W the 27B's batched decode drew 571.8 W average, so that cap barely binds; Flash-Next drew only 484.7 W average there and its batched decode is flat-to-non-monotonic from 474 W up (553, 552, 538 tok/s at 474/526/585 W), so above roughly 530 W there is nothing left to buy for it. Neither sweep is complete: the 27B run was interrupted at 285 W (285-200 W never collected) and Flash-Next never got 204 W or 200 W.
Measuring power at all
A Corsair HX1200i exposes USB-HID telemetry at /sys/class/hwmon/hwmon3 (corsairpsu, still
present 2026-09-18), world-readable, ~2 ms per read: whole-system power needs no root and no
external meter. power1 is DC output, not AC input — true AC draw is 8-12% higher. DC power
has sd ≈ 50 W with a ~16 s autocorrelation time, so the smallest provable difference is ~22 W
over 10 min and ~9 W over 60 min: any power claim below ~10 W is unfalsifiable here. Under
sustained load the card is simultaneously power- and thermally-capped (598.76 W, 91 C,
clocks_event_reasons.sw_power_cap = Active), so it has no headroom to surrender. (Measured
2026-08-17, not re-measured.) These figures are why a context-aware power daemon was rejected in
favour of a static cap — see DECISIONS.md.
Xid history and what the codes mean
journalctl -k implies --boot=0 and this box reboots often: 0 Xid lines on the current boot,
790 across journalctl -k -b all (2026-09-18), so always pass -b all. dmesg is unreadable
(kernel.dmesg_restrict = 1). servedeck/xid_watch.py passes -b all for exactly this reason —
a fatal Xid is often the event that caused the reboot that hid it.
| Code | Occurrences here | Action |
|---|---|---|
| 13, SM warp exception | 752 lines in one storm, 2026-08-24 23:03 and 23:45 | may recover on restart |
| 31, MMU fault | ~20 between 2026-08-15 and 2026-09-11, 10 of them on 2026-09-02 | may recover on restart |
| 79 + 154 | 3 events: 2026-08-24, 2026-09-11, 2026-09-12 | reboot; nothing else recovers it |
| 109, CTX SWITCH TIMEOUT | 1, 2026-08-29 14:19, followed instantly by an Xid 31 | treat as 31 |
Anything not in that table is "do not assume", and if nvidia-smi -L fails at all, never
restart a model — the card is gone and a restart only produces a confusing second failure.
servedeck/gpu.py:264 carries this classification in code.
Two Xid 31 causes must not be conflated: GLM-5.3's MMU faults reproduce under an agent loop in
the SM90 sparse-MLA path and are unresolved (models/glm53.md), while the 27B's
appear as Xid 31 or a segfault in CUDAGraph::replay and are the upstream vLLM GDN-Mamba race
(models/qwen27b.md). Neither is related to the 79/154 events, and a decision
made on the wrong family will be wrong.
Host RAM, /dev/shm and /tmp
swapon --show prints nothing. With no swap a host-RAM spike does not slow down, it reaches the
OOM killer. The structural commitments while Flash-Next serves are its FP8 PLE table (~47.7 GiB
resident) and the 40 GiB pinned KV-offload buffer in /dev/shm; free -g read 182 total / 108
used / 40 shared / 74 available on 2026-09-18, but run it rather than trusting that — it moves by
tens of GiB with what is loaded. start() preflights host RAM against models.toml host_ram_gib
(flashnext 95, glm53 155) and refuses rather than letting the kernel arbitrate; the reason is
2026-08-27, when a second model load OOM-killed a 103 GiB process and throughput fell from 93 to
2.8 tok/s while thrashing (source-only, not re-measured). Never start a second big model.
/dev/shm is a 92 G tmpfs and /tmp is a separate 92 G tmpfs; both are host RAM and both
sizes are ceilings, not reservations. /dev/shm currently holds vllm_offload_<uuid>.mmap,
42,945,576,960 bytes (40.0 GiB) — vLLM's CPU KV-offload region, sized by --kv-offloading-size,
and the shared column in free -g. So a multi-GB write to /tmp is a multi-GB claim on the RAM
a serving model holds, with the OOM killer as arbiter: put scratch files on disk. And everything
in /tmp is gone at reboot — evidence written there has been lost twice, which is why the
cutover doc's original advice to put a state dir under /tmp was corrected.
vLLM unlinks its offload buffer only inside its own graceful shutdown path, so flashnext and
glm53 pass --shutdown-timeout 30 and the units carry TimeoutStopSec=120; a force-kill leaks
40 GiB until reboot. servedeck's reaper cleans an orphan but fails closed: it deletes only when it
could read every vLLM process's and every live model's mappings, because an unreadable holder is
indistinguishable from no holder and "no holder" means "delete 40 GiB" — see
ARCHITECTURE.md.
sysctl
/etc/sysctl.d/90-servedeck.conf, all three confirmed live 2026-09-18:
| Key | Value | Why |
|---|---|---|
kernel.yama.ptrace_scope |
0 | Flash-Next's PLE CUDA-IPC handoff needs it to boot from a unit with no tty, and the offload reaper needs it to read a sibling engine's maps at all |
fs.inotify.max_user_watches |
1048576 | the default 65,536 was exhausted |
fs.inotify.max_user_instances |
1024 | default 128, of which 84 were in use |
The inotify raise has a specific cause: on 2026-09-16 kimi died at startup with ENOSPC: System
limit for number of file watchers reached. VS Code's file-watcher process held 59,628 of the
then-default 65,536 watches, its workspace being all of ~/Projects (over 1M files: vllm-glm53
227k, cvi-scratch 153k, vllm-qwen38next 129k, local_llm 125k); nothing in the serving stack holds
watches. files.watcherExclude entries were also added to ~/.config/Code/User/settings.json,
effective only after a window reload. Diagnostic: count per-process watches from /proc/*/fdinfo
before blaming the tool.
Docs elsewhere still name /etc/sysctl.d/90-vllm.conf; that file does not exist.
/etc/sysctl.d/99-vllm-swap.conf is a leftover setting vm.swappiness = 10 on a box with no
swap, so it is inert, and its rationale (a swapped PLE table cost roughly 35% of decode) cannot
happen without swap. serve-abliterated.sh:42 still makes a pointless
sudo sysctl -q vm.swappiness=10 call. Removing both is cleanup, not a fix.
PCIe, and why the CPU cannot rescue host offload
The link is PCIe 5.0 x16, and nvidia-smi reports pcie.link.gen.current = 1 at idle — the
driver downclocks link and memory clock when the card is unused, returning to gen 5 under load.
Measured 2026-09-01 with FreeToken's ft bench bw (not re-measured): CPU STREAM read 64.6 GB/s
at 16c/16t against PCIe H2D 56.4 and D2H 57.3 GB/s. Host memory is only 1.15x PCIe here,
because this is an X870E desktop board — dual-channel DDR5, not the quad-channel that was
assumed. CPU co-execution for weight offload therefore caps at about 1.36x aggregate (70.2 vs
51.8 GB/s) and cannot pay: FreeToken's own policy requires a 2.0x CPU/PCIe ratio before picking
its hybrid backend and selects plain offload here, and a hybrid attempt measured 0.895x. Published
results that look better came from a Xeon Platinum 8559C at ~178 GB/s, about 3x this box.
Disk
/ is one 915 G partition on nvme0n1p2: 746 G used, 123 G free, 86% (2026-09-18). The Hugging
Face hub cache is 360 G of that — GLM-5.3 182 G, Flash-Next 127 G, the 27B 24 G, LFM2.5-350M
681 M, all four in the registry. Reclaimable: models--openai--gpt-oss-20b 25 G and two
models--CedricHwang--qwen2.5-0.5b-modelopt-fp8-* repos totalling 1.2 G, none in models.toml.
Keep models--Qwen--Qwen3-0.6B (1.5 G) — the real-vLLM gate tests load it. Everything launches
from this cache and never the network (HF_HUB_OFFLINE=1 is in [defaults.env] for every model),
so a deleted repo is a failed boot, not a silent re-download.
The other large trees are vllm-glm53 24 G, local_llm 15 G, vllm-qwen38next 7.7 G and
servedeck 80 M; /var/log/journal is 811 M and /var/cache/apt 593 M. Ten servedeck checkouts
exist (main, v2, worktrees v2-p1-v2-p7 and v2-p9; there is no p8) at 13-53 MB each, and
all nine packet branches are already ancestors of main, so they carry no unique work. Do not du
across ~/Projects as a whole — the AlgoTrading worktrees make that scan cost minutes.