Files
TheRock/rocm-libraries/dnn-providers/integration-tests/tests/gpu-ref/TestGpuFpReferenceValidation.cpp
T
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

879 lines
26 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <hip/hip_runtime.h>
#include <hipdnn_data_sdk/types.hpp>
#include <hipdnn_data_sdk/utilities/Tensor.hpp>
#include <hipdnn_test_sdk/utilities/CpuFpReferenceValidation.hpp>
#include <hipdnn_test_sdk/utilities/TestUtilities.hpp>
#include <hipdnn-gpu-ref/GpuReferenceValidationFactory.hpp>
#include <cmath>
#include <cstdint>
#include <limits>
#include <vector>
using namespace hipdnn_data_sdk::utilities;
using namespace hipdnn_data_sdk::types;
using namespace hipdnn_test_sdk::utilities;
using namespace hipdnn_gpu_ref;
namespace
{
// ============================================================================
// Floating-point validation tests
// ============================================================================
template <typename T>
class TestGpuFpValidation : public ::testing::Test
{
};
using FpTypes = ::testing::Types<float, half, bfloat16, double>;
TYPED_TEST_SUITE(TestGpuFpValidation, FpTypes, );
TYPED_TEST(TestGpuFpValidation, ExactMatchPasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({2, 3, 4});
Tensor<TypeParam> impl({2, 3, 4});
ref.fillWithRandomValues(static_cast<TypeParam>(-1.0f), static_cast<TypeParam>(1.0f), 42);
// Copy ref data into impl so they are identical
const auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
implHost[i] = refHost[i];
}
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, WithinTolerancePasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4, 4});
Tensor<TypeParam> impl({4, 4});
ref.fillWithRandomValues(static_cast<TypeParam>(-1.0f), static_cast<TypeParam>(1.0f), 42);
const auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
// Add small perturbation within tolerance
implHost[i] = static_cast<TypeParam>(static_cast<float>(refHost[i]) + 1e-6f);
}
const float tolerance = 1e-4f;
const GpuFpReferenceValidation<TypeParam> validator(tolerance, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, BeyondToleranceFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4, 4});
Tensor<TypeParam> impl({4, 4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1.0f);
implHost[i] = static_cast<TypeParam>(2.0f);
}
const float tolerance = 1e-6f;
const GpuFpReferenceValidation<TypeParam> validator(tolerance, 0.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, NaNFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4});
Tensor<TypeParam> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1.0f);
implHost[i] = static_cast<TypeParam>(1.0f);
}
// Set one element to NaN in the implementation
implHost[0] = static_cast<TypeParam>(std::numeric_limits<float>::quiet_NaN());
const GpuFpReferenceValidation<TypeParam> validator(1.0f, 1.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, NaNInReferenceFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4});
Tensor<TypeParam> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1.0f);
implHost[i] = static_cast<TypeParam>(1.0f);
}
// Set one element to NaN in the reference
refHost[0] = static_cast<TypeParam>(std::numeric_limits<float>::quiet_NaN());
const GpuFpReferenceValidation<TypeParam> validator(1.0f, 1.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, InfFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4});
Tensor<TypeParam> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1.0f);
implHost[i] = static_cast<TypeParam>(1.0f);
}
// Set one element to Inf in the reference
refHost[0] = static_cast<TypeParam>(std::numeric_limits<float>::infinity());
const GpuFpReferenceValidation<TypeParam> validator(1.0f, 1.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, InfInImplementationFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4});
Tensor<TypeParam> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1.0f);
implHost[i] = static_cast<TypeParam>(1.0f);
}
// Set one element to Inf in the implementation
implHost[0] = static_cast<TypeParam>(std::numeric_limits<float>::infinity());
const GpuFpReferenceValidation<TypeParam> validator(1.0f, 1.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, SingleElementExactMatchPasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({1});
Tensor<TypeParam> impl({1});
ref.memory().hostData()[0] = static_cast<TypeParam>(1.0f);
impl.memory().hostData()[0] = static_cast<TypeParam>(1.0f);
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, DimensionMismatchFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({2, 3});
Tensor<TypeParam> impl({3, 2});
const GpuFpReferenceValidation<TypeParam> validator;
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, EmptyTensorsPasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref(std::vector<int64_t>{});
Tensor<TypeParam> impl(std::vector<int64_t>{});
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpValidation, RelativeToleranceWorks)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4});
Tensor<TypeParam> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
// Large values with small relative difference
refHost[0] = static_cast<TypeParam>(100.0f);
implHost[0] = static_cast<TypeParam>(100.05f);
refHost[1] = static_cast<TypeParam>(100.0f);
implHost[1] = static_cast<TypeParam>(100.05f);
refHost[2] = static_cast<TypeParam>(100.0f);
implHost[2] = static_cast<TypeParam>(100.05f);
refHost[3] = static_cast<TypeParam>(100.0f);
implHost[3] = static_cast<TypeParam>(100.05f);
// rtol=0.001 gives threshold = 0.0 + 0.001 * 100.0 = 0.1 > 0.05 => pass
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.001f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
// ============================================================================
// Integer validation tests
// ============================================================================
template <typename T>
class TestGpuIntValidation : public ::testing::Test
{
};
using IntTypes = ::testing::Types<int8_t, uint8_t, int32_t>;
TYPED_TEST_SUITE(TestGpuIntValidation, IntTypes, );
TYPED_TEST(TestGpuIntValidation, ExactMatchPasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({2, 3, 4});
Tensor<TypeParam> impl({2, 3, 4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(i % 10);
implHost[i] = static_cast<TypeParam>(i % 10);
}
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuIntValidation, MismatchFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({4});
Tensor<TypeParam> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1);
implHost[i] = static_cast<TypeParam>(1);
}
// Introduce a single mismatch
implHost[2] = static_cast<TypeParam>(2);
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuIntValidation, SingleElementExactMatchPasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({1});
Tensor<TypeParam> impl({1});
ref.memory().hostData()[0] = static_cast<TypeParam>(5);
impl.memory().hostData()[0] = static_cast<TypeParam>(5);
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuIntValidation, DimensionMismatchFails)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({2, 3});
Tensor<TypeParam> impl({3, 2});
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuIntValidation, EmptyTensorsPasses)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref(std::vector<int64_t>{});
Tensor<TypeParam> impl(std::vector<int64_t>{});
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_TRUE(validator.allClose(ref, impl));
}
// ============================================================================
// Factory function tests
// ============================================================================
TEST(TestGpuValidatorFactory, CreatesFloatValidator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::FLOAT);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, CreatesHalfValidator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::HALF);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, CreatesBfloat16Validator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::BFLOAT16);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, CreatesDoubleValidator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::DOUBLE);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, CreatesInt8Validator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::INT8);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, CreatesUint8Validator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::UINT8);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, CreatesInt32Validator)
{
const auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::INT32);
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, FloatValidatorPassesMatchingData)
{
SKIP_IF_NO_DEVICES();
auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::FLOAT, 0.0f, 0.0f);
ASSERT_NE(validator, nullptr);
Tensor<float> ref({4});
Tensor<float> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<float>(i);
implHost[i] = static_cast<float>(i);
}
ASSERT_TRUE(validator->allClose(ref, impl));
// Introduce a mismatch to confirm the validator actually checks values
implHost[0] = 999.0f;
impl.markHostModified();
ASSERT_FALSE(validator->allClose(ref, impl));
}
TEST(TestGpuValidatorFactory, Int32ValidatorPassesMatchingData)
{
SKIP_IF_NO_DEVICES();
auto validator = createGpuAllCloseValidator(hipdnn_frontend::DataType::INT32);
ASSERT_NE(validator, nullptr);
Tensor<int32_t> ref({4});
Tensor<int32_t> impl({4});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<int32_t>(i);
implHost[i] = static_cast<int32_t>(i);
}
ASSERT_TRUE(validator->allClose(ref, impl));
// Introduce a mismatch to confirm the validator actually checks values
implHost[0] = 999;
impl.markHostModified();
ASSERT_FALSE(validator->allClose(ref, impl));
}
TEST(TestGpuValidatorFactory, ThrowsOnUnsupportedType)
{
ASSERT_THROW(createGpuAllCloseValidator(hipdnn_frontend::DataType::FP8_E4M3),
std::runtime_error);
}
TEST(TestGpuValidatorFactory, TemplatedCreatesFloat)
{
const auto validator = createGpuAllCloseValidator<float>();
ASSERT_NE(validator, nullptr);
}
TEST(TestGpuValidatorFactory, TemplatedCreatesInt32)
{
const auto validator = createGpuAllCloseValidator<int32_t>();
ASSERT_NE(validator, nullptr);
}
// ============================================================================
// Constructor validation tests
// ============================================================================
TEST(TestGpuFpValidationConstruction, NegativeAbsoluteToleranceThrows)
{
ASSERT_THROW(GpuFpReferenceValidation<float>(-1.0f, 0.0f), std::invalid_argument);
}
TEST(TestGpuFpValidationConstruction, NegativeRelativeToleranceThrows)
{
ASSERT_THROW(GpuFpReferenceValidation<float>(0.0f, -1.0f), std::invalid_argument);
}
TEST(TestGpuFpValidationConstruction, NaNToleranceThrows)
{
ASSERT_THROW(GpuFpReferenceValidation<float>(std::numeric_limits<float>::quiet_NaN(), 0.0f),
std::invalid_argument);
}
TEST(TestGpuFpValidationConstruction, InfToleranceThrows)
{
ASSERT_THROW(GpuFpReferenceValidation<float>(std::numeric_limits<float>::infinity(), 0.0f),
std::invalid_argument);
}
// ============================================================================
// GPU vs CPU consistency tests
// ============================================================================
template <typename T>
class TestGpuVsCpuValidation : public ::testing::Test
{
};
using GpuCpuFpTypes = ::testing::Types<float, half, bfloat16>;
TYPED_TEST_SUITE(TestGpuVsCpuValidation, GpuCpuFpTypes, );
TYPED_TEST(TestGpuVsCpuValidation, AgreeOnPass)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({8, 8});
Tensor<TypeParam> impl({8, 8});
ref.fillWithRandomValues(static_cast<TypeParam>(-1.0f), static_cast<TypeParam>(1.0f), 42);
const auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
implHost[i] = static_cast<TypeParam>(static_cast<float>(refHost[i]) + 1e-7f);
}
const float atol = 1e-4f;
const float rtol = 0.0f;
const GpuFpReferenceValidation<TypeParam> gpuValidator(atol, rtol);
const CpuFpReferenceValidation<TypeParam> cpuValidator(atol, rtol);
const auto gpuResult = gpuValidator.allClose(ref, impl);
const auto cpuResult = cpuValidator.allClose(ref, impl);
ASSERT_EQ(gpuResult, cpuResult)
<< "GPU and CPU validators disagree: GPU=" << gpuResult << ", CPU=" << cpuResult;
}
TYPED_TEST(TestGpuVsCpuValidation, AgreeOnFail)
{
SKIP_IF_NO_DEVICES();
Tensor<TypeParam> ref({8, 8});
Tensor<TypeParam> impl({8, 8});
auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
refHost[i] = static_cast<TypeParam>(1.0f);
implHost[i] = static_cast<TypeParam>(2.0f);
}
const float atol = 1e-6f;
const float rtol = 1e-6f;
const GpuFpReferenceValidation<TypeParam> gpuValidator(atol, rtol);
const CpuFpReferenceValidation<TypeParam> cpuValidator(atol, rtol);
const auto gpuResult = gpuValidator.allClose(ref, impl);
const auto cpuResult = cpuValidator.allClose(ref, impl);
ASSERT_EQ(gpuResult, cpuResult)
<< "GPU and CPU validators disagree: GPU=" << gpuResult << ", CPU=" << cpuResult;
}
// ============================================================================
// Large tensor test
// ============================================================================
TEST(TestGpuFpValidationLargeTensor, LargeTensorExactMatch)
{
SKIP_IF_NO_DEVICES();
// Test with a reasonably large tensor (64K elements)
Tensor<float> ref({64, 32, 32});
Tensor<float> impl({64, 32, 32});
ref.fillWithRandomValues(-1.0f, 1.0f, 42);
const auto* refHost = ref.memory().hostData();
auto* implHost = impl.memory().hostData();
for(size_t i = 0; i < ref.elementCount(); ++i)
{
implHost[i] = refHost[i];
}
const GpuFpReferenceValidation<float> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
// ============================================================================
// Strided (unpacked) tensor validation tests
// ============================================================================
// Increment a multi-dimensional index counter (innermost-first).
// Returns false when all positions have been exhausted (wraps back to zero).
inline bool incrementIndices(std::vector<int64_t>& indices, const std::vector<int64_t>& dims)
{
for(auto d = indices.size(); d-- > 0;)
{
if(++indices[d] < dims[d])
{
return true;
}
indices[d] = 0;
}
return false;
}
// Helper: copy data element-wise between two tensors with potentially different strides.
// Both tensors must have the same dims.
template <typename T>
void copyByLogicalIndex(Tensor<T>& dst, const Tensor<T>& src)
{
const auto& dims = src.dims();
std::vector<int64_t> indices(dims.size(), 0);
for(size_t i = 0; i < src.elementCount(); ++i)
{
dst(indices) = src(indices);
incrementIndices(indices, dims);
}
}
template <typename T>
class TestGpuFpStridedValidation : public ::testing::Test
{
};
TYPED_TEST_SUITE(TestGpuFpStridedValidation, FpTypes, );
TYPED_TEST(TestGpuFpStridedValidation, StridedExactMatchPasses)
{
SKIP_IF_NO_DEVICES();
// Non-contiguous strides: N-stride is 120 instead of 60, creating gaps
const std::vector<int64_t> dims = {2, 3, 4, 5};
const std::vector<int64_t> strides = {120, 20, 5, 1};
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
ASSERT_FALSE(ref.isPacked());
// Fill ref with values via logical indices
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
ref(indices) = static_cast<TypeParam>(static_cast<float>(i) * 0.1f);
incrementIndices(indices, dims);
}
copyByLogicalIndex(impl, ref);
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpStridedValidation, StridedMismatchFails)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 3, 4, 5};
const std::vector<int64_t> strides = {120, 20, 5, 1};
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
// Fill both with same data
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
auto val = static_cast<TypeParam>(1.0f);
ref(indices) = val;
impl(indices) = val;
incrementIndices(indices, dims);
}
// Inject mismatch at a specific logical position
impl(1, 2, 3, 4) = static_cast<TypeParam>(999.0f);
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpStridedValidation, RefPackedImplStridedPasses)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 3, 4, 5};
const std::vector<int64_t> implStrides = {120, 20, 5, 1};
Tensor<TypeParam> ref(dims); // packed NCHW
Tensor<TypeParam> impl(dims, implStrides); // strided (gaps in N dim)
ASSERT_TRUE(ref.isPacked());
ASSERT_FALSE(impl.isPacked());
// Fill ref with values
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
ref(indices) = static_cast<TypeParam>(static_cast<float>(i) * 0.1f);
incrementIndices(indices, dims);
}
// Copy by logical index so impl has the same logical values in NHWC layout
copyByLogicalIndex(impl, ref);
const GpuFpReferenceValidation<TypeParam> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuFpStridedValidation, StridedNaNFails)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 3, 4, 5};
const std::vector<int64_t> strides = {120, 20, 5, 1};
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
// Fill both with 1.0
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
ref(indices) = static_cast<TypeParam>(1.0f);
impl(indices) = static_cast<TypeParam>(1.0f);
incrementIndices(indices, dims);
}
// Inject NaN at a position that would be missed if kernel used linear indexing
impl(1, 2, 1, 3) = static_cast<TypeParam>(std::numeric_limits<float>::quiet_NaN());
const GpuFpReferenceValidation<TypeParam> validator(1.0f, 1.0f);
ASSERT_FALSE(validator.allClose(ref, impl));
}
// Strided integer tests
template <typename T>
class TestGpuIntStridedValidation : public ::testing::Test
{
};
TYPED_TEST_SUITE(TestGpuIntStridedValidation, IntTypes, );
TYPED_TEST(TestGpuIntStridedValidation, StridedExactMatchPasses)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 3, 4, 5};
const std::vector<int64_t> strides = {120, 20, 5, 1};
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
auto val = static_cast<TypeParam>(i % 10);
ref(indices) = val;
impl(indices) = val;
incrementIndices(indices, dims);
}
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_TRUE(validator.allClose(ref, impl));
}
TYPED_TEST(TestGpuIntStridedValidation, StridedMismatchFails)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 3, 4, 5};
const std::vector<int64_t> strides = {120, 20, 5, 1};
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
auto val = static_cast<TypeParam>(1);
ref(indices) = val;
impl(indices) = val;
incrementIndices(indices, dims);
}
impl(1, 2, 3, 4) = static_cast<TypeParam>(9);
const GpuIntReferenceValidation<TypeParam> validator;
ASSERT_FALSE(validator.allClose(ref, impl));
}
// GPU vs CPU consistency for strided tensors
TYPED_TEST(TestGpuVsCpuValidation, StridedAgreeOnPass)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 4, 4, 4};
const std::vector<int64_t> strides = {128, 16, 4, 1}; // N-stride doubled for gaps
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
// Fill with identical data via logical indices
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
auto val = static_cast<TypeParam>(static_cast<float>(i) * 0.01f);
ref(indices) = val;
impl(indices) = static_cast<TypeParam>(static_cast<float>(val) + 1e-7f);
incrementIndices(indices, dims);
}
const float atol = 1e-4f;
const float rtol = 0.0f;
const GpuFpReferenceValidation<TypeParam> gpuValidator(atol, rtol);
const CpuFpReferenceValidation<TypeParam> cpuValidator(atol, rtol);
const auto gpuResult = gpuValidator.allClose(ref, impl);
const auto cpuResult = cpuValidator.allClose(ref, impl);
ASSERT_EQ(gpuResult, cpuResult)
<< "GPU and CPU validators disagree (strided): GPU=" // NOLINT(readability-implicit-bool-conversion)
<< gpuResult << ", CPU=" << cpuResult;
}
TYPED_TEST(TestGpuVsCpuValidation, StridedAgreeOnFail)
{
SKIP_IF_NO_DEVICES();
const std::vector<int64_t> dims = {2, 4, 4, 4};
const std::vector<int64_t> strides = {128, 16, 4, 1};
Tensor<TypeParam> ref(dims, strides);
Tensor<TypeParam> impl(dims, strides);
std::vector<int64_t> indices(4, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
ref(indices) = static_cast<TypeParam>(1.0f);
impl(indices) = static_cast<TypeParam>(2.0f);
incrementIndices(indices, dims);
}
const float atol = 1e-6f;
const float rtol = 1e-6f;
const GpuFpReferenceValidation<TypeParam> gpuValidator(atol, rtol);
const CpuFpReferenceValidation<TypeParam> cpuValidator(atol, rtol);
const auto gpuResult = gpuValidator.allClose(ref, impl);
const auto cpuResult = cpuValidator.allClose(ref, impl);
ASSERT_EQ(gpuResult, cpuResult)
<< "GPU and CPU disagree (strided fail): GPU=" // NOLINT(readability-implicit-bool-conversion)
<< gpuResult << ", CPU=" << cpuResult;
}
// High-dimensional strided test
TEST(TestGpuFpValidationStrided, HighDimensionStridedPasses)
{
SKIP_IF_NO_DEVICES();
// 6D tensor with non-packed strides (stride[5]=2 instead of 1)
const std::vector<int64_t> dims = {2, 2, 2, 2, 2, 3};
const std::vector<int64_t> strides = {192, 96, 48, 24, 12, 2};
Tensor<float> ref(dims, strides);
Tensor<float> impl(dims, strides);
ASSERT_FALSE(ref.isPacked());
std::vector<int64_t> indices(6, 0);
for(size_t i = 0; i < ref.elementCount(); ++i)
{
auto val = static_cast<float>(i) * 0.1f;
ref(indices) = val;
impl(indices) = val;
incrementIndices(indices, dims);
}
const GpuFpReferenceValidation<float> validator(0.0f, 0.0f);
ASSERT_TRUE(validator.allClose(ref, impl));
}
} // namespace