xref: /aosp_15_r20/external/executorch/runtime/core/exec_aten/testing_util/tensor_factory.h (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1 // Copyright (c) Meta Platforms, Inc. and affiliates.
2 
3 #pragma once
4 
5 #include <algorithm>
6 #include <cstdint>
7 
8 #include <executorch/runtime/core/exec_aten/exec_aten.h>
9 #include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
10 #include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
11 #include <executorch/runtime/core/tensor_shape_dynamism.h>
12 #include <executorch/runtime/platform/assert.h>
13 
14 #ifdef USE_ATEN_LIB
15 #include <ATen/ATen.h>
16 #else // !USE_ATEN_LIB
17 #include <memory>
18 #include <numeric>
19 #include <vector>
20 #endif // !USE_ATEN_LIB
21 
22 namespace executorch {
23 namespace runtime {
24 namespace testing {
25 
26 namespace internal {
27 
28 /**
29  * Returns the number of elements in the tensor, given the dimension
30  * sizes, assuming contiguous data.
31  */
sizes_to_numel(const std::vector<int32_t> & sizes)32 inline size_t sizes_to_numel(const std::vector<int32_t>& sizes) {
33   size_t n = 1;
34   for (auto s : sizes) {
35     n *= s;
36   }
37   return n;
38 }
39 
40 /**
41  * Check if given strides is legal under given sizes. In the `make` function,
42  * the `strides` shall ensure:
43  *  - a. strides.size() == sizes.size()
44  *  - b. all strides are positive.
45  *  - c. All underlying data be accessed.
46  *  - d. All legal indexes can access an underlying data.
47  *  - e. No two indexes access a same data.
48  *  - f. No out of bounds data can be accessed.
49  *
50  * @param[in] sizes The sizes of the dimensions of the Tensor.
51  * @param[in] strides The desired strides for creating new tensor.
52  * @return The strides is legal or not
53  */
54 
check_strides(const std::vector<int32_t> sizes,const std::vector<executorch::aten::StridesType> strides)55 inline bool check_strides(
56     const std::vector<int32_t> sizes,
57     const std::vector<executorch::aten::StridesType> strides) {
58   if (sizes.size() != strides.size()) {
59     // The length of stride vector shall equal to size vector.
60     return false;
61   }
62 
63   if (strides.size() == 0) {
64     // Both sizes and strides are empty vector. Legal!
65     return true;
66   }
67 
68   // Check if input non-empty strides is legal. The defination of legal is in
69   // the comment above function. To check it, we first reformat the strides into
70   // contiguous style, in where the strides should be sorted from high to low.
71   // Then rearrange the size based on same transformation. After that, we can
72   // check if strides[i] == strides[i + 1] * sizes[i + 1] for all i in
73   // [0, sizes.size() - 1) and strides[sizes.size() - 1] == 1
74 
75   // Get the mapping between current strides and sorted strides (from high to
76   // low, if equal then check if correspond size is 1 or 0 in same dimension)
77   // e.g. a = tensor(3, 2, 1).permute(2, 1, 0), a.size() == (1, 2, 3) and
78   // a.strides == (1, 1, 2). We want to sort create a mapping to make the
79   // sorted_stride as (2, 1, 1) while sorted_size == (3, 2, 1)
80   std::vector<std::int32_t> sorted_idx(sizes.size());
81   for (size_t i = 0; i < sizes.size(); i++) {
82     sorted_idx[i] = i;
83   }
84   std::sort(
85       sorted_idx.begin(),
86       sorted_idx.end(),
87       [&](const int32_t& a, const int32_t& b) {
88         if (strides[a] != strides[b]) {
89           return strides[a] > strides[b];
90         } else {
91           // When strides equal to each other, put the index whose
92           // coresponding size equal to 0 or 1 to the right. Update the rule to
93           // the following comparsion to circumvent strict weak ordering.
94           return (sizes[a] ? sizes[a] : 1) > (sizes[b] ? sizes[b] : 1);
95         }
96       });
97 
98   // Use the mapping to rearrange the sizes and strides
99   std::vector<std::int32_t> sorted_sizes(sizes.size());
100   std::vector<std::int32_t> sorted_strides(sizes.size());
101   for (size_t i = 0; i < sizes.size(); i++) {
102     sorted_sizes[i] = sizes[sorted_idx[i]] == 0 ? 1 : sizes[sorted_idx[i]];
103     sorted_strides[i] = strides[sorted_idx[i]];
104   }
105 
106   // All strides should be positive. We have sorted it mainly based on strides,
107   // so sorted_strides[-1] has lowest value.
108   if (sorted_strides[strides.size() - 1] <= 0) {
109     return false;
110   }
111 
112   // Check if strides is legal
113   bool legal = sorted_strides[strides.size() - 1] == 1;
114   for (size_t i = 0; i < strides.size() - 1 && legal; i++) {
115     legal = legal &&
116         (sorted_strides[i] == sorted_strides[i + 1] * sorted_sizes[i + 1]);
117   }
118 
119   return legal;
120 }
121 
122 /**
123  * Check that a given dim order array is valid. A dim order array is valid if
124  * each value from 0 to sizes.size() - 1 appears exactly once in the dim_order
125  * array.
126  */
check_dim_order(const std::vector<int32_t> & sizes,const std::vector<uint8_t> & dim_order)127 inline bool check_dim_order(
128     const std::vector<int32_t>& sizes,
129     const std::vector<uint8_t>& dim_order) {
130   if (sizes.size() != dim_order.size()) {
131     return false;
132   }
133   size_t gauss_sum = 0;
134   std::vector<int> count(dim_order.size(), 0);
135   for (int i = 0; i < dim_order.size(); i++) {
136     if (dim_order[i] < 0 || dim_order[i] >= sizes.size()) {
137       return false;
138     }
139     gauss_sum += static_cast<size_t>(dim_order[i]) + 1;
140   }
141   // Use the gaussian sum to verify each dim appears exactly once
142   size_t expected_sum = (sizes.size() * (sizes.size() + 1)) / 2;
143   if (gauss_sum != expected_sum) {
144     return false;
145   }
146 
147   return true;
148 }
149 
strides_from_dim_order(const std::vector<int32_t> & sizes,const std::vector<uint8_t> & dim_order)150 inline std::vector<executorch::aten::StridesType> strides_from_dim_order(
151     const std::vector<int32_t>& sizes,
152     const std::vector<uint8_t>& dim_order) {
153   bool legal = check_dim_order(sizes, dim_order);
154   ET_CHECK_MSG(legal, "The input dim_order variable is illegal.");
155 
156   size_t ndim = sizes.size();
157   std::vector<executorch::aten::StridesType> strides(ndim);
158   strides[dim_order[ndim - 1]] = 1;
159   for (int i = ndim - 2; i >= 0; --i) {
160     uint8_t cur_dim = dim_order[i];
161     uint8_t next_dim = dim_order[i + 1];
162     strides[cur_dim] = (!sizes[next_dim]) ? strides[next_dim]
163                                           : strides[next_dim] * sizes[next_dim];
164   }
165   return strides;
166 }
167 
channels_last_dim_order(size_t dims)168 inline std::vector<uint8_t> channels_last_dim_order(size_t dims) {
169   ET_CHECK_MSG(
170       dims >= 4 && dims <= 5,
171       "Channels last dim order only valid for 4-dim and 5-dim tensors!");
172 
173   std::vector<uint8_t> dim_order(dims);
174   // Channels is always assigned to dim 1
175   dim_order[dims - 1] = 1;
176 
177   dim_order[0] = 0;
178   int d = 1;
179   while (d < dims - 1) {
180     dim_order[d] = d + 1;
181     d++;
182   }
183   return dim_order;
184 }
185 
186 } // namespace internal
187 
188 #ifdef USE_ATEN_LIB
189 
190 // Note that this USE_ATEN_LIB section uses ATen-specific namespaces instead of
191 // exec_aten because we know that we're working with ATen, and many of these
192 // names aren't mapped into executorch::aten::.
193 
194 namespace internal {
195 
196 // This wrapper lets us override the C type associated with some ScalarType
197 // values while using the defaults for everything else.
198 template <c10::ScalarType DTYPE>
199 struct ScalarTypeToCppTypeWrapper {
200   using ctype = typename c10::impl::ScalarTypeToCPPTypeT<DTYPE>;
201 };
202 
203 // Use a C type of `uint8_t` instead of `bool`. The C type will be used to
204 // declare a `std::vector<CTYPE>`, and `std::vector<bool>` is often optimized to
205 // store a single bit per entry instead of using an array of separate `bool`
206 // elements. Since the tensor data will point into the vector, it needs to use
207 // one byte per element.
208 template <>
209 struct ScalarTypeToCppTypeWrapper<c10::ScalarType::Bool> {
210   using ctype = uint8_t;
211 };
212 
213 } // namespace internal
214 
215 template <at::ScalarType DTYPE>
216 class TensorFactory {
217  public:
218   /*
219    * The C types that backs the associated DTYPE. E.g., `float` for
220    * `ScalarType::Float`.
221    */
222 
223   /**
224    * Used for the vector provided to the factory functions. May differ
225    * from the type usually associate with the ScalarType.
226    *
227    * Used for the vector<> parameters passed to the factory functions.
228    */
229   using ctype = typename internal::ScalarTypeToCppTypeWrapper<DTYPE>::ctype;
230 
231   /**
232    * The official C type for the scalar type. Used when accessing elements
233    * of a constructed Tensor.
234    */
235   using true_ctype = typename c10::impl::ScalarTypeToCPPTypeT<DTYPE>;
236 
237   TensorFactory() = default;
238 
239   /**
240    * Returns a new Tensor with the specified shape, data and stride.
241    *
242    * @param[in] sizes The sizes of the dimensions of the Tensor.
243    * @param[in] data The data that the Tensor should be initialized with. The
244    *     size of this vector must be equal to the product of the elements of
245    *     `sizes`.
246    * @param[in] strides The strides for each dimensions of the Tensor. If empty
247    *     or not specificed, the function will return a contiguous tensor based
248    *     on data and size. If not, the strides shall follow the rules:
249    *            - a. strides.size() == sizes.size().
250    *            - b. all strides are positive.
251    *            - c. All underlying data be accessed.
252    *            - d. All legal indexes can access an underlying data.
253    *            - e. No two indexes access a same data.
254    *            - f. No out of bounds data can be accessed.
255    *
256    * @return A new Tensor with the specified shape and data.
257    */
258   at::Tensor make(
259       const std::vector<int32_t>& sizes,
260       const std::vector<ctype>& data,
261       const std::vector<executorch::aten::StridesType> strides = {},
262       ET_UNUSED TensorShapeDynamism dynamism =
263           TensorShapeDynamism::DYNAMIC_UNBOUND) {
264     auto expected_numel = internal::sizes_to_numel(sizes);
265     ET_CHECK_MSG(
266         expected_numel == data.size(),
267         "Number of data elements %zd "
268         "does not match expected number of elements %zd",
269         data.size(),
270         expected_numel);
271 
272     at::Tensor t;
273     if (strides.empty()) {
274       t = zeros(sizes);
275     } else {
276       bool legal = internal::check_strides(sizes, strides);
277       ET_CHECK_MSG(legal, "The input strides variable is illegal.");
278 
279       t = empty_strided(sizes, strides);
280     }
281     if (t.nbytes() > 0) {
282       memcpy(t.template data<true_ctype>(), data.data(), t.nbytes());
283     }
284     return t;
285   }
286 
287   /**
288    * Returns a new Tensor with the specified shape, data and dim order.
289    *
290    * @param[in] sizes The sizes of the dimensions of the Tensor.
291    * @param[in] data The data that the Tensor should be initialized with. The
292    *     size of this vector must be equal to the product of the elements of
293    *     `sizes`.
294    * @param[in] dim_order The dim order describing how tensor memory is laid
295    * out. If empty or not specificed, the function will use a contiguous dim
296    * order of {0, 1, 2, 3, ...}
297    *
298    * @return A new Tensor with the specified shape and data.
299    */
300   at::Tensor make_with_dimorder(
301       const std::vector<int32_t>& sizes,
302       const std::vector<ctype>& data,
303       const std::vector<uint8_t> dim_order = {},
304       ET_UNUSED TensorShapeDynamism dynamism =
305           TensorShapeDynamism::DYNAMIC_UNBOUND) {
306     auto expected_numel = internal::sizes_to_numel(sizes);
307     ET_CHECK_MSG(
308         expected_numel == data.size(),
309         "Number of data elements %zd "
310         "does not match expected number of elements %zd",
311         data.size(),
312         expected_numel);
313 
314     at::Tensor t;
315     if (dim_order.empty()) {
316       t = zeros(sizes);
317     } else {
318       auto strides = internal::strides_from_dim_order(sizes, dim_order);
319       t = empty_strided(sizes, strides);
320     }
321     if (t.nbytes() > 0) {
322       memcpy(t.template data<true_ctype>(), data.data(), t.nbytes());
323     }
324     return t;
325   }
326 
327   /**
328    * Returns a new Tensor with the specified shape and data in channels last
329    * memory layout.
330    *
331    * @param[in] sizes The sizes of the dimensions of the Tensor.
332    * @param[in] data The data that the Tensor should be initialized with. The
333    *     size of this vector must be equal to the product of the elements of
334    *     `sizes`.
335    *
336    * @return A new Tensor with the specified shape and data.
337    */
338   at::Tensor make_channels_last(
339       const std::vector<int32_t>& sizes,
340       const std::vector<ctype>& data,
341       ET_UNUSED TensorShapeDynamism dynamism =
342           TensorShapeDynamism::DYNAMIC_UNBOUND) {
343     return make_with_dimorder(
344         sizes, data, internal::channels_last_dim_order(sizes.size()), dynamism);
345   }
346 
347   /**
348    * Given data in contiguous memory format, returns a new Tensor with the
349    * specified shape and the same data but in channels last memory format.
350    *
351    * @param[in] sizes The sizes of the dimensions of the Tensor.
352    * @param[in] data The data in contiguous memory format that the Tensor should
353    * be initialized with. The size of this vector must be equal to the product
354    * of the elements of `sizes`.
355    *
356    * @return A new Tensor with the specified shape and data in channls last
357    * memory format.
358    */
359   at::Tensor channels_last_like(
360       const at::Tensor& input,
361       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
362     ET_CHECK_MSG(
363         input.sizes().size() == 4, "Only 4D tensors can be channels last");
364 
365     const std::vector<int32_t> sizes(
366         input.sizes().begin(), input.sizes().end());
367 
368     std::vector<uint8_t> contiguous_dim_order(sizes.size());
369     for (uint8_t i = 0; i < sizes.size(); i++) {
370       contiguous_dim_order[i] = i;
371     }
372     std::vector<executorch::aten::StridesType> contiguous_strides =
373         internal::strides_from_dim_order(sizes, contiguous_dim_order);
374 
375     for (int32_t i = 0; i < input.dim(); i++) {
376       ET_CHECK_MSG(
377           input.strides()[i] == contiguous_strides[i],
378           "Input tensor is not contiguous");
379     }
380 
381     int32_t N = sizes[0];
382     int32_t C = sizes[1];
383     int32_t H = sizes[2];
384     int32_t W = sizes[3];
385 
386     std::vector<ctype> contiguous_data(
387         input.data_ptr<ctype>(), input.data_ptr<ctype>() + input.numel());
388     std::vector<ctype> channels_last_data(
389         N * C * H * W); // Create a new blob with the same total size to contain
390                         // channels_last data
391     for (int32_t n = 0; n < N; ++n) {
392       for (int32_t c = 0; c < C; ++c) {
393         for (int32_t h = 0; h < H; ++h) {
394           for (int32_t w = 0; w < W; ++w) {
395             // Calculate the index in the original blob
396             int32_t old_index = ((n * C + c) * H + h) * W + w;
397             // Calculate the index in the new blob
398             int32_t new_index = ((n * H + h) * W + w) * C + c;
399             // Copy the data
400             channels_last_data[new_index] = contiguous_data[old_index];
401           }
402         }
403       }
404     }
405 
406     return make_with_dimorder(
407         sizes,
408         channels_last_data,
409         internal::channels_last_dim_order(sizes.size()),
410         dynamism);
411   }
412 
413   /**
414    * Returns a new Tensor with the specified shape, containing contiguous
415    * data will all elements set to `value`.
416    *
417    * @param[in] sizes The sizes of the dimensions of the Tensor.
418    * @param[in] value The value of all elements of the Tensor.
419    * @return A new Tensor with the specified shape.
420    */
421   at::Tensor full(
422       const std::vector<int32_t>& sizes,
423       ctype value,
424       ET_UNUSED TensorShapeDynamism dynamism =
425           TensorShapeDynamism::DYNAMIC_UNBOUND) {
426     auto sizes64 = vec_32_to_64(sizes);
427     return at::full(at::IntArrayRef(sizes64), value, at::dtype(DTYPE));
428   }
429 
430   /**
431    * Returns a new Tensor with the specified shape, containing channels-last
432    * contiguous data will all elements set to `value`.
433    *
434    * @param[in] sizes The sizes of the dimensions of the Tensor.
435    * @param[in] value The value of all elements of the Tensor.
436    * @return A new Tensor with the specified shape.
437    */
438   at::Tensor full_channels_last(
439       const std::vector<int32_t>& sizes,
440       ctype value,
441       ET_UNUSED TensorShapeDynamism dynamism =
442           TensorShapeDynamism::DYNAMIC_UNBOUND) {
443     auto sizes64 = vec_32_to_64(sizes);
444     return at::full(at::IntArrayRef(sizes64), value, at::dtype(DTYPE))
445         .to(at::MemoryFormat::ChannelsLast);
446   }
447 
448   /**
449    * Returns a new Tensor with the specified shape, containing contiguous data
450    * with all `0` elements.
451    *
452    * @param[in] sizes The sizes of the dimensions of the Tensor.
453    * @return A new Tensor with the specified shape.
454    */
455   at::Tensor zeros(
456       const std::vector<int32_t>& sizes,
457       ET_UNUSED TensorShapeDynamism dynamism =
458           TensorShapeDynamism::DYNAMIC_UNBOUND) {
459     auto sizes64 = vec_32_to_64(sizes);
460     return at::zeros(at::IntArrayRef(sizes64), at::dtype(DTYPE));
461   }
462 
463   /**
464    * Returns a new Tensor with the specified shape, containing contiguous data
465    * with all `1` elements.
466    *
467    * @param[in] sizes The sizes of the dimensions of the Tensor.
468    * @return A new Tensor with the specified shape.
469    */
470   at::Tensor ones(
471       const std::vector<int32_t>& sizes,
472       ET_UNUSED TensorShapeDynamism dynamism =
473           TensorShapeDynamism::DYNAMIC_UNBOUND) {
474     auto sizes64 = vec_32_to_64(sizes);
475     return at::ones(at::IntArrayRef(sizes64), at::dtype(DTYPE));
476   }
477 
478   /**
479    * Returns a new Tensor with the same shape as the input tensor, containing
480    * contiguous data with all `0` elements.
481    *
482    * @param[in] input The tensor that supplies the shape of the new Tensor.
483    * @return A new Tensor with the specified shape.
484    */
485   at::Tensor zeros_like(
486       const at::Tensor& input,
487       ET_UNUSED TensorShapeDynamism dynamism =
488           TensorShapeDynamism::DYNAMIC_UNBOUND) {
489     std::vector<int64_t> sizes64 = {input.sizes().begin(), input.sizes().end()};
490     return at::full(at::IntArrayRef(sizes64), 0, at::dtype(DTYPE));
491   }
492 
493   /**
494    * Returns a new Tensor with the same shape as the input tensor, containing
495    * contiguous data with all `1` elements.
496    *
497    * @param[in] input The tensor that supplies the shape of the new Tensor.
498    * @return A new Tensor with the specified shape.
499    */
500   at::Tensor ones_like(
501       const at::Tensor& input,
502       ET_UNUSED TensorShapeDynamism dynamism =
503           TensorShapeDynamism::DYNAMIC_UNBOUND) {
504     std::vector<int64_t> sizes64 = {input.sizes().begin(), input.sizes().end()};
505     return at::full(at::IntArrayRef(sizes64), 1, at::dtype(DTYPE));
506   }
507 
508  private:
509   /// Copies an int32_t vector into a new int64_t vector.
510   static std::vector<int64_t> vec_32_to_64(const std::vector<int32_t>& in) {
511     std::vector<int64_t> out{};
512     out.reserve(in.size());
513     for (auto i : in) {
514       out.push_back(i);
515     }
516     return out;
517   }
518 
519   /**
520    * Returns a new Tensor with the specified shape and stride.
521    *
522    * @param[in] sizes The sizes of the dimensions of the Tensor.
523    * @param[in] strides The strides for each dimensions of the Tensor
524    * @return A new Tensor with the specified shape and strides.
525    */
526   at::Tensor empty_strided(
527       const std::vector<int32_t>& sizes,
528       const std::vector<executorch::aten::StridesType>& strides,
529       ET_UNUSED TensorShapeDynamism dynamism =
530           TensorShapeDynamism::DYNAMIC_UNBOUND) {
531     auto sizes64 = vec_32_to_64(sizes);
532     return at::empty_strided(
533         sizes64,
534         strides,
535         DTYPE,
536         /*layout_opt=*/at::Layout::Strided,
537         /*device_opt=*/at::Device(at::DeviceType::CPU),
538         /*pin_memory_opt=*/false);
539   }
540 };
541 
542 #else // !USE_ATEN_LIB
543 
544 namespace {
545 /*
546  * Dimension order represents how dimensions are laid out in memory,
547  * starting from the inner-most to the outer-most dimension.
548  * Thus, the conversion from strides is done by sorting the strides
549  * from larger to smaller since the dimension with the largest stride
550  * is the outer-most and the dimension with the smallest stride is the
551  inner-most.
552  * For example, tensor with sizes = (3, 5, 2) and strides = (5, 1, 15), implies
553  * dimension order of (2, 0, 1), because 2nd dimension has the biggest stride of
554  15,
555  * followed by 0th dimension with stride of 5 and then innermost dimension being
556  the 1st
557  * dimension with size of 1. This order of (2, 0, 1) can be obtained
558  * by sorting strides from large to smaller.
559 
560  * When strides do not convey dimension order unambiguously, dimension order
561  * returned is dependent on stability of sort. We employ stable sort to preserve
562  * original order. Thus when strides = (4, 3, 1, 1) returned value is (0, 1, 2,
563  3)
564  * Another example is: sizes = (1, 3, 1, 1) with strides = (3, 1, 3, 3),
565  returned
566  * value is (0, 2, 3, 1)
567 */
568 // copied from
569 // https://stackoverflow.com/questions/1577475/c-sorting-and-keeping-track-of-indexes
570 // TODO: Add assert for strides[i] != 0 because strides of 0 is really used,
571 // by pytorch/aten, to convey broadcasting dim.
572 
573 inline std::vector<uint8_t> dim_order_from_stride(
574     const std::vector<int32_t>& v) {
575   std::vector<uint8_t> indices(v.size());
576   std::iota(indices.begin(), indices.end(), 0);
577   stable_sort(indices.begin(), indices.end(), [&v](size_t i1, size_t i2) {
578     return v[i1] > v[i2];
579   });
580   return indices;
581 }
582 
583 inline void validate_strides(
584     const std::vector<int32_t>& sizes,
585     const std::vector<int32_t>& strides) {
586   if (sizes.size() != strides.size()) {
587     ET_CHECK_MSG(false, "Stride and sizes are not equal in length");
588   }
589   for (const auto& s : strides) {
590     if (s == 0) {
591       ET_CHECK_MSG(false, "Stride value of 0 is not supported");
592     }
593   }
594   // No two dimensions can have same stride value
595   for (int32_t i = 0; i < strides.size(); ++i) {
596     for (int32_t j = i + 1; j < strides.size(); ++j) {
597       if ((sizes[i] == 0) || (sizes[j] == 0) ||
598           ((sizes[i] == 1) || (sizes[j] == 1))) {
599         continue;
600       }
601       if ((strides[i] == strides[j])) {
602         ET_CHECK_MSG(
603             false,
604             "Stride value and size dont comply at index %d."
605             " strides[%d]: %d, strides[%d] = %d, sizes[%d] = %d, sizes[%d] = %d",
606             i,
607             i,
608             strides[i],
609             j,
610             strides[j],
611             i,
612             sizes[i],
613             j,
614             sizes[j]);
615       }
616     }
617   }
618 }
619 
620 } // namespace
621 
622 // Note that this !USE_ATEN_LIB section uses ExecuTorch-specific namespaces
623 // instead of exec_aten to make it clear that we're dealing with ETensor, and
624 // because many of these names aren't mapped into executorch::aten::.
625 
626 namespace internal {
627 
628 // This wrapper lets us override the C type associated with some ScalarType
629 // values while using the defaults for everything else.
630 template <torch::executor::ScalarType DTYPE>
631 struct ScalarTypeToCppTypeWrapper {
632   using ctype =
633       typename ::executorch::runtime::ScalarTypeToCppType<DTYPE>::type;
634 };
635 
636 // Use a C type of `uint8_t` instead of `bool`. The C type will be used to
637 // declare a `std::vector<CTYPE>`, and `std::vector<bool>` is often optimized to
638 // store a single bit per entry instead of using an array of separate `bool`
639 // elements. Since the tensor data will point into the vector, it needs to use
640 // one byte per element.
641 template <>
642 struct ScalarTypeToCppTypeWrapper<torch::executor::ScalarType::Bool> {
643   using ctype = uint8_t;
644 };
645 
646 // Use a C type of `uint16_t` instead of `Bits16` to simplify code reuse when
647 // testing multiple integer types.
648 template <>
649 struct ScalarTypeToCppTypeWrapper<torch::executor::ScalarType::Bits16> {
650   using ctype = uint16_t;
651 };
652 
653 // Use a C type of `uint16_t` instead of `UInt16` to simplify code reuse when
654 // testing multiple integer types.
655 template <>
656 struct ScalarTypeToCppTypeWrapper<torch::executor::ScalarType::UInt16> {
657   using ctype = uint16_t;
658 };
659 
660 // To allow implicit conversion between simple types to `ctype`
661 #define SPECIALIZE_ScalarTypeToCppTypeWrapper(CTYPE, DTYPE)               \
662   template <>                                                             \
663   struct ScalarTypeToCppTypeWrapper<torch::executor::ScalarType::DTYPE> { \
664     using ctype = typename CTYPE::underlying;                             \
665   };
666 
667 ET_FORALL_QINT_TYPES(SPECIALIZE_ScalarTypeToCppTypeWrapper)
668 
669 #undef SPECIALIZE_ScalarTypeToCppTypeWrapper
670 
671 } // namespace internal
672 
673 /**
674  * A helper class for creating Tensors, simplifying memory management.
675  *
676  * NOTE: A given TensorFactory instance owns the memory pointed to by all
677  * Tensors that it creates, and must live longer than those Tensors.
678  *
679  * Example:
680  * @code{.cpp}
681  * // A factory instance will create Tensors of a single dtype.
682  * TensorFactory<ScalarType::Int> tf;
683  *
684  * // You can create more factories if you need tensors of multiple
685  * // dtypes.
686  * TensorFactory<ScalarType::Float> tf_float;
687  *
688  * // The factory will copy the vectors provided to it, letting callers provide
689  * // inline literals.
690  * Tensor t1 = tf.make(
691  *     {2, 2}, // sizes
692  *     {1, 2, 3, 4}); // data
693  *
694  * // There are helpers for creating Tensors with all 1 or 0 elements.
695  * Tensor z = tf.zeros({2, 2});
696  * Tensor o = tf_float.ones({2, 2});
697  *
698  * // Sometimes it's helpful to share parameters.
699  * std::vector<int32_t> sizes = {2, 2};
700  * Tensor t3 = tf.make(sizes, {1, 2, 3, 4});
701  * Tensor t4 = tf.ones(sizes);
702  *
703  * // But remember that the inputs are copied, so providing the same data vector
704  * // to two Tensors will not share the same underlying data.
705  * std::vector<int> data = {1, 2, 3, 4};
706  * Tensor t5 = tf.make(sizes, data);
707  * Tensor t6 = tf.make(sizes, data);
708  * t5.mutable_data_ptr<int>()[0] = 99;
709  * EXPECT_NE(t5, t6);
710  * @endcode
711  *
712  * @tparam DTYPE The dtype of Tensors created by this factory, as a ScalarType
713  *     value like `ScalarType::Int`.
714  */
715 template <torch::executor::ScalarType DTYPE>
716 class TensorFactory {
717  public:
718   /**
719    * The C type that backs the associated DTYPE. E.g., `float` for
720    * `ScalarType::Float`.
721    */
722   using ctype = typename internal::ScalarTypeToCppTypeWrapper<DTYPE>::ctype;
723 
724   TensorFactory() = default;
725 
726   /**
727    * Returns a new Tensor with the specified shape, data and stride.
728    *
729    * @param[in] sizes The sizes of the dimensions of the Tensor.
730    * @param[in] data The data that the Tensor should be initialized with. The
731    *     size of this vector must be equal to the product of the elements of
732    *     `sizes`.
733    * @param[in] strides The strides for each dimensions of the Tensor. If empty
734    *     or not specificed, the function will return a contiguous tensor based
735    *     on data and size. If not, the strides shall follow the rules:
736    *            - a. strides.size() == sizes.size().
737    *            - b. all strides are positive.
738    *            - c. All underlying data be accessed.
739    *            - d. All legal indexes can access an underlying data.
740    *            - e. No two indexes access a same data.
741    *            - f. No out of bounds data can be accessed.
742    *
743    * @return A new Tensor with the specified shape and data.
744    */
745   torch::executor::Tensor make(
746       const std::vector<int32_t>& sizes,
747       const std::vector<ctype>& data,
748       const std::vector<executorch::aten::StridesType> strides = {},
749       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
750     std::vector<int32_t> default_strides;
751     // Generate strides from the tensor dimensions, assuming contiguous data if
752     // given strides is empty.
753     if (!sizes.empty() && strides.empty()) {
754       default_strides.resize(sizes.size(), 1);
755       for (size_t i = sizes.size() - 1; i > 0; --i) {
756         // For sizes[i] == 0, treat it as 1 to be consistent with core Pytorch
757         auto sizes_i = sizes[i] ? sizes[i] : 1;
758         default_strides[i - 1] = default_strides[i] * sizes_i;
759       }
760     }
761     auto& actual_strides = default_strides.empty() ? strides : default_strides;
762     validate_strides(sizes, actual_strides);
763     auto dim_order = dim_order_from_stride(actual_strides);
764 
765     auto expected_numel = internal::sizes_to_numel(sizes);
766     ET_CHECK_MSG(
767         expected_numel == data.size(),
768         "Number of data elements %zd "
769         "does not match expected number of elements %zd",
770         data.size(),
771         expected_numel);
772 
773     bool legal = internal::check_strides(sizes, actual_strides);
774     ET_CHECK_MSG(legal, "The input strides variable is illegal.");
775 
776     memory_.emplace_back(std::make_unique<TensorMemory>(
777         sizes, data, dim_order, actual_strides, dynamism));
778     return torch::executor::Tensor(&memory_.back()->impl_);
779   }
780 
781   /**
782    * Returns a new Tensor with the specified shape, data and dim order.
783    *
784    * @param[in] sizes The sizes of the dimensions of the Tensor.
785    * @param[in] data The data that the Tensor should be initialized with. The
786    *     size of this vector must be equal to the product of the elements of
787    *     `sizes`.
788    * @param[in] dim_order The dim order describing how tensor memory is laid
789    * out. If empty or not specificed, the function will use a contiguous dim
790    * order of {0, 1, 2, 3, ...}
791    *
792    * @return A new Tensor with the specified shape and data.
793    */
794   torch::executor::Tensor make_with_dimorder(
795       const std::vector<int32_t>& sizes,
796       const std::vector<ctype>& data,
797       const std::vector<uint8_t> dim_order = {},
798       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
799     std::vector<uint8_t> default_dim_order;
800     // Generate strides from the tensor dimensions, assuming contiguous data if
801     // given strides is empty.
802     if (!sizes.empty() && dim_order.empty()) {
803       default_dim_order.resize(sizes.size(), 1);
804       for (size_t i = 0; i < sizes.size(); ++i) {
805         default_dim_order[i] = i;
806       }
807     }
808     auto& actual_dim_order =
809         default_dim_order.empty() ? dim_order : default_dim_order;
810 
811     auto strides = internal::strides_from_dim_order(sizes, actual_dim_order);
812 
813     auto expected_numel = internal::sizes_to_numel(sizes);
814     ET_CHECK_MSG(
815         expected_numel == data.size(),
816         "Number of data elements %zd "
817         "does not match expected number of elements %zd",
818         data.size(),
819         expected_numel);
820 
821     memory_.emplace_back(std::make_unique<TensorMemory>(
822         sizes, data, actual_dim_order, strides, dynamism));
823     return torch::executor::Tensor(&memory_.back()->impl_);
824   }
825 
826   /**
827    * Returns a new Tensor with the specified shape and data in channels last
828    * memory format.
829    *
830    * @param[in] sizes The sizes of the dimensions of the Tensor.
831    * @param[in] data The data that the Tensor should be initialized with. The
832    *     size of this vector must be equal to the product of the elements of
833    *     `sizes`.
834    *
835    * @return A new Tensor with the specified shape and data.
836    */
837   torch::executor::Tensor make_channels_last(
838       const std::vector<int32_t>& sizes,
839       const std::vector<ctype>& data,
840       const std::vector<uint8_t> dim_order = {},
841       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
842     return make_with_dimorder(
843         sizes, data, internal::channels_last_dim_order(sizes.size()), dynamism);
844   }
845 
846   /**
847    * Given data in contiguous memory format, returns a new Tensor with the
848    * specified shape and the same data but in channels last memory format.
849    *
850    * @param[in] sizes The sizes of the dimensions of the Tensor.
851    * @param[in] data The data in contiguous memory format that the Tensor should
852    * be initialized with. The size of this vector must be equal to the product
853    * of the elements of `sizes`.
854    *
855    * @return A new Tensor with the specified shape and data in channls last
856    * memory format.
857    */
858   torch::executor::Tensor channels_last_like(
859       const torch::executor::Tensor& input,
860       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
861     const std::vector<int32_t> sizes(
862         input.sizes().begin(), input.sizes().end());
863 
864     ET_CHECK_MSG(sizes.size() == 4, "Only 4D tensors can be channels last");
865     ET_CHECK_MSG(
866         is_contiguous_dim_order(input.dim_order().data(), input.dim()) == true,
867         "Input tensor is not contiguous");
868     int32_t N = sizes[0];
869     int32_t C = sizes[1];
870     int32_t H = sizes[2];
871     int32_t W = sizes[3];
872 
873     std::vector<ctype> contiguous_data(
874         input.data_ptr<ctype>(), input.data_ptr<ctype>() + input.numel());
875     std::vector<ctype> channels_last_data(
876         N * C * H * W); // Create a new blob with the same total size to contain
877                         // channels_last data
878     for (int32_t n = 0; n < N; ++n) {
879       for (int32_t c = 0; c < C; ++c) {
880         for (int32_t h = 0; h < H; ++h) {
881           for (int32_t w = 0; w < W; ++w) {
882             // Calculate the index in the original blob
883             int32_t old_index = ((n * C + c) * H + h) * W + w;
884             // Calculate the index in the new blob
885             int32_t new_index = ((n * H + h) * W + w) * C + c;
886             // Copy the data
887             channels_last_data[new_index] = contiguous_data[old_index];
888           }
889         }
890       }
891     }
892 
893     return make_with_dimorder(
894         sizes,
895         channels_last_data,
896         internal::channels_last_dim_order(sizes.size()),
897         dynamism);
898   }
899 
900   /**
901    * Returns a new Tensor with the specified shape, containing contiguous data
902    * will all elements set to `value`.
903    *
904    * @param[in] sizes The sizes of the dimensions of the Tensor.
905    * @param[in] value The value of all elements of the Tensor.
906    * @return A new Tensor with the specified shape.
907    */
908   torch::executor::Tensor full(
909       const std::vector<int32_t>& sizes,
910       ctype value,
911       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
912     std::vector<ctype> data(internal::sizes_to_numel(sizes), value);
913     return make(sizes, data, /* empty strides */ {}, dynamism);
914   }
915 
916   /**
917    * Returns a new Tensor with the specified shape, containing channels last
918    * contiguous data will all elements set to `value`.
919    *
920    * @param[in] sizes The sizes of the dimensions of the Tensor.
921    * @param[in] value The value of all elements of the Tensor.
922    * @return A new Tensor with the specified shape.
923    */
924   torch::executor::Tensor full_channels_last(
925       const std::vector<int32_t>& sizes,
926       ctype value,
927       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
928     std::vector<ctype> data(internal::sizes_to_numel(sizes), value);
929     return make_with_dimorder(
930         sizes, data, internal::channels_last_dim_order(sizes.size()), dynamism);
931   }
932 
933   /**
934    * Returns a new Tensor with the specified shape, containing contiguous data
935    * in channels last memory format with all `0` elements.
936    *
937    * @param[in] sizes The sizes of the dimensions of the Tensor.
938    * @return A new Tensor with the specified shape.
939    */
940   torch::executor::Tensor zeros_channels_last(
941       const std::vector<int32_t>& sizes,
942       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
943     return full_channels_last(sizes, 0, dynamism);
944   }
945 
946   /**
947    * Returns a new Tensor with the specified shape, containing contiguous data
948    * in contiguous memory format with all `0` elements.
949    *
950    * @param[in] sizes The sizes of the dimensions of the Tensor.
951    * @return A new Tensor with the specified shape.
952    */
953   torch::executor::Tensor zeros(
954       const std::vector<int32_t>& sizes,
955       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
956     return full(sizes, 0, dynamism);
957   }
958 
959   /**
960    * Returns a new Tensor with the specified shape, containing contiguous data
961    * with all `1` elements.
962    *
963    * @param[in] sizes The sizes of the dimensions of the Tensor.
964    * @return A new Tensor with the specified shape.
965    */
966   torch::executor::Tensor ones(
967       const std::vector<int32_t>& sizes,
968       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
969     return full(sizes, 1, dynamism);
970   }
971 
972   /**
973    * Returns a new Tensor with the same shape as the input tensor, containing
974    * contiguous data with all `0` elements.
975    *
976    * @param[in] input The tensor that supplies the shape of the new Tensor.
977    * @return A new Tensor with the specified shape.
978    */
979   torch::executor::Tensor zeros_like(
980       const torch::executor::Tensor& input,
981       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
982     std::vector<int32_t> sizes = {input.sizes().begin(), input.sizes().end()};
983     return full(sizes, 0, dynamism);
984   }
985 
986   /**
987    * Returns a new Tensor with the same shape as the input tensor, containing
988    * contiguous data with all `1` elements.
989    *
990    * @param[in] input The tensor that supplies the shape of the new Tensor.
991    * @return A new Tensor with the specified shape.
992    */
993   torch::executor::Tensor ones_like(
994       const torch::executor::Tensor& input,
995       TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC) {
996     std::vector<int32_t> sizes = {input.sizes().begin(), input.sizes().end()};
997     return full(sizes, 1, dynamism);
998   }
999 
1000  private:
1001   /**
1002    * Owns all backing memory for a single Tensor.
1003    */
1004   struct TensorMemory {
1005     TensorMemory(
1006         const std::vector<int32_t>& sizes,
1007         const std::vector<ctype>& data,
1008         const std::vector<uint8_t>& dim_order,
1009         const std::vector<int32_t>& strides,
1010         TensorShapeDynamism dynamism = TensorShapeDynamism::STATIC)
1011         : sizes_(sizes),
1012           data_(data),
1013           dim_order_(dim_order),
1014           strides_(strides),
1015           impl_(
1016               DTYPE,
1017               /*dim=*/sizes_.size(),
1018               sizes_.data(),
1019               data_.data(),
1020               dim_order_.data(),
1021               strides_.data(),
1022               dynamism) {}
1023 
1024     std::vector<int32_t> sizes_;
1025     std::vector<ctype> data_;
1026     std::vector<uint8_t> dim_order_;
1027     std::vector<executorch::aten::StridesType> strides_;
1028     torch::executor::TensorImpl impl_;
1029   };
1030 
1031   /**
1032    * The memory pointed to by Tensors created by this factory. This is a vector
1033    * of pointers so that the TensorMemory objects won't move if the vector needs
1034    * to resize/realloc.
1035    */
1036   std::vector<std::unique_ptr<TensorMemory>> memory_;
1037 };
1038 
1039 #endif // !USE_ATEN_LIB
1040 
1041 /**
1042  * A helper class for creating TensorLists, simplifying memory management.
1043  *
1044  * NOTE: A given TensorListFactory owns the memory pointed to by all TensorLists
1045  * (and Tensors they contain), and must live longer than those TensorLists and
1046  * Tensors.
1047  */
1048 template <executorch::aten::ScalarType DTYPE>
1049 class TensorListFactory final {
1050  public:
1051   TensorListFactory() = default;
1052   ~TensorListFactory() = default;
1053 
1054   /**
1055    * Returns a TensorList containing Tensors with the same shapes as the
1056    * provided Tensors, but filled with zero elements. The dtypes of the template
1057    * entries are ignored.
1058    */
1059   executorch::aten::TensorList zeros_like(
1060       const std::vector<executorch::aten::Tensor>& templates) {
1061     memory_.emplace_back(
1062         std::make_unique<std::vector<executorch::aten::Tensor>>());
1063     auto& vec = memory_.back();
1064     std::for_each(
1065         templates.begin(),
1066         templates.end(),
1067         [&](const executorch::aten::Tensor& t) {
1068           vec->push_back(tf_.zeros_like(t));
1069         });
1070     return executorch::aten::TensorList(vec->data(), vec->size());
1071   }
1072 
1073  private:
1074   TensorFactory<DTYPE> tf_;
1075   /**
1076    * The memory pointed to by TensorLists created by this factory. This is a
1077    * vector of pointers so that the elements won't move if the vector needs to
1078    * resize/realloc.
1079    */
1080   std::vector<std::unique_ptr<std::vector<executorch::aten::Tensor>>> memory_;
1081 };
1082 
1083 } // namespace testing
1084 } // namespace runtime
1085 } // namespace executorch
1086 
1087 namespace torch {
1088 namespace executor {
1089 namespace testing {
1090 // TODO(T197294990): Remove these deprecated aliases once all users have moved
1091 // to the new `::executorch` namespaces.
1092 using ::executorch::runtime::testing::TensorFactory;
1093 using ::executorch::runtime::testing::TensorListFactory;
1094 } // namespace testing
1095 } // namespace executor
1096 } // namespace torch
1097