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/* Warps per block candidate pinned by NEKO_DMMA_NW, or -1 to leave it to the
154 sweep, see neko_eb_pin() in elem_block_tune.h */
155static int neko_dmma_pin()
156{
157 const char *v = getenv("NEKO_DMMA_NW");
158 int c;
159
160 if (v == NULL) {
161 return -1;
162 }
163
164 c = atoi(v);
166 c = 0;
167 }
168 return c;
169}
170
171/* Report every measured DMMA candidate, see NEKO_TUNE_LOG in
172 elem_block_tune.h. The label is padded to 13 as a whole rather than by
173 hand counted field widths, so it stays column aligned with the 1D, KSTEP
174 and Chose lines whatever the warp and element counts render as */
175#define NEKO_TUNE_LOG_DMMA(LX, T3) \
176 do { \
177 for (int c = 0; c < NEKO_DMMA_CANDIDATES; c++) { \
178 if ((T3)[c] >= NEKO_TUNE_INIT) { continue; } \
179 char lbl_[16]; \
180 sprintf(lbl_, "DMMA %dw %de", \
181 NEKO_DMMA_NW(c), NEKO_DMMA_PACK(LX)); \
182 sprintf(neko_log_buf, "%-13s: %9.2f us/call", lbl_, \
183 NEKO_TUNE_US((T3)[c], iters)); \
184 log_message(neko_log_buf); \
185 } \
186 } while (0)
187
188/*
189 * Was any architecture with fp64 tensor cores among the ones this translation
190 * unit was compiled for? Unlike HIP, a binary built for an older arch still
191 * runs on a newer device by JIT compiling its PTX, and the kernel body below
192 * is guarded on __CUDA_ARCH__ -- so a build for, say, sm_70 running on an
193 * H100 would JIT the *no-op* body and silently return a zero Ax. Checking the
194 * compiled arch list keeps the strategy from ever being offered in that case.
195 *
196 * __CUDA_ARCH_LIST__ needs CUDA >= 11.5; on an older toolkit the strategy is
197 * off unless forced with -DNEKO_DMMA_ARCH_COMPILED=1.
198 */
199#ifndef NEKO_DMMA_ARCH_COMPILED
200#if defined(__CUDA_ARCH_LIST__)
201static inline bool dmma_arch_compiled()
202{
203 const int arch[] = { __CUDA_ARCH_LIST__ };
204
205 for (int i = 0; i < (int) (sizeof(arch)/sizeof(arch[0])); i++) {
206 if (arch[i] >= 800 && arch[i] < 1000) {
207 return true;
208 }
209 }
210 return false;
211}
212#else
213static inline bool dmma_arch_compiled()
214{
215 return false;
216}
217#endif
218#else
219static inline bool dmma_arch_compiled()
220{
221 return (NEKO_DMMA_ARCH_COMPILED != 0);
222}
223#endif
224
230static inline bool cuda_have_dmma()
231{
232 static int cached = -1;
233
234 if (cached < 0) {
235 int dev = 0;
237
238 if (!dmma_arch_compiled()) {
239 cached = 0;
240 } else if (cudaGetDevice(&dev) == cudaSuccess &&
242 cached = ((prop.major == 8 && prop.minor == 0) ||
243 (prop.major == 9)) ? 1 : 0;
244 } else {
245 cached = 0;
246 }
247 }
248 return cached == 1;
249}
250
278template< const int LX >
279static inline bool dmma_lx_supported()
280{
281 return (sizeof(real) == 8) && (LX >= 2) && (LX <= DMMA_P);
282}
283
300template< const int LX >
301static inline bool dmma_vector_lx_supported()
302{
303 return (sizeof(real) == 8) && (LX >= 4) && (LX <= DMMA_P);
304}
305
306#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) && (__CUDA_ARCH__ < 1000)
307
308/*
309 * Index map from the flat staging loop counter p to the padded cube offset and
310 * the global element offset, specialised on whether the cube holds more than
311 * one element.
312 *
313 * The two cases have to *generate* different code, not merely fold to it. With
314 * PACK > 1 the grid is ceil(nelv/PACK) blocks, so the last block can own slots
315 * past nelv: those are clamped on the way in -- the cube has to be finite, see
316 * the note above -- and dropped on the way out. With PACK == 1 the grid is
317 * exactly nelv blocks and neither is needed, but leaving that to the optimiser
318 * does NOT work: nvcc cannot prove blockIdx.x < nelv, so the select survives on
319 * every staging and metric load where the unpacked kernel had a single hoisted
320 * blockIdx.x * LX3, and the guarded store keeps its branch. Hence the explicit
321 * PPA == 1 specialisation below, which restores that addressing verbatim and
322 * makes `live` a compile time true.
323 */
324struct dmma_idx {
325 int c; /* offset into the padded cube */
326 int g; /* offset into the global element arrays */
327 int l; /* offset within the element, i.e. g minus the
328 element's base. Needed by the operators that
329 also read an array shared by every element,
330 such as opgrad's quadrature weights w3 */
331 bool live; /* false only for a padded tail slot, PACK > 1 */
332};
333
334template< const int LX, const int PPA >
335struct dmma_pack {
336 enum { PACK = PPA * PPA * PPA,
337 LX3 = LX * LX * LX,
338 NP = PACK * LX3 };
339
340 /* Loop invariant part of the addressing, hoisted by the caller */
341 __device__ __forceinline__ static int ebase() {
342 return blockIdx.x * PACK;
343 }
344
345 __device__ __forceinline__ static dmma_idx map(const int p, const int ebase,
346 const int nelv) {
347 const int q = p / LX3;
348 const int r = p - q * LX3;
349 const int i = r % LX;
350 const int jk = r / LX;
351 const int j = jk % LX;
352 const int k = jk / LX;
353 const int qa = q % PPA;
354 const int qb = (q / PPA) % PPA;
355 const int qc = q / (PPA * PPA);
356 const int eq = ebase + q;
357 dmma_idx x;
358
359 x.c = (qa * LX + i) + DMMA_SI * (qb * LX + j) + DMMA_SJ * (qc * LX + k);
360 x.g = r + (eq < nelv ? eq : nelv - 1) * LX3;
361 x.l = r;
362 x.live = (eq < nelv);
363 return x;
364 }
365};
366
367/*
368 * One element per cube: the grid covers nelv exactly, so there is no tail to
369 * clamp, no store to predicate, and ebase() is the element's base offset
370 * outright rather than an element index. This is the addressing the kernel had
371 * before packing existed, and at LX == DMMA_P the cube offset reduces to p.
372 */
373template< const int LX >
374struct dmma_pack< LX, 1 > {
375 enum { PACK = 1,
376 LX3 = LX * LX * LX,
377 NP = LX3 };
378
379 __device__ __forceinline__ static int ebase() {
380 return blockIdx.x * LX3;
381 }
382
383 __device__ __forceinline__ static dmma_idx map(const int p, const int ebase,
384 const int) {
385 const int i = p % LX;
386 const int jk = p / LX;
387 const int j = jk % LX;
388 const int k = jk / LX;
389 dmma_idx x;
390
391 x.c = i + DMMA_SI * j + DMMA_SJ * k;
392 x.g = ebase + p;
393 x.l = p;
394 x.live = true;
395 return x;
396 }
397};
398
399/*
400 * How the staged cube is read as an M x N matrix when contracting axis AXIS,
401 * see the layout note above. COL_MAJOR is carried alongside the layout tag
402 * because the pointer offset of a K step depends on it: the contraction index
403 * is the row index of B, so it advances by one element in a column major view
404 * and by ldm in a row major one.
405 */
406template< const int AXIS >
407struct dmma_view;
408
409template< >
410struct dmma_view< 0 > {
411 typedef nvcuda::wmma::col_major layout;
412 enum { LDM = DMMA_SI, COL_MAJOR = 1 };
413 __device__ __forceinline__ static int base(const int t) {
414 return DMMA_SI * DMMA_P * t;
415 }
416};
417
418template< >
419struct dmma_view< 1 > {
420 typedef nvcuda::wmma::row_major layout;
421 enum { LDM = DMMA_SI, COL_MAJOR = 0 };
422 __device__ __forceinline__ static int base(const int t) {
423 return DMMA_SJ * t;
424 }
425};
426
427template< >
428struct dmma_view< 2 > {
429 typedef nvcuda::wmma::row_major layout;
430 enum { LDM = DMMA_SJ, COL_MAJOR = 0 };
431 __device__ __forceinline__ static int base(const int t) {
432 return DMMA_SI * t;
433 }
434};
435
436/*
437 * The A operand is the staged derivative matrix, D(m,l) = dmat[m + DMMA_P*l].
438 * Untransposed that is a column major 8x8 with ldm = DMMA_P; transposed it is
439 * the same storage read row major. Here the contraction index is the column
440 * index of A, so the K step offset is the mirror of the B one above.
441 */
442template< const bool TRANSPOSE >
443struct dmma_amat;
444
445template< >
446struct dmma_amat< false > {
447 typedef nvcuda::wmma::col_major layout;
448 __device__ __forceinline__ static int koff(const int l0) {
449 return DMMA_P * l0;
450 }
451};
452
453template< >
454struct dmma_amat< true > {
455 typedef nvcuda::wmma::row_major layout;
456 __device__ __forceinline__ static int koff(const int l0) {
457 return l0;
458 }
459};
460
461/*
462 * NW cooperating warps (wf = threadIdx.x / 32) contract the staged derivative
463 * matrix 'dmat' with the staged cube 'in' along axis AXIS into the staged cube
464 * 'out':
465 *
466 * out[idx(m,n)] (+)= sum_l D(m,l) * in[idx(l,n)] (TRANSPOSE = false)
467 * out[idx(m,n)] (+)= sum_l D(l,m) * in[idx(l,n)] (TRANSPOSE = true)
468 *
469 * ACCUM selects += over =. Warp wf takes tiles wf, wf+NW, ..., so every lane
470 * of a warp follows the same tile sequence and the warp wide WMMA operations
471 * are never reached divergently.
472 */
473template< const int AXIS, const bool TRANSPOSE, const bool ACCUM,
474 const int NW >
476void dmma_contract(double * __restrict__ out,
477 const double * __restrict__ dmat,
478 const double * __restrict__ in,
479 const int wf)
480{
481 namespace wmma = nvcuda::wmma;
482 typedef dmma_view< AXIS > view;
484
485 const wmma::layout_t mem_layout =
486 view::COL_MAJOR ? wmma::mem_col_major : wmma::mem_row_major;
487
488 /* A is the same for every tile of the contraction, so it is loaded once
489 rather than once per tile */
490 wmma::fragment< wmma::matrix_a, DMMA_P, DMMA_P, DMMA_KS, double,
491 typename amat::layout > a[DMMA_KSTEPS];
492#pragma unroll
493 for (int ks = 0; ks < DMMA_KSTEPS; ks++) {
494 wmma::load_matrix_sync(a[ks], dmat + amat::koff(ks * DMMA_KS), DMMA_P);
495 }
496
497 const int npass = (DMMA_NTILES + NW - 1) / NW;
498
499#pragma unroll
500 for (int p = 0; p < npass; p++) {
501 const int t = wf + p * NW;
502
503 if (t < DMMA_NTILES) {
504 wmma::fragment< wmma::accumulator, DMMA_P, DMMA_P, DMMA_KS, double > acc;
505
506 if (ACCUM) {
507 wmma::load_matrix_sync(acc, out + view::base(t), view::LDM, mem_layout);
508 } else {
509 wmma::fill_fragment(acc, 0.0);
510 }
511
512#pragma unroll
513 for (int ks = 0; ks < DMMA_KSTEPS; ks++) {
514 const int l0 = ks * DMMA_KS;
515 const int koff = view::COL_MAJOR ? l0 : (l0 * (int) view::LDM);
516 wmma::fragment< wmma::matrix_b, DMMA_P, DMMA_P, DMMA_KS, double,
517 typename view::layout > b;
518
519 wmma::load_matrix_sync(b, in + view::base(t) + koff, view::LDM);
520 wmma::mma_sync(acc, a[ks], b, acc);
521 }
522
523 wmma::store_matrix_sync(out + view::base(t), acc, view::LDM, mem_layout);
524 }
525 }
526}
527
528#endif // __CUDA_ARCH__ in [800, 1000)
529
530#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 bool dmma_lx_supported()
static int neko_dmma_pin()
NEKTON map.
Definition map.f90:3