Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
elem_block.h
Go to the documentation of this file.
1#ifndef __MATH_ELEM_BLOCK_H__
2#define __MATH_ELEM_BLOCK_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
37#include "wave.h"
38
73#ifndef NEKO_EB_WAVE
74#define NEKO_EB_WAVE NEKO_WAVE_SIZE_UNIFORM
75#endif
76
77/* LDS per CU, and the per workgroup maximum, on CDNA */
78#ifndef NEKO_EB_MAX_LDS
79#define NEKO_EB_MAX_LDS 65536
80#endif
81
82#define NEKO_EB_CANDIDATES 3
83
84template< int LX, int C >
85struct elem_block {
86 static const int waves = 2 << C;
87 static const int fit = (NEKO_EB_WAVE * waves)/(LX * LX);
88 static const int value = (C == 0) ? 1 : (fit > 0 ? fit : 1);
89};
90
91/*
92 * Launch bounds.
93 *
94 * HIP's second argument is MIN_WARPS_PER_EXECUTION_UNIT -- minimum wavefronts
95 * resident per SIMD -- and not CUDA's min blocks per SM. The first argument
96 * must track the real block size: launching more threads than declared is a
97 * launch failure, not a slowdown.
98 *
99 * The second argument defaults to 1, i.e. no constraint. It carried 3
100 * historically, which holds the compiler to a 512/3 VGPR budget on every
101 * kstep launch. The kstep kernels keep ru[LX] and rw[LX] -- 16 doubles, 32
102 * VGPRs -- live across a fully unrolled k loop that also issues seven metric
103 * loads per step, and the vector variants keep six such arrays, ~96 VGPRs,
104 * before any addressing. That is the shape that spills to scratch under a
105 * cap.
106 *
107 * Measured on MI250X, lx = 8, 13824 elements, on two builds differing only
108 * in this setting: kstep eb=1 runs 1317 us with the cap at 3 and 456 us
109 * without it, eb=4 4824 against 2254. Over the same pair the 1d kernel,
110 * which declares no launch bounds at all, moves 449.82 -> 455.53, and the
111 * MFMA kernels, which carry a bare __launch_bounds__ with no second
112 * argument, move less than 4%. Only the kernels this macro touches moved.
113 *
114 * Note what the fix does not buy: kstep eb=1 uncapped only draws level with
115 * the 1d kernel (456 against 450), because both are at the DRAM roof. It
116 * matters for the vector operator, which has no 1d variant at all, and for
117 * not paying for spilled candidates on every tune.
118 *
119 * The CUDA header records the same failure mode from its own second argument
120 * -- "squeezed registers to 80 at lx >= 14 and made cdtp spill 736 bytes per
121 * thread" -- and dropped it; see cuda/elem_block.h.
122 *
123 * Set -DNEKO_EB_MIN_WAVES_EU=3 to restore the old behaviour, and check
124 * ScratchSize with -Rpass-analysis=kernel-resource-usage when changing it.
125 */
126#ifndef NEKO_EB_MIN_WAVES_EU
127#define NEKO_EB_MIN_WAVES_EU 1
128#endif
129
130/* A minimum of one wave per SIMD is no constraint at all, so emit the
131 single argument form rather than a bound that only looks like one. */
132#if NEKO_EB_MIN_WAVES_EU > 1
133#define NEKO_EB_BOUNDS(NT) __launch_bounds__((NT), NEKO_EB_MIN_WAVES_EU)
134#else
135#define NEKO_EB_BOUNDS(NT) __launch_bounds__((NT))
136#endif
137
202#ifndef NEKO_EB_WAVE_BARRIER
203#define NEKO_EB_WAVE_BARRIER 0
204#endif
205
206/*
207 * Is a kstep slice wavefront contained, so that a wave barrier covers it?
208 *
209 * Deliberately NOT keyed on NEKO_EB_WAVE. That macro is the command line
210 * width, and it sizes kernel template arguments and launch geometry, where
211 * the host and device passes have to agree on one number; getting it wrong
212 * there costs occupancy, not correctness. Here it would cost correctness, so
213 * this asks the target instead of being told.
214 *
215 * __builtin_amdgcn_wavefrontsize() is the supported spelling.
216 * __AMDGCN_WAVEFRONT_SIZE__ is deprecated -- clang warns that compile-time
217 * constant access to the wavefront size is going away -- so there is no
218 * static_assert to be had, and wanting one was the wrong instinct: under a
219 * fixed --offload-arch the builtin folds to a constant and the branch below
220 * disappears, and under a multi target fatbin it correctly resolves per
221 * target, which a build time check could not do at all.
222 */
223__device__ __host__ constexpr bool neko_eb_slice_in_wave(unsigned wave, int lx)
224{
225 return ((unsigned) (lx * lx) <= wave) &&
226 ((wave % (unsigned) (lx * lx)) == 0);
227}
228
229template< int LX >
231{
232#if NEKO_EB_WAVE_BARRIER && defined(__HIP_DEVICE_COMPILE__)
233 /* Uniform across the whole block, so the __syncthreads() arm is never
234 reached by only part of it */
239 } else {
241 }
242#else
244#endif
245}
246
247/*
248 * Launch geometry helpers. NELV is the element count; the grid is sized so
249 * the tail block is partially filled and clamps, rather than the kernel
250 * returning early -- these kernels have __syncthreads() in the k loop, so an
251 * early return would leave the barrier unmatched.
252 */
253#define NEKO_EB(LX, C) (elem_block<LX, C>::value)
254#define NEKO_EB_NTHRDS(LX, C) dim3((LX), (LX), NEKO_EB(LX, C))
255#define NEKO_EB_NBLCKS(NELV, LX, C) \
256 dim3(((NELV) + NEKO_EB(LX, C) - 1)/NEKO_EB(LX, C), 1, 1)
257#define NEKO_EB_SEL(LX, SEL) \
258 ((SEL) == 0 ? NEKO_EB(LX, 0) : (SEL) == 1 ? NEKO_EB(LX, 1) : NEKO_EB(LX, 2))
259
274#define NEKO_CHUNKS_CANDIDATES 4
275
276/*
277 * Candidate C selects 1024 >> C, i.e. 1024, 512, 256, 128. Candidate 0 is the
278 * historical value, so it is always available as an A/B baseline.
279 *
280 * A heuristic is deliberately avoided here. The best chunk depends on how the
281 * shared memory footprint (which grows as LX^3) caps the resident block count
282 * on the target device, and that differs between architectures -- 164 kB per
283 * SM on A100, 228 kB on H100, 64 kB of LDS per CU on CDNA. Picking by thread
284 * utilisation alone gets it wrong from LX = 6 upward, because once the block
285 * count is capped by shared memory a smaller block simply wastes thread slots.
286 * So all four are instantiated and the tuner measures them.
287 */
288template< int LX, int C >
289struct chunk_block {
290 static const int raw = 1024 >> C;
291 static const int value = (raw >= LX * LX) ? raw : 1024;
292};
293
294#define NEKO_CHUNKS(LX, C) (chunk_block<LX, C>::value)
295#define NEKO_CHUNKS_NTHRDS(LX, C) dim3(NEKO_CHUNKS(LX, C), 1, 1)
296#define NEKO_CHUNKS_SEL(LX, SEL) \
297 ((SEL) == 0 ? NEKO_CHUNKS(LX, 0) : (SEL) == 1 ? NEKO_CHUNKS(LX, 1) : \
298 (SEL) == 2 ? NEKO_CHUNKS(LX, 2) : NEKO_CHUNKS(LX, 3))
299
300#endif // __MATH_ELEM_BLOCK_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)
__syncthreads()
__device__ __host__ constexpr bool neko_eb_slice_in_wave(unsigned wave, int lx)
Definition elem_block.h:223
#define NEKO_EB_WAVE
Definition elem_block.h:74
__device__ __forceinline__ void neko_eb_kstep_barrier()
Definition elem_block.h:230
static const int raw
Definition elem_block.h:141
static const int value
Definition elem_block.h:142
static const int waves
Definition elem_block.h:86
static const int fit
Definition elem_block.h:87
static const int value
Definition elem_block.h:68