Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
linalg/backends/cpu/reductions.hpp
Go to the documentation of this file.
1#pragma once
2
10#include <uni20/core/math.hpp>
20
21#include <array>
22#include <cmath>
23#include <concepts>
24#include <cstddef>
25#include <functional>
26#include <type_traits>
27#include <utility>
28
29namespace uni20::linalg
30{
31namespace detail
32{
33
34template <class Span> consteval bool reduction_value_is_readable()
35{
36 using span_type = std::remove_cvref_t<Span>;
37 return requires(typename span_type::reference element) { static_cast<typename span_type::value_type>(element); };
38}
39
40template <class Output> consteval bool reduction_output_is_column_major()
41{
42 using output_type = std::remove_cvref_t<Output>;
44 return std::same_as<typename output_type::layout_type, stdex::layout_left>;
45 else
46 return false;
47}
48
49template <class Output, class Index, class Scalar, std::size_t... Axis>
50void write_reduction_output(Output& output, Index const& index, Scalar value, std::index_sequence<Axis...>)
51{
53 {
54 using output_type = std::remove_cvref_t<Output>;
55 using index_type = typename output_type::index_type;
56 output.operator[](static_cast<index_type>(index[Axis])...) = value;
57 }
58 else
59 {
60 static_assert(sizeof...(Axis) == 0);
61 output = value;
62 }
63}
64
65template <class Reference, class... Others>
66void check_reduction_extents(Reference const& reference, Others const&... others)
67{
68 if constexpr (sizeof...(Others) > 0)
69 {
70 constexpr std::size_t rank = std::remove_cvref_t<Reference>::rank();
71 if constexpr (rank > 0)
72 {
73 auto check_one = [&](auto const& other) {
74 for (std::size_t axis = 0; axis < rank; ++axis)
75 CHECK_EQUAL(reference.extent(axis), other.extent(axis));
76 };
77 (check_one(others), ...);
78 }
79 }
80}
81
82template <class Span, class Index, std::size_t... Axis>
83decltype(auto) reduction_element(Span& span, Index const& index, std::index_sequence<Axis...>)
84{
85 using span_type = std::remove_cvref_t<Span>;
86 using index_type = typename span_type::index_type;
87 return span.operator[](static_cast<index_type>(index[Axis])...);
88}
89
90template <std::size_t Depth, bool ColumnMajor, class Axes, class Accumulate, class State, class FirstSpan,
91 class InputIndex, class... RestSpans>
92void accumulate_reduced_elements(Axes const& axes, Accumulate& accumulate, State& state, FirstSpan& first,
93 InputIndex& input_index, RestSpans&... rest)
94{
95 if constexpr (Depth == Axes::reduced_rank)
96 {
97 constexpr auto input_axes = std::make_index_sequence<Axes::input_rank>{};
98 std::invoke(accumulate, state, reduction_element(first, input_index, input_axes),
99 reduction_element(rest, input_index, input_axes)...);
100 }
101 else
102 {
103 constexpr std::size_t position = ColumnMajor ? Axes::reduced_rank - Depth - 1 : Depth;
104 std::size_t const axis = axes.reduced[position];
105 using index_type = typename std::remove_cvref_t<FirstSpan>::index_type;
106 for (index_type i = 0; i < static_cast<index_type>(first.extent(axis)); ++i)
107 {
108 input_index[axis] = i;
109 accumulate_reduced_elements<Depth + 1, ColumnMajor>(axes, accumulate, state, first, input_index, rest...);
110 }
111 }
112}
113
114template <std::size_t Depth, bool OutputColumnMajor, bool InputColumnMajor, class Output, class Axes, class Initialize,
115 class Accumulate, class Finalize, class OutputIndex, class InputIndex, class FirstSpan, class... RestSpans>
116void emit_reduction_outputs(Output& output, Axes const& axes, Initialize& initialize, Accumulate& accumulate,
117 Finalize& finalize, OutputIndex& output_index, InputIndex& input_index, FirstSpan& first,
118 RestSpans&... rest)
119{
120 if constexpr (Depth == Axes::output_rank)
121 {
122 auto state = std::invoke(initialize);
123 accumulate_reduced_elements<0, InputColumnMajor>(axes, accumulate, state, first, input_index, rest...);
124 auto value = std::invoke(finalize, state);
125 write_reduction_output(output, output_index, std::move(value), std::make_index_sequence<Axes::output_rank>{});
126 }
127 else
128 {
129 constexpr std::size_t output_axis = OutputColumnMajor ? Axes::output_rank - Depth - 1 : Depth;
130 std::size_t const input_axis = axes.surviving[output_axis];
131 using index_type = typename std::remove_cvref_t<FirstSpan>::index_type;
132 for (index_type i = 0; i < static_cast<index_type>(first.extent(input_axis)); ++i)
133 {
134 output_index[output_axis] = i;
135 input_index[input_axis] = i;
136 emit_reduction_outputs<Depth + 1, OutputColumnMajor, InputColumnMajor>(
137 output, axes, initialize, accumulate, finalize, output_index, input_index, first, rest...);
138 }
139 }
140}
141
142template <class Output, std::size_t InputRank, std::size_t ReducedRank, class Initialize, class Accumulate,
143 class Finalize, class FirstSpan, class... RestSpans>
144void reference_reduce_axes(Output& output, ReductionAxes<InputRank, ReducedRank> const& axes, Initialize&& initialize,
145 Accumulate&& accumulate, Finalize&& finalize, FirstSpan& first, RestSpans&... rest)
146{
147 using first_type = std::remove_cvref_t<FirstSpan>;
148 static_assert(first_type::rank() == InputRank);
149 static_assert(((std::remove_cvref_t<RestSpans>::rank() == InputRank) && ...));
151 static_assert(std::remove_cvref_t<Output>::rank() == InputRank - ReducedRank);
152
154 check_reduction_extents(first, rest...);
155
157 {
158 for (std::size_t output_axis = 0; output_axis < InputRank - ReducedRank; ++output_axis)
159 CHECK_EQUAL(output.extent(output_axis), first.extent(axes.surviving[output_axis]));
160 }
161
162 std::array<typename first_type::index_type, InputRank - ReducedRank> output_index{};
163 std::array<typename first_type::index_type, InputRank> input_index{};
164 constexpr bool output_column_major = reduction_output_is_column_major<Output>();
165 constexpr bool input_column_major = std::same_as<typename first_type::layout_type, stdex::layout_left>;
166 auto initialize_operation = std::forward<Initialize>(initialize);
167 auto accumulate_operation = std::forward<Accumulate>(accumulate);
168 auto finalize_operation = std::forward<Finalize>(finalize);
169 emit_reduction_outputs<0, output_column_major, input_column_major>(output, axes, initialize_operation,
170 accumulate_operation, finalize_operation,
171 output_index, input_index, first, rest...);
172}
173
174template <class LhsSpan, class RhsSpan> consteval bool inner_product_inputs_are_supported()
175{
176 using lhs_type = std::remove_cvref_t<LhsSpan>;
177 using rhs_type = std::remove_cvref_t<RhsSpan>;
178 using scalar_type = typename lhs_type::value_type;
179 if constexpr (lhs_type::rank() != rhs_type::rank() || !std::same_as<scalar_type, typename rhs_type::value_type> ||
180 !uni20::RealOrComplex<scalar_type> || !reduction_value_is_readable<LhsSpan>() ||
181 !reduction_value_is_readable<RhsSpan>())
182 {
183 return false;
184 }
185 else
186 {
187 return requires(scalar_type lhs, scalar_type rhs) {
188 uni20::conj(lhs) * rhs;
189 backends::cpu::detail::CompensatedSum<scalar_type>{}.add(uni20::conj(lhs) * rhs);
190 };
191 }
192}
193
194template <class Output, class LhsSpan, class RhsSpan>
195void reference_inner_product(Output& output, LhsSpan& lhs, RhsSpan& rhs)
196{
197 using lhs_type = std::remove_cvref_t<LhsSpan>;
198 using scalar_type = typename lhs_type::value_type;
199 using state_type = backends::cpu::detail::CompensatedSum<scalar_type>;
200
201 auto initialize = [] { return state_type{}; };
202 auto accumulate = [](state_type& state, auto&& lhs_element, auto&& rhs_element) {
203 auto const lhs_value = static_cast<scalar_type>(lhs_element);
204 auto const rhs_value = static_cast<scalar_type>(rhs_element);
205 state.add(uni20::conj(lhs_value) * rhs_value);
206 };
207 auto finalize = [](state_type const& state) { return state.value(); };
208 reference_reduce_axes(output, all_reduction_axes<lhs_type::rank()>(), initialize, accumulate, finalize, lhs, rhs);
209}
210
211template <class Real> void update_scaled_sum_of_squares(Real component, Real& scale, Real& sum_of_squares)
212{
213 using std::abs;
214 component = abs(component);
215 if (component == Real{} || !(sum_of_squares == sum_of_squares)) return;
216
217 if (!(component == component))
218 {
219 sum_of_squares = component;
220 }
221 else if (component > scale)
222 {
223 Real const ratio = scale / component;
224 sum_of_squares = Real{1} + sum_of_squares * ratio * ratio;
225 scale = component;
226 }
227 else if (component == scale)
228 {
229 sum_of_squares += Real{1};
230 }
231 else
232 {
233 Real const ratio = component / scale;
234 sum_of_squares += ratio * ratio;
235 }
236}
237
238template <class InputSpan> consteval bool norm_input_is_supported()
239{
240 using input_type = std::remove_cvref_t<InputSpan>;
241 using scalar_type = typename input_type::value_type;
242 if constexpr (!uni20::RealOrComplex<scalar_type> || !reduction_value_is_readable<InputSpan>())
243 {
244 return false;
245 }
246 else
247 {
248 using real_type = uni20::make_real_t<scalar_type>;
249 return requires(real_type value) { value += value * value; };
250 }
251}
252
253template <class Real> struct ScaledSumOfSquaresState
254{
255 Real scale{};
256 Real sum_of_squares{};
257};
258
259template <class Output, class InputSpan> void reference_norm(Output& output, InputSpan& input)
260{
261 using input_type = std::remove_cvref_t<InputSpan>;
262 using scalar_type = typename input_type::value_type;
263 using result_type = uni20::make_real_t<scalar_type>;
264 using state_type = ScaledSumOfSquaresState<result_type>;
265
266 auto initialize = [] { return state_type{}; };
267 auto accumulate = [](state_type& state, auto&& element) {
268 auto const value = static_cast<scalar_type>(element);
269 if constexpr (uni20::Complex<scalar_type>)
270 {
271 update_scaled_sum_of_squares(value.real(), state.scale, state.sum_of_squares);
272 update_scaled_sum_of_squares(value.imag(), state.scale, state.sum_of_squares);
273 }
274 else
275 {
276 update_scaled_sum_of_squares(value, state.scale, state.sum_of_squares);
277 }
278 };
279 auto finalize = [](state_type const& state) {
280 if (!(state.sum_of_squares == state.sum_of_squares)) return state.sum_of_squares;
281 if (state.scale == result_type{}) return result_type{};
282 using std::sqrt;
283 return state.scale * sqrt(state.sum_of_squares);
284 };
285 reference_reduce_axes(output, all_reduction_axes<input_type::rank()>(), initialize, accumulate, finalize, input);
286}
287
288template <class InputSpan> consteval bool sum_input_is_supported()
289{
290 using input_type = std::remove_cvref_t<InputSpan>;
291 using scalar_type = typename input_type::value_type;
292 if constexpr (!uni20::RealOrComplex<scalar_type> || !reduction_value_is_readable<InputSpan>())
293 {
294 return false;
295 }
296 else
297 {
298 return requires(scalar_type value) { backends::cpu::detail::CompensatedSum<scalar_type>{}.add(value); };
299 }
300}
301
302template <class Output, std::size_t InputRank, std::size_t ReducedRank, class InputSpan>
303void reference_sum(Output& output, sum_reduction_op<InputRank, ReducedRank> const& operation, InputSpan& input)
304{
305 using scalar_type = typename std::remove_cvref_t<InputSpan>::value_type;
306 using state_type = backends::cpu::detail::CompensatedSum<scalar_type>;
307
308 auto initialize = [] { return state_type{}; };
309 auto accumulate = [](state_type& state, auto&& element) { state.add(static_cast<scalar_type>(element)); };
310 auto finalize = [](state_type const& state) { return state.value(); };
311 reference_reduce_axes(output, operation.axes, initialize, accumulate, finalize, input);
312}
313
314template <class Output, class LhsSpan, class RhsSpan> consteval auto inner_product_acceptance()
315{
316 using scalar_type = typename LhsSpan::value_type;
317 if constexpr (inner_product_inputs_are_supported<LhsSpan, RhsSpan>() &&
318 reduction_detail::output_is_supported<Output, scalar_type, 0>())
319 return kernel_types_yes;
320 else
321 return kernel_types_no;
322}
323
324template <class Output, class InputSpan> consteval auto norm_acceptance()
325{
326 using scalar_type = typename InputSpan::value_type;
327 using result_type = uni20::make_real_t<scalar_type>;
328 if constexpr (norm_input_is_supported<InputSpan>() && reduction_detail::output_is_supported<Output, result_type, 0>())
329 return kernel_types_yes;
330 else
331 return kernel_types_no;
332}
333
334template <class Output, std::size_t InputRank, std::size_t ReducedRank, class InputSpan> consteval auto sum_acceptance()
335{
336 using input_type = std::remove_cvref_t<InputSpan>;
337 using scalar_type = typename input_type::value_type;
338 if constexpr (input_type::rank() == InputRank && sum_input_is_supported<InputSpan>() &&
339 reduction_detail::output_is_supported<Output, scalar_type, InputRank - ReducedRank>())
340 return kernel_types_yes;
341 else
342 return kernel_types_no;
343}
344
345} // namespace detail
346
348template <class Output, uni20::MdspecLike LhsMdspan, uni20::MdspecLike RhsMdspan>
349 requires reduction_detail::HostOutput<Output> && uni20::HostReadableMdspec<LhsMdspan> &&
351consteval auto kernel_accepts_types(CpuReferenceBackend const&, inner_product_op const&, Output&, LhsMdspan&,
352 RhsMdspan&)
353{
354 using output_type = reduction_detail::host_output_t<Output>;
357 constexpr auto acceptance = detail::inner_product_acceptance<output_type, lhs_span, rhs_span>();
358 if constexpr (acceptance == KernelTypeAcceptance::yes)
359 return kernel_types_yes;
360 else
361 return kernel_types_no;
362}
363
365template <class Output, uni20::MdspecLike LhsMdspan, uni20::MdspecLike RhsMdspan>
366 requires reduction_detail::HostOutput<Output> && uni20::HostReadableMdspec<LhsMdspan> &&
368KernelAttempt try_kernel(CpuReferenceBackend, inner_product_op const&, Output& output, LhsMdspan& lhs, RhsMdspan& rhs)
369{
370 auto lhs_access = acquire_host_read_access_sync(lhs);
371 auto rhs_access = acquire_host_read_access_sync(rhs);
372 auto lhs_span = lhs_access.mdspan();
373 auto rhs_span = rhs_access.mdspan();
374 return reduction_detail::with_host_output(output, [&](auto& resolved_output) {
375 detail::reference_inner_product(resolved_output, lhs_span, rhs_span);
377 });
378}
379
381template <class Output, uni20::MdspecLike InputMdspan>
382 requires reduction_detail::HostOutput<Output> && uni20::HostReadableMdspec<InputMdspan>
383consteval auto kernel_accepts_types(CpuReferenceBackend const&, norm_op const&, Output&, InputMdspan&)
384{
385 using output_type = reduction_detail::host_output_t<Output>;
387 constexpr auto acceptance = detail::norm_acceptance<output_type, input_span>();
388 if constexpr (acceptance == KernelTypeAcceptance::yes)
389 return kernel_types_yes;
390 else
391 return kernel_types_no;
392}
393
395template <class Output, uni20::MdspecLike InputMdspan>
396 requires reduction_detail::HostOutput<Output> && uni20::HostReadableMdspec<InputMdspan>
397KernelAttempt try_kernel(CpuReferenceBackend, norm_op const&, Output& output, InputMdspan& input)
398{
399 auto input_access = acquire_host_read_access_sync(input);
400 auto input_span = input_access.mdspan();
401 return reduction_detail::with_host_output(output, [&](auto& resolved_output) {
402 detail::reference_norm(resolved_output, input_span);
404 });
405}
406
408template <class Output, std::size_t InputRank, std::size_t ReducedRank, uni20::MdspecLike InputMdspan>
409 requires reduction_detail::HostOutput<Output> && uni20::HostReadableMdspec<InputMdspan>
411 Output&, InputMdspan&)
412{
413 using output_type = reduction_detail::host_output_t<Output>;
415 constexpr auto acceptance = detail::sum_acceptance<output_type, InputRank, ReducedRank, input_span>();
416 if constexpr (acceptance == KernelTypeAcceptance::yes)
417 return kernel_types_yes;
418 else
419 return kernel_types_no;
420}
421
423template <class Output, std::size_t InputRank, std::size_t ReducedRank, uni20::MdspecLike InputMdspan>
424 requires reduction_detail::HostOutput<Output> && uni20::HostReadableMdspec<InputMdspan>
426 InputMdspan& input)
427{
428 auto input_access = acquire_host_read_access_sync(input);
429 auto input_span = input_access.mdspan();
430 return reduction_detail::with_host_output(output, [&](auto& resolved_output) {
431 CHECK(reduction_axes_are_valid(operation.axes));
432 detail::reference_sum(resolved_output, operation, input_span);
434 });
435}
436
437} // namespace uni20::linalg
RAII leases that resolve device descriptors and tensor views to mdspans.
Concept for complex scalar types.
Definition scalar_concepts.hpp:75
Mdspec metadata supporting host read acquisition.
Definition access.hpp:688
A readable type implementing the mdspan observers and multidimensional subscript contract.
Definition mdspan/concepts.hpp:291
A mutable mdspan-like type with a specified static rank.
Definition mdspan/concepts.hpp:494
Concept for types that are either real or complex.
Definition scalar_concepts.hpp:87
locale_t value
Definition data_table.cpp:49
Operation-tag backend dispatch over tensor-view operands.
uni20::complex< T > conj(detail::standard_complex< T > x)
Returns the complex conjugate for complex-valued scalars.
Definition math.hpp:70
typename make_real< T >::type make_real_t
Alias for the underlying real-valued type of T.
Definition scalar_traits.hpp:168
Scalar math helper utilities.
Mdspan concept and accessor extensions for Uni20.
Runtime initialize(RuntimeConfig config)
Install and return a scoped process-wide CUDA runtime owner.
Definition runtime.cpp:807
R ratio(DiagnosticMagnitude< R > const &a, DiagnosticMagnitude< R > const &b)
Definition gmres_diagnostics.hpp:125
typename HostOutputType< Output >::type host_output_t
Resolved output type used by a host reduction backend probe.
Definition reduction_output.hpp:56
consteval bool output_is_supported()
Report whether an output can receive a reduction value of fixed rank.
Definition reduction_output.hpp:24
KernelAttempt with_host_output(Output &output, Function &&function)
Invoke a synchronous reduction with a resolved host output.
Definition reduction_output.hpp:68
Definition linalg/async.hpp:28
KernelAttempt try_kernel(BlasBackend, gemm_op const &, C &c, S const &alpha, A &a, B &b, S const &beta)
Pack logical host values and execute GEMM through value-preserving provider conversion.
Definition linalg/backends/blas/binary80.hpp:30
KernelAttempt
Result of one runtime kernel backend attempt.
Definition kernel_attempt.hpp:27
constexpr bool reduction_axes_are_valid(ReductionAxes< InputRank, ReducedRank > const &axes) noexcept
Return whether a reduction-axis descriptor partitions every input axis.
Definition reduction_axes.hpp:68
constexpr auto all_reduction_axes() -> ReductionAxes< InputRank, InputRank >
Construct the full-reduction descriptor for a fixed input rank.
Definition reduction_axes.hpp:125
constexpr KernelAcceptance< KernelTypeAcceptance::no > kernel_types_no
Report that a backend rejects the argument types.
Definition dispatch_error.hpp:34
consteval auto kernel_accepts_types(BlasBackend, gemm_op const &, C &, S const &, A &, B &, S const &)
Accept host-readable native binary80 operands, including transforming accessors.
Definition linalg/backends/blas/binary80.hpp:19
constexpr KernelAcceptance< KernelTypeAcceptance::yes > kernel_types_yes
Report that a backend accepts every instance of the argument types.
Definition dispatch_error.hpp:40
constexpr auto abs(basic_half_int< T > value) -> basic_half_int< T >
Return the absolute value of a half-integer.
Definition half_int.hpp:431
exact_rational sqrt(X const &value)
Return the nonnegative rational square root, or require finite precision.
Definition exact_rational.hpp:355
std::remove_cvref_t< decltype(std::declval< detail::host_read_mdspan_lease_t< Mdspec > & >().mdspan())> host_read_mdspan_t
Read-only mdspan type resolved by host descriptor acquisition.
Definition access.hpp:679
stdex::layout_left ColumnMajor
Conventional column-major contiguous mdspan layout.
Definition tensor.hpp:33
auto acquire_host_read_access_sync(Mdspan const &mdspan)
Acquire a directly host-accessible read-only mdspan.
Definition access.hpp:616
std::ptrdiff_t index_type
Definition types.hpp:19
Backend-independent operation values used with normalized kernel operands.
std::size_t span
Definition presentation.cpp:2017
Shared host-output handling for synchronous scalar reductions.
Backend value for the reference host CPU dense linalg oracle.
Definition backend_selector.hpp:55
Conjugate-linear-left tensor inner-product operation tag.
Definition operation_tags.hpp:105
Euclidean tensor norm operation tag.
Definition operation_tags.hpp:111
Sum reduction over one or more fixed-rank input axes.
Definition operation_tags.hpp:195
Concepts for tensor-level objects that resolve mdspan kernel operands.
#define CHECK(cond,...)
Definition trace.hpp:299
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309