From 85d8f7e83499e434c24993e6e1f3800568b5adfd Mon Sep 17 00:00:00 2001 From: kingjones30 Date: Thu, 3 Sep 2026 12:33:12 +0000 Subject: [PATCH] fix(qwen4exp): serialize QSA indexer cache in context checkpoints llama_memory_hybrid_idx inherited state_write/state_read from the hybrid base and never overrode them, so checkpoints omitted mem_idx. A restore then left the QSA index pointing at a different position than the KV caches; the next kernel could wedge the GPU SDMA queue (field report: kingjones777/Qwen3.8-Flash-Next-ROCmFP4-STRIX-GGUF#6, @liusecret). Also treat empty seq_rm ranges as no-ops and honor mem_idx->seq_rm. --- common/common.cpp | 16 ++++++++++++++++ src/llama-memory-hybrid-idx.cpp | 34 ++++++++++++++++++++++++++++++++- src/llama-memory-hybrid-idx.h | 3 +++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/common/common.cpp b/common/common.cpp index 1421040..e64a71b 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -1510,6 +1510,22 @@ done: void common_context_seq_rm(llama_context * ctx, llama_seq_id seq_id, llama_pos p0, llama_pos p1) { auto * mem = llama_get_memory(ctx); + if (mem == nullptr) { + return; + } + // empty range is a no-op, not a fatal. recurrent caches refuse p0 == n_past, + // p1 == -1 instead of succeeding, which used to abort the server. + const llama_pos p0n = p0 < 0 ? 0 : p0; + if (p1 >= 0) { + if (p0n >= p1) { + return; + } + } else { + const llama_pos p_max = llama_memory_seq_pos_max(mem, seq_id); + if (p_max >= 0 && p0n > p_max) { + return; + } + } if (!llama_memory_seq_rm(mem, seq_id, p0, p1)) { GGML_ABORT("%s", string_format("failed to remove sequence %d with p0=%d, p1=%d\n", seq_id, p0, p1).c_str()); } diff --git a/src/llama-memory-hybrid-idx.cpp b/src/llama-memory-hybrid-idx.cpp index 586b340..b32b1b7 100644 --- a/src/llama-memory-hybrid-idx.cpp +++ b/src/llama-memory-hybrid-idx.cpp @@ -142,6 +142,16 @@ void llama_memory_hybrid_idx::clear(bool data) { } bool llama_memory_hybrid_idx::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1) { + // empty range is a no-op. the recurrent cache refuses p0 == n_past, p1 == -1 + // (rollback past n_rs_seq) instead of succeeding, which made common_context_seq_rm abort. + { + const llama_pos p0n = p0 < 0 ? 0 : p0; + const llama_pos p_max = llama_memory_hybrid::seq_pos_max(seq_id); + if (p1 >= 0 ? p0n >= p1 : (p_max >= 0 && p0n > p_max)) { + return true; + } + } + // same order as llama_memory_hybrid::seq_rm: try the recurrent cache first since it is the // one that may refuse, and if it does the caches are left untouched if (!get_mem_recr()->seq_rm(seq_id, p0, p1)) { @@ -149,7 +159,9 @@ bool llama_memory_hybrid_idx::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_po } if (mem_idx) { - mem_idx->seq_rm(seq_id, p0, p1); + if (!mem_idx->seq_rm(seq_id, p0, p1)) { + return false; + } } return get_mem_attn()->seq_rm(seq_id, p0, p1); @@ -199,6 +211,26 @@ std::map llama_memory_hybrid_idx::memory_bre return mb; } +void llama_memory_hybrid_idx::state_write(llama_io_write_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) const { + llama_memory_hybrid::state_write(io, seq_id, flags); + + // mem_idx is a KV cache — same PARTIAL_ONLY rule as mem_attn in the base. + // without this, context checkpoints restore attn+recr and silently drop the QSA + // indexer. after a restore the index describes a different position than the KV + // caches, and a later kernel can wedge the GPU SDMA queue. + if (mem_idx && (flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) { + mem_idx->state_write(io, seq_id, flags); + } +} + +void llama_memory_hybrid_idx::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) { + llama_memory_hybrid::state_read(io, seq_id, flags); + + if (mem_idx && (flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) { + mem_idx->state_read(io, seq_id, flags); + } +} + llama_kv_cache * llama_memory_hybrid_idx::get_mem_idx() const { return mem_idx.get(); } diff --git a/src/llama-memory-hybrid-idx.h b/src/llama-memory-hybrid-idx.h index d5e75ef..18a64c4 100644 --- a/src/llama-memory-hybrid-idx.h +++ b/src/llama-memory-hybrid-idx.h @@ -75,6 +75,9 @@ public: std::map memory_breakdown() const override; + void state_write(llama_io_write_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) const override; + void state_read (llama_io_read_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) override; + // // llama_memory_hybrid_idx specific API // -- 2.43.0