Repository navigation
Misc. bug: llama-server Denial Of Service exchaust VRAM #30577
Copy link
Copy link
Open
Labels
Description
Name and Version
./llama-server --version
version: 0.6.0-dev (build 11646, commit 733f57a)
built with Clang 23.1.3 for FreeBSD amd64
Operating systems
BSD
Which llama.cpp modules do you know to be affected?
llama-server
Command line
llama-server --cache-prompt --chat-template-file /froggeric-chat_template.jinja --host 127.0.0.1 --log-colors on --metrics --min-p 0.0 --no-mmproj-auto --no-host --port 27118 --presence-penalty 0.0 --reasoning-budget -1 --reasoning-preserve --repeat-penalty 1.0 --spec-draft-n-max 3 --n-gpu-layers-draft all --cache-type-k-draft f16 --cache-type-v-draft f16 --spec-type draft-mtp --temperature 1.0 --top-k 20 --top-p 0.95 --no-warmup --alias Qwen3.8-27B-Q4-112 --agent --batch-size 512 --ctx-size 114688 --cont-batching --cache-ram 16384 --cache-type-k f16 --cache-type-v f16 --device Vulkan0 --flash-attn on --fit off --no-kv-unified --load-mode none --model /Qwen3.8-27B-Q4_K_M.gguf --n-gpu-layers -1 --parallel 1 --ubatch-size 512Problem description & steps to reproduce
After starting the server, open Claude Code and begin chatting. Memory is allocated in 2 MB increments with each prefill, unless doing so would cause an out-of-memory (OOM) condition. My command uses 24,050 MB out of the available 24,525 MB. With the changes introduced in PR #27703, memory is continuously allocated and never released, so it is inevitable that VRAM will eventually be exhausted unless the task is terminated.
@0cc4m & @Nathanw1014 - you introduce DoS into master head.
First Bad Commit
Relevant log output
Logs
[48215] 12.26.256.571 E ggml_vulkan: device lost on Vulkan0
[48215] 12.26.256.662 E srv update_slots: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.26.256.665 E srv send_error: task id = 10326, error: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.26.256.668 I slot release: id 0 | task 10326 | stop processing: n_tokens = 61224, truncated = 0
[48215] 12.26.256.672 W srv stop: cancel task, id_task = 10326
15.07.901.603 I srv proxy_reques: proxying request to model Qwen3.8-27B-Q4-112 on port 48215
[48215] 12.26.319.858 I slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.912 (> 0.100 thold), f_keep = 1.000
[48215] 12.26.320.472 I slot launch_slot_: id 0 | task 10330 | processing task, is_child = 0
[48215] 12.26.327.346 E ggml_vulkan: device lost on Vulkan0
[48215] 12.26.327.396 E srv update_slots: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.26.327.398 E srv send_error: task id = 10330, error: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.26.327.401 I slot release: id 0 | task 10330 | stop processing: n_tokens = 61736, truncated = 0
[48215] 12.26.327.406 W srv stop: cancel task, id_task = 10330
15.08.596.386 I srv proxy_reques: proxying request to model Qwen3.8-27B-Q4-112 on port 48215
[48215] 12.27.014.812 I slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.920 (> 0.100 thold), f_keep = 1.000
[48215] 12.27.015.418 I slot launch_slot_: id 0 | task 10333 | processing task, is_child = 0
[48215] 12.27.023.379 E ggml_vulkan: device lost on Vulkan0
[48215] 12.27.023.431 E srv update_slots: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.27.023.433 E srv send_error: task id = 10333, error: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.27.023.436 I slot release: id 0 | task 10333 | stop processing: n_tokens = 62248, truncated = 0
[48215] 12.27.023.440 W srv stop: cancel task, id_task = 10333
15.09.908.448 I srv proxy_reques: proxying request to model Qwen3.8-27B-Q4-112 on port 48215
[48215] 12.28.326.986 I slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.928 (> 0.100 thold), f_keep = 1.000
[48215] 12.28.327.569 I slot launch_slot_: id 0 | task 10336 | processing task, is_child = 0
[48215] 12.28.335.211 E ggml_vulkan: device lost on Vulkan0
[48215] 12.28.335.261 E srv update_slots: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.28.335.263 E srv send_error: task id = 10336, error: decode() failed: vk::Queue::submit: ErrorDeviceLost
[48215] 12.28.335.266 I slot release: id 0 | task 10336 | stop processing: n_tokens = 62760, truncated = 0
[48215] 12.28.335.270 W srv stop: cancel task, id_task = 10336
^C[48215] 14.34.611.926 I srv operator(): operator(): cleaning up before exit...
17.16.253.527 I srv operator(): operator(): cleaning up before exit...
Activity
0cc4m commentedon Oct 15, 2026
It's expected to use more VRAM, it grows with used kv cache. The performance gain is significant though, so you may be able to get to a faster overall speed by reducing VRAM use elsewhere.
vlforit commentedon Oct 15, 2026
I don't see how reducing --ctx-size addresses the actual problem. If VRAM keeps growing by ~2 MB on every prefill and is never freed, then OOM is mathematically inevitable for long-running sessions.
Lowering VRAM usage elsewhere only postpones the failure. It does not prevent it.
For agentic workloads such as Claude Code, which can run continuously for hours while compacting conversation history, this effectively turns the issue into a delayed denial-of-service condition: sooner or later all available VRAM will be consumed.
What am I missing here? Is there some mechanism that eventually releases or reuses those allocations? Otherwise, reducing --ctx-size seems like a workaround that merely delays the crash.
0cc4m commentedon Oct 15, 2026
It doesn't grow forever, your kv cache is limited in size.
vlforit commentedon Oct 15, 2026
I understand that the KV cache itself is bounded by --ctx-size and therefore VRAM growth is not literally infinite.
However, my concern is different.
I reduced the context size and the initial allocation dropped to about 22,940 MB out of 24,525 MB available. Despite that, I still hit an OOM during this discussion because of the additional allocations introduced by this PR.
From a user's perspective, this makes VRAM usage difficult to predict. Previously, I could estimate whether a configuration would fit based on the reported allocation at startup. After this change, there is an additional amount of VRAM consumed dynamically during runtime, but it is not obvious how much will eventually be required.
As a result, the only practical way to determine whether a configuration is safe is to keep reducing parameters such as --ctx-size and repeatedly test until OOMs stop occurring.
That's why I believe the current design is problematic. The issue is not that memory grows forever. The issue is that the runtime VRAM overhead is effectively hidden from the user, making capacity planning much harder.
Users need a way to understand and estimate the worst-case VRAM usage. Otherwise, a configuration that appears to fit at startup may still fail later under normal workloads, especially with long-running agentic tools such as Claude Code.
ggerganov commentedon Oct 15, 2026
Ideally, the worst case memory should indeed be allocated at the start. However, the usage of dynamic memory pools in some cases breaks this contract. The CUDA backend also has a similar shortcoming.
In the Metal backend we solved this by allocating extra memory in the compute graph by overloading the
get_alloc_sizeof the buffer types.This was introduced here: #15966
We now use it for a few ops that require extra allocations:
llama.cpp/ggml/src/ggml-metal/ggml-metal.cpp
Lines 220 to 255 in 47f5a64
0cc4m commentedon Oct 15, 2026
Yes, it would make sense to do it that way instead of the current internal buffers. I'll look into it.