Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
dmma_kernel.h
Go to the documentation of this file.
1#ifndef __MATH_DMMA_KERNEL_H__
2#define __MATH_DMMA_KERNEL_H__
3/*
4 Copyright (c) 2026, The Neko Authors
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10
11 * Redistributions of source code must retain the above copyright
12 notice, this list of conditions and the following disclaimer.
13
14 * Redistributions in binary form must reproduce the above
15 copyright notice, this list of conditions and the following
16 disclaimer in the documentation and/or other materials provided
17 with the distribution.
18
19 * Neither the name of the authors nor the names of its
20 contributors may be used to endorse or promote products derived
21 from this software without specific prior written permission.
22
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 POSSIBILITY OF SUCH DAMAGE.
35*/
36
99#include <stdlib.h>
100#include <stdio.h>
101#include <cuda_runtime.h>
102#include <mma.h>
103#include <device/device_config.h>
104
105enum {
106 DMMA_P = 8, /* Tile M and N, and the padded cube extent */
107 DMMA_KS = 4, /* Tile K */
108 DMMA_SI = DMMA_P, /* Padded cube stride between j */
109 DMMA_SJ = DMMA_P * DMMA_P, /* Padded cube stride between k */
112 DMMA_NTILES = DMMA_P, /* Tiles (or slabs) per contraction */
115
116/*
117 * Warps per block for the DMMA kernels. Candidate C selects 2^(C+1) warps,
118 * which stripe the DMMA_NTILES tiles of every contraction among themselves;
119 * at the top candidate each warp owns exactly one tile. Picked between at
120 * runtime by the operator's autotuner, like the elements per block candidates
121 * of the kstep variants.
122 */
123#define NEKO_DMMA_CANDIDATES 3
124#define NEKO_DMMA_NW(C) (1 << ((C) + 1))
125#define NEKO_DMMA_NTHRDS(C) dim3(32 * NEKO_DMMA_NW(C), 1, 1)
126
127/*
128 * Elements packed into one padded cube.
129 *
130 * The staging pads every cube to DMMA_P^3 so that the fixed 8x8x4 tile is
131 * always full, which at LX = DMMA_P is exact and below it is waste: at LX = 4
132 * only 64 of 512 points are real, so seven eighths of every contraction is
133 * spent on zeros. Where LX divides DMMA_P that waste can be turned back into
134 * work by packing DMMA_P/LX elements along each axis -- one per sub-cube --
135 * and making the staged derivative matrix block diagonal, one LX x LX copy of
136 * D per sub-cube. Because D is then block diagonal, the contraction along any
137 * axis couples only indices within the same sub-cube, so the single padded
138 * contraction computes every packed element independently and correctly.
139 *
140 * LX = 8 packs one element and is exactly what it was; LX = 4 packs eight,
141 * LX = 2 packs sixty-four. LX = 3, 5, 6 and 7 do not divide DMMA_P and keep
142 * one element with the old padding waste. This matters because p-multigrid
143 * smooths at LX = 4 and 2, so low order Ax is hot rather than incidental --
144 * and note the packing costs no extra shared memory at all, the cube is
145 * DMMA_P^3 either way.
146 */
147#define NEKO_DMMA_PPA(LX) ((DMMA_P % (LX) == 0) ? (DMMA_P / (LX)) : 1)
148#define NEKO_DMMA_PACK(LX) \
149 (NEKO_DMMA_PPA(LX) * NEKO_DMMA_PPA(LX) * NEKO_DMMA_PPA(LX))
150#define NEKO_DMMA_NBLCKS(NELV, LX) \
151 dim3(((NELV) + NEKO_DMMA_PACK(LX) - 1) / NEKO_DMMA_PACK(LX), 1, 1)
152
153/* Forced candidate, used when NEKO_AUTOTUNE pins the DMMA variant */
154static int neko_dmma_env()
155{
156 const char *v = getenv("NEKO_DMMA_NW");
157 int c = (v != NULL) ? atoi(v) : 0;
158
160 c = 0;
161 }
162 return c;
163}
164
165/* Report every measured DMMA candidate, see NEKO_TUNE_LOG in
166 elem_block_tune.h. The label is padded to 13 as a whole rather than by
167 hand counted field widths, so it stays column aligned with the 1D, KSTEP
168 and Chose lines whatever the warp and element counts render as */
169#define NEKO_TUNE_LOG_DMMA(LX, T3) \
170 do { \
171 for (int c = 0; c < NEKO_DMMA_CANDIDATES; c++) { \
172 if ((T3)[c] >= NEKO_TUNE_INIT) { continue; } \
173 char lbl_[16]; \
174 sprintf(lbl_, "DMMA %dw %de", \
175 NEKO_DMMA_NW(c), NEKO_DMMA_PACK(LX)); \
176 sprintf(neko_log_buf, "%-13s: %9.2f us/call", lbl_, \
177 NEKO_TUNE_US((T3)[c], iters)); \
178 log_message(neko_log_buf); \
179 } \
180 } while (0)
181
182/*
183 * Was any architecture with fp64 tensor cores among the ones this translation
184 * unit was compiled for? Unlike HIP, a binary built for an older arch still
185 * runs on a newer device by JIT compiling its PTX, and the kernel body below
186 * is guarded on __CUDA_ARCH__ -- so a build for, say, sm_70 running on an
187 * H100 would JIT the *no-op* body and silently return a zero Ax. Checking the
188 * compiled arch list keeps the strategy from ever being offered in that case.
189 *
190 * __CUDA_ARCH_LIST__ needs CUDA >= 11.5; on an older toolkit the strategy is
191 * off unless forced with -DNEKO_DMMA_ARCH_COMPILED=1.
192 */
193#ifndef NEKO_DMMA_ARCH_COMPILED
194#if defined(__CUDA_ARCH_LIST__)
195static inline bool dmma_arch_compiled()
196{
197 const int arch[] = { __CUDA_ARCH_LIST__ };
198
199 for (int i = 0; i < (int) (sizeof(arch)/sizeof(arch[0])); i++) {
200 if (arch[i] >= 800 && arch[i] < 1000) {
201 return true;
202 }
203 }
204 return false;
205}
206#else
207static inline bool dmma_arch_compiled()
208{
209 return false;
210}
211#endif
212#else
213static inline bool dmma_arch_compiled()
214{
215 return (NEKO_DMMA_ARCH_COMPILED != 0);
216}
217#endif
218
224static inline bool cuda_have_dmma()
225{
226 static int cached = -1;
227
228 if (cached < 0) {
229 int dev = 0;
231
232 if (!dmma_arch_compiled()) {
233 cached = 0;
234 } else if (cudaGetDevice(&dev) == cudaSuccess &&
236 cached = ((prop.major == 8 && prop.minor == 0) ||
237 (prop.major == 9)) ? 1 : 0;
238 } else {
239 cached = 0;
240 }
241 }
242 return cached == 1;
243}
244
272template< const int LX >
273static inline bool dmma_lx_supported()
274{
275 return (sizeof(real) == 8) && (LX >= 2) && (LX <= DMMA_P);
276}
277
294template< const int LX >
295static inline bool dmma_vector_lx_supported()
296{
297 return (sizeof(real) == 8) && (LX >= 4) && (LX <= DMMA_P);
298}
299
300#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) && (__CUDA_ARCH__ < 1000)
301
302/*
303 * Index map from the flat staging loop counter p to the padded cube offset and
304 * the global element offset, specialised on whether the cube holds more than
305 * one element.
306 *
307 * The two cases have to *generate* different code, not merely fold to it. With
308 * PACK > 1 the grid is ceil(nelv/PACK) blocks, so the last block can own slots
309 * past nelv: those are clamped on the way in -- the cube has to be finite, see
310 * the note above -- and dropped on the way out. With PACK == 1 the grid is
311 * exactly nelv blocks and neither is needed, but leaving that to the optimiser
312 * does NOT work: nvcc cannot prove blockIdx.x < nelv, so the select survives on
313 * every staging and metric load where the unpacked kernel had a single hoisted
314 * blockIdx.x * LX3, and the guarded store keeps its branch. Hence the explicit
315 * PPA == 1 specialisation below, which restores that addressing verbatim and
316 * makes `live` a compile time true.
317 */
318struct dmma_idx {
319 int c; /* offset into the padded cube */
320 int g; /* offset into the global element arrays */
321 int l; /* offset within the element, i.e. g minus the
322 element's base. Needed by the operators that
323 also read an array shared by every element,
324 such as opgrad's quadrature weights w3 */
325 bool live; /* false only for a padded tail slot, PACK > 1 */
326};
327
328template< const int LX, const int PPA >
329struct dmma_pack {
330 enum { PACK = PPA * PPA * PPA,
331 LX3 = LX * LX * LX,
332 NP = PACK * LX3 };
333
334 /* Loop invariant part of the addressing, hoisted by the caller */
335 __device__ __forceinline__ static int ebase() {
336 return blockIdx.x * PACK;
337 }
338
339 __device__ __forceinline__ static dmma_idx map(const int p, const int ebase,
340 const int nelv) {
341 const int q = p / LX3;
342 const int r = p - q * LX3;
343 const int i = r % LX;
344 const int jk = r / LX;
345 const int j = jk % LX;
346 const int k = jk / LX;
347 const int qa = q % PPA;
348 const int qb = (q / PPA) % PPA;
349 const int qc = q / (PPA * PPA);
350 const int eq = ebase + q;
351 dmma_idx x;
352
353 x.c = (qa * LX + i) + DMMA_SI * (qb * LX + j) + DMMA_SJ * (qc * LX + k);
354 x.g = r + (eq < nelv ? eq : nelv - 1) * LX3;
355 x.l = r;
356 x.live = (eq < nelv);
357 return x;
358 }
359};
360
361/*
362 * One element per cube: the grid covers nelv exactly, so there is no tail to
363 * clamp, no store to predicate, and ebase() is the element's base offset
364 * outright rather than an element index. This is the addressing the kernel had
365 * before packing existed, and at LX == DMMA_P the cube offset reduces to p.
366 */
367template< const int LX >
368struct dmma_pack< LX, 1 > {
369 enum { PACK = 1,
370 LX3 = LX * LX * LX,
371 NP = LX3 };
372
373 __device__ __forceinline__ static int ebase() {
374 return blockIdx.x * LX3;
375 }
376
377 __device__ __forceinline__ static dmma_idx map(const int p, const int ebase,
378 const int) {
379 const int i = p % LX;
380 const int jk = p / LX;
381 const int j = jk % LX;
382 const int k = jk / LX;
383 dmma_idx x;
384
385 x.c = i + DMMA_SI * j + DMMA_SJ * k;
386 x.g = ebase + p;
387 x.l = p;
388 x.live = true;
389 return x;
390 }
391};
392
393/*
394 * How the staged cube is read as an M x N matrix when contracting axis AXIS,
395 * see the layout note above. COL_MAJOR is carried alongside the layout tag
396 * because the pointer offset of a K step depends on it: the contraction index
397 * is the row index of B, so it advances by one element in a column major view
398 * and by ldm in a row major one.
399 */
400template< const int AXIS >
401struct dmma_view;
402
403template< >
404struct dmma_view< 0 > {
405 typedef nvcuda::wmma::col_major layout;
406 enum { LDM = DMMA_SI, COL_MAJOR = 1 };
407 __device__ __forceinline__ static int base(const int t) {
408 return DMMA_SI * DMMA_P * t;
409 }
410};
411
412template< >
413struct dmma_view< 1 > {
414 typedef nvcuda::wmma::row_major layout;
415 enum { LDM = DMMA_SI, COL_MAJOR = 0 };
416 __device__ __forceinline__ static int base(const int t) {
417 return DMMA_SJ * t;
418 }
419};
420
421template< >
422struct dmma_view< 2 > {
423 typedef nvcuda::wmma::row_major layout;
424 enum { LDM = DMMA_SJ, COL_MAJOR = 0 };
425 __device__ __forceinline__ static int base(const int t) {
426 return DMMA_SI * t;
427 }
428};
429
430/*
431 * The A operand is the staged derivative matrix, D(m,l) = dmat[m + DMMA_P*l].
432 * Untransposed that is a column major 8x8 with ldm = DMMA_P; transposed it is
433 * the same storage read row major. Here the contraction index is the column
434 * index of A, so the K step offset is the mirror of the B one above.
435 */
436template< const bool TRANSPOSE >
437struct dmma_amat;
438
439template< >
440struct dmma_amat< false > {
441 typedef nvcuda::wmma::col_major layout;
442 __device__ __forceinline__ static int koff(const int l0) {
443 return DMMA_P * l0;
444 }
445};
446
447template< >
448struct dmma_amat< true > {
449 typedef nvcuda::wmma::row_major layout;
450 __device__ __forceinline__ static int koff(const int l0) {
451 return l0;
452 }
453};
454
455/*
456 * NW cooperating warps (wf = threadIdx.x / 32) contract the staged derivative
457 * matrix 'dmat' with the staged cube 'in' along axis AXIS into the staged cube
458 * 'out':
459 *
460 * out[idx(m,n)] (+)= sum_l D(m,l) * in[idx(l,n)] (TRANSPOSE = false)
461 * out[idx(m,n)] (+)= sum_l D(l,m) * in[idx(l,n)] (TRANSPOSE = true)
462 *
463 * ACCUM selects += over =. Warp wf takes tiles wf, wf+NW, ..., so every lane
464 * of a warp follows the same tile sequence and the warp wide WMMA operations
465 * are never reached divergently.
466 */
467template< const int AXIS, const bool TRANSPOSE, const bool ACCUM,
468 const int NW >
470void dmma_contract(double * __restrict__ out,
471 const double * __restrict__ dmat,
472 const double * __restrict__ in,
473 const int wf)
474{
475 namespace wmma = nvcuda::wmma;
476 typedef dmma_view< AXIS > view;
478
479 const wmma::layout_t mem_layout =
480 view::COL_MAJOR ? wmma::mem_col_major : wmma::mem_row_major;
481
482 /* A is the same for every tile of the contraction, so it is loaded once
483 rather than once per tile */
484 wmma::fragment< wmma::matrix_a, DMMA_P, DMMA_P, DMMA_KS, double,
485 typename amat::layout > a[DMMA_KSTEPS];
486#pragma unroll
487 for (int ks = 0; ks < DMMA_KSTEPS; ks++) {
488 wmma::load_matrix_sync(a[ks], dmat + amat::koff(ks * DMMA_KS), DMMA_P);
489 }
490
491 const int npass = (DMMA_NTILES + NW - 1) / NW;
492
493#pragma unroll
494 for (int p = 0; p < npass; p++) {
495 const int t = wf + p * NW;
496
497 if (t < DMMA_NTILES) {
498 wmma::fragment< wmma::accumulator, DMMA_P, DMMA_P, DMMA_KS, double > acc;
499
500 if (ACCUM) {
501 wmma::load_matrix_sync(acc, out + view::base(t), view::LDM, mem_layout);
502 } else {
503 wmma::fill_fragment(acc, 0.0);
504 }
505
506#pragma unroll
507 for (int ks = 0; ks < DMMA_KSTEPS; ks++) {
508 const int l0 = ks * DMMA_KS;
509 const int koff = view::COL_MAJOR ? l0 : (l0 * (int) view::LDM);
510 wmma::fragment< wmma::matrix_b, DMMA_P, DMMA_P, DMMA_KS, double,
511 typename view::layout > b;
512
513 wmma::load_matrix_sync(b, in + view::base(t) + koff, view::LDM);
514 wmma::mma_sync(acc, a[ks], b, acc);
515 }
516
517 wmma::store_matrix_sync(out + view::base(t), acc, view::LDM, mem_layout);
518 }
519 }
520}
521
522#endif // __CUDA_ARCH__ in [800, 1000)
523
524#endif // __MATH_DMMA_KERNEL_H__
__global__ void ale_add_kinematics_kernel(const int n, T *__restrict__ wx, T *__restrict__ wy, T *__restrict__ wz, const T *__restrict__ x_ref, const T *__restrict__ y_ref, const T *__restrict__ z_ref, const T *__restrict__ phi, const T *__restrict__ x, const T *__restrict__ y, const T *__restrict__ z, const kinematics_params_t kin_params)
const int i
__global__ void T *__restrict__ T *__restrict__ const T *__restrict__ const T *__restrict__ v
const int j
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const int nelv
__global__ void const T *__restrict__ x
double real
#define NEKO_DMMA_CANDIDATES
@ DMMA_SI
@ DMMA_CUBE
@ DMMA_SJ
@ DMMA_KSTEPS
@ DMMA_MAT
@ DMMA_P
@ DMMA_NTILES
@ DMMA_KS
static bool dmma_arch_compiled()
static bool dmma_vector_lx_supported()
static bool cuda_have_dmma()
static int neko_dmma_env()
static bool dmma_lx_supported()
NEKTON map.
Definition map.f90:3