Repository navigation
llama-server: Use reference-counted checkpoints instead of deep-copies - #30137
Conversation
d133b80 to
d2d35a3
Compare
|
Would the shared references to the checkpoints also reduce the memory footprint of the prompt cache? If so, it might be useful to include numbers in the table. |
No, this patch uses |
I see. But just for clarification, the scenario I had in mind was when 2 prompts share the same prefix (eg. system prompt+tool schema) but different user messages. With this pr, do they share the same checkpoint object for the matching system prompt? I'm curious because if the original behaviour was a vector copy, it sounds like it's going to store the same checkpoint twice. |
|
Yes, sorry, you're right: If there are the same prefixes (a growing conversation), this saves (peak) RAM. It does not save RAM unrelated histories, even if they have the same input (there is no deduplication). Added the Memory table |
|
Sweet! I have only a few GB of RAM left once I load the model/prompt cache, so even a few hundred MB of savings would be meaningful to me. Looking forward to seeing this in mainline. |
|
/bot review |
Automated code reviewReview of PR #30137 (llama-server: reference-counted checkpoints). I checked the quick-reject gate, security, and the server/general checklists (paths touched: Summary of what I verified before the findings: every path that fills a Will slow the review (point 1) Cache accounting now over-counts shared checkpoint memory, and eviction cannot free it. (point 2) PR description/diff mismatch: the description's third bullet ("Reject broken requests before inserting into the prompt cache ... cheap because it's just an (point 3) (point 4) Test coverage: the new sharing between slot, cache entries, and Nits (point 5) (point 6) In Security note (no finding, but worth keeping in mind) The no-init vector is correct today only because all three fill sites validate completeness; the type itself enforces nothing. Any future Overall: the approach (immutable, reference-counted checkpoints) is the right one and the fill-path validation is done properly. Please address the accounting issue (point 1) and the description mismatch (point 2) before requesting maintainer review. This review was generated automatically by pi coding agent using |
|
Here for my support of this, I run this pr (augmented with a few patches of my own). It's stable, and saves anywhere between 6% and 30% of time when it hits. I have several downstream patches for better caching that gets my workload up to 94% cached for limited pinned streams - allowing a fused (cache load + processed) TPS of 26k tokens processed a second on a 5090 with qwen 3.8 27b q6ms @ 70k ctx. |
|
Is there something that is still required to get this merged? |
|
Yeah, I've been running this since day one. It's great for throughput. |
Picked from ggml-org#30137 (open at the time of picking), head a437af6, net diff against its merge base 0893ec9: c893c4b checkpoints: reduce overhead d2d35a3 rebase Merge notes: the fork's "server: reuse evicted prompt checkpoint buffers" (PrismML ggml-org#312) moves the buffers of an evicted checkpoint into the new one. With shared const checkpoints it now takes them only from a checkpoint that nothing else holds (use_count() == 1), since the prompt cache may still share it. The no-init allocator already removes the zero fill; the reuse still saves the allocation and the first-touch page faults. Assisted-by: Claude (Opus 5.5), conflict resolution
|
Will take a look - please rebase again on latest |
Assisted-by: Codex
Adapt the slot save/restore checkpoint appendix from ggml-org#26004 to the shared_ptr checkpoint list and the common_state_data buffers. Assisted-by: Claude Opus 5.5
a437af6 to
c73ed8d
Compare
|
Thanks, rebased |
ServeurpersoCom
left a comment
There was a problem hiding this comment.
Tested on a rootless container with an RTX PRO 6000, Qwen3.6-35B-A3B UD-Q8_K_XL on one slot, two conversations alternating for 10 turns each so every request saves and reloads the prompt cache, 15 checkpoints of 63 MiB per conversation.
Without this PR the time spent outside prompt processing grows to 182 ms per request at 25k tokens and the RAM peak includes a full copy of the checkpoints.
With this PR it drops to 104 ms, the RAM peak is 940 MiB lower and all 20 outputs are identical to master.
LGTM!
@cwriter few small nits, nothing blocking:
- the third bullet of the description does not match the diff anymore, the state is validated after insertion and popped on failure
- src/llama-impl.h already has no_init for the same purpose, you could mention why common needs its own allocator
|
@ServeurpersoCom @cwriter Following up on #31391, there was still some CPU stall when saving Qwen3.8-27B prompt state. The CUDA gather improved the transfer, but each save in this workload still deep-copied two retained recurrent-state checkpoints, totalling 299.25 MiB. These were earlier checkpoints already on the CPU, copied into the RAM prompt-cache entryβnot the new GPU-to-CPU snapshot transfer. In instrumented baseline runs, cache insertion/checkpoint copying took 42.66 ms, and allocating/zeroing the new snapshot buffer took another 28.98 ms. This PR addresses those costs by sharing immutable checkpoints and skipping buffer zero-initialization before serialization fills them. I applied the diff at
RTX 5090, CUDA 13.4, Release build, Qwen3.8-27B Q4_K_M; np4/KVU/concurrency 4, context 20,480, FA on, Q8_0 K/V. AIPerf: 64 streaming requests, ISL512/OSL512, output standard deviation 128. No profiler or speculative decoding. These are medians of per-run statistics. Every run completed 64 requests and recorded 60 saves, with matching request payloads, output-token counts and snapshot lengths. Save intervals include CPU work and backend completion; the saves exercise the shared LRU/idle-slot Here to provide some numbers in support of this PR! :D |
|
Adding data on top of this PR (running it since day one, now at c73ed8d on master 5851a8e incl. #31391; RTX 5090, Qwen3.8-27B Q6_K_S, 128k ctx, q8_0 KV, A further duplicate copy remains after this PR: when a task restores a checkpoint at the last-user-message boundary, Also confirming review point 1: |
ggerganov
left a comment
There was a problem hiding this comment.
@ServeurpersoCom Please merge if all is good on your end.
|
Thanks @cwriter, and thanks @bhargav191098 and @Nthalk for the extra numbers. Two follow-ups, none of them a regression vs master: the prompt cache still counts a shared checkpoint once per holder, so --cache-ram evicts earlier than it needs to now that holders share memory (point 1 of the bot review, confirmed by @Nthalk); and the remaining nits from my review (description bullet, reuse of no_init from llama-impl.h). @Nthalk the restored checkpoint that gets superseded by an identical save is what #30683 addresses, it will be rebased on top of this one. |
Overview
llama-servercurrently creates and reuses context-checkpoints through a vector copy.Since the checkpoints are immutable by design, this patch introduces reference-counted sharing instead.
This PR includes 3 changes to the system:
std::shared_ptrfor the context checkpoints, allowing cheap (ref-counted) copiesstd::moveAdditional information
This applies mainly to the host-side copies. The effects become larger as context grows. The effects are most visible when queuing events, as they need to wait on the serialized cache-checkpoint copies.
Tested on 3x Arc Pro B60, Threadripper 1950X x399 platform, 128GB RAM, qwen3.8-flash-next:UD-IQ4XS, RAM prompt cache 8GB, 16 checkpoints, checkpoint spacing 8192 tokens, no MTP, running on the SYCL backend (but should not affect this code).
Memory savings:
Requirements