group_blas_symm.hh
Go to the documentation of this file.
1#pragma once
2
5
6namespace batchlas::device {
7
8namespace detail::generic {
9
10template <typename Tag, typename Group, typename T>
11inline constexpr void symm(const Group& group,
13 MatrixMatrixOperand<T> operand) {
14 const int local_id = detail::group_local_linear_id(group);
15 const int local_size = detail::group_local_linear_range(group);
16 const int row_extent = operand.c.rows();
17 const int col_extent = operand.c.cols();
18 const int contract_extent = Tag::side == Side::Left ? operand.b.rows() : operand.b.cols();
19
20 for (int col = 0; col < col_extent; ++col) {
21 for (int row = 0; row < row_extent; ++row) {
22 T partial{};
23 for (int k = local_id; k < contract_extent; k += local_size) {
24 if constexpr (Tag::side == Side::Left) {
25 partial += detail::symmetric_matrix_entry<Tag>(a, row, k) * operand.b(k, col);
26 } else {
27 partial += operand.b(row, k) * detail::symmetric_matrix_entry<Tag>(a, k, col);
28 }
29 }
30 partial = detail::reduce_sum_group(group, partial);
31 if (detail::group_is_leader(group)) {
32 detail::write_matrix_output(operand, row, col, partial);
33 }
34 }
35 }
36}
37
38} // namespace detail::generic
39
40namespace detail::subgroup {
41
42template <typename Item, typename T>
43inline constexpr void symm(const Item& item,
46 SymmetricTransform transform) {
47 const auto sg = item.get_sub_group();
48 const int sg_size = subgroup_size(item);
49 const int lane = subgroup_local_id(item);
50 const int sg_id = subgroup_group_id(item);
51 const int total_sg = subgroup_count(item);
52 const int row_extent = operand.c.rows();
53 const int col_extent = operand.c.cols();
54 const int contract_extent = transform.side == Side::Left ? operand.b.rows() : operand.b.cols();
55 const int tile_k = matrix_tile_k(contract_extent, sg_size, transform.side);
56 const int rows_per_sg = matrix_rows_per_subgroup(row_extent, total_sg, sg_size, contract_extent, transform.side);
57 const int col_tiles = (col_extent + sg_size - 1) / sg_size;
58 const int row_tiles = (row_extent + rows_per_sg - 1) / rows_per_sg;
59
60 for (int linear_tile = sg_id; linear_tile < row_tiles * col_tiles; linear_tile += total_sg) {
61 const int tile_row = linear_tile / col_tiles;
62 const int tile_col = linear_tile % col_tiles;
63 const int base_row = tile_row * rows_per_sg;
64 const int base_col = tile_col * sg_size;
65 const int col = base_col + lane;
66 std::array<T, detail::subgroup::kMaxMatrixRowsPerSubgroup> partials{};
67
68 for (int k_base = 0; k_base < contract_extent; k_base += tile_k) {
69 const int tile_extent = std::min(tile_k, contract_extent - k_base);
70
71 if (transform.side == Side::Left) {
72 for (int tile_offset = 0; tile_offset < tile_extent; ++tile_offset) {
73 const int k = k_base + tile_offset;
74 const T b_value = col < col_extent ? operand.b(k, col) : T(0);
75 for (int row_offset = 0; row_offset < rows_per_sg; ++row_offset) {
76 const int row = base_row + row_offset;
77 if (row >= row_extent || col >= col_extent) {
78 continue;
79 }
80 const T a_lane = lane == row_offset ? detail::symmetric_matrix_entry(a, row, k, transform) : T(0);
81 const T a_value = batchlas::portable::select_from_group(sg, a_lane, static_cast<uint32_t>(row_offset));
82 partials[static_cast<std::size_t>(row_offset)] += a_value * b_value;
83 }
84 }
85 } else {
86 for (int tile_offset = 0; tile_offset < tile_extent; ++tile_offset) {
87 const int k = k_base + tile_offset;
88 const T a_value = col < col_extent ? detail::symmetric_matrix_entry(a, k, col, transform) : T(0);
89 for (int row_offset = 0; row_offset < rows_per_sg; ++row_offset) {
90 const int row = base_row + row_offset;
91 const T b_lane = row < row_extent && lane == row_offset ? operand.b(row, k) : T(0);
92 const T b_value = batchlas::portable::select_from_group(sg, b_lane, static_cast<uint32_t>(row_offset));
93 if (row >= row_extent || col >= col_extent) {
94 continue;
95 }
96 partials[static_cast<std::size_t>(row_offset)] += b_value * a_value;
97 }
98 }
99 }
100 }
101
102 if (col < col_extent) {
103 for (int row_offset = 0; row_offset < rows_per_sg; ++row_offset) {
104 const int row = base_row + row_offset;
105 if (row >= row_extent) {
106 continue;
107 }
108 detail::write_matrix_output(operand, row, col, partials[static_cast<std::size_t>(row_offset)]);
109 }
110 }
111 }
112}
113
114template <typename Item, typename T>
115inline constexpr void symm_register_tiled(const Item& item,
118 SymmetricTransform transform,
119 RegisterMatrixWorkspace<T>* workspace) {
120 const int linear_tid = detail::item_local_linear_id(item);
121 const int subgroup_id = subgroup_group_id(item);
122 const int local_row = linear_tid / kRegisterMatrixLocalCols;
123 const int local_col = linear_tid % kRegisterMatrixLocalCols;
124 const int row_extent = operand.c.rows();
125 const int col_extent = operand.c.cols();
126 const int contract_extent = transform.side == Side::Left ? operand.b.rows() : operand.b.cols();
127 const int row_tiles = (row_extent + kRegisterMatrixTileM - 1) / kRegisterMatrixTileM;
128 const int col_tiles = (col_extent + kRegisterMatrixTileN - 1) / kRegisterMatrixTileN;
129 const int tile_row_start = matrix_tile_group_row(item);
130 const int tile_col_start = matrix_tile_group_col(item);
131 const int tile_row_stride = matrix_tile_group_row_stride(item);
132 const int tile_col_stride = matrix_tile_group_col_stride(item);
133 for (int tile_row = tile_row_start; tile_row < row_tiles; tile_row += tile_row_stride) {
134 for (int tile_col = tile_col_start; tile_col < col_tiles; tile_col += tile_col_stride) {
135 const int row_base = tile_row * kRegisterMatrixTileM + local_row * kRegisterMatrixThreadTileRows;
136 const int col_base = tile_col * kRegisterMatrixTileN + local_col * kRegisterMatrixThreadTileCols;
138
139 const int tile_count = (contract_extent + kRegisterMatrixTileK - 1) / kRegisterMatrixTileK;
140 if (tile_count > 0) {
141 auto lhs_loader = [&](int tile_r, int global_k) {
142 const int global_row = tile_row * kRegisterMatrixTileM + tile_r;
143 if (global_row >= row_extent) {
144 return T(0);
145 }
146 return transform.side == Side::Left ?
147 detail::symmetric_matrix_entry(a, global_row, global_k, transform) :
148 operand.b(global_row, global_k);
149 };
150 auto rhs_loader = [&](int tile_c, int global_k) {
151 const int global_col = tile_col * kRegisterMatrixTileN + tile_c;
152 if (global_col >= col_extent) {
153 return T(0);
154 }
155 return transform.side == Side::Left ?
156 operand.b(global_k, global_col) :
157 detail::symmetric_matrix_entry(a, global_k, global_col, transform);
158 };
159
161 workspace,
162 linear_tid,
163 subgroup_id,
164 0,
165 0,
166 true,
167 true,
168 0,
169 std::min(kRegisterMatrixTileK, contract_extent),
170 lhs_loader,
171 rhs_loader);
172 sycl::group_barrier(item.get_group());
173
174 for (int tile_idx = 0; tile_idx < tile_count; ++tile_idx) {
175 const int current_k_base = tile_idx * kRegisterMatrixTileK;
176 const int current_tile_extent = std::min(kRegisterMatrixTileK, contract_extent - current_k_base);
177 const int next_tile_idx = tile_idx + 1;
178 const int current_lhs_stage = register_matrix_lhs_stage(tile_idx);
179 const int current_rhs_stage = register_matrix_rhs_stage(tile_idx);
180
181 if (next_tile_idx < tile_count) {
182 const int next_k_base = next_tile_idx * kRegisterMatrixTileK;
183 const int next_tile_extent = std::min(kRegisterMatrixTileK, contract_extent - next_k_base);
184 if constexpr (kRegisterMatrixRhsStages > 1) {
186 workspace,
187 linear_tid,
188 subgroup_id,
189 current_lhs_stage,
190 register_matrix_rhs_stage(next_tile_idx),
191 false,
193 next_k_base,
194 next_tile_extent,
195 lhs_loader,
196 rhs_loader);
197 }
198 }
199
201 workspace,
202 subgroup_id,
203 current_lhs_stage,
204 current_rhs_stage,
205 local_row,
206 local_col,
207 current_tile_extent,
208 accum);
209
210 if (next_tile_idx < tile_count) {
211 const int next_k_base = next_tile_idx * kRegisterMatrixTileK;
212 const int next_tile_extent = std::min(kRegisterMatrixTileK, contract_extent - next_k_base);
213 if constexpr (kRegisterMatrixRhsStages == 1) {
214 sycl::group_barrier(item.get_group());
215 } else {
216 sycl::group_barrier(item.get_sub_group());
217 }
219 workspace,
220 linear_tid,
221 subgroup_id,
222 0,
223 kRegisterMatrixRhsStages == 1 ? 0 : register_matrix_rhs_stage(next_tile_idx),
224 true,
226 next_k_base,
227 next_tile_extent,
228 lhs_loader,
229 rhs_loader);
230 sycl::group_barrier(item.get_group());
231 }
232 }
233 }
234
235 write_register_matrix_tile(operand, row_base, col_base, row_extent, col_extent, accum);
236 }
237 }
238}
239
240} // namespace detail::subgroup
241
242namespace detail {
243
244template <typename Tag, DeviceBlasPolicy Policy, typename T>
245inline constexpr std::size_t symm_workspace_elements(const DeviceBlasLaunchInfo& launch,
246 int row_extent,
247 int col_extent) {
248 assert(row_extent >= 0 && col_extent >= 0 && "device::symm workspace query expects non-negative matrix extents");
250 return 0;
251 }
252
253 const int contract_extent = Tag::side == Side::Left ? row_extent : col_extent;
254 if (detail::subgroup::can_use_matrix_register_fast_path<T>(launch, row_extent, col_extent, contract_extent, Policy)) {
256 }
257 return 0;
258}
259
260template <typename Tag, DeviceBlasPolicy Policy, typename Exec, typename T>
261inline constexpr void dispatch_symm(const Exec& exec,
264 T* workspace = nullptr) {
265 const SymmetricTransform transform{.side = Tag::side, .uplo = Tag::uplo, .hermitian = Tag::hermitian};
266 validate_symmetric_operand(a, operand, transform);
267
268 if constexpr (detail::NdItemLike<Exec>) {
269 const int row_extent = operand.c.rows();
270 const int col_extent = operand.c.cols();
271 const int contract_extent = transform.side == Side::Left ? operand.b.rows() : operand.b.cols();
272 auto* register_workspace = workspace == nullptr ? nullptr : detail::workspace_ptr_cast<detail::subgroup::RegisterMatrixWorkspace<T>>(workspace);
273 if (register_workspace != nullptr &&
274 detail::subgroup::can_use_matrix_register_fast_path<T>(exec, row_extent, col_extent, contract_extent, Policy) &&
275 std::same_as<std::remove_cvref_t<Exec>, sycl::nd_item<3>>) {
276 detail::subgroup::symm_register_tiled(exec, a, operand, transform, register_workspace);
277 return;
278 }
279 if (detail::subgroup::can_use_matrix_fast_path<T>(exec, row_extent, col_extent, contract_extent, Policy)) {
280 detail::subgroup::symm(exec, a, operand, transform);
281 return;
282 }
283 }
284
285 generic::symm<Tag>(exec, a, operand);
286}
287
288} // namespace detail
289
290/// @addtogroup api_device_blas
291/// @{
292
293/// @brief Symmetric matrix-matrix product \f$ C := \alpha\,A\,B + \beta\,C \f$ (Left) or \f$ C := \alpha\,B\,A + \beta\,C \f$ (Right), reading only the `UploV` triangle of `A`.
294///
295/// There is no device `hemm`. The fast paths are as for trmm(), except that
296/// `B` and `C` must not overlap.
297/// @tparam SideV side `A` multiplies from
298/// @tparam UploV stored triangle of `A`
299/// @tparam Group `sycl::group`, `sycl::sub_group` or an `nd_item`; every work-item must call
300/// @param group executor
301/// @param a single square matrix, order `B.rows()` (Left) or `B.cols()` (Right)
302/// @param operand `B`, `C` (same shape as `B`, not overlapping it), `alpha`, `beta`
303/// @param workspace local memory of symm_workspace_elements() elements, or `nullptr`
304/// @pre `C` holds finite values even when `beta == 0`.
305/// @note On the generic path each `C` element is written by the group leader; barrier before other work-items read `C`.
306template <Side SideV = Side::Left, Uplo UploV = Uplo::Upper, typename Group, typename T>
307inline constexpr void symm(const Group& group,
310 T* workspace = nullptr) {
312}
313
314/// @brief As symm(), with an explicit DeviceBlasPolicy.
315template <DeviceBlasPolicy Policy,
316 Side SideV = Side::Left,
317 Uplo UploV = Uplo::Upper,
318 typename Group,
319 typename T>
320inline constexpr void symm(const Group& group,
323 T* workspace = nullptr) {
325}
326
327/// @brief Local memory, in elements of `T`, that symm() can use; 0 means pass `nullptr`.
328/// @param launch description of the launch the call runs in
329/// @param row_extent rows of `C`
330/// @param col_extent columns of `C`
331/// @return element count, or 0 when no staged path applies
332/// @ingroup api_device_blas_lowlevel
333template <typename T, Side SideV = Side::Left, Uplo UploV = Uplo::Upper>
334inline constexpr std::size_t symm_workspace_elements(const DeviceBlasLaunchInfo& launch,
335 int row_extent,
336 int col_extent) {
338}
339
340/// @brief Local memory, in elements of `T`, that symm() can use under `Policy`; 0 means pass `nullptr`.
341/// @param launch description of the launch the call runs in
342/// @param row_extent rows of `C`
343/// @param col_extent columns of `C`
344/// @return element count, or 0 when no staged path applies
345/// @ingroup api_device_blas_lowlevel
346template <typename T, DeviceBlasPolicy Policy, Side SideV = Side::Left, Uplo UploV = Uplo::Upper>
347inline constexpr std::size_t symm_workspace_elements(const DeviceBlasLaunchInfo& launch,
348 int row_extent,
349 int col_extent) {
351}
352
353/// @brief As symm(), with the operands passed separately instead of in an operand struct.
354template <Side SideV = Side::Left, Uplo UploV = Uplo::Upper, typename Group, typename T>
355inline constexpr void symm(const Group& group,
359 T alpha = T(1),
360 T beta = T(0),
361 T* workspace = nullptr) {
362 symm<SideV, UploV>(group, a, make_matmat_operand(b, c, alpha, beta), workspace);
363}
364
365/// @brief As symm(), with an explicit DeviceBlasPolicy and the operands passed separately.
366template <DeviceBlasPolicy Policy,
367 Side SideV = Side::Left,
368 Uplo UploV = Uplo::Upper,
369 typename Group,
370 typename T>
371inline constexpr void symm(const Group& group,
375 T alpha = T(1),
376 T beta = T(0),
377 T* workspace = nullptr) {
378 symm<Policy, SideV, UploV>(group, a, make_matmat_operand(b, c, alpha, beta), workspace);
379}
380
381/// @}
382
383} // namespace batchlas::device
Policy
Synchronous or asynchronous execution policy.
constexpr std::size_t symm_workspace_elements(const DeviceBlasLaunchInfo &launch, int row_extent, int col_extent)
Local memory, in elements of T, that symm() can use; 0 means pass nullptr.
constexpr MatrixMatrixOperand< T > make_matmat_operand(const KernelMatrixView< T, MatrixFormat::Dense > &b, const KernelMatrixView< T, MatrixFormat::Dense > &c, T alpha=T(1), T beta=T(0))
Builds a MatrixMatrixOperand.
DeviceBlasPolicy
Which implementation family a device BLAS call may use.
constexpr void symm(const Group &group, const KernelMatrixView< T, MatrixFormat::Dense > &a, MatrixMatrixOperand< T > operand, T *workspace=nullptr)
Symmetric matrix-matrix product (Left) or (Right), reading only the UploV triangle of A.
@ Auto
any eligible path; the sub-group fast paths accept sub-group size 16 or 32
Uplo
Which triangle of a symmetric, Hermitian or triangular operand is referenced.
Definition enums.hh:287
Side
Side on which a special operand multiplies: op(A) * B or B * op(A).
Definition enums.hh:319
@ Upper
The upper triangle (i <= j).
Definition enums.hh:288
@ Left
The special operand is on the left.
Definition enums.hh:320
T select_from_group(Group g, T x, Id id)
sycl::select_from_group for any trivially copyable T. Collective.
constexpr void symm(const Group &group, const KernelMatrixView< T, MatrixFormat::Dense > &a, MatrixMatrixOperand< T > operand)
constexpr bool can_use_matrix_fast_path(const Item &item, int row_extent, int col_extent, int contract_extent, DeviceBlasPolicy policy)
constexpr int subgroup_group_id(const Item &item)
constexpr int matrix_tile_group_row(const Item &)
constexpr void symm(const Item &item, const KernelMatrixView< T, MatrixFormat::Dense > &a, MatrixMatrixOperand< T > operand, SymmetricTransform transform)
constexpr int subgroup_size(const Item &item)
constexpr void symm_register_tiled(const Item &item, const KernelMatrixView< T, MatrixFormat::Dense > &a, MatrixMatrixOperand< T > operand, SymmetricTransform transform, RegisterMatrixWorkspace< T > *workspace)
constexpr int matrix_tile_group_col(const Item &)
constexpr int matrix_rows_per_subgroup(int row_extent, int total_sg, int sg_size, int contract_extent, Side side)
constexpr void write_register_matrix_tile(MatrixMatrixOperand< T > operand, int row_base, int col_base, int row_extent, int col_extent, const RegisterMatrixAccumTile< T > &accum)
constexpr void load_register_matrix_stage(const Item &item, RegisterMatrixWorkspace< T > *workspace, int linear_tid, int subgroup_id, int lhs_stage, int rhs_stage, bool load_lhs, bool load_rhs, int k_base, int tile_extent, LhsLoader &&lhs_loader, RhsLoader &&rhs_loader)
constexpr int matrix_tile_k(int contract_extent, int sg_size, Side side)
constexpr int matrix_tile_group_row_stride(const Item &)
constexpr int matrix_tile_group_col_stride(const Item &)
constexpr int register_matrix_rhs_stage(int tile_idx)
constexpr int register_matrix_lhs_stage(int tile_idx)
constexpr bool can_use_matrix_register_fast_path(const Item &item, int row_extent, int col_extent, int contract_extent, DeviceBlasPolicy policy)
constexpr int subgroup_local_id(const Item &item)
constexpr void accumulate_register_matrix_stage(const Item &item, const RegisterMatrixWorkspace< T > *workspace, int subgroup_id, int lhs_stage, int rhs_stage, int local_row, int local_col, int tile_extent, RegisterMatrixAccumTile< T > &accum)
constexpr bool is_nd_item_3d_launch(const Exec &)
constexpr int subgroup_count(const Item &item)
constexpr void write_matrix_output(const MatrixMatrixOperand< T > &operand, int row, int col, const T &value)
constexpr void dispatch_symm(const Exec &exec, const KernelMatrixView< T, MatrixFormat::Dense > &a, MatrixMatrixOperand< T > operand, T *workspace=nullptr)
constexpr int group_local_linear_id(const Group &group)
constexpr void validate_symmetric_operand(const KernelMatrixView< T, MatrixFormat::Dense > &a, const MatrixVectorOperand< T > &operand, SymmetricTransform transform)
constexpr int group_local_linear_range(const Group &group)
constexpr Workspace * workspace_ptr_cast(T *workspace)
constexpr T symmetric_matrix_entry(const KernelMatrixView< T, MatrixFormat::Dense > &a, int row, int col)
constexpr T reduce_sum_group(const Group &group, const T &value)
constexpr int item_local_linear_id(const Item &item)
constexpr std::size_t symm_workspace_elements(const DeviceBlasLaunchInfo &launch, int row_extent, int col_extent)
constexpr std::size_t workspace_elements_v
constexpr bool group_is_leader(const Group &group)
Trivially copyable view of a MatrixView for use inside a SYCL kernel.
Definition matrix.hh:145
auto rows() const
Row count (the capacity on a heterogeneous batch).
Definition matrix.hh:219
auto cols() const
Column count (the capacity on a heterogeneous batch).
Definition matrix.hh:223
Host-side description of a launch, for the *_workspace_elements queries.
Operands of a matrix-matrix operation: .
KernelMatrixView< T, MatrixFormat::Dense > c
output matrix; read (scaled by beta) and overwritten
KernelMatrixView< T, MatrixFormat::Dense > b
second input matrix
Mode of a symmetric or Hermitian product (symv, hemv, symm).
Side side
A multiplies from the left or the right