ggml-meta : bound the stack use of the split state walk - #10
Open
Piggidragon wants to merge 1 commit into
Open
Piggidragon wants to merge 1 commit into
Piggidragon wants to merge 1 commit into
Conversation
get_split_state recurses into the sources of a tensor. A stale entry clears the whole cache, so a late node derives the full chain again, one large frame per node: a 65 layer graph needs ~66 MB of stack and dies on the default 8 MiB. Fill the sources from the leaves up first, iteratively, so the recursion finds them cached and stays one level deep. Assisted-by: Claude Opus 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
ggml_backend_meta_get_split_statederives the split state of a tensor from the split states of itssources, by calling itself for every source. One frame of that function measures about 42 kB, since
it holds several copies of
ggml_backend_meta_split_state, which is ~2 kB wide becausenecovers16 segments per device.
A cache normally keeps that recursion one level deep: the allocator walks the graph in topological
order, so the sources of a node are already cached when the node is reached. But a stale entry
clears the whole cache, not only that entry. When that happens at a late node, every source is gone
and the node derives the full chain back to the inputs again. On a 65 layer graph that is about 1560
frames, ~66 MB of stack, and the process dies on the default 8 MiB thread stack.
Fill the sources from the leaves up first, with an iterative walk, so the recursion finds them
cached and stays one level deep. Two details matter:
The walk therefore validates an entry itself and clears up front. The cache is empty afterwards,
and filling an empty cache cannot clear again, which bounds the walk to two attempts.
O(graph) per node.
The walk reaches what the recursion reaches: a leaf carries its own state and has no sources, and
the
ggml_nelements() == 0shortcut is kept. No tensor is queried that was not queried before, sono new assert becomes reachable.
The same code is in upstream master unchanged, so it is affected as well.
Testing
65 layer hybrid model,
--split-mode tensorover two devices, host resident KV cache, DFlash2speculative decoding. A short text conversation followed by a prompt with an image, reproduced
several times:
ggml_backend_meta_get_split_state, 197 recursion frames on the stack, thefaulting stack pointer below the stack mapping, peak stack 66.5 MiB
Equivalence of the derived states, from
GGML_META_DEBUG=1on both builds: the two runs agree onall 7607 shared keys of source states, name, op and per device shapes, including the 135 keys that
carry two states for
assume_synctrue and false. No key differs.test-alloc: 42 passed, exit 0.Throughput on the same image prompt: prompt eval 27.59 -> 27.63 ms per token, eval
41.10 -> 40.82 ms per token.
Requirements
🤖 Generated with Claude Code