Skip to content

Fix non-deterministic results in k_grouped_fp8_gemm_nt_contiguous - #375

Open
Functionhx wants to merge 1 commit into
deepseek-ai:mainfrom
Functionhx:fix/fp8-gemm-nondeterministic
Open

Fix non-deterministic results in k_grouped_fp8_gemm_nt_contiguous#375
Functionhx wants to merge 1 commit into
deepseek-ai:mainfrom
Functionhx:fix/fp8-gemm-nondeterministic

Conversation

@Functionhx

Copy link
Copy Markdown

Summary

Fixes #365: non-deterministic numerical results in FP8 grouped GEMM when one expert has exactly 128 tokens.

Root Cause

Race condition in sm90_fp8_gemm_1d1d.cuh: tensormap.replace operations and in-flight TMA data loads were in the same cp.async.bulk commit group. Per PTX ISA, operations within a commit group may complete in any order, allowing descriptor modifications to become visible before pending TMA reads complete.

Fix

Restructure group-switch into three phases:

  1. Drain: commit_group + wait_group all pending TMA data operations
  2. Modify + Fence: issue tensormap.replace, then commit_group + wait_group in a separate group
  3. Publish: copy updated descriptors to global memory, release_gpu + acquire_gpu

…iguous

The SM90 FP8 1D1D kernel updates TMA tensor map descriptors in-place
during k-grouped GEMM group switches. The original code issued
tensormap.replace operations before draining in-flight TMA data loads,
committing both into the same cp.async.bulk commit group. Per the PTX
ISA, operations within a commit group may complete in any order, which
allows the tensor map descriptor modifications to become visible before
pending TMA data loads complete.

This race manifests as non-deterministic numerical errors when one
expert has exactly 128 tokens (k=BLOCK_K, a single k-block) and a column
of the activation tensor is all zeros. The shorter pipeline depth with
k=128 exposes the race, while the all-zero column with a non-negligible
scale factor (e.g., 1.0) makes wrong reads visible.

Fix: drain all pending TMA data operations before modifying tensor map
descriptors, then fence the modifications in a separate commit group.
This ensures clean separation between data operations and descriptor
updates.

Fixes deepseek-ai#365

Test Plan:
  Reproduced the non-determinism with the reproducer from deepseek-ai#365 on a
  GH200/H100 GPU and confirmed the fix eliminates all failures across
  10,000 iterations for all scale factor values: 1, 1e-4, 1e-8, 1e-30.

Signed-off-by: Yuchen Fan <functionhx@gmail.com>
@@ -191,26 +191,29 @@ sm90_fp8_gemm_1d1d_impl(__nv_fp8_e4m3* gmem_a_ptr, __nv_fp8_e4m3* gmem_b_ptr,
if (kGemmType == GemmType::KGroupedContiguous and last_group_idx != scheduler.current_group_idx) {
last_group_idx = scheduler.current_group_idx;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔵 suggestion: Root-cause wording is slightly imprecise. tensormap.replace is not a cp.async.bulk operation and is not part of any commit group, so the description's claim that replace and TMA loads 'compete within the same commit group' is not technically accurate. The real hazard is that the #343 fix placed the drain AFTER the replace, so the replace could run while a prior TMA descriptor read was still in flight (a missing happens-before). The fix itself is correct; please tighten the commit-message/comment framing to avoid future confusion.

🤖 v3

// compete within the same commit group, which the PTX ISA allows to resolve in any order.
cute::tma_desc_commit_group();
cute::tma_desc_wait_group();
__syncwarp(1U << lane_idx);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔵 suggestion: __syncwarp(1U << lane_idx) uses a single-lane mask, which is intentional here since this block runs under elect_one_sync() (only one lane active) and the __syncwarp acts purely as a compiler-ordering barrier. This diff removed the previous explanatory comment ('Only used to prevent ptxas from moving ... Shouldn't be needed otherwise, since we only use one thread'). Consider restoring a one-line comment on the single-lane __syncwarp so future readers don't mistake the mask for a bug.

🤖 v3

@ds-review-bot

Copy link
Copy Markdown
Collaborator

🤖 ds-review-bot Code Review

v4

⚠️ 未完成评审(no_result_file:模型未产出结果文件)

v3

This PR restructures the k-grouped GEMM group-switch tensor-map update in deep_gemm/include/deep_gemm/impls/sm90_fp8_gemm_1d1d.cuh into three phases: (1) drain all pending TMA data loads via tma_desc_commit_group + tma_desc_wait_group BEFORE any tensormap.replace, (2) issue the tensormap.replace operations then fence them with a second commit/wait group, and (3) copy the updated descriptors to global memory with release_gpu/acquire_gpu. Compared to the previous fix (#343), the only functional addition is the drain that now precedes the replace calls, and it is correct: cp.async.bulk.wait_group.read 0 (emitted by tma_desc_wait_group) waits for prior in-flight TMA tensor loads, establishing a happens-before so those loads complete before descriptor bytes are modified. The three-phase structure mirrors CUTLASS's tensormaps_perform_update / tensormaps_cp_fence_release / tensormaps_fence_acquire pattern (deep_gemm adds an explicit pre-drain where CUTLASS relies on producer_acquire/load_tail). The second commit/wait group after the replace is retained from #343 and correctly fences the modifications before the smem->gmem copy; the first commit_group may produce a harmless empty bulk-async group. Performance impact is negligible since group switches occur once per expert, not in the k-loop. Verdict: LGTM, sound and minimal hardening of a real race, with only minor documentation suggestions below.

Files reviewed: 1
Issues found: 🔵 2 suggestion
Inline comments posted: 2

⚠️ Parse warning: [v4] no_result_file:模型未产出结果文件

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.

[BUG] Non-deterministic numerical problem of k_grouped_fp8_gemm_nt_contiguous

2 participants