Skip to content

Fix grouped MXFP8 dequantize race on concurrent CUDA streams - #3775

Closed
XFDG wants to merge 2 commits into
NVIDIA:mainfrom
XFDG:fix/3630-grouped-dequantize-concurrency
Closed

XFDG wants to merge 2 commits into
NVIDIA:mainfrom
XFDG:fix/3630-grouped-dequantize-concurrency

Conversation

@XFDG

@XFDG XFDG commented Oct 29, 2026

Copy link
Copy Markdown

Same file-global TMA-descriptor race as #3774, but in the grouped MXFP8 dequantize path. Allocate per-invocation CUtensorMap arrays with cudaMallocAsync and pass them to both the descriptor-update and consumer kernels, releasing with cudaFreeAsync. Independent of #3774 (no shared files), so the two PRs do not conflict. Grouped-quantize counterpart: #3774.

Grouped MXFP8 dequantization stored its per-tensor TMA descriptor storage in
file-global `static __device__` arrays (`g_tensor_maps_input` /
`g_tensor_maps_output`). Like the quantize path, that storage is shared by
every kernel launch across all CUDA streams, so concurrent `group_dequantize`
calls on different streams race on the descriptors: one stream's
`update_tma_descriptors` overwrites tensor maps another stream is still
consuming, producing incorrect output.

Allocate per-invocation `CUtensorMap` arrays with `cudaMallocAsync` and pass
them to both the descriptor-update and consumer kernels, then release them with
`cudaFreeAsync`. The storage stays in global memory (required for TMA to consume
the descriptors) but is disjoint across concurrent invocations; the
stream-ordered allocate/free keeps the descriptors alive until the kernels that
use them finish. This is the same fix as the quantize path in NVIDIA#3774, applied to
the sibling dequantize path, which had the identical file-global buffer.

Signed-off-by: zhaoye <772971548@qq.com>
@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Oct 29, 2026
XFDG added a commit to XFDG/TransformerEngine that referenced this pull request Oct 29, 2026
Split the grouped MXFP8 dequantize race fix into its own PR (NVIDIA#3775) so the
quantize and dequantize fixes can be reviewed independently. This reverts the
dequantize change from this branch; the quantize fix (grouped_tma.cuh +
group_quantize_mxfp8.cuh) and its regression test remain here.

Signed-off-by: zhaoye <772971548@qq.com>
@XFDG
XFDG marked this pull request as ready for review October 29, 2026 10:56
@XFDG
XFDG requested a review from Oleg-Goncharov as a code owner October 29, 2026 10:56
@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects grouped MXFP8 dequantization calls that use multiple CUDA streams.

The race fix appears safe to merge, with non-blocking cleanup and test improvements.

Findings

  1. P2 Failed allocation leaks memory ▶
  2. P2 Changed branch lacks regression tests ▶

Summary

Replaces shared MXFP8 dequantize descriptors with arrays owned by each call.

  • Grouped MXFP8 calls keep their tensor maps separate across streams.

Diagram

sequenceDiagram
  participant L as Launcher
  participant S as CUDA stream
  participant M as Private descriptor arrays
  L->>S: Allocate input and output arrays
  S->>M: Write descriptors
  S->>M: Read descriptors during dequantize
  S->>M: Free arrays after kernel completion
Loading

Reviews (1) · Last reviewed commit: "[pre-commit.ci] auto fixes from pre-comm..." · Reviewed by Greptile

Comment on lines +485 to +488
NVTE_CHECK_CUDA(
cudaMallocAsync(reinterpret_cast<void **>(&tensor_maps_input), tmap_bytes, stream));
NVTE_CHECK_CUDA(cudaMallocAsync(reinterpret_cast<void **>(&tensor_maps_output),
tmap_bytes, stream));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Failed allocation leaks memory

If the first cudaMallocAsync succeeds but the second runs out of memory, NVTE_CHECK_CUDA throws before either cudaFreeAsync runs. The raw tensor_maps_input pointer is then lost, leaving its device memory allocated. Use scope-based cleanup or one allocation for both arrays so a failed call releases what it already allocated.

Comment on lines 483 to +488
if (!is_single_tensor) {
const size_t tmap_bytes = MAX_SUPPORTED_TENSOR_DESCRIPTORS * sizeof(CUtensorMap);
NVTE_CHECK_CUDA(
cudaMallocAsync(reinterpret_cast<void **>(&tensor_maps_input), tmap_bytes, stream));
NVTE_CHECK_CUDA(cudaMallocAsync(reinterpret_cast<void **>(&tensor_maps_output),
tmap_bytes, stream));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Changed branch lacks regression tests

The new allocation path has no regression test in this PR. The existing test_group_dequantize_cudagraph_capturable uses equal shapes, which selects is_single_tensor and skips these allocations entirely. Add a test with different last dimensions that checks independent outputs on two concurrent CUDA streams. Also capture and replay that shape case to cover the new allocation path.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@Oleg-Goncharov

Copy link
Copy Markdown
Collaborator

This has the same issue as #3774.
As mentioned there, allocating and freeing internal descriptor storage on every invocation is not an approach we want to use in TE. Common code should not own this memory. Temporary storage should be provided by the caller and reused across invocations rather than going through cudaMallocAsync / cudaFreeAsync on every call.
The dequantize path should follow the same ownership model as the quantize path. We are already addressing this class of race in #3766 using caller-owned workspace storage.
I'm closing this PR for the same reason as #3774.

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

Labels

community-contribution PRs from external contributor outside the core maintainers, representing community-driven work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants