Skip to content

[BUG] llama-server SIGSEGV in qwen4exp_ple_prefetch → llama_ple_disk::page_cached() — this pointer is heap-corrupted #79

Description

@nanpaidashi

Summary

Running a Qwen3.8-27B dense GGUF with speculative MTP (--spec-type draft-mtp --spec-draft-adaptive) on the Strix Halo (gfx1151, native Vulkan build) crashes the server with SIGSEGV after completing one request, when a second request starts decoding.

The crash frame is llama_ple_disk::page_cached(), but that function is 5 instructions and cannot fault itself — the this pointer it receives is garbage (it contains string bytes), so this is heap corruption upstream of the call. This started with the PLE-on-disk / lazy-mode work merged in 77024dd95 --ngram-on-disk and lazymode merge (#29).

Environment

  • Build: strix-official /home/gsq/strix-official, HEAD = origin/master 8c1c282ec (build 11124), clean tree (git status empty). Local == GitHub master, so it is upstream code as-is.
  • HW/OS: AMD Strix Halo APU (Radeon 8060S gfx1151), Arch / Omarchy, 64 GB unified memory, Vulkan/RADV backend.
  • Reproduces on two different models (unsloth Qwen3.8-27B and huihui Qwen3.8-27B-abliterated) → not a model-file issue.

Reproduction

Any normal llama-server invocation of the 27B dense GGUF, e.g.:

llama-server -m <qwen3.8-27b-dense-q4gguf> --device Vulkan0 -c 98304 -ngl 999 \
  --load-mode none -fa on --cache-type-k q8_0 --cache-type-v q8_0 \
  --batch-size 4096 --ubatch-size 512 -t 16 --parallel 1 --jinja --cache-prompt \
  --spec-type draft-mtp --spec-draft-adaptive --spec-draft-n-min 3 --spec-draft-n-max 4 \
  ... --mmproj <mmproj.gguf>

No --lazy-mode / --ngram-on-disk flag is passed, and no LLAMA_ARG_LAZY_MODE/LLAMA_ARG_NGRAM_ON_DISK env is set. The crash is deterministic: 7/7 runs die with SIGSEGV at the same frame.

Evidence

  1. Repeated identical crash — coredumpctl list: every run is SIGSEGV (si_code: SI_KERNEL), e.g. PIDs 66933, 67449, 67868, 71127, 74154, 74406, 80400 on the same day.

    #0 llama_ple_disk::page_cached() const
    #1 qwen4exp_ple_prefetch(llama_model const&, ...)
    #2 llama_context::decode(llama_batch const&)
    #3 llama_decode
    #.. server decode loop / update_slots / start_loop
    
  2. The faulting instruction is the first of page_cached() (a load of the vtable/object pointer), and rdi (this) holds string bytes — captured with gdb from the core:

    => page_cached()+0 : mov (%rdi),%rax      ; SIGSEGV here
       rdi = 0x22202c22676e6964
    

    rdi as a little-endian byte sequence is "ding, 2" — i.e. the object pointer was overwritten with text data. A valid call would have a heap pointer in rdi.

  3. The process was mid-request when it died. Server log: task 0 completes (prompt eval + token output + MTP draft acceptance), slot released, then task 2 starts → crash. So the server handled one request fine and corrupts on the subsequent decode — consistent with a use-after-free / buffer overrun in the PLE path rather than at model load.

  4. Not OOM (64 GB machine; no oom-kill in journal) and not a missing-flag issue: no PLE n-gram table stays on disk: log line appears anywhere, confirming the ple-disk object was not intentionally built by lazy-mode for these runs.

Source cross-reference (HEAD 8c1c282ec)

// src/models/qwen4exp.cpp:1748  qwen4exp_ple_prefetch(...)
    if (!tokens || n_tokens < 4096) return;
    const auto & pmodel = static_cast<const llama_model_qwen4exp &>(model_base);
    if (!pmodel.ple_disk || !pmodel.ple_disk->page_cached()) return;   // <- this line

// src/llama-ple-disk.cpp:391
bool llama_ple_disk::page_cached() const { return !pimpl->direct; }    // no pimpl null-check

At minimum page_cached() should be defensive (pimpl &&), but that would only mask the corruption. The real defect is the corrupted ple_disk/PLE object being present and reachable in decode despite lazy-mode not having built it for this model — pointing at lifetime / shared_ptr handling of ple_disk introduced with the PLE-on-disk merge (77024dd95).

Suggested next steps

  • Add an assert/null-check + log in qwen4exp_ple_prefetch and page_cached() to make this fail loudly instead of segfaulting.
  • Audit the lifetime/ownership of llama_model_qwen4exp::ple_disk (shared_ptr reset timing) around decode with speculative MTP — especially whether it can be observed concurrently while being destroyed/reset, or whether PLE scratch state is shared between slots without a barrier.

Question for maintainers

Is the combination dense Qwen3.x + --spec-type draft-mtp --spec-draft-adaptive on Vulkan supported in this build? If MTP on dense Qwen should avoid the PLE/prefetch path, I can also verify with MTP disabled as a fallback.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions