blob: 223bc482ca6b3c3802af59d28d77f73c9ff86aa2 [file] [log] [blame]
Jack Frankland62737b12023-09-13 15:47:48 +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
15#include <cmath>
16#include <limits>
17#include <memory>
18#include <type_traits>
19
20#include "verifiers.h"
21
22namespace TosaReference
23{
24
25namespace
26{
27static_assert(std::numeric_limits<float>::is_iec559,
28 "TOSA Reference Model has not been built with standard IEE574 32-bit float support; ULP based "
29 "verifcation is invalid");
30static_assert(std::numeric_limits<double>::is_iec559,
31 "TOSA Reference Model has not been built with standard IEE574 64-bit float support; ULP based "
32 "verifcation is invalid");
33
34bool tosaCheckULP(float testValue, double referenceValue, int64_t ulpCount)
35{
36
37 // Start by sanitizing the input.
38
39 // The concept of ULP isn't defined for NaN's
40 if (std::isnan(referenceValue) || std::isnan(testValue))
41 {
42 return false;
43 }
44
45 // Make the sign of the reference value positive
46 // and adjust the test value appropriately.
47 if (referenceValue < 0)
48 {
49 referenceValue = -referenceValue;
50 testValue = -testValue;
51 }
52
53 // At this point we are ready to calculate the ULP bounds for the reference value.
54 double referenceMin, referenceMax;
55
56 // If the reference is infinity e.g. the result of an overflow the test value must
57 // be infinity of an appropriate sign.
58 if (std::isinf(referenceValue))
59 {
60 // We already canonicalized the input such that the reference value is positive
61 // so no need to check again here.
62 referenceMin = std::numeric_limits<float>::infinity();
63 referenceMax = std::numeric_limits<float>::infinity();
64 }
65 else if (referenceValue == 0)
66 {
67 // For zero we require that the results match exactly with the correct sign.
68 referenceMin = 0;
69 referenceMax = 0;
70 }
71 else
72 {
73 // Find the exponent of the reference value.
74 int referenceExponent;
75 std::frexp(referenceValue, &referenceExponent);
76
77 // Work out the values magnitude - by raising 2 to the power of the
78 // exponent and taking the normalized minimum for denormal values
79 const double referencePower2 =
80 std::max(std::ldexp(1.0, referenceExponent), static_cast<double>(std::numeric_limits<float>::min()));
81 // Get the value of changing the last bit - by shifting the least significant bit to this magnitude
82 // i.e. the ULP.
83 double ulpValue = referencePower2 * std::ldexp(1.0, -23);
84
85 // It is possible that within one ULP we cross a boundary where we need to change the exponent,
86 // if this happens we will take the ULP for the larger exponent.
87 if (referenceValue + ulpValue > 2 * referencePower2)
88 {
89 ulpValue = 2 * ulpValue;
90 }
91
92 // Scale by the number of ULPs requested by the user.
93 referenceMax = referenceValue + ulpValue * ulpCount;
94 referenceMin = referenceValue - ulpValue * ulpCount;
95
96 // Handle the overflow cases.
97 if (referenceMax > std::numeric_limits<float>::max())
98 {
99 referenceMax = std::numeric_limits<float>::infinity();
100 }
101
102 if (referenceMin > std::numeric_limits<float>::max())
103 {
104 referenceMin = std::numeric_limits<float>::infinity();
105 }
106
107 // And the underflow cases.
108 if (referenceMax < std::numeric_limits<float>::min())
109 {
110 referenceMax = std::numeric_limits<float>::min();
111 }
112
113 if (referenceMin < std::numeric_limits<float>::min())
114 {
115 referenceMin = 0;
116 }
117 }
118
119 // And finally... Do the comparison.
120 return static_cast<double>(testValue) >= referenceMin && static_cast<double>(testValue) <= referenceMax;
121}
122} // namespace
123
124bool verifyULP(const CTensor* referenceTensor, const CTensor* implementationTensor, uint64_t ulp)
125{
126 // Validate that tensors are provided
127 TOSA_REF_REQUIRE(referenceTensor != nullptr, "reference tensor is missing");
128 TOSA_REF_REQUIRE(implementationTensor != nullptr, "implementation tensor is missing");
129
130 // Get number of elements
131 const auto elementCount =
132 numElements(std::vector<int32_t>(referenceTensor->shape, referenceTensor->shape + referenceTensor->num_dims));
133 TOSA_REF_REQUIRE(elementCount > 0, "invalid shape for reference tensor");
134
135 switch (implementationTensor->data_type)
136 {
137 case tosa_datatype_fp32_t: {
138 const auto* refData = reinterpret_cast<const float*>(referenceTensor->data);
139 TOSA_REF_REQUIRE(refData != nullptr, "missing data for reference");
140 const auto* impData = reinterpret_cast<const float*>(implementationTensor->data);
141 TOSA_REF_REQUIRE(impData != nullptr, "missing data for implementation");
142 return std::equal(refData, std::next(refData, elementCount), impData, std::next(impData, elementCount),
143 [ulp](const auto& referenceValue, const auto& implementationValue) {
144 return tosaCheckULP(referenceValue, implementationValue, ulp);
145 });
146 }
147 default:
Jeremy Johnsonbb0935f2023-09-14 16:43:48 +0100148 WARNING("tosa verifier: data-type not supported.");
Jack Frankland62737b12023-09-13 15:47:48 +0100149 break;
150 }
151
152 return false;
153}
154} // namespace TosaReference