@@ -195,6 +195,103 @@ constexpr int RM_FRAGS_N = RM_WN / RM_WMMA_N; // 2
195195constexpr int RM_LDA_SMEM = RM_BK + 8 ; // 40, for A tile (BM rows, BK cols)
196196constexpr int RM_LDB_SMEM = RM_BN + 8 ; // 72, for B tile (BK rows, BN cols)
197197
198+ // One thread's share of a K-slab, as int4s (8 x 16-bit each).
199+ constexpr int RM_A_SLAB_REGS = (RM_BM * RM_BK / 8 ) / RM_THREADS_PER_CTA ; // 2
200+ constexpr int RM_B_SLAB_REGS = (RM_BK * RM_BN / 8 ) / RM_THREADS_PER_CTA ; // 2
201+
202+ // Register-staged software prefetch — same latency-hiding transform as
203+ // fp16_matmul.cu's ABT kernel. The single-buffered loop stalled on each slab's
204+ // global load between the two __syncthreads; here the next slab's GLOBAL reads
205+ // are issued (into registers) *before* the WMMA compute so HBM latency overlaps
206+ // the tensor-core math, and the fast register->shared deposit happens at the top
207+ // of the next iteration. Shared memory stays single-buffered (occupancy is
208+ // unchanged) and the mma_sync order is identical, so results are bit-for-bit the
209+ // same. A tile is (BM,BK) accessed along K (identical to ABT's A); B tile is
210+ // (BK,BN) accessed along N — the natural (K,N) row-major layout, no transpose.
211+ template <typename T>
212+ __device__ __forceinline__ void rm_prefetch_A (
213+ int4 (&a_reg)[RM_A_SLAB_REGS], const T* __restrict__ A,
214+ int M, int K, int k0, int block_m, int tid) {
215+ using TR = rm_traits<T>;
216+ constexpr int kEPL = 8 ;
217+ #pragma unroll
218+ for (int li = 0 ; li < RM_A_SLAB_REGS ; ++li) {
219+ const int lin = tid + li * RM_THREADS_PER_CTA ;
220+ const int row = lin / (RM_BK / kEPL );
221+ const int gcol = (lin % (RM_BK / kEPL )) * kEPL ;
222+ const int grow = block_m + row;
223+ const int gk = k0 + gcol;
224+ T tmp[kEPL ];
225+ if (grow < M && gk + kEPL <= K) {
226+ *reinterpret_cast <int4 *>(tmp) =
227+ *reinterpret_cast <const int4 *>(&A[grow * K + gk]);
228+ } else {
229+ #pragma unroll
230+ for (int q = 0 ; q < kEPL ; ++q) {
231+ const int gk_q = gk + q;
232+ tmp[q] = (grow < M && gk_q < K) ? A[grow * K + gk_q]
233+ : TR::from_f32 (0 .0f );
234+ }
235+ }
236+ a_reg[li] = *reinterpret_cast <int4 *>(tmp);
237+ }
238+ }
239+
240+ template <typename T>
241+ __device__ __forceinline__ void rm_prefetch_B (
242+ int4 (&b_reg)[RM_B_SLAB_REGS], const T* __restrict__ B,
243+ int N, int K, int k0, int block_n, int tid) {
244+ using TR = rm_traits<T>;
245+ constexpr int kEPL = 8 ;
246+ #pragma unroll
247+ for (int li = 0 ; li < RM_B_SLAB_REGS ; ++li) {
248+ const int lin = tid + li * RM_THREADS_PER_CTA ;
249+ const int row = lin / (RM_BN / kEPL ); // K-tile row
250+ const int gcol = (lin % (RM_BN / kEPL )) * kEPL ; // N-tile col
251+ const int gk = k0 + row;
252+ const int gn = block_n + gcol;
253+ T tmp[kEPL ];
254+ if (gk < K && gn + kEPL <= N) {
255+ *reinterpret_cast <int4 *>(tmp) =
256+ *reinterpret_cast <const int4 *>(&B[size_t (gk) * N + gn]);
257+ } else {
258+ #pragma unroll
259+ for (int q = 0 ; q < kEPL ; ++q) {
260+ const int gn_q = gn + q;
261+ tmp[q] = (gk < K && gn_q < N) ? B[size_t (gk) * N + gn_q]
262+ : TR::from_f32 (0 .0f );
263+ }
264+ }
265+ b_reg[li] = *reinterpret_cast <int4 *>(tmp);
266+ }
267+ }
268+
269+ template <typename T>
270+ __device__ __forceinline__ void rm_deposit_A (
271+ const int4 (&a_reg)[RM_A_SLAB_REGS], T As[RM_BM][RM_LDA_SMEM], int tid) {
272+ constexpr int kEPL = 8 ;
273+ #pragma unroll
274+ for (int li = 0 ; li < RM_A_SLAB_REGS ; ++li) {
275+ const int lin = tid + li * RM_THREADS_PER_CTA ;
276+ const int row = lin / (RM_BK / kEPL );
277+ const int gcol = (lin % (RM_BK / kEPL )) * kEPL ;
278+ *reinterpret_cast <int4 *>(&As[row][gcol]) = a_reg[li];
279+ }
280+ }
281+
282+ template <typename T>
283+ __device__ __forceinline__ void rm_deposit_B (
284+ const int4 (&b_reg)[RM_B_SLAB_REGS], T Bs[RM_BK][RM_LDB_SMEM], int tid) {
285+ constexpr int kEPL = 8 ;
286+ #pragma unroll
287+ for (int li = 0 ; li < RM_B_SLAB_REGS ; ++li) {
288+ const int lin = tid + li * RM_THREADS_PER_CTA ;
289+ const int row = lin / (RM_BN / kEPL );
290+ const int gcol = (lin % (RM_BN / kEPL )) * kEPL ;
291+ *reinterpret_cast <int4 *>(&Bs[row][gcol]) = b_reg[li];
292+ }
293+ }
294+
198295template <typename T>
199296__launch_bounds__ (RM_THREADS_PER_CTA )
200297__global__ void matmul_rm_wmma_kernel(const T* __restrict__ A,
@@ -223,72 +320,28 @@ __global__ void matmul_rm_wmma_kernel(const T* __restrict__ A,
223320 }
224321 }
225322
226- for (int k0 = 0 ; k0 < K; k0 += RM_BK ) {
227- // ---- Load A tile (RM_BM x RM_BK): natural row-major (M,K) access ----
228- {
229- constexpr int kElemsPerLoad = 8 ; // int4 = 8 x 16-bit
230- constexpr int kTotalElems = RM_BM * RM_BK ;
231- constexpr int kLoadsTotal = kTotalElems / kElemsPerLoad ;
232- constexpr int kLoadsPerThr = kLoadsTotal / RM_THREADS_PER_CTA ;
233-
234- #pragma unroll
235- for (int li = 0 ; li < kLoadsPerThr ; ++li) {
236- const int lin = tid + li * RM_THREADS_PER_CTA ;
237- const int row = lin / (RM_BK / kElemsPerLoad );
238- const int col_grp = lin % (RM_BK / kElemsPerLoad );
239- const int gcol = col_grp * kElemsPerLoad ;
240- const int grow = block_m + row;
241- const int gk = k0 + gcol;
242-
243- T tmp[kElemsPerLoad ];
244- if (grow < M && gk + kElemsPerLoad <= K) {
245- const int4 * src = reinterpret_cast <const int4 *>(&A[grow * K + gk]);
246- *reinterpret_cast <int4 *>(tmp) = *src;
247- } else {
248- #pragma unroll
249- for (int q = 0 ; q < kElemsPerLoad ; ++q) {
250- const int gk_q = gk + q;
251- tmp[q] = (grow < M && gk_q < K) ? A[grow * K + gk_q] : TR::from_f32 (0 .0f );
252- }
253- }
254- *reinterpret_cast <int4 *>(&As[row][gcol]) = *reinterpret_cast <int4 *>(tmp);
255- }
256- }
323+ // K loop with register-staged prefetch (see rm_prefetch_* above): the next
324+ // slab's global reads are hoisted ahead of the WMMA math so HBM latency
325+ // overlaps the tensor cores. Shared memory is single-buffered; the mma_sync
326+ // order is unchanged, so FP32 accumulation is bit-for-bit identical.
327+ int4 a_reg[RM_A_SLAB_REGS ];
328+ int4 b_reg[RM_B_SLAB_REGS ];
329+ rm_prefetch_A<T>(a_reg, A, M, K, 0 , block_m, tid);
330+ rm_prefetch_B<T>(b_reg, B, N, K, 0 , block_n, tid);
257331
258- // ---- Load B tile (RM_BK x RM_BN): natural row-major (K,N) access,
259- // no transpose — this is the key difference from the ABT kernel. ----
260- {
261- constexpr int kElemsPerLoad = 8 ;
262- constexpr int kTotalElems = RM_BK * RM_BN ;
263- constexpr int kLoadsTotal = kTotalElems / kElemsPerLoad ;
264- constexpr int kLoadsPerThr = kLoadsTotal / RM_THREADS_PER_CTA ;
332+ for (int k0 = 0 ; k0 < K; k0 += RM_BK ) {
333+ // Deposit the slab prefetched last iteration, then publish it.
334+ rm_deposit_A<T>(a_reg, As, tid);
335+ rm_deposit_B<T>(b_reg, Bs, tid);
336+ __syncthreads ();
265337
266- #pragma unroll
267- for (int li = 0 ; li < kLoadsPerThr ; ++li) {
268- const int lin = tid + li * RM_THREADS_PER_CTA ;
269- const int row = lin / (RM_BN / kElemsPerLoad ); // K-tile row
270- const int col_grp = lin % (RM_BN / kElemsPerLoad );
271- const int gcol = col_grp * kElemsPerLoad ; // N-tile col
272- const int gk = k0 + row;
273- const int gn = block_n + gcol;
274-
275- T tmp[kElemsPerLoad ];
276- if (gk < K && gn + kElemsPerLoad <= N) {
277- const int4 * src = reinterpret_cast <const int4 *>(&B[size_t (gk) * N + gn]);
278- *reinterpret_cast <int4 *>(tmp) = *src;
279- } else {
280- #pragma unroll
281- for (int q = 0 ; q < kElemsPerLoad ; ++q) {
282- const int gn_q = gn + q;
283- tmp[q] = (gk < K && gn_q < N) ? B[size_t (gk) * N + gn_q] : TR::from_f32 (0 .0f );
284- }
285- }
286- *reinterpret_cast <int4 *>(&Bs[row][gcol]) = *reinterpret_cast <int4 *>(tmp);
287- }
338+ // Issue the next slab's global loads BEFORE compute so they overlap.
339+ const int k_next = k0 + RM_BK ;
340+ if (k_next < K) {
341+ rm_prefetch_A<T>(a_reg, A, M, K, k_next, block_m, tid);
342+ rm_prefetch_B<T>(b_reg, B, N, K, k_next, block_n, tid);
288343 }
289344
290- __syncthreads ();
291-
292345 // ---- Compute on shared mem tiles ----
293346 // A frag: row_major from As, leading dim RM_LDA_SMEM, sub-tile at
294347 // (warp_m*WM+i*WMMA_M, kk).
0 commit comments