1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
/*******************************************************************************
* Copyright 2019 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
#ifndef GEMMSTONE_INCLUDE_GEMMSTONE_STRATEGY_HPP
#define GEMMSTONE_INCLUDE_GEMMSTONE_STRATEGY_HPP
#include "gemmstone/config.hpp"
#include "internal/ngen_includes.hpp"
#include "internal/utils.hpp"
#include "gemmstone/driver_info.hpp"
#include "ngen_emulation.hpp"
#include "gemmstone/problem.hpp"
#include "gemmstone/type.hpp"
GEMMSTONE_NAMESPACE_START
/* Zero-padding to allow various types to be bytewise compared. */
#define ZPAD(x, bytes) uint8_t pad##x[bytes] = {};
// Selects the load/store/atomic messages used to access a matrix.
enum class AccessType : uint8_t {
Scattered, // Use scattered accesses (transposes matrices)
ChannelScattered, // Use untyped surface reads (transposes matrices)
Block, // Use block messages
PseudoBlock, // Use scattered accesses to emulate block accesses
Block2D, // Use 2D block messages
Block2DTranspose, // Use 2D block messages with transposition
Block2DVNNI, // Use 2D block messages with VNNI transform
CacheLine, // Use scattered accesses with one element per cache line
};
static inline bool isBlocklike(AccessType t)
{
return one_of(t, {AccessType::Block, AccessType::PseudoBlock});
}
static inline bool isBlock2D(AccessType t)
{
return one_of(t, {AccessType::Block2D, AccessType::Block2DTranspose, AccessType::Block2DVNNI});
}
static inline bool isTransposing(AccessType t)
{
return one_of(t, {AccessType::Scattered, AccessType::ChannelScattered, AccessType::Block2DTranspose});
}
// Strategies for choosing scattered access SIMD width.
enum class ScatterSIMD : uint8_t {
Default,
Wide, // Prefer wider SIMD (more scattered lanes)
Narrow // Prefer narrower SIMD (more consecutive access)
};
// Strategies for accessing a matrix from memory.
struct MatrixAddressingStrategy {
ngen::AddressBase base; // Base for addressing (A64/BTS/...)
AccessType accessType
= AccessType::Block; // Block/scattered/etc. access
uint8_t tileR = 0, tileC = 0; // Desired tiling (0 if none) in registers.
ScatterSIMD smode = ScatterSIMD::Default; // SIMD selection for scattered accesses.
uint8_t padded : 1; // Allow read/write overruns?
uint8_t atomic : 1; // Atomic access? (only relevant for C)
uint8_t address2D : 1; // Use 2D addressing? (media block-style loads)
uint8_t prefetch : 1; // Prefetch only?
uint8_t pfLoad : 1; // Arrange blocks as if prefetching, but actually load data.
uint8_t newDP : 1; // Use new dataport messages? (XeHPG+)
uint8_t dpasw : 1; // DPASW half layout?
uint8_t noExtraPad : 1; // Avoid extra padding?
uint8_t noCoalesce : 1; // Disable address coalescing?
uint8_t pad0 : 7;
ngen::CacheSettingsLSC cachingR // Cache policies for LSC reads.
= ngen::CacheSettingsLSC::Default;
ngen::CacheSettingsLSC cachingW // Cache policies for LSC writes.
= ngen::CacheSettingsLSC::Default;
MatrixAddressingStrategy() : padded(false)
, atomic(false)
, address2D(false)
, prefetch(false)
, pfLoad(false)
, newDP(false)
, dpasw(false)
, noExtraPad(false)
, noCoalesce(false)
, pad0(0) {}
void preflight(ngen::HW hw);
void forceA64();
void assignSurface(uint8_t index) { if (!base.isStateless()) base.setIndex(index); }
ngen::GlobalAccessType getGlobalAccessType() const {
return base.isStateless() ? ngen::GlobalAccessType::Stateless : ngen::GlobalAccessType::Surface;
}
};
inline void transposeAccessType(MatrixAddressingStrategy &astrategy)
{
astrategy.accessType = isTransposing(astrategy.accessType) ? AccessType::Block :
astrategy.base.isStateless() ? AccessType::Scattered
: AccessType::ChannelScattered;
}
// Remainder handling strategies.
enum class RemainderHandling : uint8_t {
Ignore, // Assume no remainder, or handled by hardware bounds checking.
General, // Handle all remainder cases.
Split, // Generate copies of the kernel with and without remainder handling.
KnownRemainder, // Assume remainder case; don't create special code for non-remainder case.
};
// How to split A/B amongst threads in a workgroup.
enum class CoopSplit {
K, // Split in k dimension (within each thread's m/n range)
MN, // Split in m/n dimensions
Linear, // Split in linear index order
FullK, // Split in k dimension (within the entire workgroup's m/n range)
};
// Methods for traversing a matrix.
enum class WalkOrder : uint8_t {
HW2D, // Rely on HW thread dispatch for ordering
SimpleLinear, // Simple 1D->2D mapping in column-major/row-major order
NestedLinear, // Fixed-size blocks of WGs traversed in column/row-major order
Hilbertlike, // Cache-oblivious Hilbert curve-based order
Boustrophedon, // Cache-aware panel boustrophedon walk order
};
// Places to store r0 header.
enum class MoveR0 {
None, Acc, Addr, GRF
};
// Strategy parameters shared between different kernel types.
struct CommonStrategy {
int subgroupSize = 8; // Subgroup size provided to OpenCL runtime.
int GRFs = 128; // # of GRFs to use.
bool fused = false; // Fused EU handling enabled?
bool dualGRF = true; // Enable two-GRF instructions.
bool ieeeDenormals = true; // Enable IEEE-compliant denormals.
bool spf = true; // Enable Single Program Flow (SPF) mode in EUs.
MoveR0 moveR0 = MoveR0::Acc; // Where to store r0 information.
bool readSuppressionWA = true; // Workaround for HW issue with read suppression after fused sends.
bool multitile = true; // Enable multitile (implicit scaling) support?
bool wgInSS = false; // Pretend to use barriers so that each WG belongs to 1 SS/DSS.
bool finalFence = false; // Issue global memory fence before EOT.
int pauseCycles = 0x0100; // Number of cycles to pause when waiting in a spin-loop.
bool simulation = false; // For use in simulator?
bool systolicAvailable = false; // True if systolic array present.
bool avoidIncConflicts = true; // If true, duplicate address increments across banks to avoid bundle conflicts.
bool isDSLGenerator = false;
ngen::HW raHW = ngen::HW::Unknown; // Pretend to be a different GPU for register allocation purposes.
ngen::ThreadArbitrationMode arbitrationMode
= ngen::ThreadArbitrationMode::Default; // Thread arbitration policy to use.
int activeThreads = 0; // # of active threads (0 = dynamic).
ngen::EmulationStrategy emulate;
ZPAD(B, 2)
CommonStrategy() = default;
CommonStrategy(ngen::HW hw, int stepping = 0);
void preflight(ngen::HW hw, const CommonProblem &problem);
};
// Strategy parameters for GEMM kernels.
struct GEMMStrategyPOD : public CommonStrategy {
int blocking[3] = {0}; // Recommended block size in each dimension (m/n/k) -- for driver.
int blockingAlt[3] = {0}; // Alternate block size in each dimension (m/n/k) -- for driver.
// m/n alternates are for Hilbert-ordered kernels when Hilbert ordering disabled.
// k alternate is for multi-tile execution with implicit scaling.
int unroll[3]; // Unrolls in each dimension (m/n/k), indexed by LoopType.
int unrollK_masked = 0; // k unroll to use when masking.
int extraKAlign = 1; // Additional k alignment when blocking.
LoopType loopOrder[3]
= {LoopM, LoopN, LoopK}; // Expected order of loops in driver code (in order from innermost to outermost).
LoopType fusedLoop = LoopM; // Direction of fusing if threads fused.
WalkOrder cWalkOrder = WalkOrder::HW2D; // Order for traversing tiles of C
bool persistent = false; // Use persistent thread model?
bool reverse[2] = {false, false}; // Reverse m/n walk order?
bool scramble[2] = {false, false}; // Scramble m/n walk order?
bool fmaBoustrophedon = false; // Use boustrophedon ordering inside FMA/DPAS blocks?
ZPAD(A, 1)
int fmaSIMD = 0; // Vector length for FMA (0 = default = 2 GRFs).
int kChain = 1; // # of FMAs to chain in k dimension.
int dotVL = 0; // If > 0, use dot products of the given length, instead of outer products.
int wg[3] = {0,0,0}; // m/n/k workgroup sizes, 0 if unconstrained. Indexed by LoopType.
WGType forceWGUpdate = WGDynamic; // Force work group update type.
bool forceFixedWGK = false; // Force fixed k workgroup size.
ZPAD(B, 2)
int wgPadFactor = 1; // If > 1, pad workgroup with empty threads.
MatrixAddressingStrategy A, B, C; // Strategies for accessing A/B/C.
MatrixAddressingStrategy AO, BO, CO; // Strategies for accessing A/B/C offsets.
MatrixAddressingStrategy A_scale, B_scale; // Strategies for accessing A/B scales.
MatrixAddressingStrategy Ag, Bg; // Strategies for accessing A/B groupwise reductions.
int ka_load, kb_load; // How much of A/B is loaded at once, in k dimension
int ka_load_masked = 0, kb_load_masked = 0; // Same as above, when masking m/n (0 = default = same as ka/kb_load)
bool loadBFirst = false; // If true, load B before A (default A then B).
bool doubleMasking = false; // Allow A/B to be masked in both dimensions.
bool kDescRem = false; // Allow descriptor-based k remainder handling for A/B.
bool slmA = false, slmB = false; // Whether to copy A/B to SLM.
bool splitCopy = false; // Separate SLM copy and compute threads?
bool tlbWarmup = false; // Enable TLB warmup?
ZPAD(C, 1)
int slmBuffers = 0; // # of A/B SLM buffers, 0 for none.
int unrollKSLM = 0; // k unroll for SLM copies (0 = auto = unroll[LoopK]/slmCopies)
int unrollKSLMMasked = 0; // Alternate value to use with masking (0 = same as unrollKSLM)
bool slmATrans = false, slmBTrans = false; // Whether A/B SLM data should be completely crosspacked (transposed).
ZPAD(D, 2)
int A_copies = 1, B_copies = 1; // # of copies of A/B matrices, for latency absorption
int slmCopies = 1; // # of copies of loaded A/B matrices for SLM copies.
bool slmRepackAhead = false; // Repack SLM data ahead of stores?
ZPAD(E, 3)
int optAlignAB = 0; // Optional alignment for A/B. If > 0, create two versions of k loop, one for A/B aligned to this value, one not.
bool optAlignAB2D = false; // If true, create two version of k loop, one for A/B aligned to block 2D requirements, one not.
ZPAD(F, 3)
AccessType unalignedAccA, unalignedAccB; // Access types to use for A/B on unaligned path.
ZPAD(G, 2)
int ka_prefetch = 0, kb_prefetch = 0; // Chunk size for prefetching A/B.
int ka_pfStride = 0, kb_pfStride = 0; // k stride between A/B prefetches.
bool cooperativePF = true; // Enable WG-cooperative A/B prefetches.
ZPAD(H, 3)
int prefetchA = 0, prefetchB = 0, prefetchC = 0; // Prefetch distances, in units of unrollK.
int prefetchAMasked = 0, prefetchBMasked = 0; // Same as above, when masking m/n.
MatrixAddressingStrategy A_prefetch, B_prefetch, C_prefetch; // Strategies for prefetching A/B/C.
bool l3PrefetchA = false; // Enable L3 prefetch for A?
bool l3PrefetchB = false; // Enable L3 prefetch for B?
ZPAD(HH, 2)
int prefetchABL3 = 0; // L3 prefetch distance for A/B.
int ka_prefetchL3 = 0, kb_prefetchL3 = 0; // Chunk size for L3 prefetch of A/B.
MatrixAddressingStrategy AB_prefetchL3; // Strategy for L3 prefetch of A/B.
enum {
CSeparate, // C stored in its own bundle, A/B in the other bundle.
ACB, // A, then C, then B
BCA, // B, then C, then A
VNC, // A/B (broadcast matrix second), then C
ABInterleave, // A/B interleaved, then C
NSeparate, // Broadcast input stored in its own bundle(s)
VAvoid, // C registers allocated to avoid non-broadcast inputs
} registerScheme = CSeparate; // Register layout scheme.
int nSeparateChunk = 0; // If > 0, chunk size for NSeparate, to facilitate switching layouts.
bool kParallel = false; // If true, generate k-parallelized kernel using global memory reduction.
bool kParallelLocal = false; // If true, generate k-parallelized kernel using local memory reduction.
bool kInterleave = false; // Interleave threads in k dimension?
ZPAD(I, 1)
int kInterleaveChunk = 0; // Minimum chunk size for interleaving (0 for automatic).
bool shrinkWGK = false; // Shrink wgK automatically to try to fit dispatch in 1 wave (or smaller)?
ZPAD(J, 3)
int fillGoal = 0; // With shrinkWGK, try to fill this fraction of available thread slots, measured in sixteenths (0 = default).
int cInterleaveChunk = 1; // Minimum chunk size for interleaving columns of C among threads
bool kParallelVariable = false; // If true, generate kernel that uses variable k-parallelization for load balancing.
bool fuseBeta = false; // Fuse beta scaling into kernel? (kParallel/kParallelVariable, requires linear ordering)
bool fusePostOps = false; // Fuse post-operations into kernel? (kParallel/kParallelVariable, requires linear ordering)
bool altFusedBeta = false; // Enable alternate beta fusion implementation? (requires sequential dispatch)
bool zeroTempC = false; // Use pre-zeroed temporary C memory.
bool relaxedAccumulation = false; // Allow downconversion of partial contributions to Tc_ext.
// If false (default), only downconvert C at the end of the calculation.
ZPAD(K, 2)
int kPadding = 32; // Pad k dimension when load balancing (kParallel/kParallelVariable)
int barrierFreq = 0; // If > 0, set a periodic barrier every barrierFreq k loops to keep threads together.
bool splitBarrier = false; // Use split barriers for these periodic barriers?
bool altCRemainder = false; // Use alternative double-loop C remainder code?
bool block2DCRemainder = false; // Generate block 2D C remainder path?
bool block2DCFull = false; // Use block 2D C remainder path even for full tiles?
int cRepackPanel = 0; // Size of panels for repacking C (0 = automatic)
int repackC = 0; // Repack C every repackC k loops.
bool cAccumulators = false; // Use accumulator registers for part of C (to save a few registers)?
bool cLoadAhead = false; // Load C before doing FMAs?
bool autoatomic = true; // Automatically use C atomics for beta = 1 kernels?
bool forceCopyC = false; // Force C to be copied before the update step?
bool noJumpTables = false; // Disallow jump tables?
RemainderHandling remHandling[3] = { // m, n, k remainder handling.
RemainderHandling::Split,
RemainderHandling::Split,
RemainderHandling::General,
};
bool jointSplit = true; // Use remainder kernel for both m and n dimensions if both are split.
ZPAD(L, 3)
int mSplitThresh = 0, nSplitThresh = 0; // m/n minimum thresholds for using split remainder handling. 0 means always use split.
bool atomicFMA = false; // Use {Atomic} FMA chains.
bool extendedAtomicFMA = false; // Use longer {Atomic} FMA chains.
bool stallAfterLoad = false; // Insert stalls after load operations.
bool checkAdd32 = false; // Check inside kernel if inner loop additions can be done in 32-bit.
bool delayABInc = true; // Delay A/B increment a few outer products in the k loop.
ZPAD(M, 3)
CoopSplit coopA = CoopSplit::K; // How to split SLM copies, cooperative prefetches amongst threads in a workgroup
CoopSplit coopB = CoopSplit::K;
bool slmEarlyKMask = false; // Prepare A/B reads to use k-masking (when applicable) in main loop, instead of waiting for remainder.
bool slmUseIncrCopy = true; // Use incremental SLM copies if needed.
bool slmAltBarriers = false; // Alternate fenceless SLM buffering algorithm.
bool strictFence = false; // Add extra SLM fences that are not usually required on HW.
bool skipFence = false; // Skip SLM fences that theoretically should be required but HW doesn't need.
bool slmFenceWARWA = false; // Work around buggy SLM fence that doesn't protect against WAR hazards.
bool systolic = false; // Use systolic array if applicable.
bool dpasw = false; // Use DPASW for fused EU architectures.
bool fixedSystolic = false; // Use hardcoded systolic inner loop for 32x32 or 32x48 unrolls.
ZPAD(N, 3)
int namedBarriers[2] = {0, 0}; // # of named barriers in m, n dimensions (0 to use regular barriers).
bool skewLocalIDs = false; // Remap local IDs for large workgroups so that threads on the same EU don't depend on the same data.
bool xParallel = false; // TRSM: parallelize in x dimension.
bool checkBeta1 = false; // If true, check for beta = 1 and handle specially.
bool panelCheck = false; // If true, check for out-of-bounds panel reads.
bool insideSK = false; // Inside a superkernel?
ZPAD(O, 3)
GEMMStrategyPOD() = default;
GEMMStrategyPOD(ngen::HW hw, int stepping = 0) : CommonStrategy(hw, stepping) {}
};
#undef ZPAD
struct GEMMStrategy : public GEMMStrategyPOD
{
std::vector<MatrixAddressingStrategy> binary; // Strategies for accessing binary postop data.
GEMMStrategy() = default;
GEMMStrategy(ngen::HW hw, int stepping = 0) : GEMMStrategyPOD(hw, stepping) {}
void preflight(ngen::HW hw, const GEMMProblem &problem);
bool minimize(ngen::HW hw, const GEMMProblem &problem);
void trimKChain(ngen::HW hw, int k, const GEMMProblem &problem);
int wgTile(LoopType l) const { return unroll[l] * wg[l]; }
bool lateExit() const { return (slmBuffers > 0) || barrierFreq || kParallelLocal || fuseBeta || fusePostOps || cooperativePF; }
int slmABufBlockSize(const GEMMProblem &problem) const { return fixedSystolic ? 1152 : int(slmA) * unroll[LoopM] * unrollKSLM * problem.Ta * problem.Ta.components(); }
int slmBBufBlockSize(const GEMMProblem &problem) const { return fixedSystolic ? 1536 : int(slmB) * unroll[LoopN] * unrollKSLM * problem.Tb * problem.Tb.components(); }
int slmGEMMABufSize(const GEMMProblem &problem) const { return slmABufBlockSize(problem) * wg[LoopM] * wg[LoopK] * slmBuffers; }
int slmGEMMBBufSize(const GEMMProblem &problem) const { return slmBBufBlockSize(problem) * wg[LoopN] * wg[LoopK] * slmBuffers; }
int slmABufSize(const GEMMProblem &problem) const { return slmGEMMABufSize(problem); }
int slmBBufSize(const GEMMProblem &problem) const { return slmGEMMBBufSize(problem); }
int slmSysgemmBlockSize() const { return 1152 * wg[LoopM] + 1536 * wg[LoopN]; }
bool variableSLM() const { return kParallelLocal; }
int slmBarriersPerUnroll() const { return (slmBuffers == 0) ? 0 :
(slmBuffers == 1) ? 2 : 1; }
int ka_inc() const { return slmA ? unrollKSLM : ka_load; }
int kb_inc() const { return slmB ? unrollKSLM : kb_load; }
bool persistentLoop() const { return persistent || kParallelVariable; }
bool needsMNLocalIDs() const { return xParallel || (slmBuffers > 0) || cooperativePF || kParallelLocal || persistentLoop()
|| namedBarriers[LoopM] || namedBarriers[LoopN] || (dpasw && !fixedSystolic); }
bool needsKLocalIDs() const { return kParallelLocal || persistentLoop(); }
bool needsKLoopBarrier() const { return (barrierFreq > 0) || (slmBuffers > 0); }
bool needsBarrier() const { return needsKLoopBarrier() || xParallel || kParallelLocal || fuseBeta || fusePostOps; }
bool needsUnnamedBarrier(const GEMMProblem &problem) const;
bool needsNamedBarriersM(const GEMMProblem &problem) const;
bool needsNamedBarriersN(const GEMMProblem &problem) const;
bool fusedM() const { return fused && (fusedLoop == LoopM); }
bool fusedN() const { return fused && (fusedLoop == LoopN); }
WGType getWGType(const GEMMProblem &problem) const {
if (forceWGUpdate == WGFixed)
return WGFixed;
if ((slmBuffers > 0) || (forceWGUpdate == WGFixed) || namedBarriers[LoopM] || namedBarriers[LoopN])
return WGFixed;
if (cooperativePF)
return WGFixed; /* until flexible cooperative PF enabled */
if (cWalkOrder == WalkOrder::NestedLinear)
return WGFixed;
if (forceWGUpdate == WGShrinkable)
return WGShrinkable;
else
return WGDynamic;
}
bool fixedWG(const GEMMProblem &problem) const { return (getWGType(problem) == WGFixed); }
bool fixedWGK() const { return forceFixedWGK || kInterleave || (kParallelLocal && wgPadFactor > 1); }
bool linearOrder() const { return cWalkOrder != WalkOrder::HW2D; }
bool legalAAlignment(const GEMMProblem &problem, int align) { return problem.isLegalAAlignment(align, unroll[LoopM]); }
bool legalBAlignment(const GEMMProblem &problem, int align) { return problem.isLegalBAlignment(align, unroll[LoopN]); }
int kAlign(const GEMMProblem &problem) const;
int statusFlagStride() const { return 64 * (int(fuseBeta) + int(fusePostOps)); }
bool needsTempC(const GEMMProblem &problem) const;
bool nondeterministic(const GEMMProblem &problem) const;
bool checkAdd32Rem() const { return checkAdd32 && emulate.emulate64; }
bool allowDoubleMasking(LoopType loop) const { return doubleMasking || unroll[loop] == 1; }
bool registerOutput() const { return C.base.getModel() == ngen::ModelInvalid; }
int aqGroupKGranularity() const { return groupKReduce(slmA ? unrollKSLM : ka_load); }
int bqGroupKGranularity() const { return groupKReduce(slmB ? unrollKSLM : kb_load); }
static int groupKReduce(int x) { while (x > 16 && (x & 1) == 0) x >>= 1; return x; }
void serialize(SerializationStream &s) const
{
const GEMMStrategyPOD &pod = *this;
s.append(pod);
for (const auto &astrategy: binary)
s.append(astrategy);
}
};
bool useAutoAtomic(ngen::HW hw, const GEMMProblem &problem, const GEMMStrategy &strategy, bool ignoreBeta = false);
void downgradeAPFAccess(const GEMMProblem &problem, GEMMStrategy &strategy);
void downgradeBPFAccess(const GEMMProblem &problem, GEMMStrategy &strategy);
GEMMSTONE_NAMESPACE_END
#endif /* header guard */