43template <
class IntermediateStorage, linalg::KernelBackendSelector ContractionSelector>
struct RightFirstRabcBackend
45 static constexpr std::string_view
name =
"right_first_rabc";
53template <
class Tensor>
56template <
class Tensor>
59template <
class Output,
class Plan,
class A,
class B,
class C>
60concept CompatibleRabcOperands =
61 WritableRabcTensor<Output> && ReadableRabcTensor<A> && ReadableRabcTensor<B> && ReadableRabcTensor<C> &&
71template <
class Tensor>
74template <
class Tensor>
77template <
class IntermediateStorage,
class ContractionSelector,
class Output,
class Scalar,
class A,
class B,
class C>
78consteval bool right_first_types_compatible()
81 using intermediate_mutable_mdspec =
decltype(
mdspec_of(std::declval<intermediate_type&>()));
82 using intermediate_const_mdspec =
decltype(
mdspec_of(std::declval<intermediate_type const&>()));
83 using output_mdspec = mutable_rabc_mdspec_t<Output>;
84 using a_mdspec = const_rabc_mdspec_t<A>;
85 using b_mdspec = const_rabc_mdspec_t<B>;
86 using c_mdspec = const_rabc_mdspec_t<C>;
88 constexpr bool right_product_is_total =
89 linalg::probe_dispatch_kernel_types<ContractionSelector const&, linalg::contract_op<2, 2, 1>,
90 intermediate_mutable_mdspec&,
Scalar&, b_mdspec&, c_mdspec&,
Scalar&>() ==
92 constexpr bool accumulation_is_total =
93 linalg::probe_dispatch_kernel_types<ContractionSelector const&, linalg::contract_op<2, 2, 1>, output_mdspec&,
94 Scalar&, a_mdspec&, intermediate_const_mdspec&,
Scalar&>() ==
96 return right_product_is_total && accumulation_is_total;
99template <
class IntermediateStorage, uni20::Scalar Scalar, TensorView Prototype>
100[[nodiscard]]
auto make_rabc_intermediate(Prototype
const& prototype,
index_type rows,
104 if constexpr (
requires {
105 mdspec_of(prototype).data_descriptor().buffer().resources();
106 intermediate_type(
mdspec_of(prototype).data_descriptor().buffer().resources(), rows, columns);
110 return intermediate_type(descriptor.data_descriptor().buffer().resources(), rows, columns);
114 return intermediate_type(rows, columns);
118template <
class Function>
void execute_rabc_batch(SerialBlockExecution, std::size_t size, Function&& function)
120 for (std::size_t index = 0; index <
size; ++index)
124template <
class Function>
void execute_rabc_batch(SchedulerBatchBlockExecution, std::size_t size, Function&& function)
129template <uni20::RealOrComplex Scalar>
struct BoundRabcTerm
131 std::size_t r_ordinal;
132 std::size_t a_ordinal;
133 std::size_t b_ordinal;
134 std::size_t c_ordinal;
138template <
class Tensor,
class Key>
139[[nodiscard]]
auto bind_rabc_keys(Tensor
const& tensor, std::span<Key const> keys) -> std::vector<std::size_t>
141 std::vector<std::size_t> result;
142 result.reserve(keys.size());
143 for (
auto const& key : keys)
145 auto const found = std::ranges::lower_bound(tensor.stored_keys(), key);
146 if (found == tensor.stored_keys().end() || *found != key)
147 throw std::invalid_argument(
"R/A/B/C logical key is not stored by its operand");
148 result.push_back(
static_cast<std::size_t
>(found - tensor.stored_keys().begin()));
153template <
class Output,
class Plan,
class A,
class B,
class C>
154 requires CompatibleRabcOperands<Output, Plan, A, B, C>
155[[nodiscard]]
auto bind_rabc_operands(Output
const& output, Plan
const& plan, A
const& a, B
const& b, C
const&
c)
156 -> std::vector<BoundRabcTerm<typename std::remove_cvref_t<Plan>::scalar_type>>
158 using scalar_type =
typename std::remove_cvref_t<Plan>::scalar_type;
159 auto const r_ordinals = bind_rabc_keys(output, plan.r_keys());
160 auto const a_ordinals = bind_rabc_keys(a, plan.a_keys());
161 auto const b_ordinals = bind_rabc_keys(b, plan.b_keys());
162 auto const c_ordinals = bind_rabc_keys(
c, plan.c_keys());
164 std::vector<BoundRabcTerm<scalar_type>> result;
165 result.reserve(plan.term_count());
166 for (
auto const& term : plan.terms())
168 BoundRabcTerm<scalar_type> binding{.r_ordinal = r_ordinals[term.r_key_index],
169 .a_ordinal = a_ordinals[term.a_key_index],
170 .b_ordinal = b_ordinals[term.b_key_index],
171 .c_ordinal = c_ordinals[term.c_key_index],
172 .coefficient = term.coefficient};
174 auto const output_block = output.block_by_ordinal(binding.r_ordinal);
175 auto const a_block = a.block_by_ordinal(binding.a_ordinal);
176 auto const b_block = b.block_by_ordinal(binding.b_ordinal);
177 auto const c_block =
c.block_by_ordinal(binding.c_ordinal);
178 if (a_block.extent(1) != b_block.extent(0) || b_block.extent(1) != c_block.extent(1) ||
179 output_block.extent(0) != a_block.extent(0) || output_block.extent(1) != c_block.extent(0))
180 throw std::invalid_argument(
"R/A/B/C contraction term has incompatible dense block extents");
181 result.push_back(std::move(binding));
184 std::ranges::stable_sort(result, [](
auto const& lhs,
auto const& rhs) {
185 return std::tuple{lhs.r_ordinal, lhs.a_ordinal, lhs.b_ordinal, lhs.c_ordinal} <
186 std::tuple{rhs.r_ordinal, rhs.a_ordinal, rhs.b_ordinal, rhs.c_ordinal};
191struct RightFirstRabcGroup
193 std::size_t b_ordinal;
194 std::size_t c_ordinal;
197template <
class Terms>
198[[nodiscard]]
auto make_right_first_groups(Terms
const& terms,
199 std::vector<std::size_t>& term_group) -> std::vector<RightFirstRabcGroup>
201 std::vector<std::size_t> order(terms.size());
202 std::iota(order.begin(), order.end(), std::size_t{});
203 std::ranges::stable_sort(order, [&](std::size_t lhs, std::size_t rhs) {
204 return std::tuple{terms[lhs].b_ordinal, terms[lhs].c_ordinal} <
205 std::tuple{terms[rhs].b_ordinal, terms[rhs].c_ordinal};
208 std::vector<RightFirstRabcGroup> groups;
209 groups.reserve(order.size());
210 for (std::size_t
const term_index : order)
212 auto const& term = terms[term_index];
213 if (groups.empty() || groups.back().b_ordinal != term.b_ordinal || groups.back().c_ordinal != term.c_ordinal)
214 groups.push_back({.b_ordinal = term.b_ordinal, .c_ordinal = term.c_ordinal});
215 term_group[term_index] = groups.size() - 1;
233template <
class IntermediateStorage, RabcPlan Plan, linalg::KernelBackendSelector ContractionSelector>
249 template <detail::WritableRabcTensor Output, detail::ReadableRabcTensor A, detail::ReadableRabcTensor B,
250 detail::ReadableRabcTensor C>
251 requires detail::CompatibleRabcOperands<Output, plan_type, A, B, C>
253 A
const& a, B
const& b, C
const&
c)
254 : selector_(
std::move(selector)), plan_(
std::move(
plan)), term_group_(plan_.term_count())
256 bound_terms_ = detail::bind_rabc_operands(output, plan_, a, b,
c);
257 groups_ = detail::make_right_first_groups(bound_terms_, term_group_);
259 intermediates_.reserve(groups_.size());
260 for (
auto const& group : groups_)
262 auto const b_block = b.block_by_ordinal(group.b_ordinal);
263 auto const c_block =
c.block_by_ordinal(group.c_ordinal);
264 intermediates_.push_back(detail::make_rabc_intermediate<IntermediateStorage, scalar_type>(
265 b_block, b_block.extent(0), c_block.extent(0)));
268 output_offsets_.assign(output.stored_block_count() + 1, 0);
269 for (
auto const& term : bound_terms_)
270 ++output_offsets_[term.r_ordinal + 1];
271 std::partial_sum(output_offsets_.begin(), output_offsets_.end(), output_offsets_.begin());
273 output_order_.resize(output.stored_block_count());
274 std::iota(output_order_.begin(), output_order_.end(), std::size_t{});
275 std::vector<long double> output_cost(output.stored_block_count(), 0);
276 for (std::size_t index = 0; index < bound_terms_.size(); ++index)
278 auto const& term = bound_terms_[index];
279 auto const a_block = a.block_by_ordinal(term.a_ordinal);
280 auto const& intermediate = intermediates_[term_group_[index]];
281 output_cost[term.r_ordinal] +=
282 static_cast<long double>(a_block.extent(0)) * a_block.extent(1) * intermediate.extent(1);
284 std::ranges::stable_sort(output_order_,
285 [&](std::size_t lhs, std::size_t rhs) {
return output_cost[lhs] > output_cost[rhs]; });
296 template <detail::WritableRabcTensor Output, detail::ReadableRabcTensor A, detail::ReadableRabcTensor B,
297 detail::ReadableRabcTensor C>
298 requires detail::CompatibleRabcOperands<Output, plan_type, A, B, C>
299 void operator()(Output& output, A
const& a, B
const& b, C
const&
c)
302 constexpr std::array<std::pair<std::size_t, std::size_t>, 1> right_dimensions{std::pair{1U, 1U}};
303 detail::execute_rabc_batch(execution_policy{}, groups_.size(), [&](std::size_t group_index) {
304 auto const& group = groups_[group_index];
305 auto const b_block = b.block_by_ordinal(group.b_ordinal);
306 auto const c_block =
c.block_by_ordinal(group.c_ordinal);
311 constexpr std::array<std::pair<std::size_t, std::size_t>, 1> left_dimensions{std::pair{1U, 0U}};
312 detail::execute_rabc_batch(execution_policy{}, output_order_.size(), [&](std::size_t ordered_index) {
313 std::size_t
const output_ordinal = output_order_[ordered_index];
314 auto output_block = output.block_by_ordinal(output_ordinal);
315 std::size_t
const first = output_offsets_[output_ordinal];
316 std::size_t
const last = output_offsets_[output_ordinal + 1];
323 for (std::size_t index = first; index < last; ++index)
325 auto const& term = bound_terms_[index];
326 auto const a_block = a.block_by_ordinal(term.a_ordinal);
328 linalg::contract(selector_, output_block, term.coefficient, a_block, intermediates_[term_group_[index]],
329 left_dimensions,
beta);
338 [[nodiscard]]
auto plan() const noexcept ->
plan_type const& {
return plan_; }
343 std::vector<detail::BoundRabcTerm<scalar_type>> bound_terms_;
344 std::vector<std::size_t> term_group_;
345 std::vector<detail::RightFirstRabcGroup> groups_;
346 std::vector<intermediate_type> intermediates_;
347 std::vector<std::size_t> output_offsets_;
348 std::vector<std::size_t> output_order_;
352template <
class IntermediateStorage,
class ContractionSelector, detail::WritableRabcTensor Output, RabcPlan Plan,
353 detail::ReadableRabcTensor A, detail::ReadableRabcTensor B, detail::ReadableRabcTensor C>
354 requires detail::CompatibleRabcOperands<Output, Plan, A, B, C>
356 rabc_contract_op const&, Output&, Plan
const&, A
const&, B
const&, C
const&)
358 using scalar_type =
typename std::remove_cvref_t<Plan>::scalar_type;
359 if constexpr (detail::right_first_types_compatible<IntermediateStorage, ContractionSelector, Output,
scalar_type, A,
367template <
class IntermediateStorage,
class ContractionSelector, detail::WritableRabcTensor Output, RabcPlan Plan,
368 detail::ReadableRabcTensor A, detail::ReadableRabcTensor B, detail::ReadableRabcTensor C>
369 requires detail::CompatibleRabcOperands<Output, Plan, A, B, C> &&
370 (detail::right_first_types_compatible<IntermediateStorage, ContractionSelector, Output,
371 typename std::remove_cvref_t<Plan>::scalar_type, A, B, C>())
373 Output& output, Plan const& plan, A const& a, B const& b, C const&
c) -> linalg::KernelAttempt
376 backend.contraction_selector, output, plan, a, b,
c);
377 prepared(output, a, b,
c);