register_tiled_common.hh
Go to the documentation of this file.
1#pragma once
2#include <cstddef>
3
7
9
10#include <src/linalg-impl.hh>
11
12#include <sycl/sycl.hpp>
13#include <utility>
14
15namespace batchlas::sycl_gemm {
16
17template <int Count, typename Fn, std::size_t... I>
18inline void static_for_impl(Fn&& fn, std::index_sequence<I...>) {
19 (fn(std::integral_constant<int, static_cast<int>(I)>{}), ...);
20}
21
22template <int Count, typename Fn>
23inline void static_for(Fn&& fn) {
24 static_for_impl<Count>(std::forward<Fn>(fn), std::make_index_sequence<Count>{});
25}
26
27template <typename T,
28 int TileM,
29 int TileN,
30 int TileK,
31 int ThreadTileRows,
32 int ThreadTileCols,
33 int VecA,
34 int VecB,
35 int UnrollK,
36 int Stages,
37 bool AlignedFastPath,
38 Transpose OpA,
39 Transpose OpB>
41
42template <typename T>
43inline int register_ceil_div(int value, int divisor) {
44 return (value + divisor - 1) / divisor;
45}
46
47template <int TileM_,
48 int TileN_,
49 int TileK_,
50 int ThreadTileRows_,
51 int ThreadTileCols_ = ThreadTileRows_,
52 int VecA_ = 4,
53 int VecB_ = 4,
54 int UnrollK_ = 1,
55 int Stages_ = 1>
57 static constexpr int TileM = TileM_;
58 static constexpr int TileN = TileN_;
59 static constexpr int TileK = TileK_;
60 static constexpr int ThreadTileRows = ThreadTileRows_;
61 static constexpr int ThreadTileCols = ThreadTileCols_;
62 static constexpr int VecA = VecA_;
63 static constexpr int VecB = VecB_;
64 static constexpr int UnrollK = UnrollK_;
65 static constexpr int Stages = Stages_;
66 static constexpr int LocalRows = TileM / ThreadTileRows;
67 static constexpr int LocalCols = TileN / ThreadTileCols;
68 static constexpr int ThreadsPerGroup = LocalRows * LocalCols;
69 static constexpr int TileAStride = TileM + 1;
70 static constexpr int TileBStride = TileK + 1;
71 static constexpr int StageASize = TileAStride * TileK;
72 static constexpr int StageBSize = TileBStride * TileN;
73};
74
75template <typename T>
77 return M.ld() == M.rows() && M.stride() == M.ld() * M.cols();
78}
79
80template <typename T, int TileM, int TileN, int TileK, int VecA, int VecB>
85 static_cast<void>(A);
86 static_cast<void>(B);
87 static_cast<void>(C);
88 return false;
89 } else {
90 return (A.rows() % TileM) == 0 &&
91 (B.cols() % TileN) == 0 &&
92 (A.cols() % TileK) == 0 &&
98 }
99}
100
101template <typename T,
102 int TileM,
103 int TileN,
104 int TileK,
105 int ThreadTileRows,
106 int ThreadTileCols = ThreadTileRows,
107 int VecA = 4,
108 int VecB = 4,
109 int UnrollK = 1,
110 int Stages = 1,
111 bool AlignedFastPath = false,
118 T alpha,
119 T beta,
120 const char* kernel_trace_name) {
122
123 BATCHLAS_KERNEL_TRACE_SCOPE(kernel_trace_name);
124
125 static_assert(TileM % ThreadTileRows == 0, "TileM must divide evenly by ThreadTileRows");
126 static_assert(TileN % ThreadTileCols == 0, "TileN must divide evenly by ThreadTileCols");
127 static_assert(TileM * TileK >= Policy::ThreadsPerGroup, "TileA footprint must cover all threads");
128 static_assert(TileN * TileK >= Policy::ThreadsPerGroup, "TileB footprint must cover all threads");
129 static_assert(TileM % VecA == 0 && TileK % VecA == 0, "VecA must divide TileM and TileK");
130 static_assert(TileN % VecB == 0 && TileK % VecB == 0, "VecB must divide TileN and TileK");
131 static_assert(UnrollK >= 1, "UnrollK must be positive");
132 static_assert(Stages >= 1, "Stages must be positive");
133 static_assert(Stages <= 2, "Register-tiled GEMM only supports one-stage and two-stage pipelines");
134 static_assert(!AlignedFastPath || (OpA == Transpose::NoTrans && OpB == Transpose::NoTrans),
135 "Aligned fast path is only supported for NN kernels");
136
137 const auto [m, k] = get_effective_dims(A, OpA);
138 const auto [_, n] = get_effective_dims(B, OpB);
139 static_cast<void>(_);
140 const bool use_packet_a = AlignedFastPath ? true : supports_aligned_packet_loads<T, VecA>(A.data_ptr(), A.ld(), A.stride());
141 const bool use_packet_b = AlignedFastPath ? true : supports_aligned_packet_loads<T, VecB>(B.data_ptr(), B.ld(), B.stride());
142
143 const sycl::range<3> local(1, Policy::LocalRows, Policy::LocalCols);
144 const int group_rows = AlignedFastPath ? (m / TileM) : register_ceil_div<T>(m, TileM);
145 const int group_cols = AlignedFastPath ? (n / TileN) : register_ceil_div<T>(n, TileN);
146 const sycl::range<3> global(static_cast<size_t>(A.batch_size()),
147 static_cast<size_t>(group_rows * Policy::LocalRows),
148 static_cast<size_t>(group_cols * Policy::LocalCols));
149
150 ctx->submit([&](sycl::handler& h) {
151 sycl::local_accessor<T, 1> tile_a(sycl::range<1>(Policy::Stages * Policy::StageASize), h);
152 sycl::local_accessor<T, 1> tile_b(sycl::range<1>(Policy::Stages * Policy::StageBSize), h);
153
154 const T* a_ptr = A.data_ptr();
155 const T* b_ptr = B.data_ptr();
156 T* c_ptr = C.data_ptr();
157 const int lda = A.ld();
158 const int ldb = B.ld();
159 const int ldc = C.ld();
160 const int stride_a = A.stride();
161 const int stride_b = B.stride();
162 const int stride_c = C.stride();
163 const int batch = A.batch_size();
164 const bool packet_a = use_packet_a;
165 const bool packet_b = use_packet_b;
166
167 batchlas::impl::parallel_for<
169 sycl::nd_range<3>(global, local), [=](sycl::nd_item<3> item) {
170 const int bid = static_cast<int>(item.get_group(0));
171 const int local_row = static_cast<int>(item.get_local_id(1));
172 const int local_col = static_cast<int>(item.get_local_id(2));
173 if (bid >= batch) {
174 return;
175 }
176
177 const int row_base = static_cast<int>(item.get_group(1)) * TileM + local_row * ThreadTileRows;
178 const int col_base = static_cast<int>(item.get_group(2)) * TileN + local_col * ThreadTileCols;
179 const int linear_tid = local_row * Policy::LocalCols + local_col;
180
181 // Rebased per item in 64 bits (bid * stride passes 2^31); offsets within an item stay int.
182 const T* const a_item = a_ptr + static_cast<std::ptrdiff_t>(bid) * stride_a;
183 const T* const b_item = b_ptr + static_cast<std::ptrdiff_t>(bid) * stride_b;
184 T* const c_item = c_ptr + static_cast<std::ptrdiff_t>(bid) * stride_c;
185 constexpr int batch_a = 0, batch_b = 0, batch_c = 0;
186 const int group_row = static_cast<int>(item.get_group(1));
187 const int group_col = static_cast<int>(item.get_group(2));
188 T accum[ThreadTileRows][ThreadTileCols];
189 for (int i = 0; i < ThreadTileRows; ++i) {
190 for (int j = 0; j < ThreadTileCols; ++j) {
191 accum[i][j] = T(0);
192 }
193 }
194
195 auto load_stage = [&](int kk0, int stage) {
196 T* tile_a_stage = &tile_a[stage * Policy::StageASize];
197 T* tile_b_stage = &tile_b[stage * Policy::StageBSize];
198
199 if constexpr (AlignedFastPath && OpA == Transpose::NoTrans && OpB == Transpose::NoTrans
201 constexpr int APacketsPerCol = TileM / VecA;
202 constexpr int APacketCount = APacketsPerCol * TileK;
203 constexpr int APacketIterations = (APacketCount + Policy::ThreadsPerGroup - 1) / Policy::ThreadsPerGroup;
204 static_for<APacketIterations>([&](auto iter) {
205 constexpr int iteration = iter;
206 const int packet = linear_tid + iteration * Policy::ThreadsPerGroup;
207 if (packet < APacketCount) {
208 const int a_row = (packet % APacketsPerCol) * VecA;
209 const int a_col = packet / APacketsPerCol;
210 const int logical_a_row = group_row * TileM + a_row;
211 const int logical_a_col = kk0 + a_col;
212 const int base = batch_a + logical_a_col * lda + logical_a_row;
213 const auto packet_values = packet_load_aligned<T, VecA>(a_item, base);
214 for (int lane = 0; lane < VecA; ++lane) {
215 tile_a_stage[a_col * Policy::TileAStride + a_row + lane] = packet_values[lane];
216 }
217 }
218 });
219
220 constexpr int BPacketsPerCol = TileK / VecB;
221 constexpr int BPacketCount = TileN * BPacketsPerCol;
222 constexpr int BPacketIterations = (BPacketCount + Policy::ThreadsPerGroup - 1) / Policy::ThreadsPerGroup;
223 static_for<BPacketIterations>([&](auto iter) {
224 constexpr int iteration = iter;
225 const int packet = linear_tid + iteration * Policy::ThreadsPerGroup;
226 if (packet < BPacketCount) {
227 const int b_row = (packet % BPacketsPerCol) * VecB;
228 const int b_col = packet / BPacketsPerCol;
229 const int logical_b_row = kk0 + b_row;
230 const int logical_b_col = group_col * TileN + b_col;
231 const int base = batch_b + logical_b_col * ldb + logical_b_row;
232 const auto packet_values = packet_load_aligned<T, VecB>(b_item, base);
233 for (int lane = 0; lane < VecB; ++lane) {
234 tile_b_stage[b_col * Policy::TileBStride + b_row + lane] = packet_values[lane];
235 }
236 }
237 });
238 return;
239 }
240
242 if (packet_a) {
243 if constexpr (OpA == Transpose::NoTrans) {
244 constexpr int PacketsPerCol = TileM / VecA;
245 constexpr int PacketCount = PacketsPerCol * TileK;
246 #pragma unroll 4
247 for (int packet = linear_tid; packet < PacketCount; packet += Policy::ThreadsPerGroup) {
248 const int a_row = (packet % PacketsPerCol) * VecA;
249 const int a_col = packet / PacketsPerCol;
250 const int logical_a_row = group_row * TileM + a_row;
251 const int logical_a_col = kk0 + a_col;
252 if (logical_a_row + (VecA - 1) < m && logical_a_col < k) {
253 const int base = batch_a + logical_a_col * lda + logical_a_row;
254 const auto packet_values = packet_load_aligned<T, VecA>(a_item, base);
255 for (int lane = 0; lane < VecA; ++lane) {
256 tile_a_stage[a_col * Policy::TileAStride + a_row + lane] = packet_values[lane];
257 }
258 } else {
259 for (int lane = 0; lane < VecA; ++lane) {
260 const int row = logical_a_row + lane;
261 tile_a_stage[a_col * Policy::TileAStride + a_row + lane] = (row < m && logical_a_col < k)
262 ? OperandAccessor<T, OpA>::load(a_item, lda, batch_a, row, logical_a_col)
263 : T(0);
264 }
265 }
266 }
267 } else {
268 constexpr int PacketsPerRow = TileK / VecA;
269 constexpr int PacketCount = TileM * PacketsPerRow;
270 #pragma unroll 4
271 for (int packet = linear_tid; packet < PacketCount; packet += Policy::ThreadsPerGroup) {
272 const int a_row = packet / PacketsPerRow;
273 const int a_col = (packet % PacketsPerRow) * VecA;
274 const int logical_a_row = group_row * TileM + a_row;
275 const int logical_a_col = kk0 + a_col;
276 if (logical_a_row < m && logical_a_col + (VecA - 1) < k) {
277 const int base = batch_a + logical_a_row * lda + logical_a_col;
278 const auto packet_values = packet_load_aligned<T, VecA>(a_item, base);
279 for (int lane = 0; lane < VecA; ++lane) {
280 tile_a_stage[(a_col + lane) * Policy::TileAStride + a_row] = packet_values[lane];
281 }
282 } else {
283 for (int lane = 0; lane < VecA; ++lane) {
284 const int col = logical_a_col + lane;
285 tile_a_stage[(a_col + lane) * Policy::TileAStride + a_row] = (logical_a_row < m && col < k)
286 ? OperandAccessor<T, OpA>::load(a_item, lda, batch_a, logical_a_row, col)
287 : T(0);
288 }
289 }
290 }
291 }
292 } else {
293 for (int index = linear_tid; index < TileM * TileK; index += Policy::ThreadsPerGroup) {
294 const int a_row = index % TileM;
295 const int a_col = index / TileM;
296 const int logical_a_row = group_row * TileM + a_row;
297 const int logical_a_col = kk0 + a_col;
298 tile_a_stage[a_col * Policy::TileAStride + a_row] = (logical_a_row < m && logical_a_col < k)
299 ? OperandAccessor<T, OpA>::load(a_item, lda, batch_a, logical_a_row, logical_a_col)
300 : T(0);
301 }
302 }
303
304 if (packet_b) {
305 if constexpr (OpB == Transpose::NoTrans) {
306 constexpr int PacketsPerCol = TileK / VecB;
307 constexpr int PacketCount = TileN * PacketsPerCol;
308 #pragma unroll 4
309 for (int packet = linear_tid; packet < PacketCount; packet += Policy::ThreadsPerGroup) {
310 const int b_row = (packet % PacketsPerCol) * VecB;
311 const int b_col = packet / PacketsPerCol;
312 const int logical_b_row = kk0 + b_row;
313 const int logical_b_col = group_col * TileN + b_col;
314 if (logical_b_row + (VecB - 1) < k && logical_b_col < n) {
315 const int base = batch_b + logical_b_col * ldb + logical_b_row;
316 const auto packet_values = packet_load_aligned<T, VecB>(b_item, base);
317 for (int lane = 0; lane < VecB; ++lane) {
318 tile_b_stage[b_col * Policy::TileBStride + b_row + lane] = packet_values[lane];
319 }
320 } else {
321 for (int lane = 0; lane < VecB; ++lane) {
322 const int row = logical_b_row + lane;
323 tile_b_stage[b_col * Policy::TileBStride + b_row + lane] = (row < k && logical_b_col < n)
324 ? OperandAccessor<T, OpB>::load(b_item, ldb, batch_b, row, logical_b_col)
325 : T(0);
326 }
327 }
328 }
329 } else {
330 constexpr int PacketsPerRow = TileN / VecB;
331 constexpr int PacketCount = TileK * PacketsPerRow;
332 #pragma unroll 4
333 for (int packet = linear_tid; packet < PacketCount; packet += Policy::ThreadsPerGroup) {
334 const int b_row = packet / PacketsPerRow;
335 const int b_col = (packet % PacketsPerRow) * VecB;
336 const int logical_b_row = kk0 + b_row;
337 const int logical_b_col = group_col * TileN + b_col;
338 if (logical_b_row < k && logical_b_col + (VecB - 1) < n) {
339 const int base = batch_b + logical_b_row * ldb + logical_b_col;
340 const auto packet_values = packet_load_aligned<T, VecB>(b_item, base);
341 for (int lane = 0; lane < VecB; ++lane) {
342 tile_b_stage[(b_col + lane) * Policy::TileBStride + b_row] = packet_values[lane];
343 }
344 } else {
345 for (int lane = 0; lane < VecB; ++lane) {
346 const int col = logical_b_col + lane;
347 tile_b_stage[(b_col + lane) * Policy::TileBStride + b_row] = (logical_b_row < k && col < n)
348 ? OperandAccessor<T, OpB>::load(b_item, ldb, batch_b, logical_b_row, col)
349 : T(0);
350 }
351 }
352 }
353 }
354 } else {
355 for (int index = linear_tid; index < TileN * TileK; index += Policy::ThreadsPerGroup) {
356 const int b_row = index % TileK;
357 const int b_col = index / TileK;
358 const int logical_b_row = kk0 + b_row;
359 const int logical_b_col = group_col * TileN + b_col;
360 tile_b_stage[b_col * Policy::TileBStride + b_row] = (logical_b_row < k && logical_b_col < n)
361 ? OperandAccessor<T, OpB>::load(b_item, ldb, batch_b, logical_b_row, logical_b_col)
362 : T(0);
363 }
364 }
365 } else {
366 for (int index = linear_tid; index < TileM * TileK; index += Policy::ThreadsPerGroup) {
367 const int a_row = index % TileM;
368 const int a_col = index / TileM;
369 const int logical_a_row = group_row * TileM + a_row;
370 const int logical_a_col = kk0 + a_col;
371 tile_a_stage[a_col * Policy::TileAStride + a_row] = (logical_a_row < m && logical_a_col < k)
372 ? OperandAccessor<T, OpA>::load(a_item, lda, batch_a, logical_a_row, logical_a_col)
373 : T(0);
374 }
375
376 for (int index = linear_tid; index < TileN * TileK; index += Policy::ThreadsPerGroup) {
377 const int b_row = index % TileK;
378 const int b_col = index / TileK;
379 const int logical_b_row = kk0 + b_row;
380 const int logical_b_col = group_col * TileN + b_col;
381 tile_b_stage[b_col * Policy::TileBStride + b_row] = (logical_b_row < k && logical_b_col < n)
382 ? OperandAccessor<T, OpB>::load(b_item, ldb, batch_b, logical_b_row, logical_b_col)
383 : T(0);
384 }
385 }
386 };
387
388 auto accumulate_stage = [&](const T* tile_a_stage, const T* tile_b_stage, int kk0) {
389 if constexpr (AlignedFastPath) {
390 for (int t0 = 0; t0 < TileK; t0 += UnrollK) {
391 for (int unroll = 0; unroll < UnrollK; ++unroll) {
392 const int t = t0 + unroll;
393 T a_frag[ThreadTileRows];
394 T b_frag[ThreadTileCols];
395 for (int i = 0; i < ThreadTileRows; ++i) {
396 a_frag[i] = tile_a_stage[t * Policy::TileAStride + local_row * ThreadTileRows + i];
397 }
398 for (int j = 0; j < ThreadTileCols; ++j) {
399 b_frag[j] = tile_b_stage[(local_col * ThreadTileCols + j) * Policy::TileBStride + t];
400 }
401 for (int i = 0; i < ThreadTileRows; ++i) {
402 for (int j = 0; j < ThreadTileCols; ++j) {
403 accum[i][j] += a_frag[i] * b_frag[j];
404 }
405 }
406 }
407 }
408 } else {
409 for (int t0 = 0; t0 < TileK && kk0 + t0 < k; t0 += UnrollK) {
410 for (int unroll = 0; unroll < UnrollK && t0 + unroll < TileK && kk0 + t0 + unroll < k; ++unroll) {
411 const int t = t0 + unroll;
412 T a_frag[ThreadTileRows];
413 T b_frag[ThreadTileCols];
414 for (int i = 0; i < ThreadTileRows; ++i) {
415 a_frag[i] = tile_a_stage[t * Policy::TileAStride + local_row * ThreadTileRows + i];
416 }
417 for (int j = 0; j < ThreadTileCols; ++j) {
418 b_frag[j] = tile_b_stage[(local_col * ThreadTileCols + j) * Policy::TileBStride + t];
419 }
420 for (int i = 0; i < ThreadTileRows; ++i) {
421 for (int j = 0; j < ThreadTileCols; ++j) {
422 accum[i][j] += a_frag[i] * b_frag[j];
423 }
424 }
425 }
426 }
427 }
428 };
429
430 const int tile_count = AlignedFastPath ? (k / TileK) : register_ceil_div<T>(k, TileK);
431 if constexpr (Policy::Stages == 1) {
432 for (int tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
433 const int kk0 = tile_idx * TileK;
434 load_stage(kk0, 0);
435 item.barrier(sycl::access::fence_space::local_space);
436 accumulate_stage(&tile_a[0], &tile_b[0], kk0);
437 item.barrier(sycl::access::fence_space::local_space);
438 }
439 } else {
440 if constexpr (AlignedFastPath) {
441 if (tile_count > 0) {
442 load_stage(0, 0);
443 item.barrier(sycl::access::fence_space::local_space);
444
445 for (int tile_idx = 0; tile_idx + 1 < tile_count; ++tile_idx) {
446 const int current_stage = tile_idx & 1;
447 const int next_stage = current_stage ^ 1;
448 load_stage((tile_idx + 1) * TileK, next_stage);
449 accumulate_stage(&tile_a[current_stage * Policy::StageASize],
450 &tile_b[current_stage * Policy::StageBSize],
451 tile_idx * TileK);
452 item.barrier(sycl::access::fence_space::local_space);
453 }
454
455 const int final_stage = (tile_count - 1) & 1;
456 accumulate_stage(&tile_a[final_stage * Policy::StageASize],
457 &tile_b[final_stage * Policy::StageBSize],
458 (tile_count - 1) * TileK);
459 item.barrier(sycl::access::fence_space::local_space);
460 }
461 } else {
462 load_stage(0, 0);
463 item.barrier(sycl::access::fence_space::local_space);
464
465 for (int tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
466 const int kk0 = tile_idx * TileK;
467 const int current_stage = tile_idx & 1;
468 const int next_tile_idx = tile_idx + 1;
469
470 if (next_tile_idx < tile_count) {
471 load_stage(next_tile_idx * TileK, current_stage ^ 1);
472 }
473
474 accumulate_stage(&tile_a[current_stage * Policy::StageASize],
475 &tile_b[current_stage * Policy::StageBSize],
476 kk0);
477 item.barrier(sycl::access::fence_space::local_space);
478 }
479 }
480 }
481
482 if constexpr (AlignedFastPath) {
483 for (int i = 0; i < ThreadTileRows; ++i) {
484 const int row = row_base + i;
485 for (int j = 0; j < ThreadTileCols; ++j) {
486 const int col = col_base + j;
487 LinearEpilogue<T>::store(c_item + batch_c + col * ldc + row, alpha, beta, accum[i][j]);
488 }
489 }
490 } else {
491 for (int i = 0; i < ThreadTileRows; ++i) {
492 const int row = row_base + i;
493 if (row >= m) {
494 continue;
495 }
496 for (int j = 0; j < ThreadTileCols; ++j) {
497 const int col = col_base + j;
498 if (col < n) {
499 LinearEpilogue<T>::store(c_item + batch_c + col * ldc + row, alpha, beta,
500 accum[i][j]);
501 }
502 }
503 }
504 }
505 });
506 });
507
508 return ctx.get_event();
509}
510
511} // namespace batchlas::sycl_gemm
Non-owning view of a batch of matrices: the argument type of every entry point.
Definition matrix.hh:485
T * data_ptr() const
Base pointer of item 0.
Definition matrix.hh:600
int rows() const
Rows per item (the capacity on a heterogeneous batch).
Definition matrix.hh:605
int cols() const
Columns per item (the capacity on a heterogeneous batch).
Definition matrix.hh:611
int ld() const
Leading dimension: element distance between columns.
Definition matrix.hh:818
int stride() const
Batch stride: element distance between items.
Definition matrix.hh:823
int batch_size() const
Number of batch items.
Definition matrix.hh:603
Policy
Synchronous or asynchronous execution policy.
Transpose
The operator op() applied to an operand: op(A) = A, A^T or A^H.
Definition enums.hh:171
@ NoTrans
op(A) = A.
Definition enums.hh:172
std::pair< int, int > get_effective_dims(const MatrixView< T, MType > &mat, Transpose trans)
Shape (rows, cols) of op(mat) for trans: swapped unless NoTrans.
Definition matrix.hh:2159
sycl::vec< T, Width > packet_load_aligned(const T *ptr, int offset)
Event launch_register_tiled(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, T beta, const char *kernel_trace_name)
bool is_contiguous_dense_matrix(const MatrixView< T, MatrixFormat::Dense > &M)
bool supports_aligned_packet_loads(const T *ptr, int ld, int stride)
bool can_use_aligned_nn_fast_path(const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C)
void static_for_impl(Fn &&fn, std::index_sequence< I... >)
int register_ceil_div(int value, int divisor)
constexpr bool supports_packet_v
Completion handle of enqueued work, returned by every entry point.
Where BatchLAS work runs: a device, a backend, an ordered SYCL queue and a workspace arena.
Event get_event() const
An Event that completes after everything enqueued on this Queue so far.
static void store(T *dst, T alpha, T beta, T accum)
static T load(const T *ptr, int ld, int batch_offset, int row, int col)
Definition accessors.hh:36