Files
serverAppConfigs/serverAppConfigs-docs/LLM/dankserver-vram-growth.md
T
Frank 219a097b39 docs(vault): one page per endpoint, forever -- 45 era pages fold into three
Every payload swap a live endpoint has ever run is now one row in that
endpoint's Eras table, with its rollback path in the same row. No per-attempt
page, no per-swap ledger, nowhere else.

- Added: LLM/frank-archon.md (radiance, 2x R9700 TP=2), LLM/frank-dragoon.md
  (halogen flash-server, 19 eras), LLM/frank-zealot.md (llama.cpp PrismML fork
  on the 3090). Each carries Current shape, Eras, Laws, wire contract, files.
- Deleted: 45 per-payload runbooks -- the frank-dragoon line (11 including the
  pre-name halogen/kingjones/kairic/CIRU/eemin/agentionai/dflash2 pages and the
  benchmark page), the frank-zealot line (llama.cpp/vLLM/ik_llama/SGLang eras),
  the frank-archon line (vLLM/FreeToken/R9V/llama.cpp/radiance eras), plus
  frank-medium, frank-npu and the ubatch/PCIe one-offs that owned nothing.
- Rewired: every wikilink and markdown-path reference to a folded page points
  at the endpoint page instead; the three Research see-also lists and the
  ubatch sweep's runbook link included. No anchored links existed in the set.
- Regenerated both index pages as pure locators -- one row, one clause, no
  numbers -- so the docs_completeness locator law holds for all 153 pages.

tests/docs_completeness.bats green: index links every page, no unresolved
links. The pages moved to .AGENTTRASH/20261018-doc-dedupe/LLM/ (gitignored,
and git history holds every byte).
2026-10-19 00:49:03 -07:00

140 lines
6.3 KiB
Markdown

