Fix SM120 FP8 output ordering before DelayedScaling amax update#3215
Fix SM120 FP8 output ordering before DelayedScaling amax update#3215AlbertYang514 wants to merge 3 commits into
Conversation
Signed-off-by: AlbertYang514 <201034045+AlbertYang514@users.noreply.github.com>
Signed-off-by: AlbertYang514 <201034045+AlbertYang514@users.noreply.github.com>
Greptile SummaryThis PR fixes a device-scope memory ordering race in the SM120 NVRTC-optimized
Confidence Score: 5/5Safe to merge; the kernel change is a single targeted fence with no effect on computed values or public APIs, and the test validates the specific failure mode. The production change is minimal — one __threadfence() call gated on an existing null-pointer check — and its placement (after all block-scope syncs, before the amax atomic) is correct. The validation and new regression test cover the specific failure scenario. The statically compiled fallback in transformer_engine/common/transpose/cast_transpose.cu (lines 203-209) has the same pre-fence pattern and would benefit from the same treatment if the RTC path is ever unavailable on SM120. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant K as cast_transpose kernel (SM120)
participant GM as GPU Global Memory
participant Atom as atomicMaxFloat (amax_ptr)
participant DGEMM as dgrad GEMM kernel
K->>GM: store output_c (rowwise FP8)
K->>GM: store output_t (columnwise FP8, via shmem)
Note over K: __syncthreads() — block-scope barrier only
Note over K: __threadfence() — device-scope fence (NEW)
K->>Atom: reduce_max + atomicMaxFloat(amax_ptr, amax)
DGEMM->>GM: read FP8 outputs (guaranteed visible after fence)
DGEMM->>Atom: read amax_ptr (for next-step scaling)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant K as cast_transpose kernel (SM120)
participant GM as GPU Global Memory
participant Atom as atomicMaxFloat (amax_ptr)
participant DGEMM as dgrad GEMM kernel
K->>GM: store output_c (rowwise FP8)
K->>GM: store output_t (columnwise FP8, via shmem)
Note over K: __syncthreads() — block-scope barrier only
Note over K: __threadfence() — device-scope fence (NEW)
K->>Atom: reduce_max + atomicMaxFloat(amax_ptr, amax)
DGEMM->>GM: read FP8 outputs (guaranteed visible after fence)
DGEMM->>Atom: read amax_ptr (for next-step scaling)
Reviews (2): Last reviewed commit: "test: relax SM120 dgrad norm bounds" | Re-trigger Greptile |
Signed-off-by: AlbertYang514 <201034045+AlbertYang514@users.noreply.github.com>
Summary
Add device-scope ordering between the FP8 output stores and the subsequent DelayedScaling amax update in the SM120 NVRTC optimized
cast_transposekernel.Problem
On SM120, a checkpointed DelayedScaling workload can intermittently corrupt the first FP8 dgrad GEMM after activation recomputation. The loss and gradients can remain finite, and the affected layer or microbatch can vary with execution timing, but the parameter gradient norm can be transiently far outside its normal range.
This change is scoped to that observed optimized RTC path. It does not claim to fix every FP8 gradient anomaly or to affect every GPU architecture.
Root cause
The optimized
cast_transposekernel writes both rowwise and columnwise FP8 outputs and then performs a relaxed atomic update of the DelayedScaling amax value. The atomic update does not by itself order the preceding ordinary global-memory output stores. Without a device-scope fence, a later dgrad consumer can intermittently observe incorrect FP8 output visibility even though a later execution with the same operands is normal.Fix
Insert a single device-scope fence after the output stores and before the amax reduction/atomic:
__threadfence();There is no host synchronization, stream synchronization, public API change, or change to the DelayedScaling math.
Reproducer
The new standalone Transformer Engine pytest uses official PyTorch APIs on SM120 with:
te.Linearmodules and DelayedScaling;The same test fails for all three seeds without the fence and passes for all three seeds with the fence.
Validation
_extra_stateround trips are exactly consistent where exact equality is required.Scope and risk
The production change is limited to the optimized RTC
cast_transposepath when an amax pointer is present. The fence has explicit device-scope ordering semantics and does not alter the values computed by the kernel or any API. The main risk is the localized cost of a device fence; control paths and the larger external workload remained stable in validation.Relationship to #3213
This issue is independent of #3213.
#3213 fixes duplicate global FP8 state-update ownership during activation checkpoint recomputation. This PR fixes missing device-scope ordering between FP8 output stores and the subsequent DelayedScaling amax atomic update in the SM120 NVRTC optimized cast-transpose kernel.
This PR is based directly on the current upstream main branch and does not depend on #3213. The two fixes were also validated together on the original external workload.
Documentation reference
CUDA documents that legacy atomic functions use relaxed memory ordering and do not act as memory fences, while
__threadfence()provides device-scope ordering: