trmm_triangular_tiles.hh
Go to the documentation of this file.
1#pragma once
2
3// Batched Side::Left TRMM that skips the zero half of A with a loop bound, not a mask: an output
4// tile rooted at row m0 reduces only over the p its triangle reaches, and only the k-tile on the
5// diagonal is masked (in the A staging). The saving is (R+1)/2R for R = m/TileM row tiles, not 2x,
6// so the row tile is sized to m (trmm_row_tile). Layout and swizzle follow syrk_gram_tiles.hh.
7// evidence: docs/perf/level3.md#trmm-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 <algorithm>
21#include <cstddef>
22#include <cstdint>
23#include <type_traits>
24#include <batchlas/settings.hh>
25
27
28inline constexpr int kTrmmTileN = 128;
29inline constexpr int kTrmmTileK = 16;
30
31// Lanes per tile side: narrower sides keep the 4-wide fragment band and drop lanes, so
32// ThreadRows stays a multiple of 4 (the 16-row tile gets 4 lanes).
33inline constexpr int trmm_lanes(int tile) {
34 if (tile >= 64) return 16;
35 return tile >= 32 ? 8 : 4;
36}
37
38template <typename T, int TileM>
40
41template <typename T, int TileM>
46 T alpha,
47 Uplo uplo,
48 Transpose transA,
49 Diag diag) {
50 BATCHLAS_KERNEL_TRACE_SCOPE("trmm_cuda_custom.triangular_tiles");
51
52 constexpr int TileN = kTrmmTileN;
53 constexpr int TileK = kTrmmTileK;
54 constexpr int LocalRows = trmm_lanes(TileM);
55 // Complex at TileM >= 64 halves the column lanes (4x16 thread tile) to get off the shared-load
56 // limit; not below 64, where the block is already small and bandwidth bound.
57 // evidence: docs/perf/level3.md#trmm-triangular-tiles-kernel-design
58 constexpr int LocalCols = (is_std_complex_v<T> && TileM >= 64)
59 ? trmm_lanes(TileN) / 2
60 : trmm_lanes(TileN);
61 constexpr int ThreadRows = TileM / LocalRows;
62 constexpr int ThreadCols = TileN / LocalCols;
63 constexpr int Threads = LocalRows * LocalCols;
64 constexpr int SA = TileM; // aligned strides
65 constexpr int SB = TileN;
66 constexpr int PacksA = TileM / 4;
67 constexpr int PacksB = TileN / 4;
68 constexpr int BandsR = ThreadRows / 4;
69 constexpr int BandsC = ThreadCols / 4;
70 constexpr int BandSpanR = TileM / BandsR;
71 constexpr int BandSpanC = TileN / BandsC;
72 constexpr int PacketsA = TileK * PacksA;
73 constexpr int PacketsB = TileK * PacksB;
74
75 static_assert(ThreadRows % 4 == 0 && ThreadCols % 4 == 0, "fragments are 4-wide bands");
76 static_assert(TileM % TileK == 0, "the diagonal tile has to fall on a k boundary");
77 static_assert((PacksA & (PacksA - 1)) == 0 && (PacksB & (PacksB - 1)) == 0,
78 "the swizzle needs a power-of-two packet count");
79
80 const int m = static_cast<int>(C.rows());
81 const int n = static_cast<int>(C.cols());
82 const int batch = A.batch_size();
83 const int tiles_m = (m + TileM - 1) / TileM;
84 const int tiles_n = (n + TileN - 1) / TileN;
85
86 const bool transposed = transA != Transpose::NoTrans;
87 // Transposing an upper triangle gives a lower one, so this single flag is
88 // everything the kernel needs from uplo and trans.
89 const bool lower_eff = (uplo == Uplo::Lower) != transposed;
90 const bool unit = diag == Diag::Unit;
91 const bool conjugated = transA == Transpose::ConjTrans;
92
93 const sycl::range<3> local(1, 1, static_cast<size_t>(Threads));
94 const sycl::range<3> global(static_cast<size_t>(batch),
95 static_cast<size_t>(tiles_m * tiles_n),
96 static_cast<size_t>(Threads));
97
98 impl::check_group_count(*ctx, sycl::nd_range<3>(global, local));
99 ctx->submit([&](sycl::handler& h) {
100 sycl::local_accessor<T, 1> tile_a(sycl::range<1>(TileK * SA), h);
101 sycl::local_accessor<T, 1> tile_b(sycl::range<1>(TileK * SB), h);
102
103 const T* a_ptr = A.data_ptr();
104 const T* b_ptr = B.data_ptr();
105 T* c_ptr = C.data_ptr();
106 const int lda = A.ld();
107 const int ldb = B.ld();
108 const int ldc = C.ld();
109 const int stride_a = A.stride();
110 const int stride_b = B.stride();
111 const int stride_c = C.stride();
112
113 batchlas::impl::parallel_for<TrmmTriangularTilesKernel<T, TileM>>(h,
114 sycl::nd_range<3>(global, local), [=](sycl::nd_item<3> item) {
115 const int bid = static_cast<int>(item.get_group(0));
116 if (bid >= batch) {
117 return;
118 }
119 const int gid = static_cast<int>(item.get_group(1));
120 const int tile_row = gid / tiles_n;
121 const int tile_col = gid % tiles_n;
122 const int m0 = tile_row * TileM;
123 const int n0 = tile_col * TileN;
124
125 const int tid = static_cast<int>(item.get_local_id(2));
126 const int tr = tid % LocalRows;
127 const int tc = tid / LocalRows;
128
129 const T* Ab = a_ptr + static_cast<std::ptrdiff_t>(bid) * stride_a;
130 const T* Bb = b_ptr + static_cast<std::ptrdiff_t>(bid) * stride_b;
131 T* Cb = c_ptr + static_cast<std::ptrdiff_t>(bid) * stride_c;
132 T* sa = tile_a.template get_multi_ptr<sycl::access::decorated::no>().get();
133 T* sb = tile_b.template get_multi_ptr<sycl::access::decorated::no>().get();
134
135 auto swizzle_a = [](int packet, int pp) {
136 return packet ^ ((pp >> 2) & (PacksA - 1));
137 };
138 auto swizzle_b = [](int packet, int pp) {
139 return packet ^ ((pp >> 2) & (PacksB - 1));
140 };
141
142 T accum[ThreadRows][ThreadCols];
143BATCHLAS_UNROLL_FULL
144 for (int i = 0; i < ThreadRows; ++i) {
145BATCHLAS_UNROLL_FULL
146 for (int j = 0; j < ThreadCols; ++j) {
147 accum[i][j] = T(0);
148 }
149 }
150
151 // The whole point: an output tile rooted at row m0 reads only
152 // the part of the reduction its triangle can reach.
153 const int p_begin = lower_eff ? 0 : m0;
154 const int p_end = lower_eff ? sycl::min(m, m0 + TileM) : m;
155
156 for (int p0 = p_begin; p0 < p_end; p0 += TileK) {
157 // op(A) tile with triangle, unit diagonal and edges resolved here, so the
158 // inner loop sees a dense tile. Staging follows op(A)'s contiguous direction.
159 if (transposed) {
160 for (int flat = tid; flat < PacketsA; flat += Threads) {
161 const int i = m0 + flat / (TileK / 4);
162 const int pp0 = (flat % (TileK / 4)) * 4;
163 const int ii = i - m0;
164 const int phys = (swizzle_a(ii >> 2, pp0) << 2) | (ii & 3);
165BATCHLAS_UNROLL_FULL
166 for (int e = 0; e < 4; ++e) {
167 const int p = p0 + pp0 + e;
168 T value = T(0);
169 if (i < m && p < m) {
170 // ConjTrans: conjugate on the way in; a unit diagonal is a literal 1.
171 if (i == p) {
172 value = unit
173 ? T(1)
174 : conjugated
175 ? batchlas::device::detail::conj(Ab[i + static_cast<std::ptrdiff_t>(i) * lda])
176 : Ab[i + static_cast<std::ptrdiff_t>(i) * lda];
177 } else if (lower_eff ? (p < i) : (p > i)) {
178 const T raw = Ab[p + static_cast<std::ptrdiff_t>(i) * lda];
179 value = conjugated ? batchlas::device::detail::conj(raw) : raw;
180 }
181 }
182 sa[(pp0 + e) * SA + phys] = value;
183 }
184 }
185 } else {
186 for (int flat = tid; flat < PacketsA; flat += Threads) {
187 const int pp = flat / PacksA;
188 const int ii0 = (flat % PacksA) * 4;
189 const int p = p0 + pp;
190 const int phys = swizzle_a(ii0 >> 2, pp) << 2;
191 // One 128-bit store of four adjacent rows: single stores would hit 8 banks.
192 TileVec4<T> packet;
193BATCHLAS_UNROLL_FULL
194 for (int e = 0; e < 4; ++e) {
195 const int i = m0 + ii0 + e;
196 T value = T(0);
197 if (i < m && p < m) {
198 if (i == p) {
199 value = unit
200 ? T(1)
201 : Ab[i + static_cast<std::ptrdiff_t>(i) * lda];
202 } else if (lower_eff ? (p < i) : (p > i)) {
203 value = Ab[i + static_cast<std::ptrdiff_t>(p) * lda];
204 }
205 }
206 packet.v[e] = value;
207 }
208 tile_store4(&sa[pp * SA + phys], packet);
209 }
210 }
211
212 // B: reduction rows [p0, p0+TileK) against columns
213 // [n0, n0+TileN). B is contiguous down its row index, so a
214 // thread takes four adjacent reduction rows of one column.
215 for (int flat = tid; flat < PacketsB; flat += Threads) {
216 const int col = flat / (TileK / 4);
217 const int pp0 = (flat % (TileK / 4)) * 4;
218 const int j = n0 + col;
219 const int phys = (swizzle_b(col >> 2, pp0) << 2) | (col & 3);
220BATCHLAS_UNROLL_FULL
221 for (int e = 0; e < 4; ++e) {
222 const int p = p0 + pp0 + e;
223 sb[(pp0 + e) * SB + phys] =
224 (j < n && p < m)
225 ? Bb[p + static_cast<std::ptrdiff_t>(j) * ldb]
226 : T(0);
227 }
228 }
229 item.barrier(sycl::access::fence_space::local_space);
230
231#pragma unroll 4
232 for (int p = 0; p < TileK; ++p) {
233 const T* rowa = &sa[p * SA];
234 const T* rowb = &sb[p * SB];
235 T af[ThreadRows];
236 T bf[ThreadCols];
237BATCHLAS_UNROLL_FULL
238 for (int b = 0; b < BandsR; ++b) {
239 const TileVec4<T> v =
240 tile_load4(rowa + (swizzle_a(b * (BandSpanR / 4) + tr, p) << 2));
241BATCHLAS_UNROLL_FULL
242 for (int e = 0; e < 4; ++e) {
243 af[b * 4 + e] = v.v[e];
244 }
245 }
246BATCHLAS_UNROLL_FULL
247 for (int b = 0; b < BandsC; ++b) {
248 const TileVec4<T> v =
249 tile_load4(rowb + (swizzle_b(b * (BandSpanC / 4) + tc, p) << 2));
250BATCHLAS_UNROLL_FULL
251 for (int e = 0; e < 4; ++e) {
252 bf[b * 4 + e] = v.v[e];
253 }
254 }
255BATCHLAS_UNROLL_FULL
256 for (int i = 0; i < ThreadRows; ++i) {
257BATCHLAS_UNROLL_FULL
258 for (int j = 0; j < ThreadCols; ++j) {
259 accum[i][j] = accumulate(accum[i][j], af[i], bf[j]);
260 }
261 }
262 }
263 item.barrier(sycl::access::fence_space::local_space);
264 }
265
266 // TRMM overwrites C; there is no beta, so nothing here reads it.
267BATCHLAS_UNROLL_FULL
268 for (int j = 0; j < ThreadCols; ++j) {
269 const int col = n0 + (j / 4) * BandSpanC + tc * 4 + (j % 4);
270 if (col >= n) {
271 continue;
272 }
273BATCHLAS_UNROLL_FULL
274 for (int i = 0; i < ThreadRows; ++i) {
275 const int row = m0 + (i / 4) * BandSpanR + tr * 4 + (i % 4);
276 if (row >= m) {
277 continue;
278 }
279 Cb[row + static_cast<std::ptrdiff_t>(col) * ldc] = alpha * accum[i][j];
280 }
281 }
282 });
283 });
284
285 return ctx.get_event();
286}
287
288// Everything the kernel needs from the problem, independent of scalar type and
289// of the queue. Left side only: the right-side product C = alpha * B * op(A)
290// puts the triangle on the column index, which is a different k-loop bound and
291// a different staging assignment, not a transpose of this one.
292template <typename T>
296 Side side) {
297 if (side != Side::Left) {
298 return false;
299 }
300 if (A.rows() != A.cols() || A.rows() != C.rows()) {
301 return false;
302 }
303 if (A.batch_size() != B.batch_size() || B.batch_size() != C.batch_size()) {
304 return false;
305 }
306 if (B.rows() != C.rows() || B.cols() != C.cols()) {
307 return false;
308 }
309 if (A.is_heterogeneous() || B.is_heterogeneous() || C.is_heterogeneous()) {
310 return false;
311 }
312 return C.rows() > 0 && C.cols() > 0;
313}
314
315// The row tile: a smaller tile does less arithmetic but re-reads B. The scalar type decides which
316// wins (float: bandwidth bound, widest tile; double and complex: compute bound, narrowest), so the
317// thresholds are per type. evidence: docs/perf/level3.md#trmm-choosing-the-row-tile-by-scalar-type
318template <typename T>
319inline int trmm_row_tile(int m) {
320 constexpr bool complex_t = is_std_complex_v<T>;
321 constexpr bool wide = sizeof(typename base_type<T>::type) > 4 || complex_t;
322 if constexpr (wide) {
323 // Complex stops at 32: its 64-row cell was a wash and the wider tile keeps the block larger.
324 if (m <= (complex_t ? 32 : 64)) {
325 return 16;
326 }
327 // Never 128: an 8x8 complex<double> thread tile fills the register file and the launch is rejected.
328 return m <= 64 ? 32 : 64;
329 } else {
330 if (m <= 32) {
331 return 32;
332 }
333 return m <= 512 ? 64 : 128;
334 }
335}
336// The row tile the launch below instantiates: BATCHLAS_TRMM_TILE_M (0 = unset, not latched) or
337// trmm_row_tile, rounded up to 16/32/64/128. can_run's grid term reads the same function.
338template <typename T>
339inline int trmm_launch_tile_m(int m) {
340 const int forced = batchlas::settings().geometry.trmm_tile_m;
341 const int tile_m = forced ? forced : trmm_row_tile<T>(m);
342 return tile_m <= 16 ? 16 : (tile_m <= 32 ? 32 : (tile_m <= 64 ? 64 : 128));
343}
344
345// Work-groups in SYCL dim 1 (CUDA grid y, capped at 65535): one per (row tile, column tile).
346template <typename T>
347inline std::int64_t trmm_tile_groups(std::int64_t m, std::int64_t n) {
348 const std::int64_t tile_m = trmm_launch_tile_m<T>(static_cast<int>(m));
349 return ((m + tile_m - 1) / tile_m) * ((n + kTrmmTileN - 1) / kTrmmTileN);
350}
351
352template <typename T>
357 T alpha,
358 Uplo uplo,
359 Transpose transA,
360 Diag diag) {
361 // BATCHLAS_TRMM_TILE_M pins the row tile so the trade-off above can be swept from one binary.
362 const int tile_m = trmm_launch_tile_m<T>(static_cast<int>(C.rows()));
363 if (tile_m <= 16) {
364 return launch_trmm_triangular_tiles<T, 16>(ctx, A, B, C, alpha, uplo, transA, diag);
365 }
366 if (tile_m <= 32) {
367 return launch_trmm_triangular_tiles<T, 32>(ctx, A, B, C, alpha, uplo, transA, diag);
368 }
369 if (tile_m <= 64) {
370 return launch_trmm_triangular_tiles<T, 64>(ctx, A, B, C, alpha, uplo, transA, diag);
371 }
372 return launch_trmm_triangular_tiles<T, 128>(ctx, A, B, C, alpha, uplo, transA, diag);
373}
374
375} // 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
bool is_heterogeneous() const
True when per-item active extents are attached.
Definition matrix.hh:621
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.
const Settings & settings()
The process-wide settings.
Transpose
The operator op() applied to an operand: op(A) = A, A^T or A^H.
Definition enums.hh:171
Diag
Whether a triangular operand's diagonal is read or assumed to be one.
Definition enums.hh:303
Uplo
Which triangle of a symmetric, Hermitian or triangular operand is referenced.
Definition enums.hh:287
constexpr bool is_std_complex_v
Value of is_std_complex<T>.
Definition enums.hh:53
Side
Side on which a special operand multiplies: op(A) * B or B * op(A).
Definition enums.hh:319
@ NoTrans
op(A) = A.
Definition enums.hh:172
@ ConjTrans
op(A) = A^H (the same as Trans for real scalars).
Definition enums.hh:174
@ Unit
The diagonal is taken as 1 and not read.
Definition enums.hh:305
@ Lower
The lower triangle (i >= j).
Definition enums.hh:289
@ Left
The special operand is on the left.
Definition enums.hh:320
The batched containers: Matrix (owning), MatrixView (non-owning), KernelMatrixView (device-side),...
Event trmm_triangular_tiles(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, Uplo uplo, Transpose transA, Diag diag)
Event launch_trmm_triangular_tiles(Queue &ctx, const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, T alpha, Uplo uplo, Transpose transA, Diag diag)
std::int64_t trmm_tile_groups(std::int64_t m, std::int64_t n)
constexpr int trmm_lanes(int tile)
bool trmm_tiles_supported(const MatrixView< T, MatrixFormat::Dense > &A, const MatrixView< T, MatrixFormat::Dense > &B, const MatrixView< T, MatrixFormat::Dense > &C, Side side)
constexpr T conj(const T &value)
Every BATCHLAS_* environment variable the library reads, captured once into batchlas::Settings,...
Completion handle of enqueued work, returned by every entry point.
int trmm_tile_m
BATCHLAS_TRMM_TILE_M. 0 = unset (a function of m); bucketed to 16, 32, 64 or 128.
Definition settings.hh:306
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.
GeometrySettings geometry
Launch geometry and tuning thresholds.
Definition settings.hh:489
T type
The real type.
Definition enums.hh:30