[BUG] SM90 cp.async + GMMA collectives: no fence.proxy.async between cp.async operand writes and wgmma reads
Maintainers usually reply within 4 days
@yunweili3 is already working on this.
Since Oct 8, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 42/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- cpp
- Domain
- performance
Research direction
Start with include/cutlass/gemm/collective/sm90_mma_multistage_gmma_ss_warpspecialized.hpp, tracing the producer’s cp.async and clear() writes through barrier arrival to the consumer’s wgmma reads. Check the PTX ordering requirements cited in the report and compare the fence placement in sm90_wgmma_transpose.hpp and sm90_epilogue_tma_warpspecialized.hpp. Done means the operand writes are correctly ordered before wgmma reads, including residue tiles; the report notes the behavior has not been tested on hardware.
Written by the indexing model from the issue text.
Description
Which component has the problem?
CUTLASS C++
Bug Report
Describe the bug
Summary. The SM90 cp.async + GMMA warp-specialized collective writes its shared-memory operands through the generic proxy and has wgmma.mma_async read them through the async proxy, with no fence.proxy.async on the path. Without the fence, PTX allows the wgmma to read operand bytes not yet visible to the async proxy, giving a silently wrong GEMM result; we have not run this on hardware, and current hardware may well make completed cp.async writes visible to the async proxy, though PTX does not state that. The fence can go on the producer side or the consumer side; see Options.
Cause
The collective include/cutlass/gemm/collective/sm90_mma_multistage_gmma_ss_warpspecialized.hpp (main 0b55a2f; unchanged since v4.4.0):
- fills the shared-memory operands with non-bulk
cp.async, and withclear()for residue K-tiles (lines 274 and 284); - publishes them with
producer_commit(..., cutlass::arch::cpasync_barrier_arrive)(291, 310); - then the consumer does
consumer_wait(411, 436, 460),warpgroup_arrive(), andwgmma.mma_async, which reads those operands.
PTX ISA 9.4, §9.7.17.7.1 (the wgmma.fence page): "An async proxy fence must be used to establish an ordering between prior writes to shared memory matrices and subsequent reads of the same matrices in a wgmma.mma_async instruction." Several facts mean nothing else on the path supplies that fence:
- Non-bulk
cp.asyncwrites are generic-proxy operations (§9.7.10.28.3.1). wgmmareads its shared operands through the async proxy (§9.7.17.4).warpgroup_arrive()(wgmma.fence) orders register accesses.- The implicit completion fence of §9.7.10.28.2 covers
cp{.reduce}.async.bulkonly. - The
cp.async.mbarrier.arrivearrive-on is ordered only with the thread's priorcp.async(§8.9.1.1), so it does not cover theclear()stores either.
Options
- Producer side: each producer thread waits for its own
cp.asyncto complete, executescutlass::arch::fence_view_async_shared(), then arrives on the barrier. The fence is executed by the writing thread, as in CUTLASS's ownsm90_wgmma_transpose.hpp(lines 233–243) and the TMA epilogue (sm90_epilogue_tma_warpspecialized.hpp:736–737). It gives up thecp.async.mbarrier.arriveoverlap; we have not measured the cost. - Consumer side: one
fence_view_async_shared()after eachconsumer_wait, beforewarpgroup_arrive(). This is cheaper, but it relies on a fence by a thread that only observed the writes. PTX says bi-directional proxy fences "take effect within a single thread" and that their effect "composes with other forms of synchronization according to the rules of the Memory Consistency Model" (§9.7.15.4); it does not state that this covers other threads' writes.
Scope
- Not observed on hardware: we have not run this on hardware, and current hardware may well make completed
cp.asyncwrites visible to the async proxy. PTX does not state that, though. - For comparison, the Triton 3.5.0 and 3.7.0 PTX we compiled for an ordinary
tl.dotmatmul also had no fence betweencp.asyncandwgmma; Triton 3.8.0 adds one.
Why there is no PR
Which placement is right is your call, given the producer side's cost and the consumer side's reliance on unstated composition. Tell us which you want and we'll write the PR.
Steps/Code to reproduce bug
We found this by checking compiled PTX against the PTX memory model with our own PTX-level checker. This instance (fp16, 64×64×64 tile, K = 320, one 384-thread CTA) was compiled with nvcc 12.6 for sm_90a:
#include "cutlass/cutlass.h"
#include "cutlass/gemm/collective/collective_builder.hpp"
#include "cutlass/epilogue/collective/collective_builder.hpp"
#include "cutlass/gemm/kernel/gemm_universal.hpp"
using namespace cute;
using BH12Tile = Shape<_64,_64,_64>;
using Cluster = Shape<_1,_1,_1>;
using H = cutlass::half_t;
using Main = cutlass::gemm::collective::CollectiveBuilder<
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
H,cutlass::layout::RowMajor,8,H,cutlass::layout::ColumnMajor,8,
float,BH12Tile,Cluster,cutlass::gemm::collective::StageCount<2>,
cutlass::gemm::KernelCpAsyncWarpSpecialized>::CollectiveOp;
using Epi = cutlass::epilogue::collective::CollectiveBuilder<
cutlass::arch::Sm90,cutlass::arch::OpClassTensorOp,BH12Tile,Cluster,
cutlass::epilogue::collective::EpilogueTileAuto,float,float,
void,cutlass::layout::RowMajor,8,H,cutlass::layout::RowMajor,8,
cutlass::epilogue::NoSmemWarpSpecialized>::CollectiveOp;
using Kernel = cutlass::gemm::kernel::GemmUniversal<Shape<int,int,int,int>,Main,Epi>;
extern "C" __global__ __launch_bounds__(Kernel::MaxThreadsPerBlock)
void bh12_cpasync_gmma(H const* A,H const* B,H* D) {
__shared__ __align__(128) char smem[Kernel::SharedStorageSize];
Kernel::Params p{};
p.mode = cutlass::gemm::GemmUniversalMode::kGemm;
p.problem_shape = make_shape(64,64,320,1);
p.mainloop.ptr_A=A; p.mainloop.ptr_B=B;
p.mainloop.dA = make_stride(int64_t(320),_1{},int64_t(0));
p.mainloop.dB = make_stride(int64_t(320),_1{},int64_t(0));
p.epilogue.thread = typename Epi::ThreadEpilogueOp::Params(1.0f);
p.epilogue.ptr_D=D;
p.epilogue.dD=make_stride(int64_t(64),_1{},int64_t(0));
Kernel{}(p,smem);
}
Its PTX has 8 cp.async and 8 wgmma.mma_async instructions and no fence.proxy (grep -c fence.proxy gives 0). Our checker finds the 16-byte cp.async that writes shared bytes [0, 16) unordered with the warpgroup's wgmma read of those bytes (it attributes the read to thread 256). The clear() path is from source reading; this instance has no residue.
Expected behavior
A fence.proxy.async.shared::cta orders the operand writes before the wgmma reads.
Environment details
Source and PTX analysis; no runtime environment involved. Hopper (sm_90a).
Additional context
The _rs_ variant (sm90_mma_multistage_gmma_rs_warpspecialized.hpp) has the same pattern for the B operand, by source reading. There, A goes through ldmatrix into registers, and its TransposeB path already fences.
- Dominant language
- C++
- Stars
- 10.7k
- Forks
- 2.2k
- Avg merge
- 3d 20h
- Merged PRs (30d)
- 4
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from NVIDIA/cutlass
-
[Bug][CuTeDSL][Fuzz] `utils.gemm.sm100.epilogue` releases the accumulator before `tcgen05.ld` completes: no `tcgen05.wait::ld` before the arrive that lets the next MMA overwrite TMEMPossibly taken @0z5a claimed this 3 days ago. Openbug CuTe DSL
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 4 days
-
? - Needs Triage question
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
NVIDIA/cutlass#3796 · 1 comment · 1 reaction ·
Maintainers usually reply within 4 days
-
[DOC] IKET quick start omits the GEMM GPU requirement and references a missing examplePossibly taken @0z5a claimed this 13 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 82/100
NVIDIA/cutlass#3761 · 1 comment ·
Maintainers usually reply within 4 days
-
[BUG] CuTe DSL from_dlpack rejects float8_e4m3fn tensors (dtype_map lists Float8E5M2 only)Possibly taken @Ammar-Alnagar claimed this 22 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
NVIDIA/cutlass#3716 · 1 comment ·
Maintainers usually reply within 4 days
-
EllGemm ColumnMajor-output workspace sizing uses the unswapped problem shape and undersizes split-K semaphoresPossibly taken @XFDG claimed this 51 days ago. OpenCUTLASS C++
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
NVIDIA/cutlass#3539 · 2 comments ·
Maintainers usually reply within 4 days
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
NVIDIA-RTX/NRI#307 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
eclipse-score/lifecycle#929 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day