Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

770 lines
22 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <cmath>
#include <gtest/gtest.h>
#include <hipdnn_data_sdk/types.hpp>
#include <hipdnn_data_sdk/utilities/Tensor.hpp>
#include <hipdnn_test_sdk/utilities/TestUtilities.hpp>
using namespace hipdnn_data_sdk::utilities;
using namespace hipdnn_data_sdk::types;
TEST(TestTensor, Swap)
{
Tensor<float> tensorA({2, 2});
Tensor<float> tensorB{{2}};
tensorA.fillWithValue(1.f);
tensorB.fillWithValue(2.f);
std::swap(tensorA, tensorB);
ASSERT_EQ(tensorA.dims().size(), 1);
ASSERT_EQ(tensorB.dims().size(), 2);
ASSERT_EQ(tensorA.memory().hostData()[0], 2.f);
ASSERT_EQ(tensorB.memory().hostData()[0], 1.f);
}
TEST(TestTensor, BasicRowMajorUsage)
{
Tensor<float> tensor({1, 2, 3, 4});
// NCHW (row-major) strides with dims {N=1, C=2, H=3, W=4}:
// N stride = C*H*W = 2*3*4 = 24
// C stride = H*W = 3*4 = 12
// H stride = W = 4
// W stride = 1 (innermost dimension)
EXPECT_EQ(tensor.memory().count(), 24);
EXPECT_EQ(tensor.strides()[0], 24);
EXPECT_EQ(tensor.strides()[1], 12);
EXPECT_EQ(tensor.strides()[2], 4);
EXPECT_EQ(tensor.strides()[3], 1);
}
TEST(TestTensor, FillWithValuesPacked)
{
Tensor<float> tensor({1, 2, 3, 4});
tensor.fillWithValue(1.0f);
auto buffer = tensor.memory().hostData();
for(size_t i = 0; i < tensor.memory().count(); i++)
{
EXPECT_FLOAT_EQ(1.0f, buffer[i]);
}
}
TEST(TestTensor, FillWithValuesNonPacked)
{
Tensor<float> tensor({1, 2, 3, 4}, {30, 15, 5, 1});
tensor.fillWithValue(1.0f);
for(auto it{tensor.cbegin()}; it != tensor.cend(); ++it)
{
EXPECT_FLOAT_EQ(1.0f, (*reinterpret_cast<const float*>((*it))));
}
}
TEST(TestTensor, FillWithRandomValuesPacked)
{
Tensor<float> tensor({1, 2, 3, 4});
tensor.fillWithRandomValues(1.0f, 3.0f);
auto buffer = tensor.memory().hostData();
for(size_t i = 0; i < tensor.memory().count(); i++)
{
EXPECT_GE(buffer[i], 1.0f);
EXPECT_LE(buffer[i], 3.0f);
}
}
TEST(TestTensor, FillWithRandomValuesNonPacked)
{
Tensor<float> tensor({1, 2, 3, 4}, {30, 15, 5, 1});
tensor.fillWithRandomValues(1.0f, 3.0f);
for(auto it{tensor.cbegin()}; it != tensor.cend(); ++it)
{
auto val{(*reinterpret_cast<const float*>((*it)))};
EXPECT_GE(val, 1.0f);
EXPECT_LE(val, 3.0f);
}
}
TEST(TestTensor, BasicNclUsage)
{
Tensor<float> tensor({1, 2, 3}, TensorLayout::NCL);
// NCL (row-major/channel-first) strides with dims {N=1, C=2, L=3}:
// N stride = C*L = 2*3 = 6
// C stride = L = 3
// L stride = 1 (innermost dimension)
EXPECT_EQ(tensor.memory().count(), 6);
EXPECT_EQ(tensor.strides()[0], 6);
EXPECT_EQ(tensor.strides()[1], 3);
EXPECT_EQ(tensor.strides()[2], 1);
}
TEST(TestTensor, BasicNlcUsage)
{
Tensor<float> tensor({1, 2, 3}, TensorLayout::NLC);
// NLC (channel-last) strides with dims {N=1, C=2, L=3}:
// N stride = L*C = 3*2 = 6
// C stride = 1 (innermost dimension)
// L stride = C = 2
EXPECT_EQ(tensor.memory().count(), 6);
EXPECT_EQ(tensor.strides()[0], 6);
EXPECT_EQ(tensor.strides()[1], 1);
EXPECT_EQ(tensor.strides()[2], 2);
}
TEST(TestTensor, BasicNhwcUsage)
{
Tensor<float> tensor({1, 2, 3, 4}, TensorLayout::NHWC);
EXPECT_EQ(tensor.memory().count(), 24);
// NHWC strides with dims {N=1, C=2, H=3, W=4}:
// N stride = H*W*C = 3*4*2 = 24
// C stride = 1 (innermost dimension)
// H stride = W*C = 4*2 = 8
// W stride = C = 2
EXPECT_EQ(tensor.strides()[0], 24);
EXPECT_EQ(tensor.strides()[1], 1);
EXPECT_EQ(tensor.strides()[2], 8);
EXPECT_EQ(tensor.strides()[3], 2);
}
TEST(TestTensor, GetAndSetHostValueNcl)
{
Tensor<float> tensor({2, 3, 4}, TensorLayout::NCL);
tensor.fillWithValue(0.0f);
tensor.setHostValue(99.0f, 1, 2, 3);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 2, 3), 99.0f);
}
TEST(TestTensor, GetAndSetHostValueNlc)
{
Tensor<float> tensor({2, 3, 4}, TensorLayout::NLC);
tensor.fillWithValue(0.0f);
tensor.setHostValue(99.0f, 1, 2, 3);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 2, 3), 99.0f);
}
TEST(TestTensor, GetAndSetHostValueNchw)
{
Tensor<float> tensor({1, 2, 3, 4});
tensor.fillWithValue(0.0f);
tensor.setHostValue(99.0f, 0, 1, 1, 2);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 1, 1, 2), 99.0f);
}
TEST(TestTensor, GetAndSetHostValueNhwc)
{
Tensor<float> tensor({1, 2, 3, 4}, TensorLayout::NHWC);
tensor.fillWithValue(0.0f);
tensor.setHostValue(99.0f, 0, 1, 1, 2);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 1, 1, 2), 99.0f);
}
TEST(TestTensor, ExceedDimensions)
{
Tensor<float> tensor({1, 2, 3, 4});
tensor.fillWithValue(0.0f);
EXPECT_THROW(tensor.setHostValue(99.0f, 0, 1, 1, 2, 3), std::invalid_argument);
}
TEST(TestTensor, GetIndex)
{
//Strides {60, 20, 5, 1}
const Tensor<float> tensor({2, 3, 4, 5});
// 1*60 + 2*20 + 3*5 + 4*1 = 119
EXPECT_EQ(tensor.getIndex(1, 2, 3, 4), 119);
const std::vector<int64_t> indices1 = {1, 2, 3, 4};
EXPECT_EQ(tensor.getIndex(indices1), 119);
// Test first element
EXPECT_EQ(tensor.getIndex(0, 0, 0, 0), 0);
const std::vector<int64_t> indices2 = {0, 0, 0, 0};
EXPECT_EQ(tensor.getIndex(indices2), 0);
// Test partial indices
EXPECT_EQ(tensor.getIndex(1, 0), 60);
const std::vector<int64_t> indices3 = {1, 0};
EXPECT_EQ(tensor.getIndex(indices3), 60);
EXPECT_EQ(tensor.getIndex(0, 1), 20);
const std::vector<int64_t> indices4 = {0, 1};
EXPECT_EQ(tensor.getIndex(indices4), 20);
}
TEST(TestTensor, GetIndexNcl)
{
// Strides {12, 4, 1}
const Tensor<float> tensor({2, 3, 4}, TensorLayout::NCL);
// 1*12 + 2*4 + 3*1 = 23
EXPECT_EQ(tensor.getIndex(1, 2, 3), 23);
const std::vector<int64_t> indices = {1, 2, 3};
EXPECT_EQ(tensor.getIndex(indices), 23);
}
TEST(TestTensor, GetIndexNlc)
{
// Strides {12, 1, 3}
const Tensor<float> tensor({2, 3, 4}, TensorLayout::NLC);
// 1*12 + 2*1 + 3*3 = 23
EXPECT_EQ(tensor.getIndex(1, 2, 3), 23);
const std::vector<int64_t> indices = {1, 2, 3};
EXPECT_EQ(tensor.getIndex(indices), 23);
}
TEST(TestTensor, GetIndexNhwc)
{
//Strides {60, 1, 15, 3}
const Tensor<float> tensor({2, 3, 4, 5}, TensorLayout::NHWC);
// 1*60 + 2*1 + 3*15 + 4*3 = 119
EXPECT_EQ(tensor.getIndex(1, 2, 3, 4), 119);
const std::vector<int64_t> indices = {1, 2, 3, 4};
EXPECT_EQ(tensor.getIndex(indices), 119);
}
TEST(TestTensor, GetIndexEdgeCases)
{
const Tensor<float> tensor({10, 20});
// Last element
EXPECT_EQ(tensor.getIndex(9, 19), 199);
std::vector<int64_t> indices = {9, 19};
EXPECT_EQ(tensor.getIndex(indices), 199);
// Single dim
EXPECT_EQ(tensor.getIndex(5), 100);
indices = {5};
EXPECT_EQ(tensor.getIndex(indices), 100);
// Empty dim
const std::vector<int64_t> emptyIndices;
EXPECT_EQ(tensor.getIndex(emptyIndices), 0);
}
TEST(TestTensor, GetIndexInvalidCases)
{
const Tensor<float> tensor({2, 3, 4});
EXPECT_THROW(tensor.getIndex(0, 1, 2, 3), std::invalid_argument);
const std::vector<int64_t> tooManyIndices = {0, 1, 2, 3};
EXPECT_THROW(tensor.getIndex(tooManyIndices), std::invalid_argument);
}
TEST(TestTensor, GetHostValueWithVector)
{
Tensor<float> tensor({2, 3, 4});
tensor.fillWithValue(0.0f);
tensor.setHostValue(42.0f, 1, 2, 3);
const std::vector<int64_t> indices2 = {1, 2, 3};
EXPECT_FLOAT_EQ(tensor.getHostValue(indices2), 42.0f);
}
TEST(TestTensor, SetHostValueWithVector)
{
Tensor<float> tensor({2, 3, 4});
tensor.fillWithValue(0.0f);
const std::vector<int64_t> indices1 = {0, 1, 2};
tensor.setHostValue(10.0f, indices1);
const std::vector<int64_t> indices2 = {1, 0, 3};
tensor.setHostValue(20.0f, indices2);
const std::vector<int64_t> indices3 = {1, 2, 1};
tensor.setHostValue(30.0f, indices3);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 1, 2), 10.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 0, 3), 20.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 2, 1), 30.0f);
}
TEST(TestTensor, FillWithData)
{
std::vector<int> data{0, 1, 2, 3};
Tensor<int> tensor({2, 2});
tensor.fillWithData(data.data(), data.size() * sizeof(int));
for(size_t i = 0; i < data.size(); i++)
{
EXPECT_EQ(data[i], tensor.memory().hostData()[i]);
}
}
TEST(TestTensor, MixedIndexingMethods)
{
Tensor<float> tensor({3, 4, 5});
tensor.fillWithValue(0.0f);
tensor.setHostValue(100.0f, 2, 3, 4);
std::vector<int64_t> indices = {2, 3, 4};
EXPECT_FLOAT_EQ(tensor.getHostValue(indices), 100.0f);
indices = {1, 2, 3};
tensor.setHostValue(200.0f, indices);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 2, 3), 200.0f);
}
TEST(TestTensor, GetIndexConsistency)
{
const Tensor<float> tensor({5, 6, 7});
for(int i = 0; i < 5; ++i)
{
for(int j = 0; j < 6; ++j)
{
for(int k = 0; k < 7; ++k)
{
auto variadicIndex = tensor.getIndex(i, j, k);
const std::vector<int64_t> vecIndices = {i, j, k};
auto vectorIndex = tensor.getIndex(vecIndices);
EXPECT_EQ(variadicIndex, vectorIndex);
}
}
}
}
TEST(TestTensor, DefaultPackedStrides1D)
{
Tensor<float> tensor({10});
EXPECT_EQ(tensor.dims(), (std::vector<int64_t>{10}));
EXPECT_EQ(tensor.strides(), (std::vector<int64_t>{1}));
EXPECT_EQ(tensor.memory().count(), 10);
}
TEST(TestTensor, DefaultPackedStrides2D)
{
Tensor<float> tensor({5, 8});
EXPECT_EQ(tensor.dims(), (std::vector<int64_t>{5, 8}));
// Row-major: outer dim stride = 8, inner dim stride = 1
EXPECT_EQ(tensor.strides(), (std::vector<int64_t>{8, 1}));
EXPECT_EQ(tensor.memory().count(), 40);
}
TEST(TestTensor, DefaultPackedStrides3D)
{
Tensor<float> tensor({4, 5, 6});
EXPECT_EQ(tensor.dims(), (std::vector<int64_t>{4, 5, 6}));
// Row-major: strides = {5*6, 6, 1} = {30, 6, 1}
EXPECT_EQ(tensor.strides(), (std::vector<int64_t>{30, 6, 1}));
EXPECT_EQ(tensor.memory().count(), 120);
}
TEST(TestTensor, DefaultPackedStrides4D)
{
Tensor<float> tensor({2, 3, 4, 5});
EXPECT_EQ(tensor.dims(), (std::vector<int64_t>{2, 3, 4, 5}));
// Row-major: strides = {3*4*5, 4*5, 5, 1} = {60, 20, 5, 1}
EXPECT_EQ(tensor.strides(), (std::vector<int64_t>{60, 20, 5, 1}));
EXPECT_EQ(tensor.memory().count(), 120);
}
TEST(TestTensor, DefaultPackedStrides5D)
{
Tensor<float> tensor({2, 3, 4, 5, 6});
EXPECT_EQ(tensor.dims(), (std::vector<int64_t>{2, 3, 4, 5, 6}));
// Row-major: strides = {3*4*5*6, 4*5*6, 5*6, 6, 1} = {360, 120, 30, 6, 1}
EXPECT_EQ(tensor.strides(), (std::vector<int64_t>{360, 120, 30, 6, 1}));
EXPECT_EQ(tensor.memory().count(), 720);
}
TEST(TestTensor, DefaultPackedStrides6D)
{
Tensor<float> tensor({1, 2, 3, 4, 5, 6});
EXPECT_EQ(tensor.dims(), (std::vector<int64_t>{1, 2, 3, 4, 5, 6}));
// Row-major: strides = {2*3*4*5*6, 3*4*5*6, 4*5*6, 5*6, 6, 1} = {720, 360, 120, 30, 6, 1}
EXPECT_EQ(tensor.strides(), (std::vector<int64_t>{720, 360, 120, 30, 6, 1}));
EXPECT_EQ(tensor.memory().count(), 720);
}
TEST(TestTensor, DefaultPackedStridesIndexing)
{
Tensor<float> tensor({3, 4, 5});
tensor.fillWithValue(0.0f);
// Test setting and getting values with default packed strides
tensor.setHostValue(1.0f, 0, 0, 0);
tensor.setHostValue(2.0f, 1, 2, 3);
tensor.setHostValue(3.0f, 2, 3, 4);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 0, 0), 1.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 2, 3), 2.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(2, 3, 4), 3.0f);
// Verify index calculation
// For (1, 2, 3) with strides {20, 5, 1}: 1*20 + 2*5 + 3*1 = 33
EXPECT_EQ(tensor.getIndex(1, 2, 3), 33);
}
TEST(TestTensor, DefaultPackedStridesCompatibleWithNcl)
{
// Default packed strides should be equivalent to NCL for 3D tensors
const std::vector<int64_t> dims = {2, 3, 4};
const Tensor<float> tensorDefault(dims);
const Tensor<float> tensorNcl(dims, TensorLayout::NCL);
EXPECT_EQ(tensorDefault.strides(), tensorNcl.strides());
}
TEST(TestTensor, DefaultPackedStridesCompatibleWithNchw)
{
// Default packed strides should be equivalent to NCHW for 4D tensors
const std::vector<int64_t> dims = {2, 3, 4, 5};
const Tensor<float> tensorDefault(dims);
const Tensor<float> tensorNchw(dims, TensorLayout::NCHW);
EXPECT_EQ(tensorDefault.strides(), tensorNchw.strides());
}
TEST(TestTensor, DefaultPackedStridesCompatibleWithNcdhw)
{
// Default packed strides should be equivalent to NCDHW for 5D tensors
const std::vector<int64_t> dims = {2, 3, 4, 5, 6};
const Tensor<float> tensorDefault(dims);
const Tensor<float> tensorNcdhw(dims, TensorLayout::NCDHW);
EXPECT_EQ(tensorDefault.strides(), tensorNcdhw.strides());
}
TEST(TestTensor, BasicNcdhwUsage)
{
Tensor<float> tensor({1, 2, 3, 4, 5}, TensorLayout::NCDHW);
// NCDHW (row-major) strides with dims {N=1, C=2, D=3, H=4, W=5}:
// N stride = C*D*H*W = 2*3*4*5 = 120
// C stride = D*H*W = 3*4*5 = 60
// D stride = H*W = 4*5 = 20
// H stride = W = 5
// W stride = 1 (innermost dimension)
EXPECT_EQ(tensor.memory().count(), 120);
EXPECT_EQ(tensor.strides()[0], 120);
EXPECT_EQ(tensor.strides()[1], 60);
EXPECT_EQ(tensor.strides()[2], 20);
EXPECT_EQ(tensor.strides()[3], 5);
EXPECT_EQ(tensor.strides()[4], 1);
}
TEST(TestTensor, BasicNdhwcUsage)
{
Tensor<float> tensor({1, 2, 3, 4, 5}, TensorLayout::NDHWC);
EXPECT_EQ(tensor.memory().count(), 120);
// NDHWC strides with dims {N=1, C=2, D=3, H=4, W=5}:
// N stride = D*H*W*C = 3*4*5*2 = 120
// C stride = 1 (innermost dimension)
// D stride = H*W*C = 4*5*2 = 40
// H stride = W*C = 5*2 = 10
// W stride = C = 2
EXPECT_EQ(tensor.strides()[0], 120);
EXPECT_EQ(tensor.strides()[1], 1);
EXPECT_EQ(tensor.strides()[2], 40);
EXPECT_EQ(tensor.strides()[3], 10);
EXPECT_EQ(tensor.strides()[4], 2);
}
TEST(TestTensor, GetAndSetHostValueNcdhw)
{
Tensor<float> tensor({1, 2, 3, 4, 5}, TensorLayout::NCDHW);
tensor.fillWithValue(0.0f);
tensor.setHostValue(99.0f, 0, 1, 2, 3, 4);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 1, 2, 3, 4), 99.0f);
}
TEST(TestTensor, GetAndSetHostValueNdhwc)
{
Tensor<float> tensor({1, 2, 3, 4, 5}, TensorLayout::NDHWC);
tensor.fillWithValue(0.0f);
tensor.setHostValue(99.0f, 0, 1, 2, 3, 4);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 1, 2, 3, 4), 99.0f);
}
TEST(TestTensor, ElementAccessOperatorNcl)
{
Tensor<float> tensor({2, 3, 4}, TensorLayout::NCL);
tensor.fillWithValue(0.0f);
tensor(1, 2, 3) = 79.0f;
EXPECT_FLOAT_EQ(tensor(1, 2, 3), 79.0f);
}
TEST(TestTensor, ElementAccessOperatorNlc)
{
Tensor<float> tensor({2, 3, 4}, TensorLayout::NLC);
tensor.fillWithValue(0.0f);
tensor(1, 2, 3) = 79.0f;
EXPECT_FLOAT_EQ(tensor(1, 2, 3), 79.0f);
}
TEST(TestTensor, ElementAccessOperatorNchw)
{
Tensor<float> tensor({1, 2, 3, 4});
tensor.fillWithValue(0.0f);
tensor(0, 1, 2, 2) = 79.0f;
EXPECT_FLOAT_EQ(tensor(0, 1, 2, 2), 79.0f);
}
TEST(TestTensor, ElementAccessOperatorNhwc)
{
Tensor<float> tensor({1, 2, 3, 4}, TensorLayout::NHWC);
tensor.fillWithValue(0.0f);
tensor(0, 1, 2, 2) = 79.0f;
EXPECT_FLOAT_EQ(tensor(0, 1, 2, 2), 79.0f);
}
TEST(TestTensor, ElementAccessOperatorNcdhw)
{
Tensor<float> tensor({1, 2, 3, 4, 5}, TensorLayout::NCDHW);
tensor.fillWithValue(0.0f);
tensor(0, 1, 2, 3, 4) = 79.0f;
EXPECT_FLOAT_EQ(tensor(0, 1, 2, 3, 4), 79.0f);
}
TEST(TestTensor, ElementAccessOperatorNdhwc)
{
Tensor<float> tensor({1, 2, 3, 4, 5}, TensorLayout::NDHWC);
tensor.fillWithValue(0.0f);
tensor(0, 1, 2, 3, 4) = 79.0f;
EXPECT_FLOAT_EQ(tensor(0, 1, 2, 3, 4), 79.0f);
}
TEST(TestTensor, GetIndex5D)
{
//Strides {120, 60, 20, 5, 1}
const Tensor<float> tensor({2, 2, 3, 4, 5}, TensorLayout::NCDHW);
// 1*120 + 1*60 + 2*20 + 3*5 + 4*1 = 239
EXPECT_EQ(tensor.getIndex(1, 1, 2, 3, 4), 239);
const std::vector<int64_t> indices1 = {1, 1, 2, 3, 4};
EXPECT_EQ(tensor.getIndex(indices1), 239);
// Test first element
EXPECT_EQ(tensor.getIndex(0, 0, 0, 0, 0), 0);
const std::vector<int64_t> indices2 = {0, 0, 0, 0, 0};
EXPECT_EQ(tensor.getIndex(indices2), 0);
// Test partial indices
EXPECT_EQ(tensor.getIndex(1, 0), 120);
const std::vector<int64_t> indices3 = {1, 0};
EXPECT_EQ(tensor.getIndex(indices3), 120);
EXPECT_EQ(tensor.getIndex(0, 1), 60);
const std::vector<int64_t> indices4 = {0, 1};
EXPECT_EQ(tensor.getIndex(indices4), 60);
}
TEST(TestTensor, GetIndex5DNdhwc)
{
//Strides {120, 1, 40, 10, 2}
const Tensor<float> tensor({2, 2, 3, 4, 5}, TensorLayout::NDHWC);
// 1*120 + 1*1 + 2*40 + 3*10 + 4*2 = 239
EXPECT_EQ(tensor.getIndex(1, 1, 2, 3, 4), 239);
const std::vector<int64_t> indices = {1, 1, 2, 3, 4};
EXPECT_EQ(tensor.getIndex(indices), 239);
}
TEST(TestTensor, ConstructorThrowsOnMismatchedDimsAndStrides)
{
const std::vector<int64_t> dims = {2, 3, 4};
const std::vector<int64_t> strides = {12, 4}; // Only 2 strides for 3 dims
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
TEST(TestTensor, ConstructorThrowsOnNegativeDimension)
{
const std::vector<int64_t> dims = {2, -3, 4};
const std::vector<int64_t> strides = {12, 4, 1};
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
TEST(TestTensor, ConstructorThrowsOnZeroDimension)
{
const std::vector<int64_t> dims = {2, 0, 4};
const std::vector<int64_t> strides = {12, 4, 1};
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
TEST(TestTensor, ConstructorThrowsOnNegativeStride)
{
const std::vector<int64_t> dims = {2, 3, 4};
const std::vector<int64_t> strides = {12, -4, 1};
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
TEST(TestTensor, ConstructorThrowsOnZeroStride)
{
const std::vector<int64_t> dims = {2, 3, 4};
const std::vector<int64_t> strides = {12, 0, 1};
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
TEST(TestTensor, ConstructorThrowsOnMultipleInvalidDimensions)
{
const std::vector<int64_t> dims = {-2, 1, -4};
const std::vector<int64_t> strides = {12, 4, 1};
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
TEST(TestTensor, ConstructorThrowsOnMultipleInvalidStrides)
{
const std::vector<int64_t> dims = {2, 3, 4};
const std::vector<int64_t> strides = {-12, 1, -1};
EXPECT_THROW(const Tensor<float> tensor(dims, strides), std::invalid_argument);
}
// Sparse (strided) tensor test
TEST(TestTensor, SparseTensorCreationAndUsage)
{
// Create a sparse tensor with dims {2,2,2,2} and strides {2,4,8,16}
// This represents a non-packed layout with gaps in memory
const std::vector<int64_t> dims = {2, 2, 2, 2};
const std::vector<int64_t> strides = {2, 4, 8, 16};
Tensor<float> tensor(dims, strides);
// Verify properties
EXPECT_EQ(tensor.dims(), dims);
EXPECT_EQ(tensor.strides(), strides);
EXPECT_EQ(tensor.elementCount(), 16); // Logical elements: 2*2*2*2
// But calculateElementSpace returns sum of (dim-1)*stride
// = (2-1)*2 + (2-1)*4 + (2-1)*8 + (2-1)*16 = 2+4+8+16 = 30
// Add the init value of 1 and you get 31
EXPECT_EQ(tensor.elementSpace(), 31);
// Verify it's not packed
EXPECT_FALSE(tensor.isPacked());
// Test setting and getting values at different indices
tensor.fillWithValue(0.0f);
// Set values at specific logical indices
tensor.setHostValue(10.0f, 0, 0, 0, 0); // Offset: 0
tensor.setHostValue(20.0f, 1, 0, 0, 0); // Offset: 1*2 = 2
tensor.setHostValue(30.0f, 0, 1, 0, 0); // Offset: 1*4 = 4
tensor.setHostValue(40.0f, 0, 0, 1, 0); // Offset: 1*8 = 8
tensor.setHostValue(50.0f, 0, 0, 0, 1); // Offset: 1*16 = 16
tensor.setHostValue(99.0f, 1, 1, 1, 1); // Offset: 2+4+8+16 = 30
// Verify values
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 0, 0, 0), 10.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 0, 0, 0), 20.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 1, 0, 0), 30.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 0, 1, 0), 40.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(0, 0, 0, 1), 50.0f);
EXPECT_FLOAT_EQ(tensor.getHostValue(1, 1, 1, 1), 99.0f);
// Verify index calculations
EXPECT_EQ(tensor.getIndex(0, 0, 0, 0), 0);
EXPECT_EQ(tensor.getIndex(1, 0, 0, 0), 2);
EXPECT_EQ(tensor.getIndex(0, 1, 0, 0), 4);
EXPECT_EQ(tensor.getIndex(0, 0, 1, 0), 8);
EXPECT_EQ(tensor.getIndex(0, 0, 0, 1), 16);
EXPECT_EQ(tensor.getIndex(1, 1, 1, 1), 30);
}
/* ======== fillWithSentinelValue tests ======== */
template <typename T>
class TensorSentinel : public ::testing::Test
{
};
using SentinelTypes = ::testing::Types<float,
double,
half,
bfloat16,
hipdnn_data_sdk::types::fp8_e4m3,
hipdnn_data_sdk::types::fp8_e5m2,
int8_t,
uint8_t,
int32_t>;
TYPED_TEST_SUITE(TensorSentinel, SentinelTypes, );
TYPED_TEST(TensorSentinel, PackedTensorFilled)
{
using hipdnn_data_sdk::types::isnan;
Tensor<TypeParam> tensor({2, 3});
tensor.fillWithSentinelValue();
for(auto valuePtr : tensor)
{
auto value = *static_cast<TypeParam*>(valuePtr);
if constexpr(std::numeric_limits<TypeParam>::has_quiet_NaN)
{
EXPECT_TRUE(isnan(value));
}
else
{
EXPECT_EQ(value, std::numeric_limits<TypeParam>::max());
}
}
}
TYPED_TEST(TensorSentinel, StridedTensorFilled)
{
using hipdnn_data_sdk::types::isnan;
const std::vector<int64_t> dims = {2, 2, 2};
const std::vector<int64_t> strides = {2, 4, 8};
Tensor<TypeParam> tensor(dims, strides);
tensor.fillWithSentinelValue();
for(auto valuePtr : tensor)
{
auto value = *static_cast<TypeParam*>(valuePtr);
if constexpr(std::numeric_limits<TypeParam>::has_quiet_NaN)
{
EXPECT_TRUE(isnan(value));
}
else
{
EXPECT_EQ(value, std::numeric_limits<TypeParam>::max());
}
}
}