1818
1919namespace ck_tile ::builder::test {
2020
21+ // / @brief Utility structure for N-dimensional iteration using a flat index
22+ // /
23+ // / This structure's main purpose is to "unmerge" a flattened index into a
24+ // / multi-dimensional index, which helps when iterating over multi-dimensional
25+ // / indices without having to write an arbitrary amount of nested for loops.
26+ // / A minimal amount of precomputation must be done to do this efficiently,
27+ // / which is handled in the constructor of this type.
28+ // /
29+ // / @details Decoding a flat index into a multi-dimensional index is done by
30+ // / first computing a reverse scan of the shape. These values can then be
31+ // / used to decode the index in the usual way:
32+ // /
33+ // / x = flat_idx / (size_y * size_z)
34+ // / y = flat_idx % (size_y * size_z) / size_z
35+ // / z = flat_idx % (size_y * size_z) % size_z
36+ // / etc
37+ // /
38+ // / The decode order is such that the innermost dimension (right in
39+ // / the shape extent) changes the fastest.
40+ // /
41+ // / @tparam RANK The rank (number of spatial dimensions) of the tensor to
42+ // / iterate.
43+ template <size_t RANK >
44+ struct NdIter
45+ {
46+ // / @brief Prepare N-dimensional iteration over a particular shape.
47+ // /
48+ // / Precompute ashape into a form that can be used to easily decode a flat
49+ // / index into a multi-dimensional index.
50+ // /
51+ // / @param shape The shape to iterate over.
52+ explicit NdIter (const Extent<RANK >& shape)
53+ {
54+ // Precompute shape_scan = [..., shape[-2] * shape[-1], shape[-1], 1]
55+
56+ numel_ = 1 ;
57+ for (int i = RANK ; i > 0 ; --i)
58+ {
59+ shape_scan_[i - 1 ] = numel_;
60+ numel_ *= shape[i - 1 ];
61+ }
62+ }
63+
64+ // / @brief Unflatten a flat index into a multi-dimensional index
65+ // /
66+ // / This applies the usual multi-dimensional indexing method over the
67+ // / precomputed shape scan to get back a multi-dimensional index.
68+ // / The decode order is such that the innermost dimension (right in
69+ // / the shape extent) changes the fastest.
70+ // /
71+ // / @param flat_index The "flattened" (1-dimensional) index of the tensor
72+ // /
73+ // / @returns A multi-dimensional index into the tensor
74+ // /
75+ // / @pre `0 <= flat_index < size()` (in other words, the `flat_index` must
76+ // / be in bounds of the tensor shape that this `NdIter` was made from).
77+ __host__ __device__ Extent<RANK > operator ()(size_t flat_index) const
78+ {
79+ Extent<RANK > index = {};
80+ auto idx = flat_index;
81+ for (size_t i = 0 ; i < RANK ; ++i)
82+ {
83+ const auto scanned_dim = shape_scan_[i];
84+ index[i] = idx / scanned_dim;
85+ idx %= scanned_dim;
86+ }
87+
88+ return index;
89+ }
90+
91+ // / @brief Return the total elements to iterate over
92+ // /
93+ // / Get the total number of elements in the shape to iterate over. This value
94+ // / can be used to construct a complete for loop to iterate over all indices
95+ // / of a tensor, for example:
96+ // /
97+ // / for(size_t i = 0; i < iter.numel(); ++i)
98+ // / {
99+ // / const auto index = iter(i);
100+ // / use(index);
101+ // / }
102+ __host__ __device__ size_t numel () const { return numel_; }
103+
104+ private:
105+ // / Reverse (right) scan of the shape to iterate over.
106+ Extent<RANK > shape_scan_;
107+
108+ // / The total number of elements in the shape. This value turns out to be almost
109+ // / always required when iterating over a shape, so just store it in this type
110+ // / so that it is easily accessible.
111+ size_t numel_;
112+ };
113+
114+ template <size_t RANK >
115+ NdIter (Extent<RANK >) -> NdIter<RANK >;
116+
21117// / @brief Concept for constraining tensor iteration functors.
22118// /
23119// / This concept checks that a functor has the correct signature for
@@ -50,28 +146,19 @@ constexpr int DEVICE_FOREACH_BLOCK_SIZE = 256;
50146// / @tparam F The type of the callback to invoke. This function must be
51147// / compatible with execution as a __device__ function.
52148// /
53- // / @param numel The total number of elements in the tensor.
54- // / @param shape_scan A right-exclusive scan of the shape of the tensor.
149+ // / @param iter An NdIter instance to help iterating over the tensor.
55150// / @param f The callback to invoke for each index of the tensor. This
56151// / functor must be eligible for running on the GPU.
57152template <int BLOCK_SIZE , size_t RANK , typename F>
58153 requires ForeachFunctor<F, RANK >
59154__global__ __launch_bounds__ (BLOCK_SIZE ) //
60- void foreach_kernel(const size_t numel, Extent <RANK > shape_scan , F f)
155+ void foreach_kernel(NdIter <RANK > iter , F f)
61156{
62157 const auto gid = blockIdx.x * BLOCK_SIZE + threadIdx.x ;
63- for (size_t flat_idx = gid; flat_idx < numel; flat_idx += gridDim.x * BLOCK_SIZE )
158+ for (size_t flat_idx = gid; flat_idx < iter. numel () ; flat_idx += gridDim.x * BLOCK_SIZE )
64159 {
65160 // Compute the current index.
66- Extent<RANK > index = {};
67-
68- size_t idx = flat_idx;
69- for (size_t i = 0 ; i < RANK ; ++i)
70- {
71- const auto scanned_dim = shape_scan[i];
72- index[i] = idx / scanned_dim;
73- idx %= scanned_dim;
74- }
161+ const auto index = iter (flat_idx);
75162
76163 // Then invoke the callback with the index.
77164 f (index);
@@ -160,26 +247,20 @@ void tensor_foreach(const Extent<RANK>& shape, ForeachFunctor<RANK> auto f)
160247 // order in the kernel is from large-to-small. Right layout is the
161248 // easiest solution for that.
162249
163- Extent<RANK > shape_scan;
164- size_t numel = 1 ;
165- for (int i = RANK ; i > 0 ; --i)
166- {
167- shape_scan[i - 1 ] = numel;
168- numel *= shape[i - 1 ];
169- }
250+ NdIter iter (shape);
170251
171252 // Reset any errors from previous launches.
172253 (void )hipGetLastError ();
173254
174- kernel<<<occupancy * multiprocessors, block_size>>>(numel, shape_scan , f);
255+ kernel<<<occupancy * multiprocessors, block_size>>>(iter , f);
175256 check_hip (hipGetLastError ());
176257}
177258
178259// / @brief Concept for tensor initializing functors.
179260// /
180261// / This concept checks that a functor has the correct signature for
181262// / use with the `fill_tensor` function.
182- template <typename F, builder:: DataType DT , size_t RANK >
263+ template <typename F, DataType DT , size_t RANK >
183264concept FillTensorFunctor = requires (const F& f, const Extent<RANK >& index) {
184265 { f (index) } -> std::convertible_to<detail::cpp_type_t <DT >>;
185266};
@@ -199,7 +280,7 @@ concept FillTensorFunctor = requires(const F& f, const Extent<RANK>& index) {
199280// / @param f A functor used to get the value at a particular coordinate.
200281// /
201282// / @see FillTensorFunctor
202- template <builder:: DataType DT , size_t RANK >
283+ template <DataType DT , size_t RANK >
203284void fill_tensor (const TensorDescriptor<DT , RANK >& desc,
204285 void * buffer,
205286 FillTensorFunctor<DT , RANK > auto f)
@@ -218,7 +299,7 @@ void fill_tensor(const TensorDescriptor<DT, RANK>& desc,
218299// /
219300// / This concept checks that a functor has the correct signature for
220301// / use with the `fill_tensor_buffer` function.
221- template <typename F, builder:: DataType DT >
302+ template <typename F, DataType DT >
222303concept FillTensorBufferFunctor = requires (const F& f, size_t index) {
223304 { f (index) } -> std::convertible_to<detail::cpp_type_t <DT >>;
224305};
@@ -239,15 +320,27 @@ concept FillTensorBufferFunctor = requires(const F& f, size_t index) {
239320// / @param f A functor used to get the value at a particular index.
240321// /
241322// / @see FillTensorBufferFunctor
242- template <builder:: DataType DT , size_t RANK >
323+ template <DataType DT , size_t RANK >
243324void fill_tensor_buffer (const TensorDescriptor<DT , RANK >& desc,
244325 void * buffer,
245326 FillTensorBufferFunctor<DT > auto f)
246327{
247328 fill_tensor (desc.get_space_descriptor (), buffer, [f](auto index) { return f (index[0 ]); });
248329}
249330
250- template <builder::DataType DT , size_t RANK >
331+ // / @brief Utility for clearing tensor buffers to a particular value.
332+ // /
333+ // / This function initializes all memory backing a particular tensor buffer to
334+ // / one specific value, zero by default. Note that this function ignores strides,
335+ // / and clears the entire buffer backing the tensor.
336+ // /
337+ // / @tparam DT The tensor element datatype
338+ // / @tparam RANK The rank (number of spatial dimensions) of the tensor.
339+ // /
340+ // / @param desc The descriptor of the tensor to initialize.
341+ // / @param buffer The memory of the tensor to initialize.
342+ // / @param value The value to initialize the tensor buffer with.
343+ template <DataType DT , size_t RANK >
251344void clear_tensor_buffer (const TensorDescriptor<DT , RANK >& desc,
252345 void * buffer,
253346 detail::cpp_type_t <DT > value = detail::cpp_type_t <DT >{0 })
0 commit comments