syrk_triangular_tiles.hh
Go to the documentation of this file.
1#pragma once
2
3// A 128x128x8 register-tiled SYRK over the triangular tile set: tiles outside
4// the requested triangle are never launched, and diagonal tiles mask their
5// epilogue (the other half is neither read nor written). Inner loop as in
6// src/ops/gemm/kernels/register_128x128.hh; both operands are A at two row offsets.
7// evidence: docs/perf/level3.md#syrk-and-syr2k-triangular-tiles-kernel-design
8
9#include <src/kernels/common/triangular_tiles.hh>
10
11#include <src/queue.hh>
12#include <src/util/kernel-trace.hh>
13#include <src/sycl/kernel_attrs.hh>
14
17
18#include <sycl/sycl.hpp>
19
20#include <cstddef>
21#include <cstdint>
22
24
25template <typename T, bool TransOperand, bool AlignedFastPath>
27
28// Does this problem satisfy everything the unpredicated path assumes? A tile
29// that is not a whole 128x128 of C, or a k the 8-deep staging cannot fill
30// exactly, has to take the predicated path, as does any operand whose base or
31// leading dimension breaks the 4-element alignment the packet forms need.
32template <typename T>
35 int n,
36 int k) {
37 if ((n % kTriangularTile) != 0 || (k % kTriangularTileK) != 0) {
38 return false;
39 }
40 auto aligned = [](const T* p, int ld, int stride) {
41 return p != nullptr && (reinterpret_cast<std::uintptr_t>(p) % (4 * sizeof(T))) == 0 &&
42 (ld % 4) == 0 && (stride % 4) == 0;
43 };
44 return aligned(A.data_ptr(), A.ld(), A.stride()) &&
45 aligned(C.data_ptr(), C.ld(), C.stride());
46}
47
48template <typename T, bool TransOperand, bool AlignedFastPath>
52 T alpha,
53 T beta,
54 Uplo uplo) {
55 BATCHLAS_KERNEL_TRACE_SCOPE("syrk_cuda_custom.triangular_tiles");
56
57 constexpr int TileM = kTriangularTile;
58 constexpr int TileN = kTriangularTile;
59 constexpr int TileK = kTriangularTileK;
60 constexpr int ThreadTile = 8; // rows and cols per thread
61 constexpr int Band = ThreadTile / 2; // 4: the vectorized band width
62 constexpr int LocalRows = TileM / ThreadTile; // 16
63 constexpr int LocalCols = TileN / ThreadTile; // 16
64 constexpr int Threads = LocalRows * LocalCols; // 256
65 constexpr int AStride = TileM;
66 constexpr int BStride = TileN;
67
68 static_assert(TileM * TileK == 4 * Threads, "staging assumes one packet per thread");
69
70 const int n = static_cast<int>(C.rows());
71 const int k = TransOperand ? static_cast<int>(A.rows()) : static_cast<int>(A.cols());
72 const int tile_count = triangular_tile_count(n);
73
74 const sycl::range<3> local(1, LocalRows, LocalCols);
75 const sycl::range<3> global(static_cast<size_t>(A.batch_size()),
76 static_cast<size_t>(tile_count * LocalRows),
77 static_cast<size_t>(LocalCols));
78
79 impl::check_group_count(*ctx, sycl::nd_range<3>(global, local));
80 ctx->submit([&](sycl::handler& h) {
81 sycl::local_accessor<T, 1> tile_a(sycl::range<1>(TileK * AStride), h);
82 sycl::local_accessor<T, 1> tile_b(sycl::range<1>(TileK * BStride), h);
83
84 const T* a_ptr = A.data_ptr();
85 T* c_ptr = C.data_ptr();
86 const int lda = A.ld();
87 const int ldc = C.ld();
88 const int stride_a = A.stride();
89 const int stride_c = C.stride();
90 const int batch = A.batch_size();
91 const bool lower = uplo == Uplo::Lower;
92
93 batchlas::impl::parallel_for<SyrkTriangularTilesKernel<T, TransOperand, AlignedFastPath>>(h,
94 sycl::nd_range<3>(global, local), [=](sycl::nd_item<3> item) {
95 const int bid = static_cast<int>(item.get_group(0));
96 if (bid >= batch) {
97 return;
98 }
99 // The m index is the fastest-varying one, so lanes that differ
100 // in m touch consecutive addresses of a column-major C.
101 const int ty = static_cast<int>(item.get_local_id(2)); // 0..15, m
102 const int tx = static_cast<int>(item.get_local_id(1)); // 0..15, n
103 const int tid = tx * LocalRows + ty;
104
105 int bi = 0;
106 int bj = 0;
107 triangular_tile_decode(static_cast<int>(item.get_group(1)), bi, bj);
108 const bool on_diagonal = bi == bj;
109
110 const int m0 = (lower ? bi : bj) * TileM;
111 const int n0 = (lower ? bj : bi) * TileN;
112
113 const T* Ab = a_ptr + static_cast<std::ptrdiff_t>(bid) * stride_a;
114 T* Cb = c_ptr + static_cast<std::ptrdiff_t>(bid) * stride_c;
115
116 T* sa = tile_a.template get_multi_ptr<sycl::access::decorated::no>().get();
117 T* sb = tile_b.template get_multi_ptr<sycl::access::decorated::no>().get();
118
119 T accum[ThreadTile][ThreadTile];
120BATCHLAS_UNROLL_FULL
121 for (int i = 0; i < ThreadTile; ++i) {
122BATCHLAS_UNROLL_FULL
123 for (int j = 0; j < ThreadTile; ++j) {
124 accum[i][j] = T(0);
125 }
126 }
127
128 // Staging coordinates. NoTrans walks the output row index,
129 // which is the contiguous direction of a column-major n x k A;
130 // Trans walks k, which is the contiguous direction of a k x n A
131 // and has to be transposed on the way into shared.
132 const int s_row = TransOperand ? tid / 2 : (tid % 32) * 4;
133 const int s_l = TransOperand ? (tid % 2) * 4 : tid / 32;
134
135 for (int k0 = 0; k0 < k; k0 += TileK) {
136 if constexpr (AlignedFastPath) {
137 if constexpr (TransOperand) {
138 const TileVec4<T> va =
139 tile_vec4(Ab + (k0 + s_l) +
140 static_cast<std::ptrdiff_t>(m0 + s_row) * lda);
141 const TileVec4<T> vb =
142 tile_vec4(Ab + (k0 + s_l) +
143 static_cast<std::ptrdiff_t>(n0 + s_row) * lda);
144BATCHLAS_UNROLL_FULL
145 for (int i = 0; i < 4; ++i) {
146 sa[(s_l + i) * AStride + s_row] = va.v[i];
147 sb[(s_l + i) * BStride + s_row] = vb.v[i];
148 }
149 } else {
150 tile_vec4(&sa[s_l * AStride + s_row]) =
151 tile_vec4(Ab + (m0 + s_row) +
152 static_cast<std::ptrdiff_t>(k0 + s_l) * lda);
153 tile_vec4(&sb[s_l * BStride + s_row]) =
154 tile_vec4(Ab + (n0 + s_row) +
155 static_cast<std::ptrdiff_t>(k0 + s_l) * lda);
156 }
157 } else {
158 // The shared tiles are always filled to their full
159 // 128x8, with zeros outside the matrix, so the inner
160 // loop below needs no bounds checks at all.
161 if constexpr (TransOperand) {
162 const int gm = m0 + s_row;
163 const int gn = n0 + s_row;
164BATCHLAS_UNROLL_FULL
165 for (int i = 0; i < 4; ++i) {
166 const int gk = k0 + s_l + i;
167 sa[(s_l + i) * AStride + s_row] =
168 (gk < k && gm < n)
169 ? Ab[gk + static_cast<std::ptrdiff_t>(gm) * lda]
170 : T(0);
171 sb[(s_l + i) * BStride + s_row] =
172 (gk < k && gn < n)
173 ? Ab[gk + static_cast<std::ptrdiff_t>(gn) * lda]
174 : T(0);
175 }
176 } else {
177 const int gk = k0 + s_l;
178BATCHLAS_UNROLL_FULL
179 for (int i = 0; i < 4; ++i) {
180 const int gm = m0 + s_row + i;
181 const int gn = n0 + s_row + i;
182 sa[s_l * AStride + s_row + i] =
183 (gm < n && gk < k)
184 ? Ab[gm + static_cast<std::ptrdiff_t>(gk) * lda]
185 : T(0);
186 sb[s_l * BStride + s_row + i] =
187 (gn < n && gk < k)
188 ? Ab[gn + static_cast<std::ptrdiff_t>(gk) * lda]
189 : T(0);
190 }
191 }
192 }
193 item.barrier(sycl::access::fence_space::local_space);
194
195BATCHLAS_UNROLL_FULL
196 for (int kk = 0; kk < TileK; ++kk) {
197 const TileVec4<T> a0 = tile_vec4(&sa[kk * AStride + ty * Band]);
198 const TileVec4<T> a1 = tile_vec4(&sa[kk * AStride + 64 + ty * Band]);
199 const TileVec4<T> b0 = tile_vec4(&sb[kk * BStride + tx * Band]);
200 const TileVec4<T> b1 = tile_vec4(&sb[kk * BStride + 64 + tx * Band]);
201 const T af[ThreadTile] = {a0.v[0], a0.v[1], a0.v[2], a0.v[3],
202 a1.v[0], a1.v[1], a1.v[2], a1.v[3]};
203 const T bf[ThreadTile] = {b0.v[0], b0.v[1], b0.v[2], b0.v[3],
204 b1.v[0], b1.v[1], b1.v[2], b1.v[3]};
205BATCHLAS_UNROLL_FULL
206 for (int i = 0; i < ThreadTile; ++i) {
207BATCHLAS_UNROLL_FULL
208 for (int j = 0; j < ThreadTile; ++j) {
209 accum[i][j] += af[i] * bf[j];
210 }
211 }
212 }
213 item.barrier(sycl::access::fence_space::local_space);
214 }
215
216 // Epilogue. Off the diagonal every element of the tile is in
217 // the requested triangle, so a whole 4-row band is one 128-bit
218 // access; on the diagonal the mask makes the band partial and
219 // the stores go one at a time.
220 auto store_element = [&](int row, int col, T value) {
221 if (row >= n || col >= n) {
222 return;
223 }
224 if (on_diagonal && (lower ? row < col : row > col)) {
225 return;
226 }
227 T* p = &Cb[row + static_cast<std::ptrdiff_t>(col) * ldc];
228 *p = beta == T(0) ? alpha * value : alpha * value + beta * *p;
229 };
230
231BATCHLAS_UNROLL_FULL
232 for (int band = 0; band < 2; ++band) {
233 const int gm = m0 + band * 64 + ty * Band;
234BATCHLAS_UNROLL_FULL
235 for (int j = 0; j < ThreadTile; ++j) {
236 const int gn = n0 + (j < Band ? tx * Band + j : 64 + tx * Band + j - Band);
237 if constexpr (AlignedFastPath) {
238 if (!on_diagonal) {
239 T* p = &Cb[gm + static_cast<std::ptrdiff_t>(gn) * ldc];
240 TileVec4<T> out;
241 if (beta == T(0)) {
242BATCHLAS_UNROLL_FULL
243 for (int i = 0; i < 4; ++i) {
244 out.v[i] = alpha * accum[band * Band + i][j];
245 }
246 } else {
247 const TileVec4<T> prior = tile_vec4(const_cast<const T*>(p));
248BATCHLAS_UNROLL_FULL
249 for (int i = 0; i < 4; ++i) {
250 out.v[i] = alpha * accum[band * Band + i][j] +
251 beta * prior.v[i];
252 }
253 }
254 tile_vec4(p) = out;
255 continue;
256 }
257 }
258BATCHLAS_UNROLL_FULL
259 for (int i = 0; i < 4; ++i) {
260 store_element(gm + i, gn, accum[band * Band + i][j]);
261 }
262 }
263 }
264 });
265 });
266
267 return ctx.get_event();
268}
269
270template <typename T>
274 T alpha,
275 T beta,
276 Uplo uplo,
277 Transpose transA) {
278 const int n = static_cast<int>(C.rows());
279 const int k = transA == Transpose::NoTrans ? static_cast<int>(A.cols())
280 : static_cast<int>(A.rows());
281 const bool fast = syrk_triangular_fast_path(A, C, n, k);
282
283 if (transA == Transpose::NoTrans) {
284 return fast ? launch_syrk_triangular_tiles<T, false, true>(ctx, A, C, alpha, beta, uplo)
285 : launch_syrk_triangular_tiles<T, false, false>(ctx, A, C, alpha, beta, uplo);
286 }
287 return fast ? launch_syrk_triangular_tiles<T, true, true>(ctx, A, C, alpha, beta, uplo)
288 : launch_syrk_triangular_tiles<T, true, false>(ctx, A, C, alpha, beta, uplo);
289}
290
291} // 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),...
Event syrk_triangular_tiles(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, T beta, Uplo uplo, Transpose transA)
Event launch_syrk_triangular_tiles(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, T beta, Uplo uplo)
bool syrk_triangular_fast_path(const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &C, int n, int k)
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.