Repository navigation
HIP/ROCm — fused Gated Delta Net op carries recurrent state across requests on a reused server slot (qwen35 / qwen35moe); earlier prompts' text is emitted verbatim in later completions #29092
Description
Name and Version
Reproduced on two llama-server builds as bundled by ollama (we run ollama in production; the repro below removes ollama entirely and drives the bare server):
bundle llama.cpp build ggml
ollama 0.32.14 7e4c0a9 (common_params_print_info: build 1 (7e4c0a9) with GNU 13.3.1) libggml.so.0.20.0
ollama 0.34.1 5d806aa libggml.so.0.23.0, libllama.so.0.4.0
Both bundles ship the same gated_delta_net_cuda<…> kernel set in rocm_v7_2/libggml-hip.so (2,576 matching symbol strings). We have not yet tested a self-built current master for gfx1151/HIP — two independent builds with different ggml versions both exhibit the defect, which argues against a since-fixed regression, but we're aware GDN correctness work has landed on master recently and are happy to run the repro against any build you point us at (or with build guidance for gfx1151 ROCm).
Environment: Linux 7.0.0-31-generic; HIP/ROCm backend rocm_v7_2 (libamdhip64.so.7.2.70201, GGML_BACKEND_PATH=…/rocm_v7_2/libggml-hip.so).
Operating systems
Linux
GGML backends
HIP
Hardware
AMD Radeon 8060S (Strix Halo, gfx1151), integrated; AMD Ryzen AI MAX+ 395; 122 GiB RAM; amdgpu.gttsize=126976 (ROCm0 reports 126,976 MiB, ~101 GiB free at load)
Models
qwen3.6:35b-a3b (official Qwen3.6-35B-A3B, Apache-2.0 — ollama run qwen3.6:35b-a3b; also on Hugging Face) — arch qwen35moe, hybrid GDN + attention MoE, Q4_K_M, ollama blob sha256-f5ee307a2982106a6eb82b62b2c00b575c9072145a759ae4660378acda8dcf2d
qwen3.8:27b (official Qwen3.8-27B, Apache-2.0 — Hugging Face Qwen/Qwen3.8-27B) — arch qwen35, dense hybrid GDN + attention, Q4_K_M, ollama blob sha256-f5f1dd8920d417aac2718b0bda3403da274301efdd6760b4f0f4b864ff2ad57d
Problem description & steps to reproduce
Summary: with the fused Gated Delta Net op enabled on HIP, a reused llama-server slot carries the previous request's recurrent (GDN) state into the next request. The leak survives both memory paths a reused slot can take — context-checkpoint restore and memory_seq_rm [0, end) with a full re-process from position 0. The visible symptom is severe: the model emits the previous request's document text, verbatim, as its answer to the current request. Deterministic at temperature 0. On longer warm-runner sequences the carried state accumulates until generation degrades into unterminated output.
This has a privacy implication worth naming: on any deployment where one server slot serves multiple users' requests in sequence, one requester's prompt content can appear in another requester's completion.
Minimal repro (one bare llama-server, one slot, three /completion requests, synthetic documents):
Both models are stock public releases; no custom weights or Modelfiles are involved — the probe sets -ngl explicitly, so it forces the fused-on condition on a default pull. Scripts at https://github.com/jgoellermaximus/gdn-hip-leak-repro (also attached as a zip): rc3/leak_probe2.py (driver + proof capture) and rc3/synth_docs.py — three deterministic synthetic documents with disjoint vocabularies ("Harbor Lantern Charter" / "Velvet Gantry Ledger Rules" / "Ember Rampart Review Procedure"), so any leak is attributable by inspection.
export LD_LIBRARY_PATH=:/rocm_v7_2 GGML_BACKEND_PATH=/rocm_v7_2/libggml-hip.so
python rc3/leak_probe2.py --blob <qwen3.6-35b-a3b.gguf> --ngl 42 --out fused_on # leaks
python rc3/leak_probe2.py --blob <qwen3.6-35b-a3b.gguf> --ngl 41 --out fused_off # does not leak
The driver starts llama-server --model --port 18080 --host 127.0.0.1 --no-webui --offline -c 24576 -np 1 --log-verbosity 4 --load-mode dio --cache-type-k q8_0 --cache-type-v q8_0 --flash-attn on -b 1024 -ub 1024 --context-shift --keep 4 -ngl N, waits for /health, then posts three /completion requests (temperature 0, seed 7, cache_prompt true, n_predict 300), ChatML with a think-off prefill, and a generic system prompt S ("extractive one-line abstract + one verbatim quote, JSON only"):
request prompt tokens server path (from the verbose log) expected observed, fused ON (-ngl 42)
R1 S + document A 1,055 fresh slot, memory_seq_rm [0, end); a context checkpoint lands at pos 30 (inside S) abstract + quote of A correct
R2 S + document B1 (same S prefix) 4,270 restored context checkpoint (pos_min = 30, pos_max = 30, n_tokens = 31, n_past = 31) → B1 processed from pos 31 abstract + quote of B1 abstract of A + a verbatim 40-char clause of A
R3 + S + document B2 (prefix broken on purpose) 3,268 checkpoint checks fail → cached n_tokens = 0, memory_seq_rm [0, end), full re-process from position 0 abstract + quote of B2 still A's abstract + a verbatim A clause
Nothing in R2/R3's prompts contains A's vocabulary. Outputs are identical across passes.
Control — same binary, same flags, -ngl 41 (layer 0 on CPU): the resolver prints fused Gated Delta Net (autoregressive) not supported, set to disabled; fused Gated Delta Net (chunked) not supported, set to disabled and the leak is gone (R3 returns the correct B2 abstract). Layer-0-on-CPU is the only available switch for the op. Placement itself is not the variable: moving 24 layers' experts to host memory with the op still enabled leaves the leak signature identical, while moving only layer 0 (793 MiB) with the op disabled is clean.
Same result on the dense sibling: qwen3.8:27b at -ngl 66 (all layers, both fused paths enabled) leaks on R2 and R3; -ngl 65 (both paths disabled) does not.
Observed vs expected:
Expected: each /completion on a slot is independent once the slot's memory is cleared (memory_seq_rm [0, end)) or restored to a checkpoint predating the previous document; the hybrid model's recurrent (GDN) state should be re-initialised from the zeroed state on every such request.
Observed: with the fused GDN op enabled on HIP, the previous request's recurrent state persists — the first ubatch of R2/R3 reads state written by R1. On longer sequences (29 unrelated documents on one warm runner, -ub 1024) the carried state accumulates until generation degrades into unterminated JSON; ubatch size moves the onset (-ub 512: ≈15 requests; -ub 1024: 2–3), not the mechanism.
Isolation: graph reuse, HIP graphs, GGML_CUDA_DISABLE_FUSION (does not reach this op on the 0.32.14 build), expert placement, --ctx-checkpoints 0, --cache-ram 0, --context-shift, f16 KV cache, and parallel-slot settings were each varied one at a time across 22 single-variable server configurations: the leak persists in all of them; only disabling the fused op clears it.
Where the state physically persists inside the fused op (a persistent workspace in ggml-cuda/gated_delta_net.cu, or the fused kernel reading the RS cell directly and bypassing the zeroed input) we could not pin from outside; the repro is intended to make that a one-sitting job for someone with the kernel in front of them.
Possibly related. Same models and operator, different symptoms (throughput/output degradation, not cross-request state persistence): #27623 (Qwen3.8-27B hybrid GDN — decode-throughput collapse past ~80K context, CUDA) and #27164 (Qwen3.8-27B garbage output traced to the CUDA DeltaNet/Gated-DeltaNet layers); #27623 also references #27442 (empty generation >16K on Metal for qwen35moe hybrids) and #27572 (draft-acceptance collapse with multi-ubatch batches) in the same family. Same kernel family on other backends, crash-class: #21383 (Qwen3.5-27B illegal memory access in the prompt-cache path with fused GDN enabled, suspected in the memory_seq_rm partial-truncation fallback for recurrent state — CUDA) and #26546 (Compute Sanitizer out-of-bounds read in gated_delta_net_cuda on Pascal).
Workaround in use and its measured cost: we re-created the affected tags with layer 0 on CPU (N−1 GPU layers) so resolve_fused_ops disables both fused GDN paths on every load. Measured on qwen3.6:35b-a3b (29-document probe, one warm runner, identical flags): prompt 0.89 → 1.10–1.12 ms/tok (+24%), decode 18.3 → 23.0–23.7 ms/tok (+26–29%). The alternative (fused op + fresh server per request) breaks even at ≈850 decode tokens/request given a 5.8 s reload.
Additional observations (companion, possibly separate issues):
On the 0.34.1 bundle, layer-0-on-CPU no longer fully clears it: a residual, deterministic 1/29 leak remains (request 14 quotes request 13's document verbatim, identical on two runs), and that build logs no fused Gated Delta Net resolver lines for these models while decoding at the fused speed — consistent with the GDN fusion having moved to a generic subgraph-fusion path that the layer-0 placement rule no longer gates. For that build we claim "defect present" only, without kernel attribution.
MTP speculative decoding (--spec-type draft-mtp, qwen3.8:27b): the draft context's resolve_fused_ops enables the fused GDN op even when the target context has it disabled; the 3-request probe does not leak on this path even fully fused, so the draft state's effect is untested.
Side effect of the unfused graph on the 0.32.14 build: with a think-off prefill the model returns EOS as its first token on a prompt-dependent subset of free-text prompts (19/24 synthetic prompts; never with the op enabled, never under a JSON grammar, 0/24 on the 0.34.1 build) — noted as a companion observation, not the same defect.
What "fixed" looks like from our side (verification we can run on request): a build that passes rc3/leak_probe2.py with the fused op ON (all layers on GPU) twice on both models, plus a 29-document warm-runner sequence with 0 cross-document leaks.
First Bad Commit
Unknown — present in both builds we can run (7e4c0a9, 5d806aa); we cannot currently bisect on this hardware (no self-built gfx1151 HIP toolchain stood up yet).
Relevant log output
fused ON (-ngl 42), qwen3.6:35b-a3b — resolver + the two memory paths:
fused Gated Delta Net (autoregressive) ... enabled
fused Gated Delta Net (chunked) ... enabled
offloaded 42/42 layers to GPU
R2:
restored context checkpoint (pos_min = 30, pos_max = 30, n_tokens = 31, n_past = 31)
R3:
checking checkpoint with [30, 30] against 3…
cached n_tokens = 0, memory_seq_rm [0, end)
control (-ngl 41):
fused Gated Delta Net (autoregressive) not supported, set to disabled
fused Gated Delta Net (chunked) not supported, set to disabled
offloaded 41/42 layers to GPU
Full verbose server logs, request/response JSON with the leak fingerprints marked, and /proc//exe + .so-map proofs for the three-request repro runs (fused-ON and the -ngl 41 / -ngl 65 controls) are in the repro repository (https://github.com/jgoellermaximus/gdn-hip-leak-repro, SHA256SUMS included) and the attached zip. The 29-document warm-runner results are summarized above (logs held privately, available to maintainers on request).
Relevant log output
fused ON (-ngl 42), qwen3.6:35b-a3b — resolver + the two memory paths:
fused Gated Delta Net (autoregressive) ... enabled
fused Gated Delta Net (chunked) ... enabled
offloaded 42/42 layers to GPU
R2:
restored context checkpoint (pos_min = 30, pos_max = 30, n_tokens = 31, n_past = 31)
R3:
checking checkpoint with [30, 30] against 3…
cached n_tokens = 0, memory_seq_rm [0, end)
control (-ngl 41):
fused Gated Delta Net (autoregressive) not supported, set to disabled
fused Gated Delta Net (chunked) not supported, set to disabled
offloaded 41/42 layers to GPU
Full verbose server logs, request/response JSON with the leak fingerprints marked, and /proc//exe + .so-map proofs for the three-request repro runs (fused-ON and the -ngl 41 / -ngl 65 controls) are in the repro repository (https://github.com/jgoellermaximus/gdn-hip-leak-repro, SHA256SUMS included) and the attached zip. The 29-document warm-runner results are summarized above (logs held privately, available to maintainers on request).
Activity
huppiflupp commentedon Sep 18, 2026
Ran
rc3/leak_probe2.py(only change: no Ollama backend redirection) on self-built binaries, gfx1151, Qwen3.6-35B-A3B UD-IQ4_XS, all layers on GPU: no leak on R2 or R3 for masterec92815HIP, master Vulkan, and a Vulkan build at 10565 (fused GDN reported enabled). The master HIP log doesn't print resolver lines, so I can't confirm the fused op was active there. Caveats: different quant than yours; the 3-request probe only, not the 29-document sequence.pwilkin commentedon Sep 19, 2026
Does this persist after #28604 ?
pwilkin commentedon Sep 19, 2026
Your second commit tested is literally one commit before the one that should fix it.
huppiflupp commentedon Sep 19, 2026
@pwilkin To place my earlier result: the master I tested (
ec92815) is 177 commits after #28604 (d4389a4dd), so it was a post-fix build. The reporter's second build,5d806aa25, is its direct parent.To isolate that one commit I ran an A/B on the same hardware as the reporter (Strix Halo gfx1151 / Radeon 8060S, Ryzen AI Max+ 395). Setup:
-DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151, distro ROCm 7.1, kernel 7.2).rc3/leak_probe2.py, changed only to use the self-built binaries instead of the Ollama bundle.5d806aa25(parent of #28604)d4389a4dd(#28604)R1 matches doc A by design, since it summarizes doc A.
So in my setup the leak doesn't reproduce even one commit before #28604. I can't confirm from this that #28604 is what fixes it. What still differs from the reporter's runs:
libamdhip64.so.7.2.70201) vs my 7.1@jgoellermaximus, running
leak_probe2.pyagainst a self-builtd4389a4dd(or current master) on your machine would settle it for your setup. I can share the exact build commands if that helps.jgoellermaximus commentedon Sep 19, 2026
Ran it on my box — stock llama-server built from master (1af554f), gfx1151, system ROCm 7.1 (libamdhip64.so.7.1.52801, confirmed in the proc maps — not ollama's bundled 7.2), unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M (K-quant), all 66/66 layers on GPU, leak_probe2.py with only the binary path changed:
R1 (summarizes doc A — matches A by design): correct
R2 (B1, checkpoint restore): abstracts/quotes B1 only — no leak
R3 (B2, memory_seq_rm): abstracts/quotes B2 only — no leak
So it doesn't reproduce on stock for me either — and this adds the quant axis @huppiflupp flagged: his A/B was IQ4_XS, mine is a K-quant (Q4_K_M family, matching the original report), and it's still clean. Between his result (no leak even at #28604's parent) and this, the source at these commits looks exonerated on gfx1151/ROCm 7.1. The remaining differences from my original leaking runs are ollama's bundled ROCm 7.2 runtime and ollama's build flags. Next clean test would be a stock build against ROCm 7.2 to isolate the runtime — that's a heavier lift here (AMD's repo vs distro), but I can attempt it. Thanks both.
huppiflupp commentedon Sep 19, 2026
Ran the runtime-isolation test on my box, which has an Ollama 0.34.1 install with its bundled ROCm 7.2 (
libamdhip64.so.7.2.70201, confirmed in/proc/<pid>/mapsfor every run). Setup: gfx1151, Qwen3.6-35B-A3B UD-IQ4_XS, all layers on GPU,leak_probe2.pyunchanged apart from the binary path.d4389a4dd(#28604)d4389a4ddLD_LIBRARY_PATH)llama-server+rocm_v7_2/libggml-hip.so)In the Ollama-bundle run, R2 and R3 both return doc A's abstract verbatim (
"The Harbor Lantern Charter establishes governance rules…") instead of summarizing B1/B2. That's the reported defect, and it reproduces on a second machine.Holding the ROCm 7.2 runtime constant, the stock build is clean and the Ollama build leaks. So the 7.2 runtime is exonerated, and so is llama.cpp source at
d4389a4dd. What remains is Ollama's build of ggml-hip: its ggml version and patches, its build flags, or the fused-op resolver path (its log prints nofused Gated Delta Netresolver lines). That points to the Ollama side rather than llama.cpp. @jgoellermaximus, it may be worth taking this to ollama/ollama with the table above.dougmaitelli commentedon Sep 20, 2026
I been having leaks on llamacpp-rocm on lemaonade too for about a month #27422
chandlermajor commentedon Sep 28, 2026
Attribution: measurements by @chandlerma on AMD Strix Halo. Consent to
cite granted by the author in the model discussion (2026-09-27). Quoting
only the measured numbers; machine-specific details (local paths,
launcher config, agent-maintained notes) intentionally omitted.
Measured on the
julianmb/Ornith-1.5-35B-A3B-ROCmFP4-GGUFquant ofqwen35moe— Ryzen AI Max+ 395 / Radeon 8060S / gfx1151 / 128 GB UMA,Fedora 43, Mesa 25.3.6 (RADV GFX1151), Vulkan 1.4.328, ROCmFPX
c49ebdb,Vulkan backend (
-dev Vulkan0),-np 1,GGML_VK_FORCE_MMVQ=1.Protocol for the determinism rows: identical prompt,
temperature: 0,cache_prompt: false, 8 sequential calls on one server process, countingdistinct outputs.
Draft depth is the controlling variable, and the failure has a signature
n-maxThe period-3 oscillation at
n-max 3looks like the most diagnosticresult here. Random divergence is what you would expect from an
unconstrained state leak, but a clean reproducible 3-cycle is not. It
suggests the leaked recurrent state has a small discrete state space that
a 3-deep draft repeatedly reaches, where a 4-deep draft reaches it
stochastically and a 2-deep draft stays inside one basin.
If the GDN recurrent state on this architecture is low-dimensional in
practice, that is actionable: it suggests the fix is about restoring or
invalidating that state at slot boundaries, not about reducing its
magnitude. Has anyone looked at the dimension of the retained state for
qwen35moe, or is it a fulld_modelvector per layer?MTP suppresses the leak rather than causing it
Counter-intuitive, and worth recording: MTP off is the worst
configuration. Drafting appears to perturb or re-derive the recurrent
state in a way that partially masks the leak.
One negative result, since it saves someone repeating it — the bypass from
this issue's own report, dropping
-nglby one to disable the fused GDNop, does not work on Vulkan.
-ngl 998measured no better than-ngl 999. So the HIP-side workaround in this thread does not transfer tothe Vulkan backend.
--spec-mtp-strict-qwenis not the active ingredientAt
n-max 2, with and without the flag and nothing else changed:n-max 2without--spec-mtp-strict-qwenn-max 2with--spec-mtp-strict-qwenIdentical. I had expected the flag's "bounded recurrent rollback" to be the
mechanism, and it is not — at this depth the rollback is either a no-op or
its effect is below measurement noise. It is still required for
-np 1per the flag's own validation, and presumably matters at greater depth,
but it is not what makes
n-max 2stable.(
--spec-mtp-strict-qwenis fork-local, ROCmFPX#54, so this specific comparison is not reproducible on upstream builds.)
--spec-draft-p-minsweep, at fixedn-max 4p-min0.6is the throughput optimum; higher buys acceptance at a net loss.Severity scales with prompt complexity; multi-turn is unaffected
Two things worth pulling out. The divergence is not always cosmetic — on the
code prompt it changed the algorithm chosen, not just wording. And
multi-turn conversation shows no leakage at all, with the anchored
history apparently suppressing it. If that holds generally, the practical
impact of this issue is concentrated in single-shot stateless requests, not
in agentic or coding sessions with a conversation history.
Artifact, for reference
qwen35moe, 41 blocks, 1nextnblock, d_model 2048, 256 experts,nextn_predict_layers = 1, 35,505,251,456 params, 19,052,438,944 bytes(4.29 bpw),
q4_0_rocmfp4_strix_lean.output.weighttoken_embd.weightnextnnormsblk.40.nextn.eh_proj.weightOpen questions
suggests it is. If so, the fix is cheaper than making the state
rollback-proof.
during verification. If that re-derivation is what incidentally cleans
it, the same call could be made on the plain decode path without paying
for drafts.
prompts? A 5-turn run with MTP off was not tested, and that is the
cell that separates "history stabilises output" from "MTP stabilises
output".
Restores wait for the queued zeroing; the decode score skips invisibl…