blob: 7b6ba9d22bd0c56f227f98837307d0be8e793f69 [file] [log] [blame]
// Copyright (c) 2023, ARM Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "verify.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <doctest.h>
#include <array>
#include <iterator>
#include <limits>
#include <numeric>
#include <random>
#include <string>
#include <type_traits>
#include <vector>
namespace
{
class TosaTensor
{
public:
TosaTensor(std::string name, tosa_datatype_t dataType, std::vector<int32_t> shape, uint8_t* data = nullptr)
: _name(std::move(name))
, _shape(std::move(shape))
{
_tensor.name = _name.c_str();
_tensor.data_type = dataType;
_tensor.num_dims = _shape.size();
_tensor.shape = _shape.data();
_tensor.data = data;
_tensor.size =
std::accumulate(_tensor.shape, std::next(_tensor.shape, _tensor.num_dims), 1, std::multiplies<>());
};
const tosa_tensor_t* cTensor() const
{
return &_tensor;
}
private:
std::string _name;
std::vector<int32_t> _shape;
tosa_tensor_t _tensor;
};
template <typename FP>
std::enable_if_t<std::is_floating_point_v<FP>, FP> increment(FP input, uint64_t steps)
{
for (uint64_t step = 0; step < steps; ++step)
input = std::nextafter(input, std::numeric_limits<FP>::infinity());
return input;
}
auto& getRandomGenerator()
{
static std::mt19937 gen(0);
return gen;
}
template <typename FP>
std::enable_if_t<std::is_floating_point_v<FP>, std::add_lvalue_reference_t<std::uniform_real_distribution<FP>>>
getUniformRealDist()
{
// Uniform real distribution generates real values in the range [a, b)
// and requires that b - a <= std::numeric_limits<FP>::max() so here
// we choose some arbitrary values that satisfy that condition.
constexpr auto min = std::numeric_limits<FP>::lowest() / 2;
constexpr auto max = std::numeric_limits<FP>::max() / 2;
static_assert(max <= std::numeric_limits<FP>::max() + min);
static std::uniform_real_distribution<FP> dis(min, max);
return dis;
}
template <typename FP>
std::enable_if_t<std::is_floating_point_v<FP>, FP> getRandomUniformFloat()
{
return getUniformRealDist<FP>()(getRandomGenerator());
}
template <typename FP>
std::enable_if_t<std::is_floating_point_v<FP>, std::vector<FP>> generateRandomTensorData(size_t elementCount,
bool includeNans = false)
{
// Generate some random floats using the full range of fp32.
auto data = std::vector<FP>(elementCount);
std::generate(std::begin(data), std::end(data), []() { return getRandomUniformFloat<FP>(); });
// Include some edge cases.
auto edgeCases = std::vector<float>{ +0.0f, -0.0f, std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity() };
if (includeNans)
{
static const auto nans =
std::vector<float>{ std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::signaling_NaN() };
std::copy(std::begin(nans), std::end(nans), std::back_inserter(edgeCases));
}
if (elementCount >= edgeCases.size())
{
// Evenly distribute the edge cases throughout the data, this way for operations like reductions all edge cases won't
// end up in the same row/column over which a reduction happens.
const auto stride = (data.size() + (edgeCases.size() - 1)) / edgeCases.size();
for (unsigned i = 0; i < edgeCases.size(); ++i)
{
data[i * stride] = edgeCases[i];
}
}
return data;
}
} // namespace
TEST_SUITE_BEGIN("verify");
TEST_CASE("negative - api")
{
std::string json_cfg = R"({
"tensors" : {
"out1" : {
"mode": "DOT_PRODUCT",
"data_type": "FP32",
"dot_product_info" : {
"s": 2,
"ks": 9
}
}
}
})";
SUBCASE("invalid json")
{
std::string invalid_json_cfg = R"({
"tensors" : {
"out1" : {
"mode": DOT_PRODUCT,
},
}
})";
const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8, 8, 8 });
REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), invalid_json_cfg.c_str()));
}
SUBCASE("mismatching dimensions")
{
const TosaTensor ref("out1", tosa_datatype_fp64_t, { 4, 4 });
const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 4, 4 });
const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8, 8, 8 });
REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), json_cfg.c_str()));
}
SUBCASE("mismatching shapes")
{
const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor imp("out1", tosa_datatype_fp32_t, { 4, 4, 4 });
REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), json_cfg.c_str()));
}
SUBCASE("mismatching data types")
{
const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor imp("out1", tosa_datatype_fp16_t, { 8, 8, 8 });
REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), json_cfg.c_str()));
}
SUBCASE("missing tensor data")
{
const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8, 8, 8 });
REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), json_cfg.c_str()));
}
}
TEST_CASE("positive - exact")
{
std::string json_cfg = R"({
"tensors" : {
"out1" : {
"mode": "EXACT",
"data_type": "FP32"
}
}
})";
const auto shape = std::vector<int32_t>{ 8, 8, 8 };
const auto elementCount = std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>());
// Generate some random floats using the full range of fp32.
auto data = generateRandomTensorData<float>(elementCount);
SUBCASE("same")
{
const auto referenceTensor =
TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data.data()));
const auto implementationTensor =
TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(data.data()));
REQUIRE(tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), json_cfg.c_str()));
}
SUBCASE("different")
{
// Generate some mismatched tensors by setting every other value to an incrementing counter.
// In theory this could be the same, but the probability is tiny.
auto otherData = std::vector<float>(elementCount);
std::generate(std::begin(otherData), std::end(otherData), [&, i = 0]() mutable {
auto oldIndex = i++;
return oldIndex % 2 ? data[oldIndex] : static_cast<float>(oldIndex);
});
const auto referenceTensor =
TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data.data()));
const auto implementationTensor =
TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(otherData.data()));
REQUIRE_FALSE(
tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), json_cfg.c_str()));
}
}
TEST_CASE("positive - ulp")
{
std::string json_cfg = R"({
"tensors" : {
"out1" : {
"mode": "ULP",
"data_type": "FP32",
"ulp_info": {
"ulp": 5
}
}
}
})";
const auto shape = std::vector<int32_t>{ 8, 8, 8 };
const auto elementCount = std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>());
// Generate some random floats using the full range of fp32.
auto data = generateRandomTensorData<float>(elementCount, false);
SUBCASE("same")
{
// Generate some data that meets the ULP requirements of the result.
auto otherData = data;
std::for_each(std::begin(otherData), std::end(otherData), [](auto& value) { value = increment(value, 5); });
const auto referenceTensor =
TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data.data()));
const auto implementationTensor =
TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(otherData.data()));
REQUIRE(tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), json_cfg.c_str()));
}
SUBCASE("different")
{
// Generate some data that exceeds a specified number of ULP for each value in the tensor.
auto otherData = std::vector<float>(elementCount);
std::for_each(std::begin(otherData), std::end(otherData), [](auto& value) { value = increment(value, 6); });
const auto referenceTensor =
TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data.data()));
const auto implementationTensor =
TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(otherData.data()));
REQUIRE_FALSE(
tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), json_cfg.c_str()));
}
}
TEST_SUITE_END(); // verify