Repository navigation
llama : report compute graph reallocations during inference - #30329
Conversation
Add an optional ggml_backend_sched_realloc_callback that user code registers with ggml_backend_sched_set_realloc_callback to be notified when the buffers reserved for a graph are not sufficient and have to be reserved again. The callback reports whether the backend assignments and the graph size changed since the previous scheduling. llama_context registers the callback when creating the scheduler (wrapped into the new llama_context::sched_new helper) and treats the reallocations that follow a change of the backend assignments as expected, counting the rest by graph size - unchanged or changed from the previously scheduled graph. Both counters are reported on one line in the perf stats. Assisted-by: pi:llama.cpp/Qwen3.8-Flash-Next
|
I'm seeing n_same increasing continuously. I assume these logs were added to help detect cases like this. Is this expected behavior? If more information is needed, I can open an issue. |
|
It's not a huge problem to see these, though it means there is some inefficiency that can be improved. What params are you running with? (depending on how many reports we get and how well we handle these, I'll consider hiding these logs behind some extra dev flag to not annoy developers/users running with |
|
I'm running 4 parallel slots, and I'm currently debugging some issues with slot allocation (or maybe prompt processing again?), so I need verbose logs: I can disable these logs myself, so that's not really an issue for me. My main question is whether this particular use case is useful for your debugging. :) |
|
Hm, not sure - I don't reproduce on my end using 2 virtual Metal devices and |
|
I am bombarded (see the times) on mtp experiment setup: config: If run without mtp it is roughly 1 message in 3s of ~25 tps decode. It is not about logs - can deal with them, but these reallocs do add some cost on decode and maybe the reason mtp is slower (just a guess) |
|
@sasa7812 Yes, that's the reason to have them - I usually fix each one when I see it. I'm aware of the graph reallocs when using |
|
@ggerganov lmk, if you need more data, I am trying to squeeze maximum of my setup |
|
What is your setup? |
|
Vulkan, RTX 3090 + RX 7900 XT, 80Gb DDR5 with slow PCIe4x4 RX 7900 connection |
|
Some things you can try:
|
|
Wow! @ggerganov thanks a lot for that! I really appreciate it. |
|
You made my day! Your mtp head and moving it to Vulkan1 had a great impact ~25 -> 33 tps on decode, thank you Georgi |
153 upstream commits (merge-base 1623d8c). Conflicts (6 files) and semantic fixes: - gated_delta_net.cu: fork kernels (column_lanes, indexed state, RDNA4 chunked WY prefill) kept; upstream GDN MMA path (ggml-org#29353, HIP only on RDNA3.5) runs after ours, skipped when the state gather was deferred to the op. - ggml-cuda.cu: n_stable reset kept, warmup reset log behind GGML_CUDA_GRAPH_DEBUG (ggml-org#30327). - llama-batch: upstream state embeddings for MTP hooks (ggml-org#30257) next to the fork embd alias; R57 extended to them: the allocator points at the batch state rows and a ubatch of consecutive rows reads them in place (no copies). - llama-context: upstream sched_new (realloc callback, ggml-org#30329) also used for the second MTP scheduler. - llama-graph: draft LM head and logits top-k kept next to build_hadamard_input / build_logits; the draft head applies the Hadamard input transform too. - llama-model: load_tensors_hadamard, then build_mtp_draft_head. - common: R59 full-ubatch warmup also sets zero state embeddings.
did that solved your reallocation message in log? |
Oh, no. It made things faster. But reallocations do happen quite with a fairly steady rate. My guess would be since it is almost always different experts in place, due to the cache. I am looking into it, but so far I'm not that deep into the kernel internals to come up with a root cause or a solution. |
Brings the fork from upstream b11553 to the v0.7.0 release (116 upstream commits). Every fork feature is kept; where both sides changed the same code the two changes are combined, not chosen between. Textual conflicts (10 hunks, 8 files): - common/speculative.cpp: upstream's LiliCorr setup (ggml-org#30506) placed ahead of the fork's fallback to the target vocab's mask token. - ggml/src/ggml-backend-meta.cpp: upstream's subgraph recreation (ggml-org#30453) is the fork's own fix; the fork's comment is kept. - ggml/src/ggml-cuda/common.cuh: the fork's context fields kept, with upstream's `= {}` initializers for the stream and cuBLAS arrays. - ggml/src/ggml-cuda/gated_delta_net.cu: includes both the fork's chunk kernel header and upstream's MMA header. - src/llama-context.cpp: both call sites use upstream's sched_new() member (it adds the graph realloc callback, ggml-org#30329); the fork's sync-non-graph-inputs policy moves into it. - src/llama-ext.h: the fork's MTP prefill and pre-norm accumulation declarations kept, upstream's wording for the decision head comment. - src/llama-model.cpp: the fork's skip for contexts with nothing to allocate kept without the per-file buffer map that upstream removed (ggml-org#30405); load_tensors() ends with upstream's load_tensors_hadamard() and then the fork's PLE prefault. - tools/server/server-context.cpp: release() resets the speculative state as the fork does and also drops the prompt of stateless tasks as upstream does. Merged without a textual conflict but changed here: - common/speculative.cpp: LiliCorr stores the projected rows of each inject in the eager path only. The fork's deferred prompt encode replays the injects in flush_pending_upto() without that step, so a LiliCorr drafter stays on the eager path. - ggml/src/ggml-cuda/gated_delta_net.cu: upstream's MMA chunked kernel (ggml-org#29353) writes only the final state. When the fork's snapshot ring keeps more than one state (K > 1) the call goes to the fork's kernels. On HIP the MMA path runs on RDNA3.5 only, so gfx906 does not take it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Overview
Add an optional
ggml_backend_sched_realloc_callbackthat user code registers withggml_backend_sched_set_realloc_callbackto be notified when the buffers reserved for a graph are not sufficient and have to be reserved again. The callback reports whether the backend assignments and the graph size changed since the previous scheduling, letting the user decide what to do with the event (trace, count, metrics, abort) instead of ggml maintaining a counter internally.llama_contextregisters the callback in the newsched_newhelper, which wraps the scheduler creation that was duplicated insched_reserve. It treats a reallocation that follows a change of the backend assignments as expected and ignores it, and counts the rest by graph size - unchanged or changed from the previously scheduled graph. Both counters are reported on one line in the perf stats:A non-zero
same sizecount means the compute buffers were re-reserved for a graph that was identical in size to a previously scheduled one - i.e. the initial reservation was not sufficient. Thesame sizecheck reuses the same condition as the existingGGML_SCHED_DEBUG_REALLOCdebug check inggml_backend_sched_alloc_splits, so the previously debug-onlygraph_size/prev_graph_sizetracking (renamed fromdebug_graph_size/debug_prev_graph_size) is now the single source of truth for both.Additional information
The per-event trace is emitted with
LLAMA_LOG_INFOfromsrc/, which maps to verbosity 4 (-lv 4, "trace") incommon_log_get_verbosity- i.e. it is hidden at the default-lv 3.Requirements