---
type: reference
title: Dankserver VRAM and RAM Growth Under Load
description: Investigation of the recurring cuBLAS crash on frank-zealot. What grows, what does not, and what to do next.
tags:
- dankserver
- llamacpp
- vram
- ram
- ctx-checkpoints
- crash
timestamp: 2026-09-03
---
> **SUPERSEDED 2026-08-17.** The Frank redo retired this topology.
> See [[frank-redo-2026-08-17]] and [[deployment]] for the active configuration.
# Dankserver VRAM and RAM Growth Under Load
This page records the investigation of the recurring cuBLAS crash on
`frank-zealot` (llama.cpp, RTX 3090 24 GiB). The service aborts
with `CUDA error: an unsupported value or parameter was passed to the
function` in `cublasSgemm_v2` at `ggml-cuda.cu:1541` during
`update_slots` -> `decode` -> `process_ubatch`. It crashed on 2026-08-13
at 19:45 and 19:55 and on 2026-08-14 at 12:00:43.
## What Was Ruled Out
### ctx-checkpoints do not grow VRAM
`--ctx-checkpoints 4` stores context checkpoints in host RAM. The log
records every checkpoint at exactly 62.813 MiB. The size never grows.
The reasoning:
- `common_prompt_checkpoint` (common/common.h) stores the serialized KV
state in `std::vector<uint8_t>` fields. These live on the host heap.
- `update_tgt()` and `update_dft()` call `llama_state_seq_get_data_ext`
with `LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY`. The serialized state moves
from VRAM to RAM for the snapshot and back on restore.
- With 4 slots and `--ctx-checkpoints 4`, the maximum checkpoint RAM is
16 x 62.813 MiB, about 1.0 GiB. That is fixed and small.
- `data_spec` (spec-decode state) is empty for ngram-mod. The ngram-mod
implementation does not override `get_state()`.
### ngram-mod does not grow VRAM
ngram-mod allocates one fixed 64 MiB CPU hash table
(`common_ngram_mod::entries`, 16M int32 entries). It never resizes. The
matcher keeps about 12 bytes per sequence. Draft generation runs on the
CPU. The speculative path allocates zero GPU memory.
### The CUDA graph cache has eviction in this build
`ggml_backend_cuda_context::cuda_graph()` sweeps every 5 seconds and
evicts graphs unused for 10 seconds. Upstream issue #25082 describes an
unbounded graph cache, but build b10108-13-g555881ebc includes the
eviction sweep. This build does not exhibit that leak.
## What Grows
### RAM growth is suspected, not yet confirmed
systemd reported MemoryPeak 46.9 GiB for the crashed process and 45.9
GiB for the replacement. RSS is flat at 32.3 GiB, which matches the
documented idle baseline of about 32 GiB. The difference is file-backed
page cache from the `--no-mmap` model read; cgroup memory.current counts
file pages attributed to the process. That gap is not proof of a leak by
itself.
The upstream pattern to watch is issue #26216: the prompt cache and
checkpoint accumulation exceed the `--cache-ram` cap and do not plateau.
The environment file does not set `--cache-ram`, so the llama.cpp
default of 8192 MiB applies. The monitor logs RSS, which separates
anonymous memory from file cache. If RSS climbs under load while VRAM is
flat, the RAM-side accumulation is real.
### VRAM suspects under load
VRAM is flat at idle: 21,268 MiB across every monitor sample. The crash
correlates with load density and slot context size (143K tokens on
slot 2), not with wall-clock time. The two mechanisms worth testing:
1. Flash-attention F16 K conversion workspace. The prefill kernel on
Ampere is `MMA_F16`. It converts the quantized K cache (q8_0) to F16
for the attention math. The workspace size scales with the attended
context length (`ggml_nelements(K) x 2`). It is allocated into the
graph buffer via `ggml_cuda_flash_attn_ext_get_alloc_size`.
2. The CUDA VMM pool grows monotonically. `ggml_cuda_pool_vmm::alloc`
only reserves more physical memory; `free` only releases the
reservation slot. The pool never shrinks during the process
lifetime. Any new peak allocation permanently expands VRAM usage.
Both match the observed pattern: growth under prefill bursts at deep
context, flat at idle, crash when a burst exceeds the ~2.8 GiB headroom.
## Monitoring
The monitor runs on Dankserver:
```
/home/dank/vram-monitor.sh
/home/dank/vram-log.csv
```
It samples GPU VRAM and process RAM every 10 seconds and appends a CSV
row. The columns are timestamp, uptime, GPU VRAM MiB, GPU utilization,
process RSS, process VSZ, and systemd MemoryPeak.
## Resolution: dual-f16 KV Cache (2026-08-14)
The fix chose the first mechanism over the second. Both K and V moved to f16 on 2026-08-14. When K is already f16, the flash-attention MMA_F16 prefill kernel needs no F16 conversion copy, so the context-proportional workspace disappears. The pool becomes static: 20,480 bytes per cell (10 full-attention layers x 2 kv heads x 256 dims x 2 bytes for K plus 2 bytes for V).
The pool at 800768 cells cost 15,640 MiB and did not fit. The load-time warmup graph (1472x1472 image) failed cudaMalloc by 248 MiB, and the service idled at 23,966 of 24,576 MiB. The context pool was refitted to 700416 cells (4 slots x 175104): KV pool 13,680 MiB, idle 22,110 MiB, about 2.0 GiB free after warmup. Parallel stays 4.
Verified 2026-08-14 after deploy: flash attention enabled, warmup clean, decode about 42-44 t/s, cached prompt tokens working. The vram monitor log shows the restart arc (23,966 -> 5,210 during load -> 22,110 idle).
## Mitigation Options
The documentation for the benchmark ([[frank-zealot]])
prescribes the first option for this crash class. None of these change
the model.
| Option | Effect | Cost |
| ------ | ------ | ---- |
| `GGML_CUDA_FORCE_MMQ=1` | Bypasses the cuBLAS path in the crashing function | Slight decode slowdown |
| `--cache-ram 2048` | Bounds the confirmed RAM growth | Cache misses on prompt reuse |
| `--ctx-checkpoints 2` | Cuts checkpoint RAM and save cost in half | More prefill work after eviction |
| Apply PR #24649 | Clears slot checkpoints before the prompt-cache save | Needs a llama.cpp rebuild |
Upstream references: issue #26216 (prompt cache growth), issue #25082
(graph cache, not applicable to this build), issue #23635 (quantized KV
with speculative decoding), PR #24649 (checkpoint-to-cache duplication),
PR #25649 (slot checkpoints freed on prompt clear, merged), issue #26119
(same cuBLAS signature, confirmed on V100 with speculative decoding).
## See Also
- [[frank-zealot]]
- [[Dankserver System Specs]]
- [[deployment]]