Skip to content

cuda: keep the GDN state gather when its ids memory is reused - #318

Merged
khosravipasha merged 1 commit into
prismfrom
fix/cuda-gdn-gather-ids-lifetime
Oct 5, 2026
Merged

khosravipasha merged 1 commit into
prismfrom
fix/cuda-gdn-gather-ids-lifetime

Conversation

@bri-prism

Copy link
Copy Markdown
Collaborator

Overview

The CUDA GDN gather fusion (ggml_cuda_try_gdn_gather_skip, added in #221) skips the recurrent-state GET_ROWS and lets the GDN kernel read the cache row through the ids tensor. ggml-alloc does not know about this deferred read: for the allocator the skipped GET_ROWS is the last reader of ids, so it can give the ids memory to a node that runs before the GDN op. The kernel then reads activation data as a slot index and crashes with an illegal memory access was encountered.

This PR keeps the fusion, but falls back to the normal GET_ROWS for a layer when:

  • any node from the GET_ROWS up to and including the GDN output overlaps the ids memory, or
  • the GDN op runs on a concurrent stream, where node order is not execution order.

Other layers keep the fused path.

Additional information

Seen on an A100 with a hybrid GDN vision-language model: a prompt with two images crashed llama-mtmd-cli and killed llama-server on the first request. With a debug build, the overlap was the last GDN layer's alpha projection, which the allocator placed on the scheduler's device copy of inp_s_copy. The faulting index decoded to the float bit pattern of a gate value. Other models with different tensor sizes did not hit the overlap. That was layout luck, so the check does not depend on the model.

Validation (A100-SXM4-80GB, CUDA 12.4):

  • The crashing case now completes in Q2_0, PQ2_0 and PTQ1_0. A second model passes with 1, 2 and 3 images in all three formats.
  • llama-server handles three consecutive two-image requests (HTTP 200) and stays up. Before this change the process died on the first request.
  • compute-sanitizer --tool memcheck: 0 errors. Before: invalid global read in gated_delta_net_cuda.
  • Greedy output is byte-identical to GGML_CUDA_GDN_GATHER_FUSION=0 (300 tokens).
  • Text-only llama-bench pp512 / tg128 on two models, 5 reps, arms interleaved twice: all differences are within one standard deviation.

Not tested: other GPUs, release binaries, and the SYCL fused GDN path.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES. Claude found the cause, wrote the change and ran the validation; I reviewed it.

The gather fusion skips the GET_ROWS and reads ids later in the GDN kernel, but ggml-alloc can give the ids memory to a node that runs in between. Keep the GET_ROWS when a node up to the GDN output overlaps ids, or when the GDN op runs on a concurrent stream.
@khosravipasha
khosravipasha merged commit 8e18ce3 into prism Oct 5, 2026
9 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The overlap check ignores writable backend allocation space beyond a tensor's logical size and lacks regression coverage.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Prevents CUDA GDN gather fusion from reading reused ids memory.

Changes:

  • Detects overlapping tensor memory before GDN execution.
  • Disables fusion when GDN runs concurrently.
File Description
ggml/​src/​ggml-cuda/​ggml-cuda.cu Adds safety checks for deferred GDN gather reads.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// ggml-alloc sees the GET_ROWS as the last reader of ids, so a later node can reuse ids memory before the GDN kernel reads it
const char * ids_beg = (const char *) ids->data;
const char * ids_end = ids_beg + ggml_nbytes(ids);
for (int k = node_idx + 1; k <= j; ++k) {
Comment on lines +2963 to +2971
const char * ids_beg = (const char *) ids->data;
const char * ids_end = ids_beg + ggml_nbytes(ids);
for (int k = node_idx + 1; k <= j; ++k) {
const ggml_tensor * t = cgraph->nodes[k];
const char * b = (const char *) t->data;
if (b != nullptr && b < ids_end && ids_beg < b + ggml_nbytes(t)) {
return false;
}
}
imaami pushed a commit to imaami/llama.cpp that referenced this pull request Oct 13, 2026
Add the planar and SoA activation layouts, dedicated PTQ1 mat-vec, native q4_0/q8_0 KV reads and fused Hadamard quantization from Cary Palmer and sudoingX. Fold recurrent-state gathers only while their row IDs remain live. Keep batch-invariant dispatch, slot-stride bounds, 64-lane quantization, pool lifetimes and HIP/MUSA fallbacks in the same integration.

Source: PrismML-Eng#221
Source: PrismML-Eng#218
Source: PrismML-Eng#215
Source: PrismML-Eng#258
Source: PrismML-Eng#303
Source: PrismML-Eng#318

Source commits (PrismML-Eng/llama.cpp):
5244cea
88c4bc6
2459f68
8e18ce3

Co-authored-by: sudoingX <200180104+sudoingX@users.noreply.github.com>
Co-authored-by: bri-prism <288398250+bri-prism@users.noreply.github.com>
Assisted-by: GitHub Copilot
Assisted-by: OpenAI Codex

Ported and rebased by Codex.
imaami pushed a commit to imaami/llama.cpp that referenced this pull request Oct 13, 2026
Add the planar and SoA activation layouts, dedicated PTQ1 mat-vec, native q4_0/q8_0 KV reads and fused Hadamard quantization from Cary Palmer and sudoingX. Fold recurrent-state gathers only while their row IDs remain live. Honor batch-invariant dispatch and slot-stride bounds, use 64-lane quantization, and preserve pool lifetimes and HIP/MUSA fallbacks.

Source: PrismML-Eng#221
Source: PrismML-Eng#218
Source: PrismML-Eng#215
Source: PrismML-Eng#258
Source: PrismML-Eng#303
Source: PrismML-Eng#318

Source commits (PrismML-Eng/llama.cpp):
5244cea
88c4bc6
2459f68
8e18ce3

Co-authored-by: sudoingX <200180104+sudoingX@users.noreply.github.com>
Co-authored-by: bri-prism <288398250+bri-prism@users.noreply.github.com>
Assisted-by: GitHub Copilot
Assisted-by: OpenAI Codex

Ported and rebased by Codex.
imaami pushed a commit to imaami/llama.cpp that referenced this pull request Oct 13, 2026
Add the planar and SoA activation layouts, dedicated PTQ1 mat-vec, native q4_0/q8_0 KV reads and fused Hadamard quantization from Cary Palmer and sudoingX. Fold recurrent-state gathers only while their row IDs remain live. Honor batch-invariant dispatch and slot-stride bounds, use 64-lane quantization, and preserve pool lifetimes and HIP/MUSA fallbacks.

Source: PrismML-Eng#221
Source: PrismML-Eng#218
Source: PrismML-Eng#215
Source: PrismML-Eng#258
Source: PrismML-Eng#303
Source: PrismML-Eng#318

Source commits (PrismML-Eng/llama.cpp):
5244cea
88c4bc6
2459f68
8e18ce3

Co-authored-by: sudoingX <200180104+sudoingX@users.noreply.github.com>
Co-authored-by: bri-prism <288398250+bri-prism@users.noreply.github.com>
Assisted-by: GitHub Copilot
Assisted-by: OpenAI Codex

Ported and rebased by Codex.
imaami pushed a commit to imaami/llama.cpp that referenced this pull request Oct 13, 2026
Add the planar and SoA activation layouts, dedicated PTQ1 mat-vec, native q4_0/q8_0 KV reads and fused Hadamard quantization from Cary Palmer and sudoingX. Fold recurrent-state gathers only while their row IDs remain live. Honor batch-invariant dispatch and slot-stride bounds, use 64-lane quantization, and preserve pool lifetimes and HIP/MUSA fallbacks.

Source: PrismML-Eng#221
Source: PrismML-Eng#218
Source: PrismML-Eng#215
Source: PrismML-Eng#258
Source: PrismML-Eng#303
Source: PrismML-Eng#318

Source commits (PrismML-Eng/llama.cpp):
5244cea
88c4bc6
2459f68
8e18ce3

Co-authored-by: sudoingX <200180104+sudoingX@users.noreply.github.com>
Co-authored-by: bri-prism <288398250+bri-prism@users.noreply.github.com>
Assisted-by: GitHub Copilot
Assisted-by: OpenAI Codex

Ported and rebased by Codex.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants