diff --git a/backends/aoti/slim/c10/core/ScalarType.h b/backends/aoti/slim/c10/core/ScalarType.h index 1ca1a1429ed..28391f012d5 100644 --- a/backends/aoti/slim/c10/core/ScalarType.h +++ b/backends/aoti/slim/c10/core/ScalarType.h @@ -12,35 +12,64 @@ #include #include +#include #include namespace executorch::backends::aoti::slim::c10 { +// Import BFloat16 from ExecuTorch's portable_type +using BFloat16 = ::executorch::runtime::etensor::BFloat16; + /// Enum representing the scalar type (dtype) of tensor elements. /// Note: Enum values must match PyTorch's c10::ScalarType for compatibility. enum class ScalarType : int8_t { - // Byte = 0, - // Char = 1, - // Short = 2, - // Int = 3, - // Long = 4, - Float = 6, - // Bool = 11, - // BFloat16 = 15, + // Byte = 0, // uint8_t - not currently needed + Char = 1, // int8_t + Short = 2, // int16_t + Int = 3, // int32_t + Long = 4, // int64_t + // Half = 5, // float16 - not currently needed + Float = 6, // float + // Double = 7, // double - not currently needed + // ComplexHalf = 8, + // ComplexFloat = 9, + // ComplexDouble = 10, + Bool = 11, // bool + // QInt8 = 12, + // QUInt8 = 13, + // QInt32 = 14, + BFloat16 = 15, // bfloat16 Undefined = -1, - NumOptions = 7, }; -/// Constant for Float scalar type. +// Type alias constants for convenience +constexpr ScalarType kChar = ScalarType::Char; +constexpr ScalarType kShort = ScalarType::Short; +constexpr ScalarType kInt = ScalarType::Int; +constexpr ScalarType kLong = ScalarType::Long; constexpr ScalarType kFloat = ScalarType::Float; +constexpr ScalarType kBool = ScalarType::Bool; +constexpr ScalarType kBFloat16 = ScalarType::BFloat16; /// Returns the size in bytes of a single element of the given scalar type. /// @param t The scalar type. /// @return The size in bytes of a single element. inline size_t elementSize(ScalarType t) { switch (t) { + case ScalarType::Char: + return sizeof(int8_t); + case ScalarType::Short: + return sizeof(int16_t); + case ScalarType::Int: + return sizeof(int32_t); + case ScalarType::Long: + return sizeof(int64_t); case ScalarType::Float: return sizeof(float); + case ScalarType::Bool: + return sizeof(bool); + case ScalarType::BFloat16: + return sizeof(BFloat16); default: ET_CHECK_MSG(false, "Unknown ScalarType: %d", static_cast(t)); } @@ -51,8 +80,20 @@ inline size_t elementSize(ScalarType t) { /// @return The name of the scalar type. inline const char* toString(ScalarType t) { switch (t) { + case ScalarType::Char: + return "Char"; + case ScalarType::Short: + return "Short"; + case ScalarType::Int: + return "Int"; + case ScalarType::Long: + return "Long"; case ScalarType::Float: return "Float"; + case ScalarType::Bool: + return "Bool"; + case ScalarType::BFloat16: + return "BFloat16"; case ScalarType::Undefined: return "Undefined"; default: @@ -64,16 +105,32 @@ inline const char* toString(ScalarType t) { /// @param t The scalar type to check. /// @return true if the scalar type is floating point, false otherwise. inline bool isFloatingType(ScalarType t) { - return t == ScalarType::Float; + return t == ScalarType::Float || t == ScalarType::BFloat16; } -/// Checks if the scalar type is an integral type (including bool). +/// Checks if the scalar type is an integral type (including bool optionally). /// @param t The scalar type to check. /// @param includeBool Whether to consider Bool as integral. /// @return true if the scalar type is integral, false otherwise. -inline bool isIntegralType(ScalarType t, bool /*includeBool*/) { - (void)t; - return false; +inline bool isIntegralType(ScalarType t, bool includeBool) { + switch (t) { + case ScalarType::Char: + case ScalarType::Short: + case ScalarType::Int: + case ScalarType::Long: + return true; + case ScalarType::Bool: + return includeBool; + default: + return false; + } +} + +/// Checks if the scalar type is a boolean type. +/// @param t The scalar type to check. +/// @return true if the scalar type is Bool, false otherwise. +inline bool isBoolType(ScalarType t) { + return t == ScalarType::Bool; } inline std::ostream& operator<<(std::ostream& stream, ScalarType scalar_type) { diff --git a/backends/aoti/slim/c10/core/targets.bzl b/backends/aoti/slim/c10/core/targets.bzl index 500620aecd1..5a9b9558938 100644 --- a/backends/aoti/slim/c10/core/targets.bzl +++ b/backends/aoti/slim/c10/core/targets.bzl @@ -36,6 +36,7 @@ def define_common_targets(): ], visibility = ["@EXECUTORCH_CLIENTS"], exported_deps = [ + "//executorch/runtime/core/portable_type:portable_type", "//executorch/runtime/platform:platform", ], ) diff --git a/backends/aoti/slim/c10/core/test/test_scalar_type.cpp b/backends/aoti/slim/c10/core/test/test_scalar_type.cpp index 673641d84c7..332f5d7d264 100644 --- a/backends/aoti/slim/c10/core/test/test_scalar_type.cpp +++ b/backends/aoti/slim/c10/core/test/test_scalar_type.cpp @@ -13,49 +13,186 @@ using namespace executorch::backends::aoti::slim::c10; -class ScalarTypeTest : public ::testing::Test {}; +// ============================================================================= +// Test Data Structures for Parameterized Tests +// ============================================================================= -TEST_F(ScalarTypeTest, FloatEnumValue) { - // Verify Float has the correct enum value (6) to match PyTorch - EXPECT_EQ(static_cast(ScalarType::Float), 6); +struct ScalarTypeTestData { + ScalarType dtype; + int expected_enum_value; + size_t expected_element_size; + const char* expected_name; + bool is_floating; + bool is_integral; + bool is_integral_with_bool; + bool is_bool; +}; + +// All supported scalar types with their expected properties +const std::vector kAllScalarTypes = { + // dtype, enum_value, element_size, name, is_float, is_int, is_int_w_bool, + // is_bool + {ScalarType::Char, 1, 1, "Char", false, true, true, false}, + {ScalarType::Short, 2, 2, "Short", false, true, true, false}, + {ScalarType::Int, 3, 4, "Int", false, true, true, false}, + {ScalarType::Long, 4, 8, "Long", false, true, true, false}, + {ScalarType::Float, 6, 4, "Float", true, false, false, false}, + {ScalarType::Bool, 11, 1, "Bool", false, false, true, true}, + {ScalarType::BFloat16, 15, 2, "BFloat16", true, false, false, false}, +}; + +// ============================================================================= +// Parameterized Test Fixture +// ============================================================================= + +class ScalarTypeParamTest + : public ::testing::TestWithParam {}; + +TEST_P(ScalarTypeParamTest, EnumValue) { + const auto& data = GetParam(); + EXPECT_EQ(static_cast(data.dtype), data.expected_enum_value) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, ElementSize) { + const auto& data = GetParam(); + EXPECT_EQ(elementSize(data.dtype), data.expected_element_size) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, ToString) { + const auto& data = GetParam(); + EXPECT_STREQ(toString(data.dtype), data.expected_name) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, IsFloatingType) { + const auto& data = GetParam(); + EXPECT_EQ(isFloatingType(data.dtype), data.is_floating) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, IsIntegralTypeWithoutBool) { + const auto& data = GetParam(); + EXPECT_EQ(isIntegralType(data.dtype, false), data.is_integral) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, IsIntegralTypeWithBool) { + const auto& data = GetParam(); + EXPECT_EQ(isIntegralType(data.dtype, true), data.is_integral_with_bool) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, IsBoolType) { + const auto& data = GetParam(); + EXPECT_EQ(isBoolType(data.dtype), data.is_bool) + << "Failed for dtype: " << toString(data.dtype); +} + +TEST_P(ScalarTypeParamTest, StreamOperator) { + const auto& data = GetParam(); + std::ostringstream oss; + oss << data.dtype; + EXPECT_EQ(oss.str(), data.expected_name) + << "Failed for dtype: " << toString(data.dtype); +} + +INSTANTIATE_TEST_SUITE_P( + AllTypes, + ScalarTypeParamTest, + ::testing::ValuesIn(kAllScalarTypes), + [](const ::testing::TestParamInfo& info) { + return std::string(info.param.expected_name); + }); + +// ============================================================================= +// Type Constant Tests +// ============================================================================= + +class ScalarTypeConstantsTest : public ::testing::Test {}; + +TEST_F(ScalarTypeConstantsTest, KCharConstant) { + EXPECT_EQ(kChar, ScalarType::Char); +} + +TEST_F(ScalarTypeConstantsTest, KShortConstant) { + EXPECT_EQ(kShort, ScalarType::Short); } -TEST_F(ScalarTypeTest, KFloatConstant) { - // Verify kFloat constant +TEST_F(ScalarTypeConstantsTest, KIntConstant) { + EXPECT_EQ(kInt, ScalarType::Int); +} + +TEST_F(ScalarTypeConstantsTest, KLongConstant) { + EXPECT_EQ(kLong, ScalarType::Long); +} + +TEST_F(ScalarTypeConstantsTest, KFloatConstant) { EXPECT_EQ(kFloat, ScalarType::Float); } -TEST_F(ScalarTypeTest, ElementSizeFloat) { - // Verify elementSize returns correct size for Float (4 bytes) - EXPECT_EQ(elementSize(ScalarType::Float), sizeof(float)); - EXPECT_EQ(elementSize(ScalarType::Float), 4); +TEST_F(ScalarTypeConstantsTest, KBoolConstant) { + EXPECT_EQ(kBool, ScalarType::Bool); } -TEST_F(ScalarTypeTest, ToStringFloat) { - // Verify toString returns correct string for Float - EXPECT_STREQ(toString(ScalarType::Float), "Float"); +TEST_F(ScalarTypeConstantsTest, KBFloat16Constant) { + EXPECT_EQ(kBFloat16, ScalarType::BFloat16); } -TEST_F(ScalarTypeTest, ToStringUndefined) { - // Verify toString returns correct string for Undefined +// ============================================================================= +// Edge Cases and Special Values +// ============================================================================= + +class ScalarTypeEdgeCasesTest : public ::testing::Test {}; + +TEST_F(ScalarTypeEdgeCasesTest, UndefinedToString) { EXPECT_STREQ(toString(ScalarType::Undefined), "Undefined"); } -TEST_F(ScalarTypeTest, IsFloatingType) { - // Verify isFloatingType works correctly - EXPECT_TRUE(isFloatingType(ScalarType::Float)); +TEST_F(ScalarTypeEdgeCasesTest, UndefinedIsNotFloating) { + EXPECT_FALSE(isFloatingType(ScalarType::Undefined)); } -TEST_F(ScalarTypeTest, IsIntegralType) { - // Verify isIntegralType works correctly - // Currently no integral types are supported, so Float should return false - EXPECT_FALSE(isIntegralType(ScalarType::Float, false)); - EXPECT_FALSE(isIntegralType(ScalarType::Float, true)); +TEST_F(ScalarTypeEdgeCasesTest, UndefinedIsNotIntegral) { + EXPECT_FALSE(isIntegralType(ScalarType::Undefined, false)); + EXPECT_FALSE(isIntegralType(ScalarType::Undefined, true)); } -TEST_F(ScalarTypeTest, StreamOperator) { - // Verify stream operator works - std::ostringstream oss; - oss << ScalarType::Float; - EXPECT_EQ(oss.str(), "Float"); +TEST_F(ScalarTypeEdgeCasesTest, UndefinedIsNotBool) { + EXPECT_FALSE(isBoolType(ScalarType::Undefined)); +} + +// ============================================================================= +// Element Size Consistency Tests +// ============================================================================= + +class ElementSizeConsistencyTest : public ::testing::Test {}; + +TEST_F(ElementSizeConsistencyTest, CharMatchesSizeofInt8) { + EXPECT_EQ(elementSize(ScalarType::Char), sizeof(int8_t)); +} + +TEST_F(ElementSizeConsistencyTest, ShortMatchesSizeofInt16) { + EXPECT_EQ(elementSize(ScalarType::Short), sizeof(int16_t)); +} + +TEST_F(ElementSizeConsistencyTest, IntMatchesSizeofInt32) { + EXPECT_EQ(elementSize(ScalarType::Int), sizeof(int32_t)); +} + +TEST_F(ElementSizeConsistencyTest, LongMatchesSizeofInt64) { + EXPECT_EQ(elementSize(ScalarType::Long), sizeof(int64_t)); +} + +TEST_F(ElementSizeConsistencyTest, FloatMatchesSizeofFloat) { + EXPECT_EQ(elementSize(ScalarType::Float), sizeof(float)); +} + +TEST_F(ElementSizeConsistencyTest, BoolMatchesSizeofBool) { + EXPECT_EQ(elementSize(ScalarType::Bool), sizeof(bool)); +} + +TEST_F(ElementSizeConsistencyTest, BFloat16MatchesSizeofBFloat16) { + EXPECT_EQ(elementSize(ScalarType::BFloat16), sizeof(BFloat16)); } 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/core/test/targets.bzl b/backends/aoti/slim/core/test/targets.bzl index ef78da59bef..c7debd46836 100644 --- a/backends/aoti/slim/core/test/targets.bzl +++ b/backends/aoti/slim/core/test/targets.bzl @@ -34,3 +34,13 @@ def define_common_targets(): "//executorch/backends/aoti/slim/core:storage", ], ) + + runtime.cxx_test( + name = "test_slimtensor_dtypes", + srcs = [ + "test_slimtensor_dtypes.cpp", + ], + deps = [ + "//executorch/backends/aoti/slim/factory:empty", + ], + ) diff --git a/backends/aoti/slim/core/test/test_slimtensor_dtypes.cpp b/backends/aoti/slim/core/test/test_slimtensor_dtypes.cpp new file mode 100644 index 00000000000..8ecb8d977b7 --- /dev/null +++ b/backends/aoti/slim/core/test/test_slimtensor_dtypes.cpp @@ -0,0 +1,290 @@ +/* + * 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 +#include + +#include + +namespace executorch::backends::aoti::slim { + +// ============================================================================= +// Test Data Structures for Parameterized Tests +// ============================================================================= + +template +struct DTypeTraits; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::Char; + static constexpr const char* name = "Char"; + static int8_t test_value(size_t i) { + return static_cast(i % 127); + } +}; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::Short; + static constexpr const char* name = "Short"; + static int16_t test_value(size_t i) { + return static_cast(i * 10); + } +}; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::Int; + static constexpr const char* name = "Int"; + static int32_t test_value(size_t i) { + return static_cast(i * 100); + } +}; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::Long; + static constexpr const char* name = "Long"; + static int64_t test_value(size_t i) { + return static_cast(i * 1000); + } +}; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::Float; + static constexpr const char* name = "Float"; + static float test_value(size_t i) { + return static_cast(i) * 1.5f; + } +}; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::Bool; + static constexpr const char* name = "Bool"; + static bool test_value(size_t i) { + return (i % 2) == 0; + } +}; + +template <> +struct DTypeTraits { + static constexpr c10::ScalarType dtype = c10::ScalarType::BFloat16; + static constexpr const char* name = "BFloat16"; + static c10::BFloat16 test_value(size_t i) { + return c10::BFloat16(static_cast(i) * 0.5f); + } +}; + +// ============================================================================= +// Typed Test Fixture +// ============================================================================= + +template +class SlimTensorDTypeTest : public ::testing::Test { + protected: + static constexpr c10::ScalarType kDType = DTypeTraits::dtype; + static constexpr size_t kNumel = 24; + static constexpr std::array kSizes = {2, 3, 4}; + + SlimTensor create_tensor() { + return empty({2, 3, 4}, kDType); + } + + void fill_tensor(SlimTensor& tensor) { + T* data = static_cast(tensor.data_ptr()); + for (size_t i = 0; i < tensor.numel(); ++i) { + data[i] = DTypeTraits::test_value(i); + } + } + + void verify_tensor_values(const SlimTensor& tensor) { + const T* data = static_cast(tensor.data_ptr()); + for (size_t i = 0; i < tensor.numel(); ++i) { + T expected = DTypeTraits::test_value(i); + if constexpr (std::is_same_v) { + EXPECT_FLOAT_EQ(data[i], expected) << "Mismatch at index " << i; + } else if constexpr (std::is_same_v) { + EXPECT_FLOAT_EQ( + static_cast(data[i]), static_cast(expected)) + << "Mismatch at index " << i; + } else { + EXPECT_EQ(data[i], expected) << "Mismatch at index " << i; + } + } + } +}; + +// Define the types to test +using DTypeTestTypes = ::testing:: + Types; + +TYPED_TEST_SUITE(SlimTensorDTypeTest, DTypeTestTypes); + +// ============================================================================= +// Core Tensor Creation Tests +// ============================================================================= + +TYPED_TEST(SlimTensorDTypeTest, CreateEmptyTensor) { + SlimTensor tensor = this->create_tensor(); + + EXPECT_TRUE(tensor.defined()); + EXPECT_EQ(tensor.dtype(), this->kDType); + EXPECT_EQ(tensor.dim(), 3u); + EXPECT_EQ(tensor.numel(), this->kNumel); + EXPECT_TRUE(tensor.is_cpu()); + EXPECT_TRUE(tensor.is_contiguous()); +} + +TYPED_TEST(SlimTensorDTypeTest, CorrectElementSize) { + SlimTensor tensor = this->create_tensor(); + EXPECT_EQ(tensor.itemsize(), sizeof(TypeParam)); +} + +TYPED_TEST(SlimTensorDTypeTest, CorrectNbytes) { + SlimTensor tensor = this->create_tensor(); + EXPECT_EQ(tensor.nbytes(), this->kNumel * sizeof(TypeParam)); +} + +TYPED_TEST(SlimTensorDTypeTest, DataPtrIsValid) { + SlimTensor tensor = this->create_tensor(); + EXPECT_NE(tensor.data_ptr(), nullptr); +} + +// ============================================================================= +// Data Read/Write Tests +// ============================================================================= + +TYPED_TEST(SlimTensorDTypeTest, WriteAndReadData) { + SlimTensor tensor = this->create_tensor(); + this->fill_tensor(tensor); + this->verify_tensor_values(tensor); +} + +TYPED_TEST(SlimTensorDTypeTest, ZeroInitialize) { + SlimTensor tensor = this->create_tensor(); + std::memset(tensor.data_ptr(), 0, tensor.nbytes()); + + const TypeParam* data = static_cast(tensor.data_ptr()); + for (size_t i = 0; i < tensor.numel(); ++i) { + if constexpr (std::is_same_v) { + EXPECT_FALSE(data[i]) << "Non-zero at index " << i; + } else if constexpr (std::is_same_v) { + EXPECT_FLOAT_EQ(data[i], 0.0f) << "Non-zero at index " << i; + } else if constexpr (std::is_same_v) { + EXPECT_FLOAT_EQ(static_cast(data[i]), 0.0f) + << "Non-zero at index " << i; + } else { + EXPECT_EQ(data[i], static_cast(0)) + << "Non-zero at index " << i; + } + } +} + +// ============================================================================= +// Copy Tests +// ============================================================================= + +TYPED_TEST(SlimTensorDTypeTest, CopyContiguousTensor) { + SlimTensor src = this->create_tensor(); + this->fill_tensor(src); + + SlimTensor dst = this->create_tensor(); + dst.copy_(src); + + this->verify_tensor_values(dst); +} + +TYPED_TEST(SlimTensorDTypeTest, CopyPreservesSourceData) { + SlimTensor src = this->create_tensor(); + this->fill_tensor(src); + + SlimTensor dst = this->create_tensor(); + dst.copy_(src); + + // Modify dst and verify src is unchanged + std::memset(dst.data_ptr(), 0, dst.nbytes()); + + // src should still have original values + this->verify_tensor_values(src); +} + +// ============================================================================= +// Empty Strided Tests +// ============================================================================= + +TYPED_TEST(SlimTensorDTypeTest, EmptyStridedCreation) { + std::vector sizes = {2, 3, 4}; + std::vector strides = {12, 4, 1}; + + SlimTensor tensor = + empty_strided(makeArrayRef(sizes), makeArrayRef(strides), this->kDType); + + EXPECT_EQ(tensor.dtype(), this->kDType); + EXPECT_TRUE(tensor.is_contiguous()); +} + +TYPED_TEST(SlimTensorDTypeTest, NonContiguousStrides) { + std::vector sizes = {3, 2}; + std::vector strides = {1, 3}; + + SlimTensor tensor = + empty_strided(makeArrayRef(sizes), makeArrayRef(strides), this->kDType); + + EXPECT_EQ(tensor.dtype(), this->kDType); + EXPECT_FALSE(tensor.is_contiguous()); +} + +// ============================================================================= +// Empty Like Tests +// ============================================================================= + +TYPED_TEST(SlimTensorDTypeTest, EmptyLikePreservesDType) { + SlimTensor original = this->create_tensor(); + SlimTensor copy = empty_like(original); + + EXPECT_EQ(copy.dtype(), original.dtype()); + EXPECT_EQ(copy.numel(), original.numel()); + EXPECT_EQ(copy.dim(), original.dim()); + EXPECT_NE(copy.data_ptr(), original.data_ptr()); +} + +// ============================================================================= +// Dimension and Shape Tests +// ============================================================================= + +TYPED_TEST(SlimTensorDTypeTest, OneDimensionalTensor) { + SlimTensor tensor = empty({10}, this->kDType); + + EXPECT_EQ(tensor.dim(), 1u); + EXPECT_EQ(tensor.numel(), 10u); + EXPECT_EQ(tensor.size(0), 10); + EXPECT_EQ(tensor.stride(0), 1); +} + +TYPED_TEST(SlimTensorDTypeTest, FourDimensionalTensor) { + SlimTensor tensor = empty({2, 3, 4, 5}, this->kDType); + + EXPECT_EQ(tensor.dim(), 4u); + EXPECT_EQ(tensor.numel(), 120u); + EXPECT_TRUE(tensor.is_contiguous()); +} + +TYPED_TEST(SlimTensorDTypeTest, ZeroSizedTensor) { + SlimTensor tensor = empty({0, 5}, this->kDType); + + EXPECT_TRUE(tensor.is_empty()); + EXPECT_EQ(tensor.numel(), 0u); + EXPECT_EQ(tensor.dtype(), this->kDType); +} + +} // namespace executorch::backends::aoti::slim 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