Repository navigation
Eval bug: server -np 4 --kv-unified returns other requests' responses verbatim on integrated HIP GPU (gfx1151) — bisected to c7d87229 #25992
Description
Summary
Under parallel mixed load (-np 4 --kv-unified), llama-server on an integrated HIP GPU (Strix Halo / gfx1151, 128 GB UMA) returns complete responses that verbatim belong to a different, earlier request. A large-prompt request receives, as its entire response, the literal answer text of a short request from an earlier round. We also observed a chimeric response fusing tokens of two different requests' expected outputs.
Bisected to a single commit: c7d8722 "ggml-cuda : restore prop.integrated on HIP builds" (#24233).
| build | result (validated responses, nonce-checked) |
|---|---|
| 505b1ed (parent window start) | clean — 0 violations / 168 |
| 5839ba3 (= parent of c7d8722) | clean — 0 / 36 |
| c7d8722 | cross-request replays: 3 hard / 36 |
| e8f19cc (window end) | cross-request replays: 7 hard + 1 chimera / 96 |
The same harness against CPU builds of both window endpoints shows no build-dependent difference (model-compliance noise only) — consistent with the culprit being the HIP integrated-memory path. This presumably explains why it went unnoticed: discrete GPUs are unaffected; it needs an integrated AMD GPU (prop.integrated == 1), multiple slots and concurrent mixed traffic.
Environment
- AMD Ryzen AI Max+ 395 (Strix Halo), gfx1151, 128 GB unified memory, ROCm 7.14, Ubuntu 24.04
- Build:
cmake -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151 -DGPU_TARGETS=gfx1151 -DCMAKE_C_COMPILER=/opt/rocm/llvm/bin/clang -DCMAKE_CXX_COMPILER=/opt/rocm/llvm/bin/clang++(Release) - Server:
llama-server -m <model> -ngl 999 -c 65536 -np 4 --kv-unified --jinja --chat-template-kwargs '{"enable_thinking":false}' - Model: Qwen3.6-35B-A3B MoE (Q4-class quant). First hit this in production behind LocalAI (same symptom after its vendored-llama.cpp bump crossed this commit); reproducing on vanilla
llama-serverrules the wrapper out.
Reproduction
Load pattern per round (~30 s, script below): (B) short "repeat exactly this code: " request → (C) large-prompt (~10k tokens) request whose system prompt requires echoing a per-request nonce in the first line → (D) burst of 4 concurrent short nonce requests (2 streamed / 2 plain). Every response is validated to contain its own nonce and no nonce of an earlier request.
On affected builds the round-N large-prompt request receives a literal foreign nonce as its complete response, typically from a round-(N−1) burst request. Verbatim from run logs at e8f19cc:
R4/C got='pd-20260722-115649-3-d1' (expected: reply echoing pc-...-4-c + a summary)
R5/C got='pd-20260722-115649-4-d2'
R6/C got='pd-20260722-115649-1-d1'
R7/C got='pd-20260722-115649-6-d1'
R8/C got='pd-20260722-115649-7-d1'
R3/C got='<!-- run:pc-20260722-115649-2-d0 -->' <-- chimera: pc-...-2 stem fused with a -d0 burst suffix
In production we additionally observed: a poisoned slot re-serving its stored answer to unrelated requests 20+ minutes later (the victim's usage then reports the foreign request's token counts), and occasional degeneration into repeated single characters (/////...).
repro-crossover.py (needs httpx)
#!/usr/bin/env python3
"""Repro: llama-server cross-request response mix-up under -np 4 --kv-unified.
Each round: (B) short "repeat exactly: <nonce>" request, (C) large-prompt
(~10k tokens) request whose system prompt requires echoing a per-request nonce,
(D) burst of 4 concurrent short nonce requests (2 streamed / 2 plain).
Every response must contain its OWN nonce and no nonce of an EARLIER request.
Usage: python3 repro-crossover.py --url http://127.0.0.1:8080 [--rounds 8]
Requires: pip install httpx
"""
import argparse, asyncio, json, random, string, sys, time
import httpx
FILLER = [
"Quantized MoE models reach higher aggregate throughput under continuous batching.",
"The KV cache grows linearly with context length and dominates memory for long prompts.",
"Speculative decoding only pays off when the draft acceptance rate is high enough.",
"A reranker only improves retrieval when the candidate set is broad enough.",
]
def big_prompt(seed, chars=30000):
rnd = random.Random(seed)
lines = [f"## Research block {seed}"]
while sum(len(l) for l in lines) < chars:
tag = "".join(rnd.choices(string.ascii_lowercase + string.digits, k=8))
lines.append(f"- [{tag}] {rnd.choice(FILLER)}")
lines.append("\nSummarize the key points in 5 sentences.")
return "\n".join(lines)
seen, violations, checks = [], [], 0
def check(phase, rnd, nonce, text):
global checks
checks += 1
stem = nonce.rsplit("-", 1)[0]
problems = []
if nonce not in text and not (phase == "C" and stem in text):
problems.append("own_nonce_missing")
problems += [f"REPLAY_of:{o}" for o in seen if o != nonce and o in text]
seen.append(nonce)
if problems:
violations.append((rnd, phase, problems, text[:120]))
print(f" VIOLATION R{rnd}/{phase}: {problems} got={text[:80]!r}", flush=True)
async def echo_req(c, url, model, nonce, stream=False):
p = {"model": model, "max_tokens": 40, "temperature": 0,
"messages": [{"role": "user", "content": f"Repeat back exactly this code and nothing else: {nonce}"}]}
if not stream:
r = await c.post(f"{url}/v1/chat/completions", json=p)
return r.json()["choices"][0]["message"]["content"]
p["stream"] = True
out = []
async with c.stream("POST", f"{url}/v1/chat/completions", json=p) as r:
async for line in r.aiter_lines():
if line.startswith("data: ") and line != "data: [DONE]":
try: out.append(json.loads(line[6:])["choices"][0]["delta"].get("content") or "")
except Exception: pass
return "".join(out)
async def synth_req(c, url, model, nonce, prompt):
sysmsg = f"The FIRST line of your reply must be exactly `<!-- run:{nonce} -->`."
r = await c.post(f"{url}/v1/chat/completions", json={
"model": model, "max_tokens": 600,
"messages": [{"role": "system", "content": sysmsg},
{"role": "user", "content": sysmsg + "\n\n" + prompt}]})
return r.json()["choices"][0]["message"]["content"]
async def main():
ap = argparse.ArgumentParser()
ap.add_argument("--url", default="http://127.0.0.1:8080")
ap.add_argument("--model", default="default")
ap.add_argument("--rounds", type=int, default=8)
a = ap.parse_args()
ts = time.strftime("%H%M%S")
async with httpx.AsyncClient(timeout=600.0) as c:
for rnd in range(1, a.rounds + 1):
bp = big_prompt("cachehit" if rnd % 2 == 0 else f"uniq-{ts}-{rnd}")
nb = f"pb-{ts}-{rnd}-b"
check("B", rnd, nb, await echo_req(c, a.url, a.model, nb))
nc = f"pc-{ts}-{rnd}-c"
check("C", rnd, nc, await synth_req(c, a.url, a.model, nc, bp))
nd = [f"pd-{ts}-{rnd}-d{i}" for i in range(4)]
res = await asyncio.gather(*[echo_req(c, a.url, a.model, n, stream=(i % 2 == 0))
for i, n in enumerate(nd)], return_exceptions=True)
for n, r in zip(nd, res):
if not isinstance(r, Exception):
check("D", rnd, n, r)
print(f"round {rnd} done — violations so far: {len(violations)}/{checks}", flush=True)
print(f"\nRESULT: {len(violations)} violations / {checks} validated responses")
return 1 if violations else 0
if __name__ == "__main__":
sys.exit(asyncio.run(main()))Happy to run additional experiments on this hardware (it reproduces deterministically within 2–4 rounds).
Activity
walcz-de commentedon Jul 22, 2026
Additional context for triage: the integrated-GPU path was already disabled once for exactly this symptom class — the surrounding code still carries
// Temporarily disabled due to issues with corrupted output (e.g. #15034). c7d8722 re-enabled it for HIP only; our bisect shows the corruption is still present on HIP iGPUs (gfx1151/UMA). Re-disabling it (revert) fixes the mix-up on our hardware with no measurable throughput cost in our runs so far — we are running that revert as a downstream patch until a proper fix lands.fix(llama-cpp): revert upstream c7d87229 — prop.integrated on HIP iGP…
imrehg commentedon Jul 22, 2026
I've been fighting similar issues, but happens on
parallel=1/no-kv-unified=trueas well, the model returning stuff that e.g. coding agent use cannot handle at all (wrong tool use, random tokens, verbatim repeated thinking block as user message multiple times, etc... Also arrived on the same commit in the debugging effort.Swithching to
--device Vulkan0with everything else the same mitigates the error.Also happy to try different stuff.
herczy commentedon Jul 22, 2026
I can confirm this as well, additionally when using MiniMax M2.7 on affected builds, I get random tokens, a bunch of garbled output. On Qwen models I just got wrong tools in the response (e.g. when asked, the model responded with the Exa websearch tools, which I have not set as an MCP).
Machine:
I also switched over to a Vulkan-based build and it fixed all issues.
ascotai commentedon Jul 23, 2026
Yes, this is a must fix for gfx1151! I have experienced broken tool calling from VSCode to Qwen3-Coder-Next-80b-A3b and Qwen3.6-27b-MTP and I had a Hermes agent go haywire thanks to this issue. I just applied the patch referenced above and everything is rosy now! OP might add to title Breaks Qwen Tool Calling from VSCode/Agents. (I also was using Vulkan in the meantime without issues.)
cezn commentedon Jul 23, 2026
This commit also caused
token_embd.weightof Qwen 27B Q8 to move to the slower Strix Halo when used in layer split mode with R9700, both HIP. PP dropped from 1300t/s to 600t/s.override-tensorhelped, but it shouldn't be required.liminfei-amd commentedon Jul 24, 2026
Thanks for the detailed investigation and reproduction.
A fix is being worked on in #25863 and is currently awaiting review. Affected gfx1150/gfx1151 users are welcome to test the PR and share their results while it awaits merge.
repudi8or commentedon Jul 24, 2026
Independent reproduction on our own gfx1151 hardware (AMD Ryzen AI Max+ 395 / Radeon 8060S, ROCm 7.2.2), with a much stronger signal than a single occurrence.
Setup: Qwen3-Coder-Next-Q4_K_M (46GB, MoE),
-ngl 999 -c 65536 --jinja --no-mmap, three builds:control=e3546c794(predates this bug's bisected commit,c7d87229)affected= current HEAD (4310aa4f8), containsc7d87229patched= HEAD with a local revert of the 4-line HIP-guard hunk fromc7d87229Used the nonce-based cross-request repro script from this issue,
-np 4 --kv-unified, 192 rounds (1152 checks) per build:So on this hardware, reverting just the 4-line HIP guard from
c7d87229eliminates the failure entirely, and it's absent on the commit predating that change.One caveat worth being precise about: the failure mode we see is not a literal replay of another request's cached text — every violation is the same response degenerating into a fixed-length run of
?characters (invalid/garbage token output), never a readable fragment of another request's response. So this may not be pixel-for-pixel the same symptom described in the original report, but it's the same class of bug (commit-specific,-np4 --kv-unified-specific, concurrent-load-triggered correctness failure on an integrated HIP GPU), fixable with the same revert.Separately, we also tested
-np 1with no--kv-unifiedon the affected commit as a possible workaround: 0 violations / 240 checks (48 + a follow-up 192-round strengthening pass). Not exhaustive, but no correctness failures observed in that posture on our hardware.Happy to share full logs/scripts if useful.
serve: force -np 1, document known upstream quirks
AIwork4me commentedon Aug 18, 2026
I reproduced on AMD Ryzen AI MAX+ PRO 395 + ROCm: 7.14.0 + llama.cpp (master 01818e4) by running unsloth/Qwen3.8-27B-UD-Q4_K_XL.gguf + mmproj-F16, btw, PR #25863 tested on the same hardware class as this issue's bisect — differential results below
Here is the results:
unpatched: 3/3 runs FAIL (one run each, three different unpatched configs) — patched: 2/2 PASS.
one early attempt ran under concurrent 16-job compile load — anchor passed, 2/8 streams stopped early; all tabled results above are idle-host runs; small samples, not claiming determinism
Here is the reproducing configs:
Here is the Full receipts
Full receipts (exact flags, per-stream shapes, verbatim anchor output)
https://raw.githubusercontent.com/AIwork4me/Qwen3.8-27B-ROCm/main/docs/results/upstream-controls/e0-build714-4df29be4.json
https://raw.githubusercontent.com/AIwork4me/Qwen3.8-27B-ROCm/main/docs/results/upstream-controls/e1-master-01818e49.json
https://raw.githubusercontent.com/AIwork4me/Qwen3.8-27B-ROCm/main/docs/results/upstream-controls/e2-master-pr25863.json
https://raw.githubusercontent.com/AIwork4me/Qwen3.8-27B-ROCm/main/docs/results/upstream-controls/e3-master-nommproj.json
I'd like to test revised patches / run further diagnostics on this hardware
This is the same "////" signature after sustained multi-stream load on gfx1151 / ROCm 7.14 — differential test of PR #25863 on this hardware reported in #25992
BoobooBubberson commentedon Aug 19, 2026
Independent bisect converging on the same first bad commit, with two additional failure modes and a verified workaround. (Full disclosure: this investigation was run by Claude driving my machine; I'm relaying its findings, which I've seen reproduce on my own hardware.)
Environment: GMKtec EVO-X2 (Ryzen AI Max+ 395 / Radeon 8060S, gfx1151, 128 GB unified), Ubuntu 24.04.4, kernel 6.17.0-35, ROCm 7.2.1, llama.cpp master
60addddf, model llama3.3-70B Q4_K_M (-ngl 999 -c 8192).Failure mode A: single-request long-context corruption (no
-np, no--kv-unified)Prompts ≳5K tokens produce degenerate output on the HIP backend — short prompts are fine. Deterministic reproducer: a synthetic 5.4K-token "chronicle" of 180 distinct records in the system prompt, then one retrieval question at
--temp 0.-fa on:The village of Emberfall a village was a year 137, shortly after a year 137. Record a village…-fa off: same class of garbage → not flash-attention-specificGGML_CUDA_FORCE_CUBLAS=1: same garbage → not MMQ-specificc7d8722HIP (testeddded58b4): correct answergit bisectbetweendded58b4(good) and60addddf(bad), 5.4K-token probe at--temp 0as the test, converged on:Same commit as the cross-request replay already bisected in this issue.
Failure mode B:
GGML_CUDA_NO_PINNED=1→ gfxhub page faults + hard hangTrying to dodge the host-buffer path via
GGML_CUDA_NO_PINNED=1on master HIP does not fall back cleanly —llama-clifaults the GPU VM during load and hangs permanently (survives SIGTERM):Verified workaround: neutralize the commit
Forcing the pre-#24233 behavior on master (ggml/src/ggml-cuda/ggml-cuda.cu, the
GGML_USE_HIPbranch added by c7d8722):info.devices[id].integrated = false;restores correct output on the 5.4K-token probe with no performance cost (llama-bench, 70B Q4_K_M,
-fa 1):dded58b4)Note
The line c7d8722 replaced was itself a workaround for corrupted output on Jetson Orin (#15034) — i.e.,
integrated = trueappears to expose the same underlying host-buffer bug on both platforms, and #24233 re-exposed it on AMD APUs. The corruption is silent and only manifests at depth, so it survives casual testing (all our short-prompt smoke tests passed; a 5K-token retrieval probe caught it).glambx commentedon Aug 25, 2026
Can confirm c7d8722 seems to break rocm (7.14 and 7.2.4, Linux 7.2) build on Strix Halo with/without unified KV and even with np=1; garbled output that breaks opencode entirely (just returns a few words like "User wants to" and stops after the initial prompt).
I pulled the latest master and manually reverted just c7d8722 and things started working normally again, though with a significant hit to prefill performance (~1300t/s -> 1000t/s on Ornith 1.5 35B/A3B Q8_0).
docs: corrections from an internal fact-check (measurement labels, bu…
docs: corrections from an internal fact-check (measurement labels, bu…
docs: corrections from an internal fact-check (measurement labels, bu…
docs: corrections from an internal fact-check (measurement labels, bu…
docs: corrections from an internal fact-check (measurement labels, bu…
github-actions commentedon Oct 9, 2026
This issue was closed because it has been inactive for 14 days since being marked as stale.