kv-cache : fix crash in state save/restore - #21576
Conversation
For iSWA models (e.g. Gemma 4), some K and V tensors in layer.k_stream/v_stream have tensor->data == NULL. The existing V loops guarded for !v but not !v->data, and the K loops had no guard at all, causing GGML_ASSERT(tensor->data != NULL) in ggml_backend_tensor_get during cache state save/restore. Add null guards matching the existing pattern for both K and V tensors in state_write_data and state_read_data.
|
This looks like it also fixes the same assert reported in #19839 and #23737 ( While reproducing that assert I traced it to the per-stream views: One thing worth considering about the added if (tensor->view_src) { offset += tensor->view_offs; tensor = tensor->view_src; }which is effectively what the on-device state writer/reader already do for their sub-views via Either way it'd be good to get this assert fixed; it bites multi-stream slot save/restore ( |
|
Reviving this... Still reproduces on current master (b9967). The per-stream views at kv-cache creation are made with I've been running this small patch succesfully on the model that hit it for months. This is the state-IO path, distinct from #25215 (which fixed the rotation input in llama-graph.cpp). It looks like the same assert as the still-open #23737 (Vulkan since b9318) and the earlier #19839; @Yoshi4470 also has a parallel fix commit referenced on #23737. Cross-linking so the duplicates can be consolidated. On my approach: this PR is the minimal guard in the state-IO paths (+10/-4, CI green, still mergeable 1277 commits later 😄 ). @liminfei-amd 's trace points at the root cause being the uninitialised views, so initialising them at creation would be the alternative. Happy either way. |
|
Thanks for the mention. After my earlier comment on #23737, I dug further into the root cause and landed on fixing the missing view init in the allocator. The change is here: The case I reproduced is with Qwen3.6-27B (with MTP layer): when a parent KV tensor exceeds the backend buffer-type The fix separates parent allocation from view init: allocate only parents in each split range, then run a final pass over the context to init all uninitialized views. This is complementary to the state-IO guards in this PR. Guarding on Hope this is useful. |
|
@ehotting Thanks for reviving this. One correction to my earlier comment: the normal KV allocator does initialize stream views via I did test the exact claimed state through fault injection: Resolving the unallocated view to Could you share the exact current-master command and failing log, including the context size, KV-unified setting, model hash, and the first null tensor's layer/stream and |
|
@Yoshi4470 Thanks, this explains the missing trigger. I ported Would you consider opening this against current upstream master with the regression test? It would give @ehotting and the maintainers a root-fix option when deciding how to update #21576. |
I reproduced this on current master and ran the experiments. Short version: the assert reproduces, the guards in this PR are the wrong fix, and @Yoshi4470's allocator fix resolves it correctly. I am moving this PR to draft so that it cannot be merged as it stands. Setup
which gives
The failing tensor@liminfei-amd, this is what you asked for: Layer 29 of 30 (the last one), stream 0, both K and V. Why it did not reproduce for youThe trigger is the buffer split. Vulkan's I confirmed that causally: with I have only tested one GPU, so the rest is inference from the code rather than measurement: ResultsSame prompt, 37 tokens saved, bytes written by the state save:
The 151,576-byte gap is exactly one layer's K and V (37 x 1024 x 2 B x 2, plus a 24-byte header). So @liminfei-amd and @Yoshi4470 are right: these guards trade the assert for silently dropping layer 29 from the saved state. The @Yoshi4470's Where this leaves the PRI have moved this to draft. The guards are wrong, and silently omitting a layer is the worst of the available failure modes: the save reports success, the file restores "cleanly", and the model then runs with a missing attention cache and no error anywhere. An assert is at least loud. I am keeping the PR open as a tracker rather than closing it, so that the issue stays visible until a root fix actually lands. @liminfei-amd, I could not find your view-preserving patch in a branch or a PR, so I assume it is not public yet. Where can I get it? I would like to run it here against the 2 GiB tensor case above. The same offer stands for @Yoshi4470's If the maintainers conclude that the allocator fix alone is sufficient and no state-IO guard is wanted, I will close this PR. |
|
@ehotting @liminfei-amd I opened an allocator-side fix for the missing view init after buft It includes a focused regression in Additional testing on your side would be very welcome, especially around the state save/restore cases you have been hitting. Thanks. |
|
For anyone landing here from #23737 or #19839: the root cause is in the allocator, not in the state-IO path, and @Yoshi4470 has now fixed it upstream in #25584. I have tested that PR on the hardware and model this one was opened for. The assert is gone and the saved state is byte-identical to the non-splitting path, including under a forced buffer split. Full report: #25584 (comment) The guards in this PR remove the assert but silently omit one layer's K and V from the saved state, so they should not be merged. Keeping this open as a tracker only until #25584 lands. |
K and V tensors in
layer.k_stream/v_streamcan havetensor->data == NULLif the tensor object exists but has no GPU memory allocated. The existing V loops guarded for!vbut not!v->data, and the K loops had no guard at all, causingGGML_ASSERT(tensor->data != NULL)inggml_backend_tensor_getduring cache state save/restore.Add
!tensor->datanull guards matching the existing pattern for both K and V tensors instate_write_dataandstate_read_data.Overview
Related to #21468
I ran into this running Gemma 4 26B-A4B with cache reuse and multiple slots. The server would crash with SIGABRT every few minutes. Both write and read paths in
llama_kv_cacheiterate all layers but some K/V tensors havedata == NULL. The write path for V already hadif (!v) continuebut K had no guard. Both lacked a check fortensor->data.Additional information
Tested on Gemma 4 26B-A4B, Vulkan backend, 4 slots, cache reuse enabled. 20/20 multi-turn requests stable, previously crashed every 4-5 min.
No impact on models without iSWA since all tensors are fully allocated and the guards never trigger.
Requirements