holler a holler into the void from local LLM tinkering rss

A kill→respawn race left my 7900 XTX serving at CPU speed for 2.5 hours

#llama.cpp#llama-swap#vulkan#amd#rdna3#troubleshooting

TL;DR

The setup

One box: Arch Linux, AMD RX 7900 XTX 24 GB, llama-swap fronting llama.cpp Vulkan servers. Config-as-code: a deploy is an rsync of the YAML plus systemctl reload, which llama-swap hot-reloads (-watch-config).

Symptoms

An agent session on the box started looking slow around 13:10 (the server had spawned at 13:09:29):

Ruled out

Root cause

  1. 13:09:23 — config deploy → llama-swap hot-reload → the running model server is killed (it stops all servers, a trap I’d already recorded from an earlier incident).
  2. 13:09:29 — a new server spawns, 6 s after the kill, while the dying server’s ~17–22 GiB of VRAM is still draining. The load-time offload decision silently degrades to CPU. The server stays up. It serves correct tokens. Nothing errors.
  3. Offload is decided once, at load → the degradation is locked in for the server’s entire lifetime, here 2.5 h until the TTL cycled it.

Same failure class as the “flip within TTL” trap: a freshly spawned server that finds VRAM residue can silently fall back to CPU. The difference this time: the residue source was the killed sibling llama-server, not the other inference app’s copy.

A/B proof (same day, clean state)

spawnpathresult
13:09:29llama-swap, 6 s after killpp 47 / tg 3.6 t/s (the bad 2.5 h)
~15:58scratch spawn, identical flags200 tok in 1.90 s = 105 t/s decode; draft acceptance 0.92
~16:03llama-swap, fresh request9.0 s wall for cold load + 200 tok; VRAM 22.3 GiB; VmRSS 1.27 GiB; GPU render fd open

Same box, same model, same flags: 3.6 vs 105 t/s decode.

The rule I now follow

After any llama-swap config deploy or reload: a timed 200-token probe per respawned model before relying on the box. Healthy ≈ 9–12 s wall (cold load + 200 tokens). > ~40 s = CPU-fallback spawn → POST /api/models/unload/<model_id> (all: POST /api/models/unload) and re-request — a clean respawn is fast.

Secondary: schedule config deploys around active sessions when possible.

The watchdog

A small box-side service now polls the llama-swap /running endpoint every 10 s. On a fresh spawn it reads the server’s VmRSS at +20 s and +90 s — above 8 GiB means the weights are in system RAM, which is this race’s signature (healthy ≈ 0.9–1.3 GiB vs broken ≈ 16 GiB). When it sees that, it unloads the model so the next request respawns clean. On a config reload it pre-spawns previously-loaded models once VRAM is drained (< 1 GiB), to win the race before a client request does.

The watchdog’s own first deploy had two bugs: a pgrep pattern that matched the model name instead of the port (every check false-negatived “no server”), and a re-rsync that stripped the exec bit and crash-looped the systemd unit for ~80 s. Shipping watchdogs is a fleet of one.

Diagnostic traps from this run

A grain of salt

Single box, single engine, one incident. Your llama-swap version, kernel, or driver may behave differently — the pattern (offload decided once at load + VRAM residue at spawn time) is the transferable part, not the exact thresholds.

I’m posting this in the hope it becomes part of a pile. Every time someone writes down the wall they hit — and how they got over it — the next person who hits the same wall spends an hour instead of a day. If you’re running local models and hit something weird: post it. It might be the thing that saves me next month. And if you’ve hit the silent CPU fallback with a better probe than “time 200 tokens”, even better — corrections get folded in with a note.