Repository navigation
Description
On a Mac Studio running GLM-5.3-Flash with llama.cpp, the first turn of a Coder Agents chat waited 17 s before the first token. Most of the prompt was the same text that every chat starts with. llama.cpp can reuse that text, but a llama.cpp profile does nothing to make sure it does.
Why
Agent clients send a large fixed prefix with every new chat. For one Coder Agents chat, Bifrost's log showed 9,397 prompt tokens:
| Part | Characters |
|---|---|
| Tool definitions (22 tools) | 25,707 |
| System prompt | about 13,500 |
| Skills, workspace note, user rules | about 5,950 |
| The user's question | 26 |
At about 545 prompt tokens/sec on the Mac, that is 17.3 s to read and 3.1 s to write the answer. Later turns of the same chat start in under a second. A new chat pays the whole 17 s again unless the server finds the prefix in its cache.
vLLM's prefix cache works per block, so any shared prefix is reused. llama.cpp works differently for GLM-5.3-Flash, because 34 of its 45 layers are linear attention. A recurrent state can not be cut back to the point where two prompts stop matching. llama.cpp can reuse a prefix only at a saved context checkpoint (ggml-org/llama.cpp#15293). It saves one at the start of the last user message (and at earlier user messages, at most one every 8,192 tokens by default), and it keeps old prompts in a host-memory cache (--cache-ram, ggml-org/llama.cpp#16391).
What the log shows
llama.cpp 0.8.0 (build 11960) on the Mac, with the profile from #64:
- The slow chat matched 49% of a slot's cached prompt (
f_keep = 0.491), and llama.cpp still read all 9,397 tokens. The other chat had 21 tools and this one had 22 (find_toolsadded). The system messages were byte for byte the same. The prompts split at about 4,000 tokens, before any checkpoint, so nothing was reusable. - Two later chats with the same tools as a cached one read only 20 and 18 new tokens. The prefix came back from the host cache in about 0.2 s.
So reuse works when a chat with the same prefix ran recently and is still in the cache. It misses for the first chat after a start or a switch, for a new tool set, and after a busy period evicts the entry. The default --cache-ram is 8,192 MiB. The log shows entries of 0.1 to 1.3 GiB evicted under load.
Ideas
- Size
--cache-ramfrom the memory that is left after the model and KV cache, like theGPU_MEMORY_UTILIZATIONfit does for vLLM, and not the 8 GiB default. - Warm the cache after
startandswitch. For example, a profile setting that names a file with a request body (the client's fixed prefix), which llm-helper sends once withmax_tokens: 1after health turns green. - Keep slot states across a restart with
--slot-save-pathand the/slotssave and restore actions. I have not checked if a saved slot includes the checkpoints that a hybrid model needs. - Show cache reuse in
statusor the panel: prompt tokens read against prompt tokens reused, from the server log or/metrics. - Write the
inspectresult for hybrid models in plain words: prefix reuse needs a checkpoint at the split point.
1 and 2 look like the smallest changes that help. With 4 we can see if they work.
Not tested yet
- A larger
--cache-ramon the Mac. - A warm-up request after a restart.
--checkpoint-min-steplower than 8,192. The checkpoint at the start of the last user message is not limited by it, and a new chat has only one user message, so I do not expect it to help here.
Generated by Coder Agents on behalf of @bpmct.
Activity
bpmct commentedon Nov 10, 2026
One more thing that the ideas above do not fix, and it needs a change in llama.cpp itself.
For a hybrid model, llama.cpp reuses a cached prompt only up to a saved checkpoint. In 0.8.0 (
tools/server/server-context.cpp), the server saves checkpoints at the start of user messages and near the end of the prompt. A new agent chat has one user message, so its only checkpoint is at the end of all the fixed text.The fixed text is not the same for everyone. For Coder Agents, the system prompt and core tools are shared. The skills, the user's own rules, the MCP tools, and the workspace note are different for each person. Two people share the first few thousand tokens and then split, before any checkpoint, so nothing is reused. Only chats that match exactly up to the user message share a cache entry.
The fix is a checkpoint at the end of the shared part:
Then all chats from one client reuse the shared system prompt, no matter what comes after it. vLLM does not need this, because its prefix cache reuses any matching block.
I did not find an upstream issue for this. If we agree, I will open one on ggml-org/llama.cpp with the numbers from this issue.
On the client side, the order of the prompt matters too. If the shared text comes first and the per-person text comes last, more of the prompt is shared. That is a change for Coder, not llm-helper.
(Coder Agents generated)