blob: 369a8cdb41ff4280155f8ca1f2ff4c21360ec242 [file] [log] [blame]
Georgios Pinitas41df4282023-05-30 12:20:31 +01001// Copyright (c) 2023, ARM Limited.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14#include "verify.h"
15
Jack Franklandaafc8502023-09-13 11:03:50 +010016#include <algorithm>
Jack Frankland62737b12023-09-13 15:47:48 +010017#include <cmath>
18#include <cstdint>
Georgios Pinitas41df4282023-05-30 12:20:31 +010019#include <doctest.h>
20
21#include <array>
Jack Franklandaafc8502023-09-13 11:03:50 +010022#include <iterator>
23#include <limits>
24#include <numeric>
25#include <random>
Georgios Pinitas7021ef02023-08-22 08:25:57 +010026#include <string>
Jack Franklandaafc8502023-09-13 11:03:50 +010027#include <type_traits>
Georgios Pinitas41df4282023-05-30 12:20:31 +010028#include <vector>
29
Georgios Pinitas7021ef02023-08-22 08:25:57 +010030namespace
Georgios Pinitas41df4282023-05-30 12:20:31 +010031{
Georgios Pinitas7021ef02023-08-22 08:25:57 +010032
33class TosaTensor
34{
35public:
Jack Franklandaafc8502023-09-13 11:03:50 +010036 TosaTensor(std::string name, tosa_datatype_t dataType, std::vector<int32_t> shape, uint8_t* data = nullptr)
Georgios Pinitas7021ef02023-08-22 08:25:57 +010037 : _name(std::move(name))
38 , _shape(std::move(shape))
Georgios Pinitas41df4282023-05-30 12:20:31 +010039 {
Georgios Pinitas7021ef02023-08-22 08:25:57 +010040 _tensor.name = _name.c_str();
41 _tensor.data_type = dataType;
42 _tensor.num_dims = _shape.size();
43 _tensor.shape = _shape.data();
Jack Franklandaafc8502023-09-13 11:03:50 +010044 _tensor.data = data;
45 _tensor.size =
46 std::accumulate(_tensor.shape, std::next(_tensor.shape, _tensor.num_dims), 1, std::multiplies<>());
Georgios Pinitas7021ef02023-08-22 08:25:57 +010047 };
Georgios Pinitas41df4282023-05-30 12:20:31 +010048
Georgios Pinitas7021ef02023-08-22 08:25:57 +010049 const tosa_tensor_t* cTensor() const
50 {
51 return &_tensor;
Georgios Pinitas41df4282023-05-30 12:20:31 +010052 }
Georgios Pinitas41df4282023-05-30 12:20:31 +010053
Georgios Pinitas7021ef02023-08-22 08:25:57 +010054private:
55 std::string _name;
56 std::vector<int32_t> _shape;
57 tosa_tensor_t _tensor;
58};
59
Jack Frankland62737b12023-09-13 15:47:48 +010060template <typename FP>
61std::enable_if_t<std::is_floating_point_v<FP>, FP> increment(FP input, uint64_t steps)
62{
63 for (uint64_t step = 0; step < steps; ++step)
64 input = std::nextafter(input, std::numeric_limits<FP>::infinity());
65 return input;
66}
67
Jack Franklandaafc8502023-09-13 11:03:50 +010068auto& getRandomGenerator()
69{
70 static std::mt19937 gen(0);
71 return gen;
72}
73
74template <typename FP>
75std::enable_if_t<std::is_floating_point_v<FP>, std::add_lvalue_reference_t<std::uniform_real_distribution<FP>>>
76 getUniformRealDist()
77{
Jeremy Johnsond41feb72023-10-12 16:03:15 +010078 // Uniform real distribution generates real values in the range [a, b]
Jack Franklandaafc8502023-09-13 11:03:50 +010079 // and requires that b - a <= std::numeric_limits<FP>::max() so here
80 // we choose some arbitrary values that satisfy that condition.
81 constexpr auto min = std::numeric_limits<FP>::lowest() / 2;
82 constexpr auto max = std::numeric_limits<FP>::max() / 2;
83 static_assert(max <= std::numeric_limits<FP>::max() + min);
84
85 static std::uniform_real_distribution<FP> dis(min, max);
86 return dis;
87}
88
89template <typename FP>
90std::enable_if_t<std::is_floating_point_v<FP>, FP> getRandomUniformFloat()
91{
92 return getUniformRealDist<FP>()(getRandomGenerator());
93}
94
95template <typename FP>
96std::enable_if_t<std::is_floating_point_v<FP>, std::vector<FP>> generateRandomTensorData(size_t elementCount,
97 bool includeNans = false)
98{
99 // Generate some random floats using the full range of fp32.
100 auto data = std::vector<FP>(elementCount);
101 std::generate(std::begin(data), std::end(data), []() { return getRandomUniformFloat<FP>(); });
102
103 // Include some edge cases.
104 auto edgeCases = std::vector<float>{ +0.0f, -0.0f, std::numeric_limits<float>::infinity(),
105 -std::numeric_limits<float>::infinity() };
106 if (includeNans)
107 {
108 static const auto nans =
109 std::vector<float>{ std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::signaling_NaN() };
110
111 std::copy(std::begin(nans), std::end(nans), std::back_inserter(edgeCases));
112 }
113
114 if (elementCount >= edgeCases.size())
115 {
116 // Evenly distribute the edge cases throughout the data, this way for operations like reductions all edge cases won't
117 // end up in the same row/column over which a reduction happens.
118 const auto stride = (data.size() + (edgeCases.size() - 1)) / edgeCases.size();
119 for (unsigned i = 0; i < edgeCases.size(); ++i)
120 {
121 data[i * stride] = edgeCases[i];
122 }
123 }
124
125 return data;
126}
127
Jack Frankland12ee1a72023-09-20 09:08:34 +0100128// Calculates the "error" in the tolerance calculation as: E = pow(1 + pow(2, -M-1), N) - 1.
129// where M is the number of mantisa bits in the floating point representation and N is the number
130// of elements in the product.
131constexpr auto reduceProductError(uint64_t M, uint64_t N)
132{
133 return std::pow(1 + std::pow(2, -static_cast<int64_t>(M) - 1), N) - 1;
134}
135
136template <typename FP>
137auto reduceProductTolerance(uint64_t M, uint64_t N, const std::vector<FP>& results)
138{
139 const auto error = reduceProductError(M, N);
140 auto tolerances = std::vector<FP>(results.size());
141 for (unsigned i = 0, end = results.size(); i < end; ++i)
142 {
143 tolerances[i] = std::abs(results[i]) * error;
144 }
145 return tolerances;
146}
147
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100148} // namespace
149
150TEST_SUITE_BEGIN("verify");
151
152TEST_CASE("negative - api")
153{
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100154 std::string jsonCfg = R"({
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100155 "tensors" : {
156 "out1" : {
157 "mode": "DOT_PRODUCT",
Jeremy Johnsonbb0935f2023-09-14 16:43:48 +0100158 "data_type": "FP32",
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100159 "dot_product_info" : {
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100160 "s": 2,
161 "ks": 9
162 }
163 }
164 }
165 })";
166
167 SUBCASE("invalid json")
168 {
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100169 std::string invalidJsonCfg = R"({
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100170 "tensors" : {
171 "out1" : {
172 "mode": DOT_PRODUCT,
173 },
174 }
175 })";
176
177 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
178 const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
179 const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8, 8, 8 });
180
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100181 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), invalidJsonCfg.c_str()));
182 }
183 SUBCASE("unknown mode")
184 {
185 std::string unknownJsonCfg = R"({
186 "tensors" : {
187 "out1" : {
188 "mode": "WIND",
189 "data_type": "FP32"
190 }
191 }
192 })";
193
194 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8 });
195 const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8 });
196
197 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), nullptr, imp.cTensor(), unknownJsonCfg.c_str()));
198 }
199 SUBCASE("unknown type")
200 {
201 std::string unknownJsonCfg = R"({
202 "tensors" : {
203 "out1" : {
204 "mode": "DOT_PRODUCT",
205 "data_type": "JOULES"
206 }
207 }
208 })";
209
210 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8 });
211 const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8 });
212
213 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), nullptr, imp.cTensor(), unknownJsonCfg.c_str()));
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100214 }
215 SUBCASE("mismatching dimensions")
216 {
217 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 4, 4 });
218 const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 4, 4 });
219 const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8, 8, 8 });
220
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100221 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), jsonCfg.c_str()));
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100222 }
223 SUBCASE("mismatching shapes")
224 {
225 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
226 const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
227 const TosaTensor imp("out1", tosa_datatype_fp32_t, { 4, 4, 4 });
228
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100229 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), jsonCfg.c_str()));
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100230 }
231 SUBCASE("mismatching data types")
232 {
233 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
234 const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
235 const TosaTensor imp("out1", tosa_datatype_fp16_t, { 8, 8, 8 });
236
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100237 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), jsonCfg.c_str()));
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100238 }
239 SUBCASE("missing tensor data")
240 {
241 const TosaTensor ref("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
242 const TosaTensor refAbs("out1", tosa_datatype_fp64_t, { 8, 8, 8 });
243 const TosaTensor imp("out1", tosa_datatype_fp32_t, { 8, 8, 8 });
244
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100245 REQUIRE_FALSE(tvf_verify_data(ref.cTensor(), refAbs.cTensor(), imp.cTensor(), jsonCfg.c_str()));
Georgios Pinitas41df4282023-05-30 12:20:31 +0100246 }
247}
Georgios Pinitas7021ef02023-08-22 08:25:57 +0100248
Jack Franklandaafc8502023-09-13 11:03:50 +0100249TEST_CASE("positive - exact")
250{
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100251 std::string jsonCfg = R"({
Jack Franklandaafc8502023-09-13 11:03:50 +0100252 "tensors" : {
253 "out1" : {
Jeremy Johnsonbb0935f2023-09-14 16:43:48 +0100254 "mode": "EXACT",
255 "data_type": "FP32"
Jack Franklandaafc8502023-09-13 11:03:50 +0100256 }
257 }
258 })";
259
260 const auto shape = std::vector<int32_t>{ 8, 8, 8 };
261 const auto elementCount = std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>());
262
263 // Generate some random floats using the full range of fp32.
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100264 auto data_fp32 = generateRandomTensorData<float>(elementCount);
265 std::vector<double> data_fp64(data_fp32.begin(), data_fp32.end());
Jack Franklandaafc8502023-09-13 11:03:50 +0100266 SUBCASE("same")
267 {
268 const auto referenceTensor =
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100269 TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data_fp64.data()));
Jack Franklandaafc8502023-09-13 11:03:50 +0100270 const auto implementationTensor =
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100271 TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(data_fp32.data()));
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100272 REQUIRE(tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), jsonCfg.c_str()));
Jack Franklandaafc8502023-09-13 11:03:50 +0100273 }
274
275 SUBCASE("different")
276 {
277 // Generate some mismatched tensors by setting every other value to an incrementing counter.
278 // In theory this could be the same, but the probability is tiny.
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100279 auto otherData_fp32 = std::vector<float>(elementCount);
280 std::generate(std::begin(otherData_fp32), std::end(otherData_fp32), [&, i = 0]() mutable {
Jack Franklandaafc8502023-09-13 11:03:50 +0100281 auto oldIndex = i++;
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100282 return oldIndex % 2 ? data_fp32[oldIndex] : static_cast<float>(oldIndex);
Jack Franklandaafc8502023-09-13 11:03:50 +0100283 });
284
285 const auto referenceTensor =
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100286 TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data_fp64.data()));
Jack Franklandaafc8502023-09-13 11:03:50 +0100287 const auto implementationTensor =
Jeremy Johnsond41feb72023-10-12 16:03:15 +0100288 TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(otherData_fp32.data()));
Jack Franklandaafc8502023-09-13 11:03:50 +0100289 REQUIRE_FALSE(
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100290 tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), jsonCfg.c_str()));
Jack Franklandaafc8502023-09-13 11:03:50 +0100291 }
292}
293
Jack Frankland12ee1a72023-09-20 09:08:34 +0100294TEST_CASE("positive - reduce product")
295{
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100296 std::string jsonCfg = R"({
Jack Frankland12ee1a72023-09-20 09:08:34 +0100297 "tensors" : {
298 "out1" : {
299 "mode": "REDUCE_PRODUCT",
Jeremy Johnson59b307d2023-10-04 14:17:26 +0100300 "data_type": "FP32",
Jack Frankland12ee1a72023-09-20 09:08:34 +0100301 "reduce_product_info": {
302 "m": 23,
303 "n": 8
304 }
305 }
306 }
307 })";
308
309 const auto inputShape = std::vector<int32_t>{ 8, 8, 8 };
310 const auto outputShape = std::vector<int32_t>{ 8, 8, 1 };
311 const auto reductionSize = inputShape[2];
312 const auto elementCount = std::accumulate(std::begin(inputShape), std::end(inputShape), 1, std::multiplies<>());
313
314 // Generate some random floats using the full range of fp32. This will be the "result" of our
315 // dot product. Here we "reduced" over the z-axis of our shape.
316 auto data = generateRandomTensorData<float>(elementCount / reductionSize, false);
317 // Calculate the tolerances for each element in the result.
318 // A float has 23 bit dedicated to the fraction.
319 constexpr uint64_t mantisa_count = 23;
320 const auto tolerances = reduceProductTolerance(mantisa_count, reductionSize, data);
321
322 SUBCASE("same")
323 {
324 // TODO: Generate some new floats that are as far away as possible from each result without
325 // exceeding the tolerance.
326 auto otherData = std::vector<float>(elementCount / reductionSize);
327 for (unsigned i = 0; i < data.size(); ++i)
328 {
329 auto newValue = data[i];
330 auto oldValue = newValue;
331 const auto target = tolerances[i] + newValue;
332
333 // Here we just increment the value until we exceed the tolerance. For simplicity we go up.
334 while (newValue < target)
335 {
336 oldValue = newValue;
337 newValue = std::nextafter(newValue, std::numeric_limits<float>::infinity());
338 }
339
340 otherData[i] = oldValue;
341 }
342
343 const auto referenceTensor =
344 TosaTensor("out1", tosa_datatype_fp64_t, outputShape, reinterpret_cast<uint8_t*>(data.data()));
345 const auto implementationTensor =
346 TosaTensor("out1", tosa_datatype_fp32_t, outputShape, reinterpret_cast<uint8_t*>(otherData.data()));
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100347 REQUIRE(tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), jsonCfg.c_str()));
Jack Frankland12ee1a72023-09-20 09:08:34 +0100348 }
349
350 SUBCASE("different")
351 {
352 // TODO: Generate some new floats that exceed the tolerance.
353 auto otherData = std::vector<float>(elementCount / reductionSize);
354 for (unsigned i = 0; i < data.size(); ++i)
355 {
356 auto newValue = data[i];
357 const auto target = tolerances[i] + newValue;
358
359 // Here we just increment the value until we exceed the tolerance. For simplicity we go up.
360 while (newValue < target)
361 {
362 newValue = std::nextafter(newValue, std::numeric_limits<float>::infinity());
363 }
364
365 otherData[i] = newValue;
366 }
367
368 const auto referenceTensor =
369 TosaTensor("out1", tosa_datatype_fp64_t, outputShape, reinterpret_cast<uint8_t*>(data.data()));
370 const auto implementationTensor =
371 TosaTensor("out1", tosa_datatype_fp32_t, outputShape, reinterpret_cast<uint8_t*>(otherData.data()));
372 REQUIRE_FALSE(
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100373 tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), jsonCfg.c_str()));
Jack Frankland12ee1a72023-09-20 09:08:34 +0100374 }
375}
376
Jack Frankland62737b12023-09-13 15:47:48 +0100377TEST_CASE("positive - ulp")
378{
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100379 std::string jsonCfg = R"({
Jack Frankland62737b12023-09-13 15:47:48 +0100380 "tensors" : {
381 "out1" : {
382 "mode": "ULP",
Jeremy Johnsonbb0935f2023-09-14 16:43:48 +0100383 "data_type": "FP32",
Jack Frankland62737b12023-09-13 15:47:48 +0100384 "ulp_info": {
385 "ulp": 5
386 }
387 }
388 }
389 })";
390
391 const auto shape = std::vector<int32_t>{ 8, 8, 8 };
392 const auto elementCount = std::accumulate(std::begin(shape), std::end(shape), 1, std::multiplies<>());
393
394 // Generate some random floats using the full range of fp32.
395 auto data = generateRandomTensorData<float>(elementCount, false);
396 SUBCASE("same")
397 {
398 // Generate some data that meets the ULP requirements of the result.
399 auto otherData = data;
400 std::for_each(std::begin(otherData), std::end(otherData), [](auto& value) { value = increment(value, 5); });
401 const auto referenceTensor =
402 TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data.data()));
403 const auto implementationTensor =
404 TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(otherData.data()));
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100405 REQUIRE(tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), jsonCfg.c_str()));
Jack Frankland62737b12023-09-13 15:47:48 +0100406 }
407
408 SUBCASE("different")
409 {
410 // Generate some data that exceeds a specified number of ULP for each value in the tensor.
411 auto otherData = std::vector<float>(elementCount);
412 std::for_each(std::begin(otherData), std::end(otherData), [](auto& value) { value = increment(value, 6); });
413
414 const auto referenceTensor =
415 TosaTensor("out1", tosa_datatype_fp64_t, shape, reinterpret_cast<uint8_t*>(data.data()));
416 const auto implementationTensor =
417 TosaTensor("out1", tosa_datatype_fp32_t, shape, reinterpret_cast<uint8_t*>(otherData.data()));
418 REQUIRE_FALSE(
Jeremy Johnsonfc5e34e2023-10-24 14:45:12 +0100419 tvf_verify_data(referenceTensor.cTensor(), nullptr, implementationTensor.cTensor(), jsonCfg.c_str()));
Jack Frankland62737b12023-09-13 15:47:48 +0100420 }
421}
422
Jack Franklandaafc8502023-09-13 11:03:50 +0100423TEST_SUITE_END(); // verify