From 57f5bde666cf99cc7061116b991fd9da008f8053 Mon Sep 17 00:00:00 2001 From: Daniele Zannotti Date: Thu, 17 Sep 2026 22:34:13 +0100 Subject: [PATCH] fix: guard the qwen4exp PLE prefetch on the model architecture llama_context::decode calls qwen4exp_ple_prefetch for every architecture once a batch reaches 4096 tokens, and the function immediately downcasts the model to llama_model_qwen4exp. On any other architecture that reads ple_disk out of an unrelated object; when those bytes are non-zero the null check passes and page_cached() dereferences them. Reproduced on gfx1151 (ROCm 7.2.1) with Signal-3.8-27B-AP-Q4_K_XL (qwen35): llama-bench -m -ngl 99 -fa on -p 4096 -b 4096 -ub 4096 -> SIGSEGV in llama_ple_disk::page_cached() const from qwen4exp_ple_prefetch(llama_model const&, int const*, int) from llama_context::decode(llama_batch const&) Any non-qwen4exp model crashes at ubatch >= 4096; ubatch <= 2048 never reaches the call. qwen4exp models are unaffected, which is why this survived review. Guards both the call site and the function. After this, the same command runs at ubatch 4096, 8192 and 16384 on qwen35 (dense) and gemma4/gpt-oss (MoE). Co-Authored-By: Claude Opus 5 (1M context) --- src/llama-context.cpp | 4 +++- src/models/qwen4exp.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/llama-context.cpp b/src/llama-context.cpp index ccfd52175747..53668fa316aa 100644 --- a/src/llama-context.cpp +++ b/src/llama-context.cpp @@ -1743,7 +1743,9 @@ int llama_context::decode(const llama_batch & batch_inp) { { // warm the page cache for this batch's per-layer-embedding rows while the first chunk is on the GPU; // posix_fadvise only, so a wrong prediction costs readahead and nothing else extern void qwen4exp_ple_prefetch(const llama_model & model, const llama_token * tokens, int32_t n_tokens); - if (batch_inp.token && batch_inp.n_tokens >= 4096) { qwen4exp_ple_prefetch(model, batch_inp.token, batch_inp.n_tokens); } + if (model.arch == LLM_ARCH_QWEN4EXP && batch_inp.token && batch_inp.n_tokens >= 4096) { + qwen4exp_ple_prefetch(model, batch_inp.token, batch_inp.n_tokens); + } } const uint32_t n_outputs_all = balloc->get_n_outputs(); diff --git a/src/models/qwen4exp.cpp b/src/models/qwen4exp.cpp index 899258ab493e..b1a127d1b546 100644 --- a/src/models/qwen4exp.cpp +++ b/src/models/qwen4exp.cpp @@ -1749,6 +1749,12 @@ void qwen4exp_ple_prefetch(const llama_model & model_base, const llama_token * t if (!tokens || n_tokens < 4096) { return; } + // llama_context::decode calls this for every architecture, so the downcast below is only valid + // once the arch is known: on any other model it reads ple_disk out of an unrelated object and + // dereferences whatever that happens to hold. + if (model_base.arch != LLM_ARCH_QWEN4EXP) { + return; + } const auto & pmodel = static_cast(model_base); if (!pmodel.ple_disk || !pmodel.ple_disk->page_cached()) { return;