From 3cecf47f7dd339bf822532b8e85f35ab429f9186 Mon Sep 17 00:00:00 2001 From: gasoonjia Date: Fri, 26 Dec 2025 12:41:37 -0800 Subject: [PATCH] [slimtensor] Add factory functions for creating empty CPU tensors This diff adds factory functions to create SlimTensor instances with allocated storage for better usage. **Key components:** 1. **`factory/Empty.h`** - Factory functions for creating empty tensors: - `empty_strided(sizes, strides, dtype, device)` - Creates tensor with explicit sizes/strides - `empty(sizes, dtype, device)` - Creates contiguous tensor (auto-computes row-major strides) - `empty(initializer_list, dtype, device)` - Convenience overload for inline size specification - `empty_like(tensor)` - Creates empty tensor with same metadata as another tensor 2. **`core/Storage.h`** - Added `new_storage()` helper function: - Computes required storage bytes from sizes, strides, and dtype - Allocates and returns a new owning Storage instance Differential Revision: [D89820292](https://our.internmc.facebook.com/intern/diff/D89820292/) [ghstack-poisoned] --- backends/aoti/slim/core/Storage.h | 19 +- backends/aoti/slim/core/targets.bzl | 2 + backends/aoti/slim/factory/Empty.h | 82 +++++++ backends/aoti/slim/factory/TARGETS | 3 + backends/aoti/slim/factory/targets.bzl | 18 ++ backends/aoti/slim/factory/test/TARGETS | 3 + backends/aoti/slim/factory/test/targets.bzl | 14 ++ .../aoti/slim/factory/test/test_empty.cpp | 232 ++++++++++++++++++ 8 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 backends/aoti/slim/factory/Empty.h create mode 100644 backends/aoti/slim/factory/TARGETS create mode 100644 backends/aoti/slim/factory/targets.bzl create mode 100644 backends/aoti/slim/factory/test/TARGETS create mode 100644 backends/aoti/slim/factory/test/targets.bzl create mode 100644 backends/aoti/slim/factory/test/test_empty.cpp diff --git a/backends/aoti/slim/core/Storage.h b/backends/aoti/slim/core/Storage.h index ed8bdf88b49..d122e86c1d4 100644 --- a/backends/aoti/slim/core/Storage.h +++ b/backends/aoti/slim/core/Storage.h @@ -8,12 +8,13 @@ #pragma once -#include #include #include #include +#include #include +#include #include namespace executorch::backends::aoti::slim { @@ -242,4 +243,20 @@ class MaybeOwningStorage { /// Multiple tensors can share the same underlying storage. using Storage = SharedPtr; +/// Creates a new owning storage with the given parameters. +/// @param sizes The sizes of each dimension. +/// @param strides The strides of each dimension. +/// @param dtype The scalar type of tensor elements. +/// @param device The target device (must be CPU). +/// @return A shared pointer to the newly allocated storage. +inline Storage new_storage( + IntArrayRef sizes, + IntArrayRef strides, + c10::ScalarType dtype, + const c10::Device& device = CPU_DEVICE) { + size_t nbytes = + compute_storage_nbytes(sizes, strides, c10::elementSize(dtype), 0); + return Storage(new MaybeOwningStorage(device, nbytes)); +} + } // namespace executorch::backends::aoti::slim diff --git a/backends/aoti/slim/core/targets.bzl b/backends/aoti/slim/core/targets.bzl index 8c352b74c28..2056b8c6866 100644 --- a/backends/aoti/slim/core/targets.bzl +++ b/backends/aoti/slim/core/targets.bzl @@ -13,7 +13,9 @@ def define_common_targets(): exported_deps = [ "//executorch/backends/aoti/slim/c10/core:device", "//executorch/backends/aoti/slim/c10/core:scalar_type", + "//executorch/backends/aoti/slim/util:array_ref_util", "//executorch/backends/aoti/slim/util:shared_ptr", + "//executorch/backends/aoti/slim/util:size_util", "//executorch/runtime/platform:platform", ], ) diff --git a/backends/aoti/slim/factory/Empty.h b/backends/aoti/slim/factory/Empty.h new file mode 100644 index 00000000000..24b4f53a647 --- /dev/null +++ b/backends/aoti/slim/factory/Empty.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace executorch::backends::aoti::slim { + +/// Creates an empty tensor with the specified sizes and strides. +/// The tensor owns its underlying storage, which is allocated but +/// uninitialized. +/// +/// @param sizes The sizes of each dimension. +/// @param strides The strides of each dimension. +/// @param dtype The scalar type of tensor elements. +/// @param device The target device (must be CPU). +/// @return A new SlimTensor with allocated but uninitialized storage. +inline SlimTensor empty_strided( + IntArrayRef sizes, + IntArrayRef strides, + c10::ScalarType dtype, + const c10::Device& device = CPU_DEVICE) { + Storage storage = new_storage(sizes, strides, dtype, device); + return SlimTensor(std::move(storage), sizes, strides, dtype, 0); +} + +/// Creates an empty contiguous tensor with the specified sizes. +/// The tensor owns its underlying storage, which is allocated but +/// uninitialized. Strides are computed automatically to be contiguous +/// (row-major order). +/// +/// @param sizes The sizes of each dimension. +/// @param dtype The scalar type of tensor elements. +/// @param device The target device (must be CPU). +/// @return A new SlimTensor with contiguous strides and uninitialized storage. +inline SlimTensor empty( + IntArrayRef sizes, + c10::ScalarType dtype, + const c10::Device& device = CPU_DEVICE) { + std::vector contig_strides = compute_contiguous_strides(sizes); + Storage storage = + new_storage(sizes, makeArrayRef(contig_strides), dtype, device); + return SlimTensor( + std::move(storage), sizes, makeArrayRef(contig_strides), dtype, 0); +} + +/// Creates an empty contiguous tensor with sizes from an initializer list. +/// Convenience overload for creating tensors with inline size specification. +/// +/// @param sizes The sizes of each dimension as an initializer list. +/// @param dtype The scalar type of tensor elements. +/// @param device The target device (must be CPU). +/// @return A new SlimTensor with contiguous strides and uninitialized storage. +inline SlimTensor empty( + std::initializer_list sizes, + c10::ScalarType dtype, + const c10::Device& device = CPU_DEVICE) { + return empty(makeArrayRef(sizes), dtype, device); +} + +/// Creates an empty tensor with the same sizes, strides, dtype, and device as +/// another tensor. +/// +/// @param other The tensor to copy metadata from. +/// @return A new SlimTensor with the same shape and properties, but +/// uninitialized storage. +inline SlimTensor empty_like(const SlimTensor& other) { + return empty_strided( + other.sizes(), other.strides(), other.dtype(), other.device()); +} + +} // namespace executorch::backends::aoti::slim diff --git a/backends/aoti/slim/factory/TARGETS b/backends/aoti/slim/factory/TARGETS new file mode 100644 index 00000000000..77871de4469 --- /dev/null +++ b/backends/aoti/slim/factory/TARGETS @@ -0,0 +1,3 @@ +load("targets.bzl", "define_common_targets") + +define_common_targets() diff --git a/backends/aoti/slim/factory/targets.bzl b/backends/aoti/slim/factory/targets.bzl new file mode 100644 index 00000000000..d6dc41aa877 --- /dev/null +++ b/backends/aoti/slim/factory/targets.bzl @@ -0,0 +1,18 @@ +load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") + +def define_common_targets(): + """Define targets for SlimTensor factory module.""" + + # Header-only library for empty tensor factory functions + runtime.cxx_library( + name = "empty", + headers = [ + "Empty.h", + ], + visibility = ["@EXECUTORCH_CLIENTS"], + exported_deps = [ + "//executorch/backends/aoti/slim/core:slimtensor", + "//executorch/backends/aoti/slim/util:array_ref_util", + "//executorch/backends/aoti/slim/util:size_util", + ], + ) diff --git a/backends/aoti/slim/factory/test/TARGETS b/backends/aoti/slim/factory/test/TARGETS new file mode 100644 index 00000000000..77871de4469 --- /dev/null +++ b/backends/aoti/slim/factory/test/TARGETS @@ -0,0 +1,3 @@ +load("targets.bzl", "define_common_targets") + +define_common_targets() diff --git a/backends/aoti/slim/factory/test/targets.bzl b/backends/aoti/slim/factory/test/targets.bzl new file mode 100644 index 00000000000..a64510b2af1 --- /dev/null +++ b/backends/aoti/slim/factory/test/targets.bzl @@ -0,0 +1,14 @@ +load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") + +def define_common_targets(): + """Define test targets for SlimTensor factory module.""" + + runtime.cxx_test( + name = "test_empty", + srcs = [ + "test_empty.cpp", + ], + deps = [ + "//executorch/backends/aoti/slim/factory:empty", + ], + ) diff --git a/backends/aoti/slim/factory/test/test_empty.cpp b/backends/aoti/slim/factory/test/test_empty.cpp new file mode 100644 index 00000000000..7d7c9cafc34 --- /dev/null +++ b/backends/aoti/slim/factory/test/test_empty.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include + +namespace executorch::backends::aoti::slim { + +// ============================================================================= +// empty_strided() Tests +// ============================================================================= + +TEST(EmptyStridedTest, Basic2x3Tensor) { + std::vector sizes = {2, 3}; + std::vector strides = {3, 1}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_TRUE(tensor.defined()); + EXPECT_EQ(tensor.dim(), 2u); + EXPECT_EQ(tensor.numel(), 6u); + EXPECT_EQ(tensor.dtype(), c10::ScalarType::Float); + EXPECT_TRUE(tensor.is_cpu()); + + auto result_sizes = tensor.sizes(); + EXPECT_EQ(result_sizes[0], 2); + EXPECT_EQ(result_sizes[1], 3); + + auto result_strides = tensor.strides(); + EXPECT_EQ(result_strides[0], 3); + EXPECT_EQ(result_strides[1], 1); +} + +TEST(EmptyStridedTest, ContiguousTensor) { + std::vector sizes = {2, 3, 4}; + std::vector strides = {12, 4, 1}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_TRUE(tensor.is_contiguous()); + EXPECT_EQ(tensor.numel(), 24u); + EXPECT_EQ(tensor.nbytes(), 24 * sizeof(float)); +} + +TEST(EmptyStridedTest, NonContiguousTensor) { + std::vector sizes = {3, 2}; + std::vector strides = {1, 3}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_FALSE(tensor.is_contiguous()); + EXPECT_EQ(tensor.numel(), 6u); +} + +TEST(EmptyStridedTest, OneDimensional) { + std::vector sizes = {10}; + std::vector strides = {1}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_EQ(tensor.dim(), 1u); + EXPECT_EQ(tensor.numel(), 10u); + EXPECT_TRUE(tensor.is_contiguous()); +} + +TEST(EmptyStridedTest, ZeroSizedTensor) { + std::vector sizes = {0, 3}; + std::vector strides = {3, 1}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_TRUE(tensor.defined()); + EXPECT_EQ(tensor.numel(), 0u); + EXPECT_TRUE(tensor.is_empty()); +} + +TEST(EmptyStridedTest, LargeDimensionalTensor) { + std::vector sizes = {2, 3, 4, 5}; + std::vector strides = {60, 20, 5, 1}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_EQ(tensor.dim(), 4u); + EXPECT_EQ(tensor.numel(), 120u); + EXPECT_TRUE(tensor.is_contiguous()); +} + +TEST(EmptyStridedTest, StorageOffset) { + std::vector sizes = {2, 3}; + std::vector strides = {3, 1}; + + SlimTensor tensor = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + + EXPECT_EQ(tensor.storage_offset(), 0); +} + +// ============================================================================= +// empty() Tests +// ============================================================================= + +TEST(EmptyTest, BasicWithArrayRef) { + std::vector sizes = {2, 3, 4}; + + SlimTensor tensor = empty(makeArrayRef(sizes), c10::ScalarType::Float); + + EXPECT_TRUE(tensor.defined()); + EXPECT_EQ(tensor.dim(), 3u); + EXPECT_EQ(tensor.numel(), 24u); + EXPECT_TRUE(tensor.is_contiguous()); +} + +TEST(EmptyTest, VerifiesContiguousStrides) { + std::vector sizes = {2, 3, 4}; + + SlimTensor tensor = empty(makeArrayRef(sizes), c10::ScalarType::Float); + + auto strides = tensor.strides(); + EXPECT_EQ(strides[0], 12); + EXPECT_EQ(strides[1], 4); + EXPECT_EQ(strides[2], 1); +} + +TEST(EmptyTest, InitializerListOverload) { + SlimTensor tensor = empty({4, 5, 6}, c10::ScalarType::Float); + + EXPECT_EQ(tensor.dim(), 3u); + EXPECT_EQ(tensor.numel(), 120u); + EXPECT_TRUE(tensor.is_contiguous()); + + auto sizes = tensor.sizes(); + EXPECT_EQ(sizes[0], 4); + EXPECT_EQ(sizes[1], 5); + EXPECT_EQ(sizes[2], 6); +} + +TEST(EmptyTest, OneDimensional) { + SlimTensor tensor = empty({10}, c10::ScalarType::Float); + + EXPECT_EQ(tensor.dim(), 1u); + EXPECT_EQ(tensor.numel(), 10u); + EXPECT_EQ(tensor.stride(0), 1); +} + +TEST(EmptyTest, ZeroSized) { + SlimTensor tensor = empty({0, 5}, c10::ScalarType::Float); + + EXPECT_TRUE(tensor.is_empty()); + EXPECT_EQ(tensor.numel(), 0u); +} + +// ============================================================================= +// empty_like() Tests +// ============================================================================= + +TEST(EmptyLikeTest, CopiesMetadata) { + std::vector sizes = {2, 3, 4}; + std::vector strides = {12, 4, 1}; + + SlimTensor original = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + SlimTensor copy = empty_like(original); + + EXPECT_EQ(copy.dim(), original.dim()); + EXPECT_EQ(copy.numel(), original.numel()); + EXPECT_EQ(copy.dtype(), original.dtype()); + EXPECT_EQ(copy.is_cpu(), original.is_cpu()); + EXPECT_EQ(copy.is_contiguous(), original.is_contiguous()); + + for (size_t i = 0; i < copy.dim(); i++) { + EXPECT_EQ(copy.size(i), original.size(i)); + EXPECT_EQ(copy.stride(i), original.stride(i)); + } +} + +TEST(EmptyLikeTest, HasDifferentStorage) { + SlimTensor original = empty({2, 3}, c10::ScalarType::Float); + SlimTensor copy = empty_like(original); + + EXPECT_NE(original.data_ptr(), copy.data_ptr()); +} + +TEST(EmptyLikeTest, NonContiguousTensor) { + std::vector sizes = {3, 2}; + std::vector strides = {1, 3}; + + SlimTensor original = empty_strided( + makeArrayRef(sizes), makeArrayRef(strides), c10::ScalarType::Float); + SlimTensor copy = empty_like(original); + + EXPECT_FALSE(copy.is_contiguous()); + EXPECT_EQ(copy.stride(0), 1); + EXPECT_EQ(copy.stride(1), 3); +} + +// ============================================================================= +// Data Access Tests +// ============================================================================= + +TEST(EmptyTest, DataPtrIsValid) { + SlimTensor tensor = empty({2, 3}, c10::ScalarType::Float); + + void* data = tensor.data_ptr(); + EXPECT_NE(data, nullptr); +} + +TEST(EmptyTest, CanWriteAndReadData) { + SlimTensor tensor = empty({2, 3}, c10::ScalarType::Float); + + float* data = static_cast(tensor.data_ptr()); + for (size_t i = 0; i < tensor.numel(); i++) { + data[i] = static_cast(i); + } + + for (size_t i = 0; i < tensor.numel(); i++) { + EXPECT_EQ(data[i], static_cast(i)); + } +} + +} // namespace executorch::backends::aoti::slim