Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
strides.hpp
Go to the documentation of this file.
1#pragma once
2
9#include "concepts.hpp"
10#include <algorithm>
11#include <array>
12#include <cstdlib>
13#include <functional>
14#include <initializer_list>
15#include <limits>
18#include <uni20/core/types.hpp>
20
21namespace uni20
22{
23
27template <std::size_t N> struct extent_strides
28{
30 std::array<std::ptrdiff_t, N> strides;
31
32 constexpr extent_strides() = default;
33
39 template <typename Ext>
40 requires std::is_integral_v<Ext>
41 constexpr extent_strides(Ext e, std::initializer_list<std::ptrdiff_t> s) : extent(static_cast<index_type>(e))
42 {
43 assert(s.size() == N);
44 std::copy(s.begin(), s.end(), strides.begin());
45 }
46
53 template <typename Ext, typename Str>
54 requires std::is_integral_v<Ext> && std::is_integral_v<Str>
55 constexpr extent_strides(Ext e, std::array<Str, N> s) : extent(static_cast<index_type>(e))
56 {
57 for (std::size_t i = 0; i < N; ++i)
58 {
59 strides[i] = static_cast<std::ptrdiff_t>(s[i]);
60 }
61 }
62
69 template <typename Ext, typename... Strides>
70 requires(std::is_integral_v<Ext> && sizeof...(Strides) == N)
71 constexpr extent_strides(Ext e, Strides... s)
72 : extent(static_cast<index_type>(e)), strides{static_cast<std::ptrdiff_t>(s)...}
73 {}
74
81 [[nodiscard]] constexpr bool can_merge_with_inner(extent_strides inner) const noexcept
82 {
83 if (extent < 0 || inner.extent < 0) return false;
84 if (extent == 1 || inner.extent == 1) return true;
85
86 auto const inner_extent = static_cast<std::ptrdiff_t>(inner.extent);
87 for (std::size_t i = 0; i < N; ++i)
88 {
89 auto const inner_stride = inner.strides[i];
90 if (inner_extent != 0 &&
91 ((inner_stride > 0 && inner_stride > std::numeric_limits<std::ptrdiff_t>::max() / inner_extent) ||
92 (inner_stride < 0 && inner_stride < std::numeric_limits<std::ptrdiff_t>::min() / inner_extent)))
93 return false;
94 if (strides[i] != inner_stride * inner_extent) return false;
95 }
96 return true;
97 }
98
104 constexpr void merge_with_inner(extent_strides inner) noexcept
105 {
106 if (extent == 1)
107 {
108 extent = inner.extent;
109 if (inner.extent == 1)
110 strides.fill(1);
111 else
112 strides = inner.strides;
113 return;
114 }
115 if (inner.extent == 1) return;
116
117 extent *= inner.extent;
118 strides = inner.strides;
119 }
120};
121
122namespace detail
123{
124
125template <std::size_t N, std::size_t R>
126void remove_singleton_dimensions(static_vector<extent_strides<N>, R>& dimensions)
127{
128 if (dimensions.empty()) return;
129
130 std::size_t write = 0;
131 for (std::size_t read = 0; read < dimensions.size(); ++read)
132 {
133 if (dimensions[read].extent != 1) dimensions[write++] = dimensions[read];
134 }
135
136 if (write == 0)
137 {
138 dimensions[0].extent = 1;
139 dimensions[0].strides.fill(1);
140 dimensions.resize(1);
141 return;
142 }
143 dimensions.resize(write);
144}
145
146template <std::size_t N, std::size_t R> void sort_and_merge_left(static_vector<extent_strides<N>, R>& out)
147{
148 remove_singleton_dimensions(out);
149 if (out.size() <= 1) return;
150
151 std::sort(out.begin(), out.end(),
152 [](auto const& lhs, auto const& rhs) { return std::abs(lhs.strides[0]) < std::abs(rhs.strides[0]); });
153
154 extent_strides<N> current = out[0];
155 std::size_t write = 0;
156 for (std::size_t i = 1; i < out.size(); ++i)
157 {
158 auto candidate = out[i];
159 if (candidate.can_merge_with_inner(current))
160 {
161 candidate.merge_with_inner(current);
162 current = candidate;
163 }
164 else
165 {
166 out[write++] = current;
167 current = candidate;
168 }
169 }
170
171 out[write++] = current;
172 out.resize(write);
173}
174
175template <std::size_t N, std::size_t R> void sort_and_merge_right(static_vector<extent_strides<N>, R>& out)
176{
177 remove_singleton_dimensions(out);
178 if (out.size() <= 1) return;
179
180 std::sort(out.begin(), out.end(),
181 [](auto const& lhs, auto const& rhs) { return std::abs(lhs.strides[0]) > std::abs(rhs.strides[0]); });
182
183 extent_strides<N> current = out[0];
184 std::size_t write = 0;
185 for (std::size_t i = 1; i < out.size(); ++i)
186 {
187 auto inner = out[i];
188 if (current.can_merge_with_inner(inner))
189 {
190 current.merge_with_inner(inner);
191 }
192 else
193 {
194 out[write++] = current;
195 current = inner;
196 }
197 }
198
199 out[write++] = current;
200 out.resize(write);
201}
202
203} // namespace detail
204
213template <typename Extents, std::size_t R>
214 requires(Extents::rank() == R)
216 std::array<std::ptrdiff_t, R> const& Stride1,
217 std::array<std::ptrdiff_t, R> const& Stride2)
218{
220 for (std::size_t i = 0; i < R; ++i)
221 {
222 out.emplace_back({ext[i], {Stride1[i], Stride2[i]}});
223 }
224
225 detail::sort_and_merge_left(out);
226
227 return out;
228}
229
238template <typename Extents, std::size_t R>
239 requires(Extents::rank() == R)
241 std::array<std::ptrdiff_t, R> const& Stride1,
242 std::array<std::ptrdiff_t, R> const& Stride2)
243{
245 for (std::size_t i = 0; i < R; ++i)
246 {
247 out.emplace_back({ext[i], {Stride1[i], Stride2[i]}});
248 }
249
250 detail::sort_and_merge_right(out);
251
252 return out;
253}
254
260template <std::size_t N, std::size_t R> void merge_strides_left(static_vector<extent_strides<N>, R>& out)
261{
262 detail::sort_and_merge_left(out);
263}
264
270template <std::size_t N, std::size_t R> void merge_strides_right(static_vector<extent_strides<N>, R>& out)
271{
272 detail::sort_and_merge_right(out);
273}
274
286template <StridedMdspanLike AType, StridedMdspanLike BType, StridedMdspanLike CType, std::size_t N>
287auto extract_strides(AType const& A, BType const& B,
288 std::array<std::pair<std::size_t, std::size_t>, N> const& contractDims, CType const& C)
289{
290 constexpr std::size_t MR = AType::rank() - N; // rank of the M group (A/C legs that are not contracted over)
291 constexpr std::size_t NR = BType::rank() - N; // rank of the N group (B/C legs that are not contracted over)
292 constexpr std::size_t KR = N; // rank of the K group (A/Blegs that are contracted over)
293
297
298 // Assemble the K array of the contracted legs and mark which legs of A and B are contracted over
299 std::array<bool, AType::rank()> AContracted{};
300 std::array<bool, BType::rank()> BContracted{};
301 for (std::size_t i = 0; i < N; ++i)
302 {
303 // check that the extents match
304 auto [ai, bi] = contractDims[i];
305 ERROR_IF(A.extent(ai) != B.extent(bi), "Extent along tensor contraction dimension does not match", ai, bi);
306 AContracted[ai] = true;
307 BContracted[bi] = true;
308 Kgroup.emplace_back(A.extent(ai), A.stride(ai), B.stride(bi));
309 }
310 // Now fill out the uncontracted dimensions and verify that they match with C
311 std::size_t ci = 0;
312 for (std::size_t ai = 0; ai < AType::rank(); ++ai)
313 {
314 if (!AContracted[ai])
315 {
316 ERROR_IF(A.extent(ai) != C.extent(ci), "Extent along uncontracted dimension does not match", ai, ci);
317 Mgroup.emplace_back(A.extent(ai), A.stride(ai), C.stride(ci));
318 ++ci;
319 }
320 }
321 for (std::size_t bi = 0; bi < BType::rank(); ++bi)
322 {
323 if (!BContracted[bi])
324 {
325 ERROR_IF(B.extent(bi) != C.extent(ci), "Extent along uncontracted dimension does not match", bi, ci);
326 Ngroup.emplace_back(B.extent(bi), B.stride(bi), C.stride(ci));
327 ++ci;
328 }
329 }
330
331 merge_strides_right(Mgroup);
332 merge_strides_right(Ngroup);
333 merge_strides_right(Kgroup);
334
335 return std::tuple{Mgroup, Ngroup, Kgroup};
336}
337
338} // namespace uni20
A fixed-capacity vector with stack storage and std::vector-like API.
Definition static_vector.hpp:24
void emplace_back(Args &&... args)
Construct a new element in-place at the end.
Definition static_vector.hpp:242
auto extract_strides(AType const &A, BType const &B, std::array< std::pair< std::size_t, std::size_t >, N > const &contractDims, CType const &C)
Extract merged stride groups for a tensor contraction.
Definition strides.hpp:287
constexpr extent_strides(Ext e, std::array< Str, N > s)
Construct from an extent and an array of strides.
Definition strides.hpp:55
static_vector< extent_strides< 2 >, R > merge_strides_left(Extents const &ext, std::array< std::ptrdiff_t, R > const &Stride1, std::array< std::ptrdiff_t, R > const &Stride2)
Build and merge stride metadata from two stride arrays favouring column-major (left) order.
Definition strides.hpp:215
constexpr void merge_with_inner(extent_strides inner) noexcept
Merge an inner dimension into this one when merging is valid.
Definition strides.hpp:104
constexpr bool can_merge_with_inner(extent_strides inner) const noexcept
Returns true if the current (outer) dimension and the given inner dimension can be merged.
Definition strides.hpp:81
static_vector< extent_strides< 2 >, R > merge_strides_right(Extents const &ext, std::array< std::ptrdiff_t, R > const &Stride1, std::array< std::ptrdiff_t, R > const &Stride2)
Build and merge stride metadata from two stride arrays favouring row-major (right) order.
Definition strides.hpp:240
constexpr extent_strides(Ext e, std::initializer_list< std::ptrdiff_t > s)
Construct from an extent and initializer-list strides.
Definition strides.hpp:41
Configured gateway to Uni20's mdspan implementation.
WriteBuffer< T > write(Async< T > &a)
Convenience helper that forwards to Async<T>::write().
Definition async/async.hpp:545
Definition async/async.hpp:26
std::ptrdiff_t index_type
Definition types.hpp:19
Defines static_vector, a fixed-capacity, stack-allocated vector with a std::vector-like API.
Structure to represent a common extent and an array of strides.
Definition strides.hpp:28
constexpr extent_strides()=default
index_type extent
Definition strides.hpp:29
std::array< std::ptrdiff_t, N > strides
Definition strides.hpp:30
Concepts for tensor-level objects that resolve mdspan kernel operands.
#define ERROR_IF(cond,...)
Definition trace.hpp:389