Skip to content

ggml-cuda : deterministic radix top-k, honour GGML_PREC_F32 for F32 x F32 MUL_MAT - #292

Open
danielhanchen wants to merge 4 commits into
dsv41-slimfrom
dsv41-cuda-exact
Open

danielhanchen wants to merge 4 commits into
dsv41-slimfrom
dsv41-cuda-exact

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Makes the DeepSeek-V4.1 CUDA path deterministic and keeps F32 matmuls in F32 when the graph asks for it. Both changes are generic ggml-cuda fixes; DeepSeek-V4.1 is where they showed up.

Stacked on the minimal DeepSeek-V4.1 PR (#289, dsv41-slim). The diff is only this change: 3 files, +201/-26.

What it adds

  • Deterministic radix top-k (ggml/src/ggml-cuda/top-k.cu):
    • The gather placed elements with atomicAdd slot counters, so both the set of tied elements at the k-th key and the order of the selected indices changed from run to run. Exact ties are common (masked or ReLU-zeroed rows).
    • Each block now owns a contiguous column chunk. A count pass records the elements above and equal to the cut per chunk, and the gather pass writes them at fixed offsets. Each row holds the elements above the cut, then the ties with the lowest column indices, both in ascending column order. The shared atomic counter is gone.
  • GGML_PREC_F32 honoured for F32 x F32 MUL_MAT at every batch size (ggml/src/ggml-cuda/ggml-cuda.cu):
    • Before, only small batches ran in exact F32; medium batches used TF32 tensor cores (MMF) and large ones cuBLAS tensor-op math, so results depended on the batch size.
    • With F32 weights and a GGML_PREC_F32 request, the F32 matrix-vector kernel runs up to MMVF_MAX_BATCH_SIZE columns, else cuBLAS SGEMM with CUBLAS_DEFAULT_MATH (restored afterwards, as in solve_tri). Other paths are unchanged.
  • Tests (test-backend-ops): TOP_K cases with fewer than k positive values and the rest exactly 0.0; MUL_MAT prec-F32 cases at NMSE 1e-10 across the MMVF / MMF / cuBLAS ranges.

Verification

All results are local, on B200 (CUDA sm_100a) and x86 CPU.

  • Ops: test-backend-ops on CUDA: TOP_K 547/547, MUL_MAT 2465/2465. A tie stress test (64 rows of 300,001 columns, 0, 3 and 50 distinct levels) passes, and the tie order matches torch.topk (lowest index first).
  • Determinism, full model (2 x B200, the published UD-Q8_K_XL GGUF, against the official F32 reference): wiki1100 and long17k are bit-identical across 4 runs on 3 different card pairs, with mean KL 6.09e-3 and 3.26e-3. Without this PR, long17k landed in one of two outcomes across 6 runs.
  • Determinism, fixture: a 1,536-token prompt is bit-identical over 4 runs, and the 20,001-token prompt reruns bit for bit.
  • Fixture gates on CUDA against the official F32 reference: mean KL 8.5e-5 to 8.3e-4 with flash attention off and 8.0e-5 to 5.1e-4 with it on (prompts of 100 to 3,001 tokens, ubatch 512): PASS. The 2,001-token prompt of the long fixture gives 3.9e-4: PASS.
  • CPU: logits are bit-identical to the parent PR on every CPU fixture config (the change is CUDA-only).
  • Real-model spread: across ubatch sizes, full-model KL against the reference spans 4.8e-3 to 8.2e-3 with and without this PR (at ubatch 256: 6.52e-3 without, 6.36e-3 with). The parent's 4.76e-3 at ubatch 512 is one draw from that spread, caused by BF16 activation rounding flipping near-tied expert and candidate choices. On F32 fixtures this PR is closer to the reference in 6/6 cells.
  • Revision: the patches are unchanged from the previously gated revision; this revision only rebases onto the parent.

Known gaps

  • Long fixture on CUDA: the 20,001-token fixture prompt gives 1.74e-3 against the 1e-3 gate (flash attention off and on). The remainder comes from TF32 and other non-F32 paths outside this PR. The full-model numbers above are in the expected band.
  • Platforms: CUDA only (sm_100a). No ROCm, Metal, Vulkan, Windows or macOS validation.

… size

F32 x F32 MUL_MAT ran in exact F32 only for small batches; medium
batches used TF32 tensor cores (MMF) and large ones cuBLAS tensor-op
math, so results depended on the batch size even at GGML_PREC_F32.

When src1 is requested at GGML_PREC_F32 ([TAG_GGML_PREC]) and the
weights are F32, use the F32 matrix-vector kernel up to
MMVF_MAX_BATCH_SIZE columns, else cuBLAS SGEMM with CUBLAS_DEFAULT_MATH
(restored to CUBLAS_TF32_TENSOR_OP_MATH afterwards, as in solve_tri).
Other paths are unchanged.

test-backend-ops: MUL_MAT prec-F32 cases at NMSE 1e-10 across the
MMVF / MMF / cuBLAS ranges.
…put)

The radix top-k gather placed elements with atomicAdd slot counters, so
both the set of tied elements at the k-th key and the order of the
selected indices changed from run to run. Exact ties are common (masked
or ReLU-zeroed rows), and consumers that gather rows in top-k order then
accumulate in a different order every run.

Each block now owns a contiguous column chunk. A count pass records the
elements above and equal to the cut per chunk; the gather pass writes
them at the sum of the earlier chunks' counts plus an ordered in-block
prefix count. Each row holds the elements above the cut, then the ties
with the lowest column indices, both in ascending column order. The
shared atomic counter is gone.

test-backend-ops: TOP_K cases with fewer than k positive values and the
rest exactly 0.0 across the radix path.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T08:06:46.630694Z 8aef986 PR opened
🔒 Security Review ✅ Completed 2026-10-19T07:56:22.472807Z 8aef986 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8aef9863b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// top_k with fewer than k positive values and the rest exactly 0: the cut falls inside a long run of ties
struct test_top_k_zero_ties : public test_top_k {
test_top_k_zero_ties(std::array<int64_t, 4> ne, int k)
: test_top_k(GGML_TYPE_F32, ne, k, true) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Assert the deterministic tie indices

Passing ties=true reuses test_top_k::err, which explicitly ignores the selected indices and compares only their source values and uniqueness. Consequently, the previous atomic gather passes every new zero-tie case because any distinct zero-valued indices are accepted, so these tests do not detect either nondeterministic selection or failure to choose the lowest columns. Override the comparison to check the expected index set or repeat the operation and compare outputs.

Useful? React with 👍 / 👎.

Comment on lines +203 to +204
// above-cut elements are written in ascending column order and ties take the lowest columns first, so the selected
// set and its order are reproducible (exact ties are common, e.g. masked or ReLU-zeroed rows)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the explanatory comment on one line

This comment splits one sentence solely to hard-wrap it across source lines; keep the sentence on one line to follow the repository's explicit comment-formatting rule.

AGENTS.md reference: AGENTS.md:L84-L84

Useful? React with 👍 / 👎.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant