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