syr2k_triangular_tiles.hh
Go to the documentation of this file.
1#pragma once
2
3// A 128x128x8 register-tiled SYR2K over the triangular tile set, fusing both
4// rank-k products into the same accumulators:
5// accum += A[bi] * B[bj]^T and accum += B[bi] * A[bj]^T
6// Products are issued sequentially, not interleaved, to keep one fragment pair
7// live (occupancy). Diagonal tiles alias the bj-side tiles to the bi-side ones
8// (block-uniform, no divergence) and mask their epilogue.
9// evidence: docs/perf/level3.md#syrk-and-syr2k-triangular-tiles-kernel-design
10
11#include <src/kernels/common/triangular_tiles.hh>
12
13#include <src/queue.hh>
14#include <src/util/kernel-trace.hh>
15#include <src/sycl/kernel_attrs.hh>
16
19
20#include <sycl/sycl.hpp>
21
22#include <cstddef>
23#include <cstdint>
24
26
27template <typename T, bool TransOperand, bool AlignedFastPath>
29
30// Does this problem satisfy everything the unpredicated path assumes? A tile
31// that is not a whole 128x128 of C, or a k the 8-deep staging cannot fill
32// exactly, has to take the predicated path, as does any operand whose base or
33// leading dimension breaks the 4-element alignment the vector forms need.
34template <typename T>
38 int n,
39 int k) {
40 if ((n % kTriangularTile) != 0 || (k % kTriangularTileK) != 0) {
41 return false;
42 }
43 auto aligned = [](const T* p, int ld, int stride) {
44 return p != nullptr && (reinterpret_cast<std::uintptr_t>(p) % (4 * sizeof(T))) == 0 &&
45 (ld % 4) == 0 && (stride % 4) == 0;
46 };
47 return aligned(A.data_ptr(), A.ld(), A.stride()) &&
48 aligned(B.data_ptr(), B.ld(), B.stride()) &&
49 aligned(C.data_ptr(), C.ld(), C.stride());
50}
51
52template <typename T, bool TransOperand, bool AlignedFastPath>
57 T alpha,
58 T beta,
59 Uplo uplo) {
60 BATCHLAS_KERNEL_TRACE_SCOPE("syr2k_cuda_custom.triangular_tiles");
61
62 constexpr int TileM = kTriangularTile;
63 constexpr int TileN = kTriangularTile;
64 constexpr int TileK = kTriangularTileK;
65 constexpr int ThreadTile = 8; // rows and cols per thread
66 constexpr int Band = ThreadTile / 2; // 4: the vectorized band width
67 constexpr int LocalRows = TileM / ThreadTile; // 16
68 constexpr int LocalCols = TileN / ThreadTile; // 16
69 constexpr int Threads = LocalRows * LocalCols; // 256
70 constexpr int Stride = TileM;
71
72 static_assert(TileM * TileK == 4 * Threads, "staging assumes one vector per thread");
73
74 const int n = static_cast<int>(C.rows());
75 const int k = TransOperand ? static_cast<int>(A.rows()) : static_cast<int>(A.cols());
76 const int tile_count = triangular_tile_count(n);
77
78 const sycl::range<3> local(1, LocalRows, LocalCols);
79 const sycl::range<3> global(static_cast<size_t>(A.batch_size()),
80 static_cast<size_t>(tile_count * LocalRows),
81 static_cast<size_t>(LocalCols));
82
83 impl::check_group_count(*ctx, sycl::nd_range<3>(global, local));
84 ctx->submit([&](sycl::handler& h) {
85 sycl::local_accessor<T, 1> tiles(sycl::range<1>(4 * TileK * Stride), h);
86
87 const T* a_ptr = A.data_ptr();
88 const T* b_ptr = B.data_ptr();
89 T* c_ptr = C.data_ptr();
90 const int lda = A.ld();
91 const int ldb = B.ld();
92 const int ldc = C.ld();
93 const int stride_a = A.stride();
94 const int stride_b = B.stride();
95 const int stride_c = C.stride();
96 const int batch = A.batch_size();
97 const bool lower = uplo == Uplo::Lower;
98
99 batchlas::impl::parallel_for<Syr2kTriangularTilesKernel<T, TransOperand, AlignedFastPath>>(h,
100 sycl::nd_range<3>(global, local), [=](sycl::nd_item<3> item) {
101 const int bid = static_cast<int>(item.get_group(0));
102 if (bid >= batch) {
103 return;
104 }
105 // The m index is the fastest-varying one, so lanes that differ
106 // in m touch consecutive addresses of a column-major C.
107 const int ty = static_cast<int>(item.get_local_id(2)); // 0..15, m
108 const int tx = static_cast<int>(item.get_local_id(1)); // 0..15, n
109 const int tid = tx * LocalRows + ty;
110
111 int bi = 0;
112 int bj = 0;
113 triangular_tile_decode(static_cast<int>(item.get_group(1)), bi, bj);
114 const bool on_diagonal = bi == bj;
115
116 const int m0 = (lower ? bi : bj) * TileM;
117 const int n0 = (lower ? bj : bi) * TileN;
118
119 const T* Ab = a_ptr + static_cast<std::ptrdiff_t>(bid) * stride_a;
120 const T* Bb = b_ptr + static_cast<std::ptrdiff_t>(bid) * stride_b;
121 T* Cb = c_ptr + static_cast<std::ptrdiff_t>(bid) * stride_c;
122
123 T* shared = tiles.template get_multi_ptr<sycl::access::decorated::no>().get();
124 T* sam = shared;
125 T* sbm = shared + TileK * Stride;
126 T* san = on_diagonal ? sam : shared + 2 * TileK * Stride;
127 T* sbn = on_diagonal ? sbm : shared + 3 * TileK * Stride;
128
129 T accum[ThreadTile][ThreadTile];
130BATCHLAS_UNROLL_FULL
131 for (int i = 0; i < ThreadTile; ++i) {
132BATCHLAS_UNROLL_FULL
133 for (int j = 0; j < ThreadTile; ++j) {
134 accum[i][j] = T(0);
135 }
136 }
137
138 // Staging coordinates. NoTrans walks the output row index,
139 // which is the contiguous direction of a column-major n x k
140 // operand; Trans walks k, which is the contiguous direction of
141 // a k x n one and has to be transposed on the way into shared.
142 const int s_row = TransOperand ? tid / 2 : (tid % 32) * 4;
143 const int s_l = TransOperand ? (tid % 2) * 4 : tid / 32;
144
145 for (int k0 = 0; k0 < k; k0 += TileK) {
146 // A shared tile is always filled to its full 128x8, with
147 // zeros outside the matrix, so the inner loop below needs
148 // no bounds checks at all.
149 auto stage = [&](const T* src, int ld, T* dst, int row0) {
150 if constexpr (TransOperand) {
151 const int row = row0 + s_row;
152 if constexpr (AlignedFastPath) {
153 const TileVec4<T> v =
154 tile_vec4(src + (k0 + s_l) +
155 static_cast<std::ptrdiff_t>(row) * ld);
156BATCHLAS_UNROLL_FULL
157 for (int i = 0; i < 4; ++i) {
158 dst[(s_l + i) * Stride + s_row] = v.v[i];
159 }
160 } else {
161BATCHLAS_UNROLL_FULL
162 for (int i = 0; i < 4; ++i) {
163 const int gk = k0 + s_l + i;
164 dst[(s_l + i) * Stride + s_row] =
165 (gk < k && row < n)
166 ? src[gk + static_cast<std::ptrdiff_t>(row) * ld]
167 : T(0);
168 }
169 }
170 } else {
171 const int gk = k0 + s_l;
172 if constexpr (AlignedFastPath) {
173 tile_vec4(&dst[s_l * Stride + s_row]) =
174 tile_vec4(src + (row0 + s_row) +
175 static_cast<std::ptrdiff_t>(gk) * ld);
176 } else {
177BATCHLAS_UNROLL_FULL
178 for (int i = 0; i < 4; ++i) {
179 const int row = row0 + s_row + i;
180 dst[s_l * Stride + s_row + i] =
181 (row < n && gk < k)
182 ? src[row + static_cast<std::ptrdiff_t>(gk) * ld]
183 : T(0);
184 }
185 }
186 }
187 };
188
189 stage(Ab, lda, sam, m0);
190 stage(Bb, ldb, sbm, m0);
191 if (!on_diagonal) {
192 stage(Ab, lda, san, n0);
193 stage(Bb, ldb, sbn, n0);
194 }
195 item.barrier(sycl::access::fence_space::local_space);
196
197BATCHLAS_UNROLL_FULL
198 for (int kk = 0; kk < TileK; ++kk) {
199BATCHLAS_UNROLL_FULL
200 for (int product = 0; product < 2; ++product) {
201 const T* srow = product == 0 ? sam : sbm;
202 const T* scol = product == 0 ? sbn : san;
203 const TileVec4<T> r0 = tile_vec4(&srow[kk * Stride + ty * Band]);
204 const TileVec4<T> r1 = tile_vec4(&srow[kk * Stride + 64 + ty * Band]);
205 const TileVec4<T> c0 = tile_vec4(&scol[kk * Stride + tx * Band]);
206 const TileVec4<T> c1 = tile_vec4(&scol[kk * Stride + 64 + tx * Band]);
207 const T rf[ThreadTile] = {r0.v[0], r0.v[1], r0.v[2], r0.v[3],
208 r1.v[0], r1.v[1], r1.v[2], r1.v[3]};
209 const T cf[ThreadTile] = {c0.v[0], c0.v[1], c0.v[2], c0.v[3],
210 c1.v[0], c1.v[1], c1.v[2], c1.v[3]};
211BATCHLAS_UNROLL_FULL
212 for (int i = 0; i < ThreadTile; ++i) {
213BATCHLAS_UNROLL_FULL
214 for (int j = 0; j < ThreadTile; ++j) {
215 accum[i][j] += rf[i] * cf[j];
216 }
217 }
218 }
219 }
220 item.barrier(sycl::access::fence_space::local_space);
221 }
222
223 // Epilogue. Off the diagonal every element of the tile is in
224 // the requested triangle, so a whole 4-row band is one 128-bit
225 // access; on the diagonal the mask makes the band partial and
226 // the stores go one at a time.
227 auto store_element = [&](int row, int col, T value) {
228 if (row >= n || col >= n) {
229 return;
230 }
231 if (on_diagonal && (lower ? row < col : row > col)) {
232 return;
233 }
234 T* p = &Cb[row + static_cast<std::ptrdiff_t>(col) * ldc];
235 *p = beta == T(0) ? alpha * value : alpha * value + beta * *p;
236 };
237
238BATCHLAS_UNROLL_FULL
239 for (int band = 0; band < 2; ++band) {
240 const int gm = m0 + band * 64 + ty * Band;
241BATCHLAS_UNROLL_FULL
242 for (int j = 0; j < ThreadTile; ++j) {
243 const int gn = n0 + (j < Band ? tx * Band + j : 64 + tx * Band + j - Band);
244 if constexpr (AlignedFastPath) {
245 if (!on_diagonal) {
246 T* p = &Cb[gm + static_cast<std::ptrdiff_t>(gn) * ldc];
247 TileVec4<T> out;
248 if (beta == T(0)) {
249BATCHLAS_UNROLL_FULL
250 for (int i = 0; i < 4; ++i) {
251 out.v[i] = alpha * accum[band * Band + i][j];
252 }
253 } else {
254 const TileVec4<T> prior = tile_vec4(const_cast<const T*>(p));
255BATCHLAS_UNROLL_FULL
256 for (int i = 0; i < 4; ++i) {
257 out.v[i] = alpha * accum[band * Band + i][j] +
258 beta * prior.v[i];
259 }
260 }
261 tile_vec4(p) = out;
262 continue;
263 }
264 }
265BATCHLAS_UNROLL_FULL
266 for (int i = 0; i < 4; ++i) {
267 store_element(gm + i, gn, accum[band * Band + i][j]);
268 }
269 }
270 }
271 });
272 });
273
274 return ctx.get_event();
275}
276
277template <typename T>
282 T alpha,
283 T beta,
284 Uplo uplo,
285 Transpose transA) {
286 const int n = static_cast<int>(C.rows());
287 const int k = transA == Transpose::NoTrans ? static_cast<int>(A.cols())
288 : static_cast<int>(A.rows());
289 const bool fast = syr2k_triangular_fast_path(A, B, C, n, k);
290
291 if (transA == Transpose::NoTrans) {
292 return fast
293 ? launch_syr2k_triangular_tiles<T, false, true>(ctx, A, B, C, alpha, beta, uplo)
294 : launch_syr2k_triangular_tiles<T, false, false>(ctx, A, B, C, alpha, beta, uplo);
295 }
296 return fast ? launch_syr2k_triangular_tiles<T, true, true>(ctx, A, B, C, alpha, beta, uplo)
297 : launch_syr2k_triangular_tiles<T, true, false>(ctx, A, B, C, alpha, beta, uplo);
298}
299
300} // namespace batchlas::backend::detail
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
Enumerations, scalar traits and scalar concepts shared by every public header.
Transpose
The operator op() applied to an operand: op(A) = A, A^T or A^H.
Definition enums.hh:171
Uplo
Which triangle of a symmetric, Hermitian or triangular operand is referenced.
Definition enums.hh:287
@ NoTrans
op(A) = A.
Definition enums.hh:172
@ Lower
The lower triangle (i >= j).
Definition enums.hh:289
The batched containers: Matrix (owning), MatrixView (non-owning), KernelMatrixView (device-side),...
bool syr2k_triangular_fast_path(const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, int n, int k)
Event syr2k_triangular_tiles(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, T beta, Uplo uplo, Transpose transA)
Event launch_syr2k_triangular_tiles(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, T beta, Uplo uplo)
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.
Element distance between consecutive batch items, as a distinct type (0 = packed).
Definition matrix.hh:83