Repository navigation
llama : synchronize before mutating graph state in process_ubatch - #28058
allenzz-dev wants to merge 2 commits into
Conversation
…ml-org#28056) The synchronize before set_inputs was gated on cparams.pipeline_parallel, which is always false on a single GPU, and the graph-rebuild path (sched_reset + alloc_graph) had no synchronization at all. graph_compute ends with ggml_backend_sched_graph_compute_async, and prefill ubatches produce no outputs, so llama_decode returns with the compute still in flight and the next ubatch's set_inputs overwrites input tensors (k_idxs/v_idxs/kq_mask/...) the device is still reading. On integrated GPUs these inputs live in pinned host memory read zero-copy by the device (discrete GPUs get a protective sync+copy via split inputs), so the race scatters in-flight K/V writes to wrong cells and leaves stale K/V from a previous request visible under the new request's mask - observed as cross-request KV-cache contamination in llama-server on an AMD gfx1151 APU (details in ggml-org#28056). Make the synchronize unconditional and hoist it above the can_reuse branch so it also covers the rebuild path. Measured cost on the affected hardware is zero: the async pipeline only queued deeper (21k-token prefill 1230.8 -> 1231.8 t/s, decode 48.5 -> 48.4 t/s).
The --cache-reuse KV surgery needs mid-range seq_rm in addition to seq_add. A recurrent state cannot represent a partial erase, but llama_memory_recurrent::seq_rm returned true for one while leaving the removed tokens in the state, so the surgery appeared to succeed and stale recurrent state leaked into the new prompt positions on hybrid/recurrent models. Reject the unrepresentable erase in seq_rm, and gate can_cache_reuse on the context being pure attention (seq_rm type PART).
|
Hi @allenzz-dev, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
Squashed cherry-pick of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs). The sync before set_inputs was gated on cparams.pipeline_parallel (always false on a single GPU), and the graph-rebuild path had no sync at all. graph_compute is fire-and-forget and prefill ubatches produce no outputs, so llama_decode returns with compute still in flight and the next ubatch's set_inputs overwrites input tensors (k_idxs/v_idxs/kq_mask/...) the device is still reading. On integrated GPUs those inputs are read zero-copy from pinned host memory, with no protective split-input copy as on dGPUs, so the race scatters in-flight K/V writes to wrong cells and leaves stale K/V from a previous request visible under the new request's mask. Also carries the PR's second commit: llama_memory_recurrent::seq_rm now rejects an unrepresentable mid-range partial erase instead of returning true, and the server only enables cache-reuse on pure attention contexts. Reproduced locally on gfx1150 (Strix Point, ROCm 10) with LFM2.5-VL-3B Q8_0: with -b 2048 -ub 512, any prompt prefilled across several ubatches returned the *previous* request's answer verbatim. Vulkan on the same host was unaffected, as were -b 512 -ub 512 and -b 2048 -ub 2048. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
I tested this on gfx1150 rather than gfx1151 and on a third model family. It fixes the issue. Radeon 890M (Strix Point) on Ubuntu 26.04, ROCm 10.0, HIP. |
Squashed cherry-pick of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs). The sync before set_inputs was gated on cparams.pipeline_parallel (always false on a single GPU), and the graph-rebuild path had no sync at all. graph_compute is fire-and-forget and prefill ubatches produce no outputs, so llama_decode returns with compute still in flight and the next ubatch's set_inputs overwrites input tensors (k_idxs/v_idxs/kq_mask/...) the device is still reading. On integrated GPUs those inputs are read zero-copy from pinned host memory, with no protective split-input copy as on dGPUs, so the race scatters in-flight K/V writes to wrong cells and leaves stale K/V from a previous request visible under the new request's mask. Also carries the PR's second commit: llama_memory_recurrent::seq_rm now rejects an unrepresentable mid-range partial erase instead of returning true, and the server only enables cache-reuse on pure attention contexts. Reproduced locally on gfx1150 (Strix Point, ROCm 10) with LFM2.5-VL-3B Q8_0: with -b 2048 -ub 512, any prompt prefilled across several ubatches returned the *previous* request's answer verbatim. Vulkan on the same host was unaffected, as were -b 512 -ub 512 and -b 2048 -ub 2048. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Squash of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs)
…stream ggml-org#28058) Upstream commits 148f18d and 06e43d5 by allenzz-dev, applied as one diff. The sched synchronize before set_inputs was conditional on pipeline parallelism; on an integrated GPU the device reads graph inputs zero-copy from pinned host memory, so an unsynchronized overwrite scattered in-flight K/V writes to wrong cells and left stale K/V visible under the new mask (cross-request contamination, upstream ggml-org#28056). GB10 reports integrated=1. Also: a recurrent memory rejects a mid-range partial erase instead of silently keeping the removed tokens, and the server's cache-reuse surgery only runs on pure attention contexts.
Squash of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs)
Squash of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs)
Squash of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs)
Tested on NVIDIA Jetson Orin NX (CUDA iGPU): this PR fixes the bugI hit the same bug on an NVIDIA Jetson. #29450 says NVIDIA testing is still needed, so here are my results. Setup
Problem
On a 1505-token prompt the result is random-looking across
What I found in the scheduler
With this PR
Speed, median of 3:
(Before the patch the image output was So on Jetson, this PR fixes the bug. Prompt processing is 0.1–0.5% slower, and generation speed does not change. I can run more tests on this board if needed. I used an AI assistant to help with the English and the analysis. I ran all the tests myself on our lab's Jetson. |
Squash of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs)
Squash of ggml-org#28058, which fixes ggml-org#28056 (cross-request KV-cache contamination on integrated GPUs)
Fixes #28056 (cross-request KV-cache contamination on integrated GPUs — full root-cause analysis and probe evidence there).
What
Two commits:
llama : synchronize before mutating graph state in process_ubatch— the core fix. The sync beforeset_inputswas gated oncparams.pipeline_parallel(always false on a single GPU), and the graph-rebuild path (sched_reset+alloc_graph) had no sync at all. Sincegraph_computeis fire-and-forget (ggml_backend_sched_graph_compute_async) and prefill ubatches produce no outputs,llama_decodereturns with compute still in flight and the next ubatch'sset_inputsoverwrites input tensors (k_idxs/v_idxs/kq_mask/…) the device is still reading. On integrated GPUs those inputs are read zero-copy from pinned host memory (no protective split-input copy like on dGPUs), so the race scatters in-flight K/V writes to wrong cells and leaves stale K/V from a previous request visible under the new request's mask. The fix makes the synchronize unconditional and hoists it above thecan_reusebranch so it also covers the rebuild path.server : do not cache-reuse on memories that cannot erase a mid-range— related hardening found while investigating. A recurrent state cannot represent a partial erase, butllama_memory_recurrent::seq_rmreturnedtruefor one while leaving the removed tokens in the state, so the--cache-reuseKV surgery appeared to succeed and stale recurrent state leaked into the new prompt positions on hybrid/recurrent models. This rejects the unrepresentable erase and gatescan_cache_reuseon the context being pure attention (seq_rm typePART). Happy to split this into a separate PR if preferred.Verification
Reproducer (AMD gfx1151 APU / Strix Halo, HIP, Qwen3.6-35B-A3B hybrid,
llama-server -b 2048 -ub 2048 -fa on -np 1, deterministic sampling): chat about a distinctive topic → send an unrelated ~21k-token document + questions. Before: 4/4 responses quote the chat verbatim, and per-cell K-cache hash probes show chat-era data scattered into the document's cell range while document chunks fail to land. After: 0/12 contaminated across 3 runs, and the probed cell shows the healthy trajectory (boot value through the chat era, document data lands at its chunk and stays put).Performance
Zero measured cost on the affected hardware — the async pipeline only queued deeper, the GPU was already saturated:
A conservative alternative would gate the sync on the integrated/zero-copy condition; unconditional measured free here and also covers any other configuration where graph inputs are device-visible without a copy.
🤖 Generated with Claude Code