Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
mfma_kernel.h
Go to the documentation of this file.
1#ifndef __MATH_MFMA_KERNEL_H__
2#define __MATH_MFMA_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
92#include <stdlib.h>
93#include <string.h>
94#include <hip/hip_runtime.h>
96#include <device/hip/check.h>
97/* NEKO_EB_MAX_LDS, which the elements-per-block ladder below is clamped by */
98#include "elem_block.h"
99
100/*
101 * Reports whether the device code really was compiled for a matrix core
102 * architecture, i.e. whether the __gfx90a__ / __gfx942__ guard below was true
103 * in the device pass.
104 *
105 * This is not the same question as "does the device have matrix cores". The
106 * contraction primitives and their call sites are all guarded on those
107 * macros, so a build whose offload arch does not include the running device's
108 * -- or a code object selected from a fat binary built for something else --
109 * turns the whole strategy into a silent no-op: the kernel launches, writes
110 * nothing, and leaves stale values in the output. That fails as bad results
111 * rather than as an error, which is the worst way for it to fail. Checking it
112 * from the device removes the guesswork.
113 */
114/* static, not merely file scope by convention: this header is included by
115 every operator that offers an MFMA strategy -- ax_helm, dudxyz, opgrad,
116 conv1 and cdtp -- and a non-template __global__ with external linkage is
117 then defined once per translation unit, which the linker rejects as a
118 multiple definition. Internal linkage gives each unit its own copy, which
119 is what the rest of this header already relies on. */
120static __global__ void hip_mfma_arch_probe(int * flag) {
121#if defined(__gfx90a__) || defined(__gfx942__)
122 *flag = 1;
123#else
124 *flag = 0;
125#endif
126}
127
136static inline bool hip_have_mfma() {
137 static int cached = -1;
138 if (cached < 0) {
139 int dev = 0;
141 cached = 0;
142 if (hipGetDevice(&dev) == hipSuccess &&
144 (strstr(prop.gcnArchName, "gfx90a") != NULL ||
145 strstr(prop.gcnArchName, "gfx942") != NULL)) {
146 int *d_flag = NULL;
147 int flag = 0;
148 if (hipMalloc(&d_flag, sizeof(int)) == hipSuccess) {
149 if (hipMemcpy(d_flag, &flag, sizeof(int),
152 d_flag);
153 if (hipGetLastError() == hipSuccess &&
154 hipMemcpy(&flag, d_flag, sizeof(int),
156 cached = flag;
157 }
158 }
159 /* Unlike the queries above, a failure here is not "the strategy is
160 unavailable" -- the pointer came from a hipMalloc that succeeded, so
161 a bad free means the context is broken. Checked rather than folded
162 into cached, and checked rather than discarded: hipFree is
163 nodiscard */
165 }
166 }
167 }
168 return cached == 1;
169}
170
177/*
178 * Both precisions are offered, and both are verified against a reference on
179 * gfx90a (mfma_probe, 144 configurations, 2026-08-22). They do not share a
180 * code path: f64 goes through the batched 4x4x4 tile, f32 has no 4x4x4
181 * instruction and uses the 16x16x4 one. If a future part disagrees, excluding
182 * a precision here is a one-line change.
183 */
184template < const int LX >
185static inline bool mfma_lx_supported() {
186 return (sizeof(real) == 8 || sizeof(real) == 4) && (LX >= 4) && (LX <= 12);
187}
188
189/*
190 * Wavefronts per block for the MFMA kernels. Candidate C selects 2^C
191 * wavefronts, i.e. 1, 2, 4 or 8.
192 *
193 * Two things are being traded. The contraction stripes its N column groups
194 * across wavefronts, and there are only NGROUPS = ceil(LX^2/16) of them --
195 * one at LX = 4, four at LX = 8 -- so past that count the extra wavefronts
196 * idle through the matrix core work. The staging and pointwise loops, on the
197 * other hand, keep scaling: at LX = 8, eight wavefronts is 512 threads for
198 * 512 points, one each, which is why the branch this came from defaulted to
199 * eight. The measured LX = 8 curve was still improving at four, hence the
200 * fourth candidate.
201 */
202/*
203 * The candidate space is two dimensional: the wavefronts per block above, and
204 * which matrix core tile the contraction is issued on. Candidate C encodes
205 * both -- NWF = 2^(C mod 4) and TILE = C / 4 -- so one selector still names a
206 * whole geometry and candidates 0..3 mean exactly what they always did.
207 *
208 * TILE 0 is the precision's default tile: the batched 4x4x4 in double
209 * precision, the 16x16x4 in single, where there is no 4x4x4 counterpart
210 * (its f32 sibling has K = 1). TILE 1 is the 16x16x4 tile in both, so in
211 * single precision the two are the same code and only TILE 0 is offered --
212 * see mfma_tile_offered().
213 *
214 * WHY THE TILE IS MEASURED RATHER THAN CHOSEN. It used to be a compile-time
215 * #ifdef, on the argument that the 4x4x4 tile fills M = LX < 16 exactly where
216 * the 16x16x4 one wastes half its rows at LX = 8. That argument tacitly
217 * assumes the two instructions run at the same rate, and they do not. From
218 * AMD's own matrix instruction calculator, identically on gfx90a and gfx942:
219 *
220 * v_mfma_f64_16x16x4f64 2048 flop / 32 cyc = 256 flop/CU/cycle, 0 wait
221 * v_mfma_f64_4x4x4f64 512 flop / 16 cyc = 128 flop/CU/cycle, 4 waits
222 *
223 * The small tile is rate-halved -- 128 flop/CU/cycle is the f64 *vector* rate,
224 * so it surrenders the whole reason to use a matrix core in double precision
225 * -- and a chain of them into one accumulator, which is exactly what
226 * mfma_contract_4x4's K loop is, owes four cycles per step where the large
227 * tile owes none. Counting issues per element per contraction (4x4x4:
228 * ceil(LX/4) M-tiles x groups x ceil(LX/4) K-steps at 16 cycles; 16x16x4:
229 * groups x K-steps at 32) the M-utilisation gain is exactly cancelled at
230 * LX = 5..8 and reversed beyond it: 4x4x4 wins 2x at LX = 4, loses ~11% at
231 * LX = 5..8 once the accumulate waits are counted, and loses 1.5x at
232 * LX = 9..12. It also re-reads the B operand once per M-tile, so it costs 2x
233 * the operand traffic at LX = 5..8 and 3x at LX = 9..12 on a kernel measured
234 * at 69-72% of the memory roof.
235 *
236 * So the analytical answer is "4x4x4 at LX = 4, 16x16x4 above it", which is
237 * not what the old default did -- and it is an analytical answer, of the same
238 * kind as the one it replaces. Both tiles are hardware verified against a CPU
239 * reference and produce bit-identical f64 results, so trying both costs
240 * nothing but tuning time. Hence: measure it.
241 */
242/*
243 * THE 16-WAVEFRONT RUNG. The ladder used to stop at eight wavefronts, 512
244 * threads, and that ceiling is what the strategy was losing on at LX = 8
245 * rather than anything to do with matrix cores. The 1D kernel that beats it
246 * there stages MORE LDS -- 6*LX^2 + 4*LX^3 against this kernel's
247 * 3*LX^2 + 4*LX^3 -- but runs CHUNKS threads on one element, so its 1024- and
248 * 512-thread candidates reach 8.00 and 6.00 wavefronts per SIMD where this
249 * ladder tops out at 3.00. A block shape the tuner cannot express is a block
250 * shape it cannot choose, so the rung is added rather than argued about.
251 *
252 * 1024 threads is the CDNA workgroup maximum, so this is the last rung there
253 * is. It is only reachable where the LDS budget allows it, which is what the
254 * clamp in NEKO_MFMA_EB_N below is for: at LX = 8 in double precision the
255 * unclamped EB = NWF/NGROUPS = 4 would ask for 4 elements x 4 cubes and blow
256 * the 64 kB workgroup limit -- as a compile error in the kernel's
257 * static_assert, not as a bad launch, but a compile error all the same.
258 */
259#define NEKO_MFMA_NWF_CANDIDATES 5
260#define NEKO_MFMA_TILE_CANDIDATES 2
261#define NEKO_MFMA_CANDIDATES \
262 (NEKO_MFMA_NWF_CANDIDATES * NEKO_MFMA_TILE_CANDIDATES)
263#define NEKO_MFMA_NWF(C) (1 << ((C) % NEKO_MFMA_NWF_CANDIDATES))
264#define NEKO_MFMA_TILE(C) ((C) / NEKO_MFMA_NWF_CANDIDATES)
265#define NEKO_MFMA_NTHRDS(C) dim3(64, NEKO_MFMA_NWF(C), 1)
266
267/*
268 * LDS PADDING OF THE STAGED CUBE.
269 *
270 * The cube is stored i + SJ*j + SK*k with SJ = LX + PAD and SK = SJ*LX. PAD
271 * is not a free parameter: CDNA's LDS is 32 banks of one dword, a b32 access
272 * is serviced 32 lanes at a time and a b64 access 16 lanes at a time (16
273 * lanes x 2 dwords = 32 dwords), and distinct addresses landing on one bank
274 * inside such a group serialise. A contraction along AXIS = 0 walks its
275 * free index n = (j,k) across the lanes, so consecutive lanes are SJ elements
276 * apart, and the whole question is what SJ does modulo the bank count:
277 *
278 * SJ odd -> f64 lanes cover 16 distinct bank pairs, f32 lanes 32 distinct
279 * banks. Conflict free.
280 * SJ even -> the stride and the bank count share a factor and the group
281 * collapses onto a few banks. At LX = 8 in double precision that
282 * is an 8-WAY conflict on both the B read and the D store.
283 *
284 * So padding an even LX by one is what removes the conflict, and padding an
285 * odd LX would introduce one -- the opposite of the usual "always pad by one"
286 * reflex. AXIS = 2 is conflict free either way (its index is n + SK*p, unit
287 * stride across lanes), AXIS = 1 sits in between.
288 *
289 * MODELLED, NOT MEASURED: a bank simulator over every lane of every LDS
290 * access to the CUBE -- the six contractions' operand reads and result
291 * stores, and the three linear passes -- for every LX in 4..12, both
292 * precisions, both tiles. The derivative matrix is padded by the same policy
293 * and is counted separately, see NEKO_MFMA_SD_N. Cycles per element, best
294 * wavefront shape, B-hoist on, padded-slot linear passes:
295 *
296 * LX = 8 f64 4x4x4 : 1696 -> 836 (-51%)
297 * LX = 8 f32 16x16 : 816 -> 642 (-21%)
298 * LX = 12 f64 4x4x4 : 3996 -> 2808 (-30%)
299 * LX = 4 f64 4x4x4 : 180 -> 136 (-24%)
300 * LX = 4 f32 16x16 : 102 -> 108 (+6%, the one regression)
301 * LX = 6, 10 : within 1% either way
302 *
303 * Hence the policy below: pad where LX is a multiple of four, which is where
304 * the conflict is worst and the win is large, and leave LX = 4 in single
305 * precision alone. That last exception is not cosmetic -- LX = 4 f32 is the
306 * only configuration where this strategy has ever been measured to win
307 * anything (1.9% over 1D on gfx90a), so it does not get handed a 6%
308 * regression on a model. LX odd is never padded; LX = 6 and 10 gain nothing
309 * because 6 and 10 are already only 2-way, and padding them costs a bigger
310 * cube.
311 *
312 * -DNEKO_MFMA_PAD=0 forces the old unpadded layout and -DNEKO_MFMA_PAD=1 pads
313 * every even LX, for an A/B against the model. Odd LX is never padded whatever
314 * the setting, because there it is known to make things worse.
315 *
316 * Padding is a property of the layout, not of the lane mapping: which lane
317 * handles which (m, n) is untouched, so it is exactly the kind of change a
318 * host reference catches -- unlike a lane-layout change, where an internally
319 * consistent error cancels in write-back (see mfma_contract_4x4).
320 */
321#ifdef NEKO_MFMA_PAD
322#define NEKO_MFMA_PAD_N(LX, SZ) (((LX) % 2 == 0) ? (NEKO_MFMA_PAD) : 0)
323#else
324#define NEKO_MFMA_PAD_N(LX, SZ) \
325 ((((LX) % 4 == 0) && !((LX) == 4 && (SZ) == 4)) ? 1 : 0)
326#endif
327/* j-stride, k-stride and total slots of one staged cube */
328#define NEKO_MFMA_SJ_N(LX, SZ) ((LX) + NEKO_MFMA_PAD_N(LX, SZ))
329#define NEKO_MFMA_SK_N(LX, SZ) (NEKO_MFMA_SJ_N(LX, SZ) * (LX))
330#define NEKO_MFMA_CUBE_N(LX, SZ) (NEKO_MFMA_SK_N(LX, SZ) * (LX))
331/*
332 * Column stride and size of a staged reference derivative matrix, which takes
333 * the same padding for the same reason.
334 *
335 * D(row, col) = dmat[row + SD*col]. The three divergence contractions read it
336 * transposed, D(l, m), so there the LANE index m walks the column stride --
337 * 16 lanes SD elements apart in the 16x16x4 tile, four in the 4x4x4 one -- and
338 * an even stride puts them on the same banks exactly as it does in the cube.
339 * It is the same policy rather than a second one because it is win or neutral
340 * at every order the policy pads, modelled the same way:
341 *
342 * LX = 8 f64 : 4x4x4 576 -> 384 cycles, 16x16 480 -> 192
343 * LX = 12 f64 : 16x16 1296 -> 648
344 * LX = 4, and both precisions at every unpadded order : unchanged
345 *
346 * A rule of "pad every even order" would also take LX = 10 f64 from 714 to
347 * 504, but costs LX = 10 f32 a 42% increase, which is the same reason the cube
348 * policy stops at multiples of four.
349 */
350#define NEKO_MFMA_SD_N(LX, SZ) NEKO_MFMA_SJ_N(LX, SZ)
351#define NEKO_MFMA_DMAT_N(LX, SZ) (NEKO_MFMA_SD_N(LX, SZ) * (LX))
352
353/*
354 * Whether candidate C's tile is a distinct thing to measure in this build.
355 *
356 * TILE 1 is the 16x16x4 tile, which is what TILE 0 already resolves to in
357 * single precision and under -DMFMA_F64_USE_16X16, so offering it there would
358 * time the same kernel twice. The launch macros instantiate both regardless --
359 * every (precision, LX, candidate) has to compile -- and this only decides
360 * what the sweep and the env pin will run.
361 */
362static inline bool mfma_tile_offered(const int c) {
363 if (NEKO_MFMA_TILE(c) == 0) {
364 return true;
365 }
366#ifdef MFMA_F64_USE_16X16
367 return false;
368#else
369 return sizeof(real) == 8;
370#endif
371}
372
373/* Which tile candidate C names, for the tuner log */
374static inline const char *mfma_tile_name(const int c) {
375#ifdef MFMA_F64_USE_16X16
376 (void) c;
377 return "16x16";
378#else
379 if (NEKO_MFMA_TILE(c) != 0) {
380 return "16x16";
381 }
382 return (sizeof(real) == 8) ? "4x4x4" : "16x16";
383#endif
384}
385
386/*
387 * Column groups per contraction -- the wavefront-parallel work one element
388 * offers. Both tiles group N the same way, 16 columns at a time, so this is
389 * ceil(LX^2/16) either way: 1 at LX = 4, 4 at LX = 8, 9 at LX = 12.
390 *
391 * A wavefront beyond that count has no matrix core work left on that element,
392 * which is what the LX = 4 single precision sweep measured: 20.5 / 23.6 /
393 * 34.0 / 60.2 us as NWF went 1, 2, 4, 8, monotonically worse, against
394 * 218 / 157 / 136.4 / 136.4 at LX = 8 where four groups exist. Rather than
395 * cap the sweep, the surplus wavefronts are given their own element: NWF is
396 * read as wavefronts per block, the block covers EB elements and WPE =
397 * NWF/EB wavefronts cooperate on each. At LX = 4 with eight wavefronts that
398 * is eight elements, one each, with nothing idle; at LX = 12 it is one
399 * element and eight cooperating wavefronts, exactly as before. This matters
400 * because p-multigrid smooths at LX = 4 and 2, so low order Ax is hot rather
401 * than incidental.
402 *
403 * EB is the driving quantity and WPE follows from it, not the other way
404 * round: the block is partitioned into EB equal groups, so EB has to divide
405 * NWF exactly or the leftover wavefronts address an element the block does
406 * not own -- past the end of the shared staging arrays, and past the end of
407 * global storage in the last block. EB = NWF/NGROUPS is therefore rounded
408 * down to a power of two, which divides NWF for every candidate since NWF is
409 * itself 2^C. WPE may then exceed NGROUPS -- at LX = 6, NGROUPS = 3 and four
410 * wavefronts give EB = 1, WPE = 4 -- which is harmless: mfma_contract_4x4()
411 * strides the groups with `ng = wf + gp * NWF` under `ng < NGROUPS`, so a
412 * wavefront without a group of its own simply issues no matrix core work,
413 * while still taking its share of the staging and pointwise passes. The
414 * alternative, capping WPE at NGROUPS and letting EB absorb the remainder,
415 * would grow the shared footprint (LX = 10 with eight wavefronts would want
416 * 66 kB) for no gain.
417 */
418#define NEKO_MFMA_NGROUPS(LX) (((LX) * (LX) + 15) / 16)
419
420/*
421 * LDS a block of EB elements occupies: three reference derivative matrices
422 * shared by the whole block, plus four staged cubes per element. Kept here
423 * rather than in the kernels so that the EB ladder below and the kernels'
424 * own static_assert cannot disagree about what fits.
425 */
426#define NEKO_MFMA_LDS_N(EB, LX, SZ) \
427 ((3 * NEKO_MFMA_DMAT_N(LX, SZ) + 4 * (EB) * NEKO_MFMA_CUBE_N(LX, SZ)) * (SZ))
428#define NEKO_MFMA_LDS_FITS(EB, LX, SZ) \
429 (NEKO_MFMA_LDS_N(EB, LX, SZ) <= NEKO_EB_MAX_LDS)
430
431/* Elements per block: surplus wavefronts, rounded down to a power of two so
432 that WPE * EB == NWF exactly, and clamped to what the 64 kB workgroup LDS
433 limit allows. The LDS clamp is not defensive tidiness: at 16 wavefronts the
434 unclamped ladder asks for EB = 4 at LX = 8 and EB = 2 at LX = 10 and 11 in
435 double precision, all three of which overflow the limit and stop the build
436 in the kernels' static_assert. */
437#define NEKO_MFMA_EB_N(NWF, LX, SZ) \
438 ((NWF) / NEKO_MFMA_NGROUPS(LX) >= 16 && NEKO_MFMA_LDS_FITS(16, LX, SZ) ? 16 :\
439 (NWF) / NEKO_MFMA_NGROUPS(LX) >= 8 && NEKO_MFMA_LDS_FITS(8, LX, SZ) ? 8 : \
440 (NWF) / NEKO_MFMA_NGROUPS(LX) >= 4 && NEKO_MFMA_LDS_FITS(4, LX, SZ) ? 4 : \
441 (NWF) / NEKO_MFMA_NGROUPS(LX) >= 2 && NEKO_MFMA_LDS_FITS(2, LX, SZ) ? 2 : 1)
442#define NEKO_MFMA_EB(LX, C) \
443 NEKO_MFMA_EB_N(NEKO_MFMA_NWF(C), LX, sizeof(real))
444/* Wavefronts cooperating on one element */
445#define NEKO_MFMA_WPE(LX, C) (NEKO_MFMA_NWF(C) / NEKO_MFMA_EB(LX, C))
446
447/*
448 * Slots per thread: the CUBE_N slots of one staged cube shared out over the
449 * WPE * 64 threads that serve it. Slots rather than points, because the
450 * staging, pointwise and write-back passes walk the LDS cube at unit stride
451 * and skip the pad slots -- see mfma_slot_point(). Where the cube is not
452 * padded a slot is a point and this is the old points-per-thread count.
453 *
454 * Only the vector operator needs this on the host side, but it belongs here
455 * with the rest of the geometry so that the device enum and the host launcher
456 * cannot drift apart -- which is how the WPE/EB split came to address past the
457 * end of its staging arrays.
458 */
459#define NEKO_MFMA_SPT_N(WPE, LX, SZ) \
460 ((NEKO_MFMA_CUBE_N(LX, SZ) + (WPE) * 64 - 1) / ((WPE) * 64))
461#define NEKO_MFMA_SPT(LX, C) \
462 NEKO_MFMA_SPT_N(NEKO_MFMA_WPE(LX, C), LX, sizeof(real))
463
464/*
465 * Whether the seven geometric factors of each of a thread's points are held in
466 * registers, or read from global memory where they are used. The cost is
467 * 7 * SPT * (sizeof(T)/4) VGPRs, budgeted at a quarter of the 256 a lane
468 * addresses -- which leaves the four cube base pointers, the two index bases,
469 * the contraction accumulator and the seven pointwise temporaries room to
470 * live alongside it.
471 *
472 * It buys two different things in the two Ax-helm kernels, and the budget is
473 * the same for both. In the vector kernel (ax_helm_mfma_vector_elem) it is
474 * reuse: the factors are read once for the three components instead of once
475 * each, which is the reason that operator exists at all. In the scalar kernel
476 * (ax_helm_mfma_elem) there is no reuse to be had -- it is latency: the loads
477 * are issued before the gradient contractions instead of behind the barrier
478 * that follows them, so they are in flight across the matrix core work rather
479 * than exposed after it.
480 *
481 * It follows from LX and the wavefront count rather than being a candidate of
482 * its own, so the two modes cannot be compared directly -- which is why the
483 * tuner reports which one each candidate ran, see NEKO_TUNE_LOG_MFMA_VEC.
484 * Without that the sweep reads as a matrix core result when the step between
485 * two candidates is really a change in memory traffic.
486 */
487#ifndef NEKO_MFMA_VECTOR_GREG_VGPRS
488#define NEKO_MFMA_VECTOR_GREG_VGPRS 64
489#endif
490#define NEKO_MFMA_VECTOR_GREG_N(SPT, SZ) \
491 ((7 * (SPT) * ((SZ) / 4)) <= NEKO_MFMA_VECTOR_GREG_VGPRS)
492#define NEKO_MFMA_VECTOR_GREG(LX, C) \
493 NEKO_MFMA_VECTOR_GREG_N(NEKO_MFMA_SPT(LX, C), sizeof(real))
494
495#define NEKO_MFMA_NBLCKS(NELV, LX, C) \
496 dim3(((NELV) + NEKO_MFMA_EB(LX, C) - 1) / NEKO_MFMA_EB(LX, C), 1, 1)
497
498/*
499 * Whether the autotuner sweeps the MFMA strategy, on by default wherever the
500 * hardware and the polynomial order allow it.
501 *
502 * It was briefly off while the matrix core contraction was known broken -- the
503 * lane layout had the block selector and the contraction index interchanged,
504 * which a "the solver converges" check had failed to catch for a long time.
505 * The layout was measured on gfx90a, corrected, and mfma_contract_4x4 now
506 * reproduces a CPU reference to ~1e-16 over every supported order, axis,
507 * transpose/accumulate mode and wavefront count, so there is no reason to
508 * withhold it from the sweep. Kept as an off switch in the shape of
509 * NEKO_EB_TUNE, for A/B work.
510 */
511static int neko_mfma_sweep()
512{
513 const char *v = getenv("NEKO_MFMA_TUNE");
514
515 if (v != NULL) {
516 return (atoi(v) != 0);
517 }
518 return 1;
519}
520
521/*
522 * Candidate pinned by NEKO_MFMA_NWF and NEKO_MFMA_TILE, or -1 to leave it to
523 * the sweep, see neko_eb_pin() in elem_block_tune.h.
524 *
525 * The two dimensions stay separate variables -- NEKO_MFMA_NWF keeps its old
526 * range and meaning, NEKO_MFMA_TILE adds the tile -- rather than one index
527 * into the combined space, so that a pin written before the tile existed
528 * still selects what it used to. Setting either one pins the candidate;
529 * leaving both unset leaves the whole geometry to the sweep. A tile this
530 * build does not offer falls back to the default one at the requested
531 * wavefront count.
532 */
533static int neko_mfma_pin()
534{
535 const char *v = getenv("NEKO_MFMA_NWF");
536 const char *t = getenv("NEKO_MFMA_TILE");
537 int nwf, tile, c;
538
539 if (v == NULL && t == NULL) {
540 return -1;
541 }
542
543 nwf = (v != NULL) ? atoi(v) : 0;
544 tile = (t != NULL) ? atoi(t) : 0;
546 nwf = 0;
547 }
549 tile = 0;
550 }
552 if (!mfma_tile_offered(c)) {
553 c = nwf;
554 }
555 return c;
556}
557
558/*
559 * The same pin restricted to the wavefront dimension, for the operators that
560 * offer only that one.
561 *
562 * The tile dimension is carried by the Helmholtz operator alone, scalar and
563 * vector: it is the operator with the headroom to be worth the doubled
564 * instantiation count, and the one every measurement so far is about. The
565 * gradient-type operators keep the four wavefront candidates and their
566 * contractions stay on the default tile. This exists so that a
567 * NEKO_MFMA_TILE=1 pin is ignored there *explicitly* rather than by falling
568 * through a switch onto some other candidate.
569 */
571{
572 const char *v = getenv("NEKO_MFMA_NWF");
573 int c;
574
575 if (v == NULL) {
576 return -1;
577 }
578
579 c = atoi(v);
581 c = 0;
582 }
583 return c;
584}
585
586/*
587 * Candidates the sweep runs: the wavefront counts on the default tile, and
588 * the same again on the 16x16x4 tile wherever this build offers that as a
589 * distinct thing to measure, see mfma_tile_offered(). The offered set is
590 * contiguous from 0, so it is a count rather than a skip and NEKO_TUNE_FOR()
591 * in elem_block_tune.h can take it directly.
592 */
598
599/* Report every measured MFMA candidate, see NEKO_TUNE_LOG in
600 elem_block_tune.h. The tile is named because it is the dimension whose
601 default was wrong for eight of the nine supported orders */
602#define NEKO_TUNE_LOG_MFMA(LX, T3) \
603 do { \
604 for (int c = 0; c < NEKO_MFMA_CANDIDATES; c++) { \
605 if ((T3)[c] >= NEKO_TUNE_INIT) { continue; } \
606 sprintf(neko_log_buf, "MFMA %s %2dwf %-2de %-5s: %9.2f us/call", \
607 mfma_tile_name(c), NEKO_MFMA_NWF(c), NEKO_MFMA_EB(LX, c), \
608 NEKO_MFMA_PAD_N(LX, sizeof(real)) ? "pad" : "plain", \
609 NEKO_TUNE_US((T3)[c], iters)); \
610 log_message(neko_log_buf); \
611 } \
612 } while (0)
613
614/*
615 * The same for the vector operator, plus the register mode each candidate
616 * ran in -- 'reg' where the geometric factors stay in registers across the
617 * three components, 'glob' where they are re-read from global memory for each
618 * one, see NEKO_MFMA_VECTOR_GREG. It is reported because it is derived from
619 * the wavefront count rather than swept, so two neighbouring candidates can
620 * differ in memory traffic as well as in block shape, and a step between them
621 * would otherwise be read as a matrix core effect.
622 */
623#define NEKO_TUNE_LOG_MFMA_VEC(LX, T3) \
624 do { \
625 for (int c = 0; c < NEKO_MFMA_CANDIDATES; c++) { \
626 if ((T3)[c] >= NEKO_TUNE_INIT) { continue; } \
627 sprintf(neko_log_buf, "MFMA %s %2dwf %-2de %-5s %-4s: %9.2f us/call", \
628 mfma_tile_name(c), NEKO_MFMA_NWF(c), NEKO_MFMA_EB(LX, c), \
629 NEKO_MFMA_PAD_N(LX, sizeof(real)) ? "pad" : "plain", \
630 NEKO_MFMA_VECTOR_GREG(LX, c) ? "reg" : "glob", \
631 NEKO_TUNE_US((T3)[c], iters)); \
632 log_message(neko_log_buf); \
633 } \
634 } while (0)
635
636#if defined(__gfx90a__) || defined(__gfx942__)
637
638/* 4-wide accumulators for the gfx90a / gfx942 matrix cores. */
639typedef double mfma_f64x4 __attribute__((ext_vector_type(4)));
640typedef float mfma_f32x4 __attribute__((ext_vector_type(4)));
641
642/*
643 * Per-precision matrix-core traits: the 4-wide accumulator type, the MFMA
644 * builtin, and the accumulator-slot -> output-row mapping (see the layout
645 * note above; f64 spreads rows with stride 4, f32 packs four contiguous rows).
646 */
647template< typename T >
648struct mfma_traits;
649
650template< >
651struct mfma_traits< double > {
652 typedef mfma_f64x4 acc_t;
653 __device__ __forceinline__ static acc_t mma(double a, double b, acc_t c) {
654 return __builtin_amdgcn_mfma_f64_16x16x4f64(a, b, c, 0, 0, 0);
655 }
656 __device__ __forceinline__ static int out_row(const int g, const int r) {
657 return g + 4 * r;
658 }
659};
660
661template< >
662struct mfma_traits< float > {
663 typedef mfma_f32x4 acc_t;
664 __device__ __forceinline__ static acc_t mma(float a, float b, acc_t c) {
665 return __builtin_amdgcn_mfma_f32_16x16x4f32(a, b, c, 0, 0, 0);
666 }
667 __device__ __forceinline__ static int out_row(const int g, const int r) {
668 return 4 * g + r;
669 }
670};
671
672/*
673 * Storage layout of one staged cube: i + SJ*j + SK*k over SIZE slots, with the
674 * j-stride padded to an odd number of elements where that removes the LDS bank
675 * conflicts -- see the padding note above for the model and the policy.
676 *
677 * PAD is carried as a template parameter rather than read from the macro so
678 * that the operators which stage a plain LX^3 cube (dudxyz, opgrad, conv1,
679 * cdtp) keep the layout they were written for: the default is 0 and only
680 * Ax-helm opts in.
681 */
682template< const int LX, const int PAD >
683struct mfma_cube {
684 enum { SJ = LX + PAD,
685 SK = (LX + PAD) * LX,
686 SIZE = (LX + PAD) * LX * LX,
687 /* column stride of a staged derivative matrix, padded with the cube
688 and for the same reason -- see the note on NEKO_MFMA_SD_N */
689 SD = LX + PAD,
690 DSIZE = (LX + PAD) * LX };
691};
692
693/*
694 * Offset of D(row, col) in a staged derivative matrix. The caller's copy is
695 * LX x LX with a column stride of LX; this is where it lives once staged.
696 */
697template< const int LX, const int PAD >
698__device__ __forceinline__ int mfma_dmat_idx(const int row, const int col) {
699 return row + mfma_cube<LX, PAD>::SD * col;
700}
701
702/*
703 * Linearised index into a staged cube, where the coordinate 'p' lies on
704 * contraction axis AXIS and 'n' enumerates the two remaining axes as
705 * n = a + LX*b.
706 */
707template< const int LX, const int AXIS, const int PAD = 0 >
708__device__ __forceinline__ int mfma_cube_idx(const int p, const int n) {
709 typedef mfma_cube<LX, PAD> C;
710 const int a = n % LX;
711 const int b = n / LX;
712 if (AXIS == 0) return p + C::SJ * a + C::SK * b; // contract i; n = (j,k)
713 if (AXIS == 1) return a + C::SJ * p + C::SK * b; // contract j; n = (i,k)
714 return a + C::SJ * b + C::SK * p; // contract k; n = (i,j)
715}
716
717/*
718 * The point of the element that LDS slot 's' holds, as an offset into the
719 * caller's LX^3 global storage, or -1 for a pad slot that holds nothing.
720 *
721 * The staging, pointwise and write-back passes walk SLOTS at unit stride and
722 * translate here, rather than walking points and translating the other way.
723 * Both directions are correct; this one is the one that keeps those passes
724 * conflict free. Walking points would leave the LDS side striding across the
725 * padding -- a 2-way conflict on every pass, which the bank model prices at
726 * more than the contraction gains back at LX = 6 and 10 -- while walking slots
727 * leaves the GLOBAL side with a hole every SJ lanes, which costs nothing: the
728 * lanes either side of the hole still touch the same cache lines, the hole
729 * lane simply takes no part.
730 *
731 * The division is by a compile-time constant and is meant to be hoisted: each
732 * kernel decodes its own slots once into a small register array and reuses it
733 * across all three passes.
734 */
735template< const int LX, const int PAD >
737 typedef mfma_cube<LX, PAD> C;
738 if (PAD == 0) {
739 return s;
740 }
741 const int k = s / C::SK;
742 const int rem = s - k * C::SK;
743 const int j = rem / C::SJ;
744 const int i = rem - j * C::SJ;
745 /* j < LX holds by construction (rem < SK = SJ*LX), i and k do not */
746 return (i < LX && k < LX) ? (i + LX * j + LX * LX * k) : -1;
747}
748
749/*
750 * One wavefront contracts the reference derivative matrix 'dmat' (LX x LX,
751 * stored column-major: D(row,col) = dmat[row + LX*col]) with the cube 'in'
752 * along axis AXIS, writing the cube 'out':
753 *
754 * out[idx(m,n)] (+)= sum_l D(m,l) * in[idx(l,n)] (TRANSPOSE = false)
755 * out[idx(m,n)] (+)= sum_l D(l,m) * in[idx(l,n)] (TRANSPOSE = true)
756 *
757 * GEMM dimensions M = LX, N = LX*LX, K = LX, tiled over 16x16x4 MFMA tiles
758 * with the partial M/N/K tiles masked. ACCUM selects += over = .
759 */
760/*
761 * NWF cooperating wavefronts (wf = 0..NWF-1) stripe the NTILES N-tiles among
762 * themselves -- wavefront wf handles nt = wf, wf+NWF, ... NWF = 1, wf = 0
763 * (the defaults) reproduces the single-wavefront contraction.
764 */
765template< typename T, const int LX, const int AXIS,
766 const bool TRANSPOSE, const bool ACCUM, const int NWF = 1,
767 const int PAD = 0 >
770 const T * __restrict__ dmat,
771 const T * __restrict__ in,
772 const int lane, const int wf = 0) {
773 typedef mfma_traits<T> mma_t;
774 const int g = lane >> 4; // lane / 16 -> 0..3
775 const int c = lane & 15; // lane % 16 -> 0..15
776 const int NTILES = (LX * LX + 15) / 16;
777 const int KSTEPS = (LX + 3) / 4;
778 const int NPASS = (NTILES + NWF - 1) / NWF; // N-tiles handled by this wave
779
780#pragma unroll
781 for (int p = 0; p < NPASS; ++p) {
782 const int nt = wf + p * NWF; // this wavefront's N-tile
783 if (nt < NTILES) {
784 const int n = nt * 16 + c; // free (column) index
785 typename mma_t::acc_t acc = {0, 0, 0, 0};
786#pragma unroll
787 for (int ks = 0; ks < KSTEPS; ++ks) {
788 const int l = ks * 4 + g; // contraction index
789 T a = 0;
790 if (c < LX && l < LX)
791 a = TRANSPOSE ? dmat[mfma_dmat_idx<LX, PAD>(l, c)] // D(l,c)
792 : dmat[mfma_dmat_idx<LX, PAD>(c, l)]; // D(c,l)
793 T b = 0;
794 if (l < LX && n < LX * LX)
796 acc = mma_t::mma(a, b, acc);
797 }
798 const T dvals[4] = { acc[0], acc[1], acc[2], acc[3] };
799#pragma unroll
800 for (int r = 0; r < 4; ++r) {
801 const int m = mma_t::out_row(g, r); // output coordinate on AXIS
802 if (m < LX && n < LX * LX) {
803 const int idx = mfma_cube_idx<LX, AXIS, PAD>(m, n);
804 if (ACCUM) out[idx] += dvals[r];
805 else out[idx] = dvals[r];
806 }
807 }
808 }
809 }
810}
811
812/*
813 * Double-precision batched matrix-core contraction using v_mfma_f64_4x4x4f64.
814 *
815 * Same contract as mfma_contract() -- out(m,n) (+)= sum_l D(m,l) in(l,n), with
816 * D(l,m) when TRANSPOSE -- but tiled with 4x4x4 MFMA tiles and the
817 * instruction's four blocks assigned to four consecutive 4-column N-subtiles.
818 * GEMM M = LX, N = LX*LX, K = LX, tiled as MT = ceil(LX/4) M-tiles,
819 * KSTEPS = ceil(LX/4) K-steps and NGROUPS = ceil(LX^2/16) column groups (each
820 * group = 4 blocks x 4 columns); partial M/N/K masked with zeros.
821 *
822 * Versus the 16x16x4 tile, the 4-wide M granularity fills M = LX < 16 exactly
823 * (LX = 8 runs the matrix core at 100% M-utilisation instead of 50%), at the
824 * cost of 4x as many, 1/4-sized MFMA issues that feed the same f64 matrix
825 * pipeline. Double precision only: the f32 4x4 instruction has K = 1, so there
826 * is no single-precision counterpart -- single precision keeps the 16x16x4 tile
827 * (see mfma_contract_sel below).
828 *
829 * Wave of 64 lanes = 4 blocks x 16 lanes. v_mfma_f64_4x4x4f64 lane layout,
830 * with lo = lane%4, gemm = (lane/4)%4 and kq = lane/16:
831 * A[i][k] : lane holds A[i = lo ][k = kq]
832 * B[k][j] : lane holds B[k = kq ][j = lo]
833 * D[i][j] : lane holds D[i = kq ][j = lo] (scalar f64 accumulator)
834 * and the four independent 4x4x4 blocks are selected by 'gemm', not by
835 * lane/16.
836 *
837 * This was MEASURED on gfx90a, not assumed: a one-hot read-out of which lane
838 * receives which element (128 launches, no assumptions) produced exactly this
839 * mapping. The previous version had 'gemm' and 'kq' interchanged -- it used
840 * lane/16 as the block selector and (lane/4)%4 as the contraction index -- and
841 * every one of 72 probe configurations disagreed with a CPU reference.
842 *
843 * It had been believed validated because the Ax-helm fluid solver converged
844 * with it. It does: a wrong but symmetric operator makes CG converge happily
845 * to the solution of a different system. Convergence is not correctness, and
846 * only a diff against a reference settles a layout. Any future correction
847 * stays localised to the four index expressions (m_a, l, n, m_d).
848 *
849 * NWF cooperating wavefronts (wf = 0..NWF-1) stripe the NGROUPS column groups
850 * among themselves -- wavefront wf handles ng = wf, wf+NWF, ... NWF = 1,
851 * wf = 0 (the defaults) reproduces the single-wavefront contraction.
852 */
853template< const int LX, const int AXIS,
854 const bool TRANSPOSE, const bool ACCUM, const int NWF = 1,
855 const int PAD = 0 >
858 const double * __restrict__ dmat,
859 const double * __restrict__ in,
860 const int lane, const int wf = 0) {
861 const int lo = lane & 3; // 0..3 : A row, B column, D column
862 const int gemm = (lane >> 2) & 3; // 0..3 : which of the four 4x4x4 blocks
863 const int kq = lane >> 4; // 0..3 : contraction index within a step
864 const int MT = (LX + 3) / 4;
865 const int KSTEPS = (LX + 3) / 4;
866 const int NGROUPS = (LX * LX + 15) / 16;
867 const int NPASS = (NGROUPS + NWF - 1) / NWF; // groups handled by this wave
868
869 /*
870 * M-tile innermost, with one accumulator per M-tile live across the K loop.
871 *
872 * The obvious nest is M-tile outermost, one accumulator, and that is what
873 * this was. It re-reads the whole B operand once per M-tile -- B depends on
874 * (group, K-step) and not on the M-tile at all -- which costs 2x the LDS
875 * operand traffic at LX = 5..8 and 3x at LX = 9..12, on a kernel that
876 * measures at 69-72% of the memory roof. The bank model puts the B read at
877 * 1408 of the 2400 LDS cycles an LX = 8 f64 element spends, and hoisting it
878 * out takes that to 704.
879 *
880 * It also breaks up the accumulate chain. AMD documents a four cycle wait on
881 * a V_MFMA_4x4x4_F64 -> V_MFMA_4x4x4_F64 SrcC dependency, and the single
882 * accumulator made the K loop exactly that chain; consecutive issues now
883 * write different accumulators wherever MT > 1, which is every order above
884 * four, so the waits overlap instead of adding up.
885 *
886 * Each accumulator still sums over ks in the same order it did, so the
887 * results are bit-identical to the previous nest, not merely equivalent.
888 */
889#pragma unroll
890 for (int gp = 0; gp < NPASS; ++gp) {
891 const int ng = wf + gp * NWF; // this wavefront's column group
892 if (ng < NGROUPS) {
893 /* the four blocks take four consecutive 4-column N-subtiles */
894 const int n = ng * 16 + gemm * 4 + lo; // column (N) for B in / D out
895 double acc[MT];
896#pragma unroll
897 for (int mt = 0; mt < MT; ++mt)
898 acc[mt] = 0.0;
899#pragma unroll
900 for (int ks = 0; ks < KSTEPS; ++ks) {
901 const int l = ks * 4 + kq; // contraction index (k = kq for A and B)
902 double b = 0.0;
903 if (l < LX && n < LX * LX)
905#pragma unroll
906 for (int mt = 0; mt < MT; ++mt) {
907 const int m_a = mt * 4 + lo; // A row (input lane layout: i = lo)
908 double a = 0.0;
909 if (m_a < LX && l < LX)
910 a = TRANSPOSE ? dmat[mfma_dmat_idx<LX, PAD>(l, m_a)] // D(l,m)
911 : dmat[mfma_dmat_idx<LX, PAD>(m_a, l)]; // D(m,l)
913 }
914 }
915#pragma unroll
916 for (int mt = 0; mt < MT; ++mt) {
917 const int m_d = mt * 4 + kq; // D row (output lane layout: i = kq)
918 if (m_d < LX && n < LX * LX) {
919 const int idx = mfma_cube_idx<LX, AXIS, PAD>(m_d, n);
920 if (ACCUM) out[idx] += acc[mt];
921 else out[idx] = acc[mt];
922 }
923 }
924 }
925 }
926}
927
928/*
929 * Precision- and tile-dispatched tensor contraction, so that one call site
930 * covers both precisions and both matrix core tiles.
931 *
932 * TILE 0 is the precision's default: the batched 4x4x4 tile in double
933 * precision (full M-utilisation for M = LX < 16, at half the FLOP rate --
934 * see the candidate space note above for why that trade is worth measuring),
935 * the 16x16x4 tile in single, which has no 4x4x4 counterpart. TILE 1 is the
936 * 16x16x4 tile in both, so in single precision the two are the same code.
937 *
938 * -DMFMA_F64_USE_16X16 makes TILE 0 the 16x16x4 tile in double precision as
939 * well, which collapses the tile dimension; the sweep then offers TILE 0
940 * alone, see mfma_tile_offered(). It is retained as a way to build without
941 * the 4x4x4 path at all, not as the way to choose between them -- that is now
942 * the autotuner's job.
943 *
944 * Both tiles are hardware verified against a CPU reference on gfx90a
945 * (mfma_probe, 144 configurations per tile) and give bit-identical f64
946 * results, so which one runs is a performance question only.
947 */
948template< typename T, const int LX, const int AXIS,
949 const bool TRANSPOSE, const bool ACCUM, const int NWF = 1,
950 const int TILE = 0, const int PAD = 0 >
951struct mfma_contract_sel {
953 static void run(T * __restrict__ out, const T * __restrict__ dmat,
954 const T * __restrict__ in, const int lane,
955 const int wf = 0) {
957 lane, wf);
958 }
959};
960
961/* Double precision, TILE 0: the batched 4x4x4 tile, or the 16x16x4 one if the
962 build asked for it */
963template< const int LX, const int AXIS, const bool TRANSPOSE, const bool ACCUM,
964 const int NWF, const int PAD >
967 static void run(double * __restrict__ out, const double * __restrict__ dmat,
968 const double * __restrict__ in, const int lane,
969 const int wf = 0) {
970#ifdef MFMA_F64_USE_16X16
972 lane, wf);
973#else
975 lane, wf);
976#endif
977 }
978};
979
980/* Double precision, TILE 1: the 16x16x4 tile, M-utilisation 50% at LX = 8 and
981 75% at LX = 12, but at twice the FLOP rate of the batched tile, a free
982 accumulate chain and a third of its operand traffic at high order */
983template< const int LX, const int AXIS, const bool TRANSPOSE, const bool ACCUM,
984 const int NWF, const int PAD >
987 static void run(double * __restrict__ out, const double * __restrict__ dmat,
988 const double * __restrict__ in, const int lane,
989 const int wf = 0) {
991 lane, wf);
992 }
993};
994
995#endif // __gfx90a__ || __gfx942__
996#endif // __MATH_MFMA_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
double real
#define HIP_CHECK(err)
Definition check.h:8
static __global__ void hip_mfma_arch_probe(int *flag)
static bool mfma_lx_supported()
static const char * mfma_tile_name(const int c)
#define NEKO_MFMA_NWF_CANDIDATES
#define NEKO_MFMA_CANDIDATES
static int neko_mfma_pin()
static int neko_mfma_candidates()
#define NEKO_MFMA_TILE_CANDIDATES
static bool hip_have_mfma()
static int neko_mfma_nwf_pin()
#define NEKO_MFMA_TILE(C)
static int neko_mfma_sweep()
static bool mfma_tile_offered(const int c)