Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
ax_helm_kernel.h
Go to the documentation of this file.
1#ifndef __MATH_AX_HELM_KERNEL_H__
2#define __MATH_AX_HELM_KERNEL_H__
3
4/*
5 Copyright (c) 2021-2024, The Neko Authors
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11
12 * Redistributions of source code must retain the above copyright
13 notice, this list of conditions and the following disclaimer.
14
15 * Redistributions in binary form must reproduce the above
16 copyright notice, this list of conditions and the following
17 disclaimer in the documentation and/or other materials provided
18 with the distribution.
19
20 * Neither the name of the authors nor the names of its
21 contributors may be used to endorse or promote products derived
22 from this software without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 POSSIBILITY OF SUCH DAMAGE.
36*/
37
38#include "elem_block.h"
39#include "mfma_kernel.h"
40
45template< typename T, const int LX, const int CHUNKS >
47 const T * __restrict__ u,
48 const T * __restrict__ dx,
49 const T * __restrict__ dy,
50 const T * __restrict__ dz,
51 const T * __restrict__ dxt,
52 const T * __restrict__ dyt,
53 const T * __restrict__ dzt,
54 const T * __restrict__ h1,
55 const T * __restrict__ g11,
56 const T * __restrict__ g22,
57 const T * __restrict__ g33,
58 const T * __restrict__ g12,
59 const T * __restrict__ g13,
60 const T * __restrict__ g23) {
61
65
69
74
75 const int e = blockIdx.x;
76 const int iii = threadIdx.x;
77 const int nchunks = (LX * LX * LX - 1)/CHUNKS + 1;
78
79 if (iii<LX*LX) {
80 shdx[iii] = dx[iii];
81 shdy[iii] = dy[iii];
82 shdz[iii] = dz[iii];
83 }
84
85 {
86 int i = iii;
87 while (i < LX * LX * LX){
88 shu[i] = u[i+e*LX*LX*LX];
89 i = i + CHUNKS;
90 }
91 }
92
94
95 if (iii<LX*LX){
96 shdxt[iii] = dxt[iii];
97 shdyt[iii] = dyt[iii];
98 shdzt[iii] = dzt[iii];
99 }
100
101 for (int n=0; n<nchunks; n++){
102 const int ijk = iii+n*CHUNKS;
103 const int jk = ijk/LX;
104 const int i = ijk-jk*LX;
105 const int k = jk/LX;
106 const int j = jk-k*LX;
107 if (i<LX && j<LX && k<LX && ijk < LX*LX*LX){
108 const T G00 = g11[ijk+e*LX*LX*LX];
109 const T G11 = g22[ijk+e*LX*LX*LX];
110 const T G22 = g33[ijk+e*LX*LX*LX];
111 const T G01 = g12[ijk+e*LX*LX*LX];
112 const T G02 = g13[ijk+e*LX*LX*LX];
113 const T G12 = g23[ijk+e*LX*LX*LX];
114 const T H1 = h1[ijk+e*LX*LX*LX];
115 T rtmp = 0.0;
116 T stmp = 0.0;
117 T ttmp = 0.0;
118#pragma unroll
119 for (int l = 0; l<LX; l++){
120 rtmp = rtmp + shdx[i+l*LX] * shu[l+j*LX+k*LX*LX];
121 stmp = stmp + shdy[j+l*LX] * shu[i+l*LX+k*LX*LX];
122 ttmp = ttmp + shdz[k+l*LX] * shu[i+j*LX+l*LX*LX];
123 }
124 shur[ijk] = H1 * (G00 * rtmp + G01 * stmp + G02 * ttmp);
125 shus[ijk] = H1 * (G01 * rtmp + G11 * stmp + G12 * ttmp);
126 shut[ijk] = H1 * (G02 * rtmp + G12 * stmp + G22 * ttmp);
127 }
128 }
129
131
132 for (int n=0; n<nchunks; n++){
133 const int ijk = iii+n*CHUNKS;
134 const int jk = ijk/LX;
135 const int k = jk/LX;
136 const int j = jk-k*LX;
137 const int i = ijk-jk*LX;
138 if (i<LX && j<LX && k<LX && ijk <LX*LX*LX){
139 T wijke = 0.0;
140#pragma unroll
141 for (int l = 0; l<LX; l++){
142 wijke = wijke
143 + shdxt[i+l*LX] * shur[l+j*LX+k*LX*LX]
144 + shdyt[j+l*LX] * shus[i+l*LX+k*LX*LX]
145 + shdzt[k+l*LX] * shut[i+j*LX+l*LX*LX];
146 }
147 w[ijk+e*LX*LX*LX] = wijke;
148 }
149 }
150}
151
152template< typename T, const int LX, const int EB >
155 const T * __restrict__ u,
166 const int nelv) {
167
168 /* Element independent, one copy per block */
169 __shared__ T shdx[LX * LX];
172
173 /* One slice per element in the block */
177
178 static_assert(sizeof(shdx) +
179 sizeof(shdy) +
180 sizeof(shdz) +
181 sizeof(shu) +
182 sizeof(shur) +
183 sizeof(shus)
185 "kstep block exceeds the LDS budget");
186
190
191 /* Threads past the last element still have to reach the block wide
192 barriers below, so clamp their reads and drop their stores rather
193 than returning early. At EB == 1 the grid covers nelv exactly, so all
194 of this bookkeeping is constant folded and the kernel is exactly what
195 it was before blocking */
196 const int eb = (EB == 1) ? 0 : threadIdx.z;
197 const int e_blk = blockIdx.x * EB + eb;
198 const bool active = (EB == 1) ? true : (e_blk < nelv);
199 const int e = active ? e_blk : (nelv - 1);
200 const int j = threadIdx.y;
201 const int i = threadIdx.x;
202 const int ij = i + j*LX;
203 const int sh = eb*LX*LX;
204 const int ele = e*LX*LX*LX;
205
206 if (eb == 0) {
207 shdx[ij] = dx[ij];
208 shdy[ij] = dy[ij];
209 shdz[ij] = dz[ij];
210 }
211
212#pragma unroll
213 for(int k = 0; k < LX; ++k){
214 ru[k] = u[ij + k*LX*LX + ele];
215 rw[k] = 0.0;
216 }
217
218
220#pragma unroll
221 for (int k = 0; k < LX; ++k){
222 const int ijk = ij + k*LX*LX;
223 const T G00 = g11[ijk+ele];
224 const T G11 = g22[ijk+ele];
225 const T G22 = g33[ijk+ele];
226 const T G01 = g12[ijk+ele];
227 const T G02 = g13[ijk+ele];
228 const T G12 = g23[ijk+ele];
229 const T H1 = h1[ijk+ele];
230 T ttmp = 0.0;
231 shu[sh + ij] = ru[k];
232#pragma unroll
233 for (int l = 0; l < LX; l++){
234 ttmp += shdz[k+l*LX] * ru[l];
235 }
237
238 T rtmp = 0.0;
239 T stmp = 0.0;
240#pragma unroll
241 for (int l = 0; l < LX; l++){
242 rtmp += shdx[i+l*LX] * shu[sh + l+j*LX];
243 stmp += shdy[j+l*LX] * shu[sh + i+l*LX];
244 }
245 shur[sh + ij] = H1
246 * (G00 * rtmp
247 + G01 * stmp
248 + G02 * ttmp);
249 shus[sh + ij] = H1
250 * (G01 * rtmp
251 + G11 * stmp
252 + G12 * ttmp);
253 rut = H1
254 * (G02 * rtmp
255 + G12 * stmp
256 + G22 * ttmp);
257
259
260 T wijke = 0.0;
261#pragma unroll
262 for (int l = 0; l < LX; l++){
263 wijke += shur[sh + l+j*LX] * shdx[l+i*LX];
264 rw[l] += rut * shdz[k+l*LX];
265 wijke += shus[sh + i+l*LX] * shdy[l + j*LX];
266 }
267 rw[k] += wijke;
268 }
269 if (active) {
270#pragma unroll
271 for (int k = 0; k < LX; ++k){
272 w[ij + k*LX*LX + ele] = rw[k];
273 }
274 }
275}
276
282template< typename T, const int LX, const int EB >
285 const T * __restrict__ u,
286 const T * __restrict__ dx,
287 const T * __restrict__ dy,
288 const T * __restrict__ dz,
289 const T * __restrict__ h1,
290 const T * __restrict__ g11,
291 const T * __restrict__ g22,
292 const T * __restrict__ g33,
293 const T * __restrict__ g12,
294 const T * __restrict__ g13,
295 const T * __restrict__ g23,
296 const int nelv) {
297
298 /* Element independent, one copy per block */
299 __shared__ T shdx[LX * (LX+1)];
300 __shared__ T shdy[LX * (LX+1)];
301 __shared__ T shdz[LX * (LX+1)];
302
303 /* One slice per element in the block */
304 __shared__ T shu[EB * LX * (LX+1)];
305 __shared__ T shur[EB * LX * LX]; // only accessed using fastest dimension
306 __shared__ T shus[EB * LX * (LX+1)];
307
308 static_assert(sizeof(shdx) +
309 sizeof(shdy) +
310 sizeof(shdz) +
311 sizeof(shu) +
312 sizeof(shur) +
313 sizeof(shus)
315 "kstep block exceeds the LDS budget");
316
317 T ru[LX];
318 T rw[LX];
319 T rut;
320
321 /* At EB == 1 the grid covers nelv exactly, so the blocking bookkeeping is
322 constant folded away and the kernel is exactly what it was before */
323 const int eb = (EB == 1) ? 0 : threadIdx.z;
324 const int e_blk = blockIdx.x * EB + eb;
325 const bool active = (EB == 1) ? true : (e_blk < nelv);
326 const int e = active ? e_blk : (nelv - 1);
327 const int j = threadIdx.y;
328 const int i = threadIdx.x;
329 const int ij = i + j*LX;
330 const int ij_p = i + j*(LX+1);
331 const int sh = eb*LX*LX;
332 const int sh_p = eb*LX*(LX+1);
333 const int ele = e*LX*LX*LX;
334
335 if (eb == 0) {
336 shdx[ij_p] = dx[ij];
337 shdy[ij_p] = dy[ij];
338 shdz[ij_p] = dz[ij];
339 }
340
341#pragma unroll
342 for(int k = 0; k < LX; ++k){
343 ru[k] = u[ij + k*LX*LX + ele];
344 rw[k] = 0.0;
345 }
346
347
349#pragma unroll
350 for (int k = 0; k < LX; ++k){
351 const int ijk = ij + k*LX*LX;
352 const T G00 = g11[ijk+ele];
353 const T G11 = g22[ijk+ele];
354 const T G22 = g33[ijk+ele];
355 const T G01 = g12[ijk+ele];
356 const T G02 = g13[ijk+ele];
357 const T G12 = g23[ijk+ele];
358 const T H1 = h1[ijk+ele];
359 T ttmp = 0.0;
360 shu[sh_p + ij_p] = ru[k];
361#pragma unroll
362 for (int l = 0; l < LX; l++){
363 ttmp += shdz[k+l*(LX+1)] * ru[l];
364 }
366
367 T rtmp = 0.0;
368 T stmp = 0.0;
369#pragma unroll
370 for (int l = 0; l < LX; l++){
371 rtmp += shdx[i+l*(LX+1)] * shu[sh_p + l+j*(LX+1)];
372 stmp += shdy[j+l*(LX+1)] * shu[sh_p + i+l*(LX+1)];
373 }
374 shur[sh + ij] = H1
375 * (G00 * rtmp
376 + G01 * stmp
377 + G02 * ttmp);
378 shus[sh_p + ij_p] = H1
379 * (G01 * rtmp
380 + G11 * stmp
381 + G12 * ttmp);
382 rut = H1
383 * (G02 * rtmp
384 + G12 * stmp
385 + G22 * ttmp);
386
388
389 T wijke = 0.0;
390#pragma unroll
391 for (int l = 0; l < LX; l++){
392 wijke += shur[sh + l+j*LX] * shdx[l+i*(LX+1)];
393 rw[l] += rut * shdz[k+l*(LX+1)];
394 wijke += shus[sh_p + i+l*(LX+1)] * shdy[l + j*(LX+1)];
395 }
396 rw[k] += wijke;
397 }
398 if (active) {
399#pragma unroll
400 for (int k = 0; k < LX; ++k){
401 w[ij + k*LX*LX + ele] = rw[k];
402 }
403 }
404}
405
406
446#if defined(__gfx90a__) || defined(__gfx942__)
447
448/* Matrix-core axhelm for one element of order LX-1 (T = float or double).
449 *
450 * NWF cooperating wavefronts (blockDim = (64, NWF, 1)) process one element,
451 * sharing a single staged cube in LDS. The matrix-core column tiles are
452 * striped across the wavefronts (wf = threadIdx.y) via mfma_contract_sel, while
453 * the staging, geometry and write-back passes parallelise over the WPE*64
454 * threads that serve one element (gtid); only the derivative matrices are
455 * staged by the whole block. The three accumulating divergence contractions
456 * stay separated
457 * by __syncthreads and each wavefront owns disjoint output columns, so the
458 * accumulation is race-free. NWF = 1 reproduces the single-wavefront kernel.
459 *
460 * mfma_contract_sel routes double precision through the batched 4x4x4 matrix
461 * core (full M-utilisation) and single precision through the 16x16x4 tile. */
462template< typename T, const int LX, const int NWF, const int TILE >
464 const T * __restrict__ u,
465 const T * __restrict__ dx,
466 const T * __restrict__ dy,
467 const T * __restrict__ dz,
468 const T * __restrict__ h1,
469 const T * __restrict__ g11,
470 const T * __restrict__ g22,
471 const T * __restrict__ g33,
472 const T * __restrict__ g12,
473 const T * __restrict__ g13,
474 const T * __restrict__ g23,
475 const int nelv) {
476 const int LX2 = LX * LX;
477 const int LX3 = LX * LX * LX;
478
479 /* NWF wavefronts per block, WPE of them cooperating on one element and the
480 block covering EB elements, see the note in mfma_kernel.h. At LX = 4 the
481 contraction offers one column group, so WPE is 1 and every wavefront gets
482 an element of its own rather than idling. */
483 enum { PAD = NEKO_MFMA_PAD_N(LX, sizeof(T)),
484 CUBE = NEKO_MFMA_CUBE_N(LX, sizeof(T)),
485 DMAT = NEKO_MFMA_DMAT_N(LX, sizeof(T)),
486 EB = NEKO_MFMA_EB_N(NWF, LX, sizeof(T)),
487 WPE = NWF / EB,
488 GNTHR = WPE * 64, /* threads serving one element */
489 SPT = NEKO_MFMA_SPT_N(WPE, LX, sizeof(T)),
490 /* Elements the batched single precision tile contracts per
491 call, from the wavefronts of the first element of each
492 batch; one for every other tile, see NEKO_MFMA_NEL_N */
493 NEL = NEKO_MFMA_NEL_N(EB, LX, sizeof(T), TILE),
494 /* Geometry prefetched into registers, or read where it is used?
495 Same budget and the same macro the tuner logs with, see the note
496 on the prefetch below */
497 GREG = NEKO_MFMA_VECTOR_GREG_N(SPT, sizeof(T)),
498 NG = GREG ? SPT : 1 };
499 static_assert(WPE * EB == NWF,
500 "wavefronts per block must split evenly over the elements");
501
505 __shared__ T shu[EB * CUBE]; // input u, later reused as output w
506 __shared__ T shr[EB * CUBE]; // d/dr -> Sr
507 __shared__ T shs[EB * CUBE]; // d/ds -> Ss
508 __shared__ T sht[EB * CUBE]; // d/dt -> St
509
510 static_assert(sizeof(shdx) + sizeof(shdy) + sizeof(shdz) +
511 sizeof(shu) + sizeof(shr) + sizeof(shs) + sizeof(sht)
513 "mfma block exceeds the shared memory budget");
514
515 const int lane = threadIdx.x; // 0..63 : lane within a wavefront
516 const int wf = threadIdx.y; // 0..NWF-1 : which wavefront
517 const int tid = wf * 64 + lane; // 0..NWF*64-1 : block-wide thread id
518 const int nthr = NWF * 64;
519
520 const int eb = wf / WPE; // which element this wavefront serves
521 const int sub = wf % WPE; // its rank among that element's waves
522 const int gtid = sub * 64 + lane; // thread id within the element group
523
524 /* Threads past the last element still have to reach the block wide
525 barriers, so clamp their reads and drop their stores rather than
526 returning early. At EB == 1 the grid covers nelv exactly and this is
527 constant folded away */
528 const int e_blk = blockIdx.x * EB + eb;
529 const bool active = (EB == 1) ? true : (e_blk < nelv);
530 const int e = active ? e_blk : (nelv - 1);
531 const int ele = e * LX3;
532 const int sh = eb * CUBE;
533
534 /* The element point each of this thread's LDS slots holds, or -1 for a pad
535 slot, decoded once and reused by every pass below. The three passes walk
536 slots at unit stride in LDS -- which is what keeps them off each other's
537 banks once the cube is padded -- and this array is the translation back
538 to the caller's LX^3 storage, see mfma_slot_point(). Where the cube is not
539 padded the decode folds away to the identity and this is the old
540 p = gtid + q*GNTHR loop. */
541 int goff[SPT];
542#pragma unroll
543 for (int q = 0; q < SPT; q++) {
544 const int sl = gtid + q * GNTHR;
546 }
547
548 /* Reference derivative matrices, one copy shared by every element. The
549 staged copy carries the padded column stride, so the linear index of the
550 caller's LX x LX copy is decoded here; the global reads stay contiguous
551 and it is the LDS side that gains the stride, which is the point. */
552 for (int p = tid; p < LX2; p += nthr) {
553 const int sd = mfma_dmat_idx<LX, PAD>(p % LX, p / LX);
554 shdx[sd] = dx[p];
555 shdy[sd] = dy[p];
556 shdz[sd] = dz[p];
557 }
558 /* Element-local field, staged by the wavefronts that own it */
559#pragma unroll
560 for (int q = 0; q < SPT; q++) {
561 if (goff[q] >= 0)
562 shu[sh + gtid + q * GNTHR] = u[goff[q] + ele];
563 }
564
566
567 /*
568 * The seven geometric factors, issued here rather than where they are used.
569 *
570 * They are needed by the pointwise pass, which sits behind the gradient
571 * contractions and a barrier, so reading them there exposes the full global
572 * latency with nothing to cover it: this kernel runs at three wavefronts per
573 * SIMD in double precision at LX = 8, a third of what the 1D kernel that
574 * beats it reaches, so there is no other occupancy to hide behind. Issued
575 * here they are in flight across the three contractions instead.
576 *
577 * The cost is 7 * SPT * (sizeof(T)/4) VGPRs, and past a budget that stops
578 * paying -- 189 values at LX = 12 on a single wavefront, against the 256 a
579 * lane addresses -- so it is chosen per instantiation by the same
580 * NEKO_MFMA_VECTOR_GREG the vector kernel uses, and reported by the tuner
581 * for the same reason: it is derived from the wavefront count rather than
582 * swept, so two neighbouring candidates can differ in more than block shape.
583 */
584 T rG00[NG], rG11[NG], rG22[NG];
585 T rG01[NG], rG02[NG], rG12[NG];
586 T rH1[NG];
587
588 if (GREG) {
589#pragma unroll
590 for (int q = 0; q < NG; q++) {
591 const int gp = (goff[q] >= 0) ? (goff[q] + ele) : ele;
592 rG00[q] = g11[gp];
593 rG11[q] = g22[gp];
594 rG22[q] = g33[gp];
595 rG01[q] = g12[gp];
596 rG02[q] = g13[gp];
597 rG12[q] = g23[gp];
598 rH1[q] = h1[gp];
599 }
600 }
601
602 /* Gradient: ur, us, ut in canonical i + SJ*j + SK*k layout. */
603 if (eb % NEL == 0)
604 mfma_contract_sel<T, LX, 0, false, false, WPE, TILE, PAD, NEL>::run(
605 shr + sh, shdx, shu + sh, lane, sub);
606 if (eb % NEL == 0)
607 mfma_contract_sel<T, LX, 1, false, false, WPE, TILE, PAD, NEL>::run(
608 shs + sh, shdy, shu + sh, lane, sub);
609 if (eb % NEL == 0)
610 mfma_contract_sel<T, LX, 2, false, false, WPE, TILE, PAD, NEL>::run(
611 sht + sh, shdz, shu + sh, lane, sub);
612
614
615 /* Geometry (pointwise): (ur,us,ut) -> (Sr,Ss,St), reusing shr/shs/sht. */
616#pragma unroll
617 for (int q = 0; q < SPT; q++) {
618 if (goff[q] >= 0) {
619 const int sl = sh + gtid + q * GNTHR;
620 T G00, G11, G22, G01, G02, G12, H1;
621 if (GREG) {
622 /* GREG is a compile time constant, so only one of these two bodies is
623 emitted; the clamp keeps the index inside the NG == 1 array that the
624 other one never reads from */
625 const int r = GREG ? q : 0;
626 G00 = rG00[r]; G11 = rG11[r]; G22 = rG22[r];
627 G01 = rG01[r]; G02 = rG02[r]; G12 = rG12[r];
628 H1 = rH1[r];
629 } else {
630 const int gp = goff[q] + ele;
631 G00 = g11[gp]; G11 = g22[gp]; G22 = g33[gp];
632 G01 = g12[gp]; G02 = g13[gp]; G12 = g23[gp];
633 H1 = h1[gp];
634 }
635 const T rr = shr[sl], ss = shs[sl], tt = sht[sl];
636 shr[sl] = H1 * (G00 * rr + G01 * ss + G02 * tt);
637 shs[sl] = H1 * (G01 * rr + G11 * ss + G12 * tt);
638 sht[sl] = H1 * (G02 * rr + G12 * ss + G22 * tt);
639 }
640 }
641
643
644 /* Divergence: w = Dr^T Sr + Ds^T Ss + Dt^T St, accumulated in shu (= w).
645 The first contraction overwrites rather than accumulates, which is what
646 lets the separate zeroing pass over the cube -- and the barrier after it
647 -- go away entirely; the summation order of the three is unchanged, so
648 this is not a reassociation. All four (TRANSPOSE, ACCUM) pairs are covered
649 by the gfx90a read-out, so the non-accumulating one is no less verified
650 than the accumulating one. */
651 if (eb % NEL == 0)
652 mfma_contract_sel<T, LX, 0, true, false, WPE, TILE, PAD, NEL>::run(
653 shu + sh, shdx, shr + sh, lane, sub);
655 if (eb % NEL == 0)
656 mfma_contract_sel<T, LX, 1, true, true, WPE, TILE, PAD, NEL>::run(
657 shu + sh, shdy, shs + sh, lane, sub);
659 if (eb % NEL == 0)
660 mfma_contract_sel<T, LX, 2, true, true, WPE, TILE, PAD, NEL>::run(
661 shu + sh, shdz, sht + sh, lane, sub);
663
664 if (active) {
665#pragma unroll
666 for (int q = 0; q < SPT; q++) {
667 if (goff[q] >= 0)
668 w[goff[q] + ele] = shu[sh + gtid + q * GNTHR];
669 }
670 }
671}
672#endif // __gfx90a__ || __gfx942__
673
674/*
675 * Compile-time dispatch onto the MFMA element kernel. The launch macros in
676 * ax_helm.hip are written for every LX the operator dispatches and for
677 * whatever `real` is, so every combination has to compile; the ones the
678 * strategy does not cover -- LX outside the supported range, a build without
679 * a matrix-core arch -- resolve to this no-op. The autotuner never selects
680 * the strategy for them, so the no-op is unreachable at runtime, see
681 * mfma_lx_supported() and hip_have_mfma() in mfma_kernel.h.
682 */
683template< typename T, const int LX, const int NWF, const int TILE >
685 __device__ static void run(T *, const T *, const T *, const T *, const T *,
686 const T *, const T *, const T *, const T *,
687 const T *, const T *, const T *, const int) {}
688};
689
690#if defined(__gfx90a__) || defined(__gfx942__)
691
692/* Keep in sync with mfma_lx_supported() in mfma_kernel.h */
693#define NEKO_AX_HELM_MFMA_DISPATCH(TYPE, LXV) \
694 template< const int NWF, const int TILE > \
695 struct ax_helm_mfma_dispatch< TYPE, LXV, NWF, TILE > { \
696 __device__ static void run(TYPE *w, const TYPE *u, \
697 const TYPE *dx, const TYPE *dy, \
698 const TYPE *dz, const TYPE *h1, \
699 const TYPE *g11, const TYPE *g22, \
700 const TYPE *g33, const TYPE *g12, \
701 const TYPE *g13, const TYPE *g23, \
702 const int nelv) { \
703 ax_helm_mfma_elem< TYPE, LXV, NWF, TILE >(w, u, dx, dy, dz, h1, \
704 g11, g22, g33, g12, g13, g23, \
705 nelv); \
706 } \
707 }
708
729
730#endif // __gfx90a__ || __gfx942__
731
732/*
733 * Note the bare __launch_bounds__ rather than NEKO_EB_BOUNDS: the kstep
734 * kernels ask for three waves per SIMD, and this kernel was validated on
735 * gfx90a/gfx942 without that constraint. Keep it byte-identical to the
736 * configuration that was confirmed on hardware.
737 */
738template< typename T, const int LX, const int NWF, const int TILE >
741 const T * __restrict__ u,
742 const T * __restrict__ dx,
743 const T * __restrict__ dy,
744 const T * __restrict__ dz,
745 const T * __restrict__ h1,
746 const T * __restrict__ g11,
747 const T * __restrict__ g22,
748 const T * __restrict__ g33,
749 const T * __restrict__ g12,
750 const T * __restrict__ g13,
751 const T * __restrict__ g23,
752 const int nelv) {
753
755 g11, g22, g33,
756 g12, g13, g23, nelv);
757}
758
759/*
760 * Vector versions
761 */
762
763template< typename T, const int LX, const int EB >
768 const T * __restrict__ u,
769 const T * __restrict__ v,
770 const T * __restrict__ w,
771 const T * __restrict__ dx,
772 const T * __restrict__ dy,
773 const T * __restrict__ dz,
774 const T * __restrict__ h1,
775 const T * __restrict__ g11,
776 const T * __restrict__ g22,
777 const T * __restrict__ g33,
778 const T * __restrict__ g12,
779 const T * __restrict__ g13,
780 const T * __restrict__ g23,
781 const int nelv) {
782
783 /* Element independent, one copy per block */
784 __shared__ T shdx[LX * LX];
785 __shared__ T shdy[LX * LX];
786 __shared__ T shdz[LX * LX];
787
788 /* One slice per element in the block */
789 __shared__ T shu[EB * LX * LX];
790 __shared__ T shur[EB * LX * LX];
791 __shared__ T shus[EB * LX * LX];
792
796
800
801 static_assert(sizeof(shdx) +
802 sizeof(shdy) +
803 sizeof(shdz) +
804 sizeof(shu) +
805 sizeof(shur) +
806 sizeof(shus) +
807 sizeof(shv) +
808 sizeof(shvr) +
809 sizeof(shvs) +
810 sizeof(shw) +
811 sizeof(shwr) +
812 sizeof(shws)
814 "kstep block exceeds the LDS budget");
815
816 T ru[LX];
818 T rw[LX];
819
823
824 T rut;
827
828 /* At EB == 1 the grid covers nelv exactly, so the blocking bookkeeping is
829 constant folded away and the kernel is exactly what it was before */
830 const int eb = (EB == 1) ? 0 : threadIdx.z;
831 const int e_blk = blockIdx.x * EB + eb;
832 const bool active = (EB == 1) ? true : (e_blk < nelv);
833 const int e = active ? e_blk : (nelv - 1);
834 const int j = threadIdx.y;
835 const int i = threadIdx.x;
836 const int ij = i + j*LX;
837 const int sh = eb*LX*LX;
838 const int ele = e*LX*LX*LX;
839
840 if (eb == 0) {
841 shdx[ij] = dx[ij];
842 shdy[ij] = dy[ij];
843 shdz[ij] = dz[ij];
844 }
845
846#pragma unroll
847 for(int k = 0; k < LX; ++k){
848 ru[k] = u[ij + k*LX*LX + ele];
849 ruw[k] = 0.0;
850
851 rv[k] = v[ij + k*LX*LX + ele];
852 rvw[k] = 0.0;
853
854 rw[k] = w[ij + k*LX*LX + ele];
855 rww[k] = 0.0;
856 }
857
858
860#pragma unroll
861 for (int k = 0; k < LX; ++k){
862 const int ijk = ij + k*LX*LX;
863 const T G00 = g11[ijk+ele];
864 const T G11 = g22[ijk+ele];
865 const T G22 = g33[ijk+ele];
866 const T G01 = g12[ijk+ele];
867 const T G02 = g13[ijk+ele];
868 const T G12 = g23[ijk+ele];
869 const T H1 = h1[ijk+ele];
870 T uttmp = 0.0;
871 T vttmp = 0.0;
872 T wttmp = 0.0;
873 shu[sh + ij] = ru[k];
874 shv[sh + ij] = rv[k];
875 shw[sh + ij] = rw[k];
876#pragma unroll
877 for (int l = 0; l < LX; l++){
878 uttmp += shdz[k+l*LX] * ru[l];
879 vttmp += shdz[k+l*LX] * rv[l];
880 wttmp += shdz[k+l*LX] * rw[l];
881 }
883
884 T urtmp = 0.0;
885 T ustmp = 0.0;
886
887 T vrtmp = 0.0;
888 T vstmp = 0.0;
889
890 T wrtmp = 0.0;
891 T wstmp = 0.0;
892#pragma unroll
893 for (int l = 0; l < LX; l++){
894 urtmp += shdx[i+l*LX] * shu[sh + l+j*LX];
895 ustmp += shdy[j+l*LX] * shu[sh + i+l*LX];
896
897 vrtmp += shdx[i+l*LX] * shv[sh + l+j*LX];
898 vstmp += shdy[j+l*LX] * shv[sh + i+l*LX];
899
900 wrtmp += shdx[i+l*LX] * shw[sh + l+j*LX];
901 wstmp += shdy[j+l*LX] * shw[sh + i+l*LX];
902 }
903
904 shur[sh + ij] = H1
905 * (G00 * urtmp
906 + G01 * ustmp
907 + G02 * uttmp);
908 shus[sh + ij] = H1
909 * (G01 * urtmp
910 + G11 * ustmp
911 + G12 * uttmp);
912 rut = H1
913 * (G02 * urtmp
914 + G12 * ustmp
915 + G22 * uttmp);
916
917 shvr[sh + ij] = H1
918 * (G00 * vrtmp
919 + G01 * vstmp
920 + G02 * vttmp);
921 shvs[sh + ij] = H1
922 * (G01 * vrtmp
923 + G11 * vstmp
924 + G12 * vttmp);
925 rvt = H1
926 * (G02 * vrtmp
927 + G12 * vstmp
928 + G22 * vttmp);
929
930 shwr[sh + ij] = H1
931 * (G00 * wrtmp
932 + G01 * wstmp
933 + G02 * wttmp);
934 shws[sh + ij] = H1
935 * (G01 * wrtmp
936 + G11 * wstmp
937 + G12 * wttmp);
938 rwt = H1
939 * (G02 * wrtmp
940 + G12 * wstmp
941 + G22 * wttmp);
942
944
945 T uwijke = 0.0;
946 T vwijke = 0.0;
947 T wwijke = 0.0;
948#pragma unroll
949 for (int l = 0; l < LX; l++){
950 uwijke += shur[sh + l+j*LX] * shdx[l+i*LX];
951 ruw[l] += rut * shdz[k+l*LX];
952 uwijke += shus[sh + i+l*LX] * shdy[l + j*LX];
953
954 vwijke += shvr[sh + l+j*LX] * shdx[l+i*LX];
955 rvw[l] += rvt * shdz[k+l*LX];
956 vwijke += shvs[sh + i+l*LX] * shdy[l + j*LX];
957
958 wwijke += shwr[sh + l+j*LX] * shdx[l+i*LX];
959 rww[l] += rwt * shdz[k+l*LX];
960 wwijke += shws[sh + i+l*LX] * shdy[l + j*LX];
961 }
962 ruw[k] += uwijke;
963 rvw[k] += vwijke;
964 rww[k] += wwijke;
965 }
966 if (active) {
967#pragma unroll
968 for (int k = 0; k < LX; ++k){
969 au[ij + k*LX*LX + ele] = ruw[k];
970 av[ij + k*LX*LX + ele] = rvw[k];
971 aw[ij + k*LX*LX + ele] = rww[k];
972 }
973 }
974}
975
976template< typename T, const int LX, const int EB >
979 T * __restrict__ av,
980 T * __restrict__ aw,
981 const T * __restrict__ u,
982 const T * __restrict__ v,
983 const T * __restrict__ w,
984 const T * __restrict__ dx,
985 const T * __restrict__ dy,
986 const T * __restrict__ dz,
987 const T * __restrict__ h1,
988 const T * __restrict__ g11,
989 const T * __restrict__ g22,
990 const T * __restrict__ g33,
991 const T * __restrict__ g12,
992 const T * __restrict__ g13,
993 const T * __restrict__ g23,
994 const int nelv) {
995
996 /* Element independent, one copy per block */
997 __shared__ T shdx[LX * (LX+1)];
998 __shared__ T shdy[LX * (LX+1)];
999 __shared__ T shdz[LX * (LX+1)];
1000
1001 /* One slice per element in the block */
1002 __shared__ T shu[EB * LX * (LX+1)];
1003 __shared__ T shur[EB * LX * LX];
1004 __shared__ T shus[EB * LX * (LX+1)];
1005
1006 __shared__ T shv[EB * LX * (LX+1)];
1007 __shared__ T shvr[EB * LX * LX];
1008 __shared__ T shvs[EB * LX * (LX+1)];
1009
1010 __shared__ T shw[EB * LX * (LX+1)];
1011 __shared__ T shwr[EB * LX * LX];
1012 __shared__ T shws[EB * LX * (LX+1)];
1013
1014 static_assert(sizeof(shdx) +
1015 sizeof(shdy) +
1016 sizeof(shdz) +
1017 sizeof(shu) +
1018 sizeof(shur) +
1019 sizeof(shus) +
1020 sizeof(shv) +
1021 sizeof(shvr) +
1022 sizeof(shvs) +
1023 sizeof(shw) +
1024 sizeof(shwr) +
1025 sizeof(shws)
1026 <= NEKO_EB_MAX_LDS,
1027 "kstep block exceeds the LDS budget");
1028
1029 T ru[LX];
1030 T rv[LX];
1031 T rw[LX];
1032
1033 T ruw[LX];
1034 T rvw[LX];
1035 T rww[LX];
1036
1037 T rut;
1038 T rvt;
1039 T rwt;
1040
1041 /* At EB == 1 the grid covers nelv exactly, so the blocking bookkeeping is
1042 constant folded away and the kernel is exactly what it was before */
1043 const int eb = (EB == 1) ? 0 : threadIdx.z;
1044 const int e_blk = blockIdx.x * EB + eb;
1045 const bool active = (EB == 1) ? true : (e_blk < nelv);
1046 const int e = active ? e_blk : (nelv - 1);
1047 const int j = threadIdx.y;
1048 const int i = threadIdx.x;
1049 const int ij = i + j*LX;
1050 const int ij_p = i + j*(LX+1);
1051 const int sh = eb*LX*LX;
1052 const int sh_p = eb*LX*(LX+1);
1053 const int ele = e*LX*LX*LX;
1054
1055 if (eb == 0) {
1056 shdx[ij_p] = dx[ij];
1057 shdy[ij_p] = dy[ij];
1058 shdz[ij_p] = dz[ij];
1059 }
1060
1061#pragma unroll
1062 for(int k = 0; k < LX; ++k){
1063 ru[k] = u[ij + k*LX*LX + ele];
1064 ruw[k] = 0.0;
1065
1066 rv[k] = v[ij + k*LX*LX + ele];
1067 rvw[k] = 0.0;
1068
1069 rw[k] = w[ij + k*LX*LX + ele];
1070 rww[k] = 0.0;
1071 }
1072
1073
1074 __syncthreads();
1075#pragma unroll
1076 for (int k = 0; k < LX; ++k){
1077 const int ijk = ij + k*LX*LX;
1078 const T G00 = g11[ijk+ele];
1079 const T G11 = g22[ijk+ele];
1080 const T G22 = g33[ijk+ele];
1081 const T G01 = g12[ijk+ele];
1082 const T G02 = g13[ijk+ele];
1083 const T G12 = g23[ijk+ele];
1084 const T H1 = h1[ijk+ele];
1085 T uttmp = 0.0;
1086 T vttmp = 0.0;
1087 T wttmp = 0.0;
1088 shu[sh_p + ij_p] = ru[k];
1089 shv[sh_p + ij_p] = rv[k];
1090 shw[sh_p + ij_p] = rw[k];
1091#pragma unroll
1092 for (int l = 0; l < LX; l++){
1093 uttmp += shdz[k+l*(LX+1)] * ru[l];
1094 vttmp += shdz[k+l*(LX+1)] * rv[l];
1095 wttmp += shdz[k+l*(LX+1)] * rw[l];
1096 }
1098
1099 T urtmp = 0.0;
1100 T ustmp = 0.0;
1101
1102 T vrtmp = 0.0;
1103 T vstmp = 0.0;
1104
1105 T wrtmp = 0.0;
1106 T wstmp = 0.0;
1107#pragma unroll
1108 for (int l = 0; l < LX; l++){
1109 urtmp += shdx[i+l*(LX+1)] * shu[sh_p + l+j*(LX+1)];
1110 ustmp += shdy[j+l*(LX+1)] * shu[sh_p + i+l*(LX+1)];
1111
1112 vrtmp += shdx[i+l*(LX+1)] * shv[sh_p + l+j*(LX+1)];
1113 vstmp += shdy[j+l*(LX+1)] * shv[sh_p + i+l*(LX+1)];
1114
1115 wrtmp += shdx[i+l*(LX+1)] * shw[sh_p + l+j*(LX+1)];
1116 wstmp += shdy[j+l*(LX+1)] * shw[sh_p + i+l*(LX+1)];
1117 }
1118
1119 shur[sh + ij] = H1
1120 * (G00 * urtmp
1121 + G01 * ustmp
1122 + G02 * uttmp);
1123 shus[sh_p + ij_p] = H1
1124 * (G01 * urtmp
1125 + G11 * ustmp
1126 + G12 * uttmp);
1127 rut = H1
1128 * (G02 * urtmp
1129 + G12 * ustmp
1130 + G22 * uttmp);
1131
1132 shvr[sh + ij] = H1
1133 * (G00 * vrtmp
1134 + G01 * vstmp
1135 + G02 * vttmp);
1136 shvs[sh_p + ij_p] = H1
1137 * (G01 * vrtmp
1138 + G11 * vstmp
1139 + G12 * vttmp);
1140 rvt = H1
1141 * (G02 * vrtmp
1142 + G12 * vstmp
1143 + G22 * vttmp);
1144
1145 shwr[sh + ij] = H1
1146 * (G00 * wrtmp
1147 + G01 * wstmp
1148 + G02 * wttmp);
1149 shws[sh_p + ij_p] = H1
1150 * (G01 * wrtmp
1151 + G11 * wstmp
1152 + G12 * wttmp);
1153 rwt = H1
1154 * (G02 * wrtmp
1155 + G12 * wstmp
1156 + G22 * wttmp);
1157
1159
1160 T uwijke = 0.0;
1161 T vwijke = 0.0;
1162 T wwijke = 0.0;
1163#pragma unroll
1164 for (int l = 0; l < LX; l++){
1165 uwijke += shur[sh + l+j*LX] * shdx[l+i*(LX+1)];
1166 ruw[l] += rut * shdz[k+l*(LX+1)];
1167 uwijke += shus[sh_p + i+l*(LX+1)] * shdy[l + j*(LX+1)];
1168
1169 vwijke += shvr[sh + l+j*LX] * shdx[l+i*(LX+1)];
1170 rvw[l] += rvt * shdz[k+l*(LX+1)];
1171 vwijke += shvs[sh_p + i+l*(LX+1)] * shdy[l + j*(LX+1)];
1172
1173 wwijke += shwr[sh + l+j*LX] * shdx[l+i*(LX+1)];
1174 rww[l] += rwt * shdz[k+l*(LX+1)];
1175 wwijke += shws[sh_p + i+l*(LX+1)] * shdy[l + j*(LX+1)];
1176 }
1177 ruw[k] += uwijke;
1178 rvw[k] += vwijke;
1179 rww[k] += wwijke;
1180 }
1181 if (active) {
1182#pragma unroll
1183 for (int k = 0; k < LX; ++k){
1184 au[ij + k*LX*LX + ele] = ruw[k];
1185 av[ij + k*LX*LX + ele] = rvw[k];
1186 aw[ij + k*LX*LX + ele] = rww[k];
1187 }
1188 }
1189}
1190
1223#if defined(__gfx90a__) || defined(__gfx942__)
1224
1225/* Matrix-core vector axhelm for one element of order LX-1 (T = float or
1226 * double), see ax_helm_mfma_elem for the block geometry this shares. */
1227template< typename T, const int LX, const int NWF, const int TILE >
1229 T * __restrict__ av,
1230 T * __restrict__ aw,
1231 const T * __restrict__ u,
1232 const T * __restrict__ v,
1233 const T * __restrict__ w,
1234 const T * __restrict__ dx,
1235 const T * __restrict__ dy,
1236 const T * __restrict__ dz,
1237 const T * __restrict__ h1,
1238 const T * __restrict__ g11,
1239 const T * __restrict__ g22,
1240 const T * __restrict__ g33,
1241 const T * __restrict__ g12,
1242 const T * __restrict__ g13,
1243 const T * __restrict__ g23,
1244 const int nelv) {
1245 const int LX2 = LX * LX;
1246 const int LX3 = LX * LX * LX;
1247
1248 /* NWF wavefronts per block, WPE of them cooperating on one element and the
1249 block covering EB elements, exactly as in ax_helm_mfma_elem */
1250 enum { PAD = NEKO_MFMA_PAD_N(LX, sizeof(T)),
1251 CUBE = NEKO_MFMA_CUBE_N(LX, sizeof(T)),
1252 DMAT = NEKO_MFMA_DMAT_N(LX, sizeof(T)),
1253 EB = NEKO_MFMA_EB_N(NWF, LX, sizeof(T)),
1254 WPE = NWF / EB,
1255 GNTHR = WPE * 64, /* threads serving one element */
1256 SPT = NEKO_MFMA_SPT_N(WPE, LX, sizeof(T)),
1257 /* Elements the batched single precision tile contracts per
1258 call, from the wavefronts of the first element of each
1259 batch; one for every other tile, see NEKO_MFMA_NEL_N */
1260 NEL = NEKO_MFMA_NEL_N(EB, LX, sizeof(T), TILE),
1261 /* Geometry in registers across the components, or re-read per
1262 component? The same macro the tuner logs with, so the reported
1263 mode cannot drift from the compiled one */
1264 GREG = NEKO_MFMA_VECTOR_GREG_N(SPT, sizeof(T)),
1265 /* One slot when the factors are re-read, so the arrays below cost
1266 nothing in that case */
1267 NG = GREG ? SPT : 1 };
1268 static_assert(WPE * EB == NWF,
1269 "wavefronts per block must split evenly over the elements");
1270
1274 __shared__ T shc[EB * CUBE]; // component in, later its result out
1275 __shared__ T shr[EB * CUBE]; // d/dr -> Sr
1276 __shared__ T shs[EB * CUBE]; // d/ds -> Ss
1277 __shared__ T sht[EB * CUBE]; // d/dt -> St
1278
1279 static_assert(sizeof(shdx) + sizeof(shdy) + sizeof(shdz) +
1280 sizeof(shc) + sizeof(shr) + sizeof(shs) + sizeof(sht)
1281 <= NEKO_EB_MAX_LDS,
1282 "mfma vector block exceeds the shared memory budget");
1283
1284 const int lane = threadIdx.x; // 0..63 : lane within a wavefront
1285 const int wf = threadIdx.y; // 0..NWF-1 : which wavefront
1286 const int tid = wf * 64 + lane; // 0..NWF*64-1 : block-wide thread id
1287 const int nthr = NWF * 64;
1288
1289 const int eb = wf / WPE; // which element this wavefront serves
1290 const int sub = wf % WPE; // its rank among that element's waves
1291 const int gtid = sub * 64 + lane; // thread id within the element group
1292
1293 /* Threads past the last element still have to reach the block wide
1294 barriers, so clamp their reads and drop their stores rather than
1295 returning early. At EB == 1 the grid covers nelv exactly and this is
1296 constant folded away */
1297 const int e_blk = blockIdx.x * EB + eb;
1298 const bool active = (EB == 1) ? true : (e_blk < nelv);
1299 const int e = active ? e_blk : (nelv - 1);
1300 const int ele = e * LX3;
1301 const int sh = eb * CUBE;
1302
1303 /* The element point behind each of this thread's LDS slots, decoded once,
1304 see ax_helm_mfma_elem */
1305 int goff[SPT];
1306#pragma unroll
1307 for (int q = 0; q < SPT; q++) {
1308 const int sl = gtid + q * GNTHR;
1310 }
1311
1312 /* Reference derivative matrices, one copy shared by every element. The
1313 staged copy carries the padded column stride, so the linear index of the
1314 caller's LX x LX copy is decoded here; the global reads stay contiguous
1315 and it is the LDS side that gains the stride, which is the point. */
1316 for (int p = tid; p < LX2; p += nthr) {
1317 const int sd = mfma_dmat_idx<LX, PAD>(p % LX, p / LX);
1318 shdx[sd] = dx[p];
1319 shdy[sd] = dy[p];
1320 shdz[sd] = dz[p];
1321 }
1322
1323 /* The geometric factors, read once and reused by all three components. The
1324 pointwise pass below strides the slots the same way, so the value for slot
1325 gtid + q * GNTHR stays in slot q and no index array is needed */
1326 T rG00[NG], rG11[NG], rG22[NG];
1327 T rG01[NG], rG02[NG], rG12[NG];
1328 T rH1[NG];
1329
1330 if (GREG) {
1331#pragma unroll
1332 for (int q = 0; q < NG; q++) {
1333 const int gp = (goff[q] >= 0) ? (goff[q] + ele) : ele;
1334 rG00[q] = g11[gp];
1335 rG11[q] = g22[gp];
1336 rG22[q] = g33[gp];
1337 rG01[q] = g12[gp];
1338 rG02[q] = g13[gp];
1339 rG12[q] = g23[gp];
1340 rH1[q] = h1[gp];
1341 }
1342 }
1343
1344#pragma unroll
1345 for (int c = 0; c < 3; c++) {
1346 /* Selected rather than indexed out of a pointer array: an array of
1347 pointers indexed by a loop counter is only free if the loop is fully
1348 unrolled, and this one carries barriers */
1349 const T * const cin = (c == 0) ? u : (c == 1) ? v : w;
1350 T * const cout = (c == 0) ? au : (c == 1) ? av : aw;
1351
1352 /* Element-local component, staged by the wavefronts that own it, over the
1353 same slot set the write-back at the end of the loop uses -- which is
1354 what makes the barrier between the two unnecessary, see there */
1355#pragma unroll
1356 for (int q = 0; q < SPT; q++) {
1357 if (goff[q] >= 0)
1358 shc[sh + gtid + q * GNTHR] = cin[goff[q] + ele];
1359 }
1360
1361 __syncthreads();
1362
1363 /* Gradient: ur, us, ut in canonical i + SJ*j + SK*k layout */
1364 if (eb % NEL == 0)
1365 mfma_contract_sel<T, LX, 0, false, false, WPE, TILE, PAD, NEL>::run(
1366 shr + sh, shdx, shc + sh, lane, sub);
1367 if (eb % NEL == 0)
1368 mfma_contract_sel<T, LX, 1, false, false, WPE, TILE, PAD, NEL>::run(
1369 shs + sh, shdy, shc + sh, lane, sub);
1370 if (eb % NEL == 0)
1371 mfma_contract_sel<T, LX, 2, false, false, WPE, TILE, PAD, NEL>::run(
1372 sht + sh, shdz, shc + sh, lane, sub);
1373
1374 __syncthreads();
1375
1376 /* Geometry (pointwise): (ur,us,ut) -> (Sr,Ss,St), reusing shr/shs/sht.
1377 The staged component is dead once the gradient is out and does not need
1378 clearing either: the first divergence contraction below overwrites it */
1379#pragma unroll
1380 for (int q = 0; q < SPT; q++) {
1381 if (goff[q] >= 0) {
1382 const int sl = sh + gtid + q * GNTHR;
1383 T G00, G11, G22, G01, G02, G12, H1;
1384 if (GREG) {
1385 /* GREG is a compile time constant, so only one of these two bodies
1386 is emitted; the clamp keeps the index inside the NG == 1 array
1387 that the other one never reads from */
1388 const int r = GREG ? q : 0;
1389 G00 = rG00[r]; G11 = rG11[r]; G22 = rG22[r];
1390 G01 = rG01[r]; G02 = rG02[r]; G12 = rG12[r];
1391 H1 = rH1[r];
1392 } else {
1393 const int gp = goff[q] + ele;
1394 G00 = g11[gp]; G11 = g22[gp]; G22 = g33[gp];
1395 G01 = g12[gp]; G02 = g13[gp]; G12 = g23[gp];
1396 H1 = h1[gp];
1397 }
1398 const T rr = shr[sl], ss = shs[sl], tt = sht[sl];
1399 shr[sl] = H1 * (G00 * rr + G01 * ss + G02 * tt);
1400 shs[sl] = H1 * (G01 * rr + G11 * ss + G12 * tt);
1401 sht[sl] = H1 * (G02 * rr + G12 * ss + G22 * tt);
1402 }
1403 }
1404
1405 __syncthreads();
1406
1407 /* Divergence: Dr^T Sr + Ds^T Ss + Dt^T St, the first one overwriting shc
1408 so that nothing has to clear it, see ax_helm_mfma_elem */
1409 if (eb % NEL == 0)
1410 mfma_contract_sel<T, LX, 0, true, false, WPE, TILE, PAD, NEL>::run(
1411 shc + sh, shdx, shr + sh, lane, sub);
1412 __syncthreads();
1413 if (eb % NEL == 0)
1414 mfma_contract_sel<T, LX, 1, true, true, WPE, TILE, PAD, NEL>::run(
1415 shc + sh, shdy, shs + sh, lane, sub);
1416 __syncthreads();
1417 if (eb % NEL == 0)
1418 mfma_contract_sel<T, LX, 2, true, true, WPE, TILE, PAD, NEL>::run(
1419 shc + sh, shdz, sht + sh, lane, sub);
1420 __syncthreads();
1421
1422 if (active) {
1423#pragma unroll
1424 for (int q = 0; q < SPT; q++) {
1425 if (goff[q] >= 0)
1426 cout[goff[q] + ele] = shc[sh + gtid + q * GNTHR];
1427 }
1428 }
1429
1430 /* Not required as the loops stand: the write-back above and the restage
1431 at the top of the next pass walk the same slot set per thread, so a
1432 thread only restages what it just read out itself, and no wavefront
1433 can run ahead into another's slots. Kept so that re-striding either
1434 loop -- coalescing the write-back over tid rather than gtid, say --
1435 cannot introduce a race silently */
1436 if (c < 2) {
1437 __syncthreads();
1438 }
1439 }
1440}
1441#endif // __gfx90a__ || __gfx942__
1442
1443/*
1444 * Compile-time dispatch onto the vector MFMA element kernel, see the note on
1445 * ax_helm_mfma_dispatch above. The vector operator dispatches every
1446 * 2 <= LX <= 16 and the strategy covers 4 <= LX <= 12, so this no-op is
1447 * reached at compile time for rather more instantiations than the scalar
1448 * one's -- and must stay unreachable at runtime: a no-op times as free and
1449 * would win the comparison, leaving stale values in au, av and aw. The
1450 * autotuner only offers the strategy where mfma_lx_supported() and
1451 * hip_have_mfma() both say yes, which is exactly the set instantiated below.
1452 */
1453template< typename T, const int LX, const int NWF, const int TILE >
1455 __device__ static void run(T *, T *, T *,
1456 const T *, const T *, const T *,
1457 const T *, const T *, const T *, const T *,
1458 const T *, const T *, const T *,
1459 const T *, const T *, const T *, const int) {}
1460};
1461
1462#if defined(__gfx90a__) || defined(__gfx942__)
1463
1464/* Keep in sync with mfma_lx_supported() in mfma_kernel.h */
1465#define NEKO_AX_HELM_MFMA_VECTOR_DISPATCH(TYPE, LXV) \
1466 template< const int NWF, const int TILE > \
1467 struct ax_helm_mfma_vector_dispatch< TYPE, LXV, NWF, TILE > { \
1468 __device__ static void run(TYPE *au, TYPE *av, TYPE *aw, \
1469 const TYPE *u, const TYPE *v, const TYPE *w, \
1470 const TYPE *dx, const TYPE *dy, \
1471 const TYPE *dz, const TYPE *h1, \
1472 const TYPE *g11, const TYPE *g22, \
1473 const TYPE *g33, const TYPE *g12, \
1474 const TYPE *g13, const TYPE *g23, \
1475 const int nelv) { \
1476 ax_helm_mfma_vector_elem< TYPE, LXV, NWF, TILE >(au, av, aw, u, v, w, \
1477 dx, dy, dz, h1, \
1478 g11, g22, g33, \
1479 g12, g13, g23, nelv); \
1480 } \
1481 }
1482
1503
1504#endif // __gfx90a__ || __gfx942__
1505
1506/* Named after its CUDA counterpart ax_helm_kernel_dmma_vector rather than
1507 after the adjacent ax_helm_kernel_vector_kstep, so that the matrix unit
1508 kernels read alike across the two backends.
1509
1510 Bare __launch_bounds__ rather than NEKO_EB_BOUNDS, for the reason given on
1511 ax_helm_kernel_mfma: the three waves per SIMD the kstep kernels ask for
1512 would tighten the register budget of a kernel whose occupancy is already
1513 set by its LDS footprint */
1514template< typename T, const int LX, const int NWF, const int TILE >
1517 T * __restrict__ av,
1518 T * __restrict__ aw,
1519 const T * __restrict__ u,
1520 const T * __restrict__ v,
1521 const T * __restrict__ w,
1522 const T * __restrict__ dx,
1523 const T * __restrict__ dy,
1524 const T * __restrict__ dz,
1525 const T * __restrict__ h1,
1526 const T * __restrict__ g11,
1527 const T * __restrict__ g22,
1528 const T * __restrict__ g33,
1529 const T * __restrict__ g12,
1530 const T * __restrict__ g13,
1531 const T * __restrict__ g23,
1532 const int nelv) {
1533
1535 dx, dy, dz, h1,
1536 g11, g22, g33,
1537 g12, g13, g23, nelv);
1538}
1539
1540template< typename T >
1542 T * __restrict__ av,
1543 T * __restrict__ aw,
1544 const T * __restrict__ u,
1545 const T * __restrict__ v,
1546 const T * __restrict__ w,
1547 const T * __restrict__ h2,
1548 const T * __restrict__ B,
1549 const int n) {
1550
1551 const int idx = blockIdx.x * blockDim.x + threadIdx.x;
1552 const int str = blockDim.x * gridDim.x;
1553
1554 for (int i = idx; i < n; i += str) {
1555 au[i] = au[i] + h2[i] * B[i] * u[i];
1556 av[i] = av[i] + h2[i] * B[i] * v[i];
1557 aw[i] = aw[i] + h2[i] * B[i] * w[i];
1558 }
1559
1560}
1561#endif // __MATH_AX_HELM_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)
shdx[ij]
const bool active
const int ij_p
__shared__ T shus[EB *LX *LX]
__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__ g33
__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__ g13
T rww[LX]
__global__ void T *__restrict__ av
const int sh
T ru[LX]
const int eb
__shared__ T shv[EB *LX *LX]
T rv[LX]
__shared__ T shwr[EB *LX *LX]
__global__ void ax_helm_kernel_1d(T *__restrict__ w, const T *__restrict__ u, const T *__restrict__ dx, const T *__restrict__ dy, const T *__restrict__ dz, const T *__restrict__ dxt, const T *__restrict__ dyt, const T *__restrict__ dzt, const T *__restrict__ h1, const T *__restrict__ g11, const T *__restrict__ g22, const T *__restrict__ g33, const T *__restrict__ g12, const T *__restrict__ g13, const T *__restrict__ g23)
const int sh_p
__shared__ T shur[EB *LX *LX]
const int i
__shared__ T shvs[EB *LX *LX]
const int ij
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ g22
T rwt
T rw[LX]
__shared__ T shw[EB *LX *LX]
__shared__ T shws[EB *LX *LX]
__shared__ T shu[EB *LX *LX]
T rut
T ruw[LX]
const int e
__global__ void T *__restrict__ T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ w
const int e_blk
T rvt
__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__ g23
T rvw[LX]
__global__ void T *__restrict__ T *__restrict__ aw
__shared__ T shvr[EB *LX *LX]
__global__ void const T *__restrict__ u
const int ele
__global__ void const T *__restrict__ const T *__restrict__ dx
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ dz
const int j
__shared__ T shdz[LX *LX]
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ dy
__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__ g12
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ h1
__syncthreads()
__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 T *__restrict__ T *__restrict__ const T *__restrict__ const T *__restrict__ v
__global__ void ax_helm_kernel_vector_part2(T *__restrict__ au, T *__restrict__ av, T *__restrict__ aw, const T *__restrict__ u, const T *__restrict__ v, const T *__restrict__ w, const T *__restrict__ h2, const T *__restrict__ B, const int n)
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ g11
__shared__ T shdy[LX *LX]
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ dyt
__shared__ T shdzt[LX *LX]
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ dzt
__shared__ T shdyt[LX *LX]
__global__ void const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ const T *__restrict__ dxt
#define NEKO_EB_BOUNDS(NT)
Definition elem_block.h:95
__global__ void __launch_bounds__((LX *LX *EB)) ax_helm_kernel_kstep(T *__restrict__ w
#define NEKO_EB_MAX_LDS
Definition elem_block.h:79
#define NEKO_MFMA_CUBE_N(LX, SZ)
#define NEKO_MFMA_NEL_N(EB, LX, SZ, TILE)
#define NEKO_MFMA_EB_N(NWF, LX, SZ)
#define NEKO_MFMA_VECTOR_GREG_N(SPT, SZ)
#define NEKO_MFMA_SPT_N(WPE, LX, SZ)
#define NEKO_MFMA_PAD_N(LX, SZ)
#define NEKO_MFMA_DMAT_N(LX, SZ)
static __device__ void run(T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const int)
static __device__ void run(T *, T *, T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const T *, const int)