Skip to content

Commit ac8efe4

Browse files
authored
Add epsilon comparison for floating point (#217)
Change BufferFuzzy to 'BufferFloatULP' and 'BufferFloatEpsilon' Implemented BufferFloatEpsilon for 16, 32, and 64 bit floating point. Add tests Closes #216
1 parent d74fa87 commit ac8efe4

File tree

11 files changed

+453
-58
lines changed

11 files changed

+453
-58
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Buffers:
8080
Data: [ 0x1, 0x2 ]
8181
Results: # Using Result can verify test values without filecheck
8282
- Result: Test1
83-
Rule: BufferFuzzy # Rule which can be used to compare Float Buffers; They are compared within a ULP range
83+
Rule: BufferFloatULP # Rule which can be used to compare Float Buffers; They are compared within a ULP range
8484
ULPT: 1 # ULP to use
8585
DenormMode: Any # if DenormMode Field is not Specified, 'Any' is the default; FTZ and Preserve are the other options.
8686
Actual: Out1 # First buffer to compare
@@ -89,6 +89,11 @@ Results: # Using Result can verify test values without filecheck
8989
Rule: BufferExact # Compares Two Buffers for == equality between each value elementwise
9090
Actual: Out1
9191
Expected: Expected1
92+
- Result: Test3
93+
Rule: BufferFloatEpsilon # Rule which can be used to compare Float Buffers; They are compared within an epsilon difference
94+
Epsilon: 0.0008
95+
Actual: Out1
96+
Expected: Expected1
9297
DescriptorSets:
9398
- Resources:
9499
- Name: Constants

include/Support/Pipeline.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace offloadtest {
2525

2626
enum class Stages { Compute };
2727

28-
enum class Rule { BufferExact, BufferFuzzy };
28+
enum class Rule { BufferExact, BufferFloatULP, BufferFloatEpsilon };
2929

3030
enum class DenormMode { Any, FTZ, Preserve };
3131

@@ -123,6 +123,7 @@ struct Result {
123123
Buffer *ExpectedPtr = nullptr;
124124
DenormMode DM = DenormMode::Any;
125125
unsigned ULPT; // ULP Tolerance
126+
double Epsilon;
126127
};
127128

128129
struct Resource {
@@ -316,7 +317,8 @@ template <> struct ScalarEnumerationTraits<offloadtest::Rule> {
316317
static void enumeration(IO &I, offloadtest::Rule &V) {
317318
#define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::Rule::Val)
318319
ENUM_CASE(BufferExact);
319-
ENUM_CASE(BufferFuzzy);
320+
ENUM_CASE(BufferFloatULP);
321+
ENUM_CASE(BufferFloatEpsilon);
320322
#undef ENUM_CASE
321323
}
322324
};

lib/Support/Check.cpp

Lines changed: 141 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
#include "Support/Check.h"
13+
#include "llvm/ADT/APFloat.h"
14+
#include "llvm/ADT/APInt.h"
1315
#include "llvm/Support/Error.h"
1416
#include "llvm/Support/raw_ostream.h"
1517
#include <cmath>
@@ -18,6 +20,16 @@ constexpr uint16_t Float16BitSign = 0x8000;
1820
constexpr uint16_t Float16BitExp = 0x7c00;
1921
constexpr uint16_t Float16BitMantissa = 0x03ff;
2022

23+
static float convertFloat16ToFloat(const uint16_t F) {
24+
const llvm::APInt API(16, F);
25+
llvm::detail::IEEEFloat IEF(llvm::APFloat::IEEEhalf(), API);
26+
bool LostInfo;
27+
// rounding mode should not matter since we are up converting
28+
IEF.convert(llvm::APFloat::IEEEsingle(),
29+
llvm::APFloatBase::rmNearestTiesToEven, &LostInfo);
30+
return IEF.convertToFloat();
31+
}
32+
2133
// limited to float, double, and long double
2234
template <typename T> static bool isDenorm(T F) {
2335
return std::fpclassify(F) == FP_SUBNORMAL;
@@ -28,6 +40,23 @@ static bool isFloat16NAN(uint16_t Val) {
2840
(Val & Float16BitMantissa) != 0;
2941
}
3042

43+
static bool compareDoubleEpsilon(const double &FSrc, const double &FRef,
44+
double Epsilon, offloadtest::DenormMode DM) {
45+
if (FSrc == FRef)
46+
return true;
47+
if (std::isnan(FSrc) || std::isnan(FRef))
48+
return std::isnan(FRef) && std::isnan(FSrc);
49+
if (DM == offloadtest::DenormMode::Any) {
50+
// If denorm expected, output can be sign preserved zero. Otherwise output
51+
// should pass the regular ulp testing.
52+
if (isDenorm(FRef) && FSrc == 0 && std::signbit(FSrc) == std::signbit(FRef))
53+
return true;
54+
}
55+
// For FTZ or Preserve mode, we should get the expected number within
56+
// epsilon for any operation
57+
return std::abs(FSrc - FRef) < Epsilon;
58+
}
59+
3160
static bool compareDoubleULP(const double &FSrc, const double &FRef,
3261
unsigned ULPTolerance,
3362
offloadtest::DenormMode DM) {
@@ -48,6 +77,24 @@ static bool compareDoubleULP(const double &FSrc, const double &FRef,
4877
return AbsDiff <= ULPTolerance;
4978
}
5079

80+
static bool compareFloatEpsilon(const float &FSrc, const float &FRef,
81+
float Epsilon, offloadtest::DenormMode DM) {
82+
if (FSrc == FRef)
83+
return true;
84+
if (std::isnan(FSrc) || std::isnan(FRef))
85+
return std::isnan(FRef) && std::isnan(FSrc);
86+
if (DM == offloadtest::DenormMode::Any) {
87+
// If denorm expected, output can be sign preserved zero. Otherwise output
88+
// should pass the regular ulp testing.
89+
if (isDenorm(FRef) && FSrc == 0 && std::signbit(FSrc) == std::signbit(FRef))
90+
return true;
91+
}
92+
93+
// For FTZ or Preserve mode, we should get the expected number within
94+
// epsilon for any operation
95+
return std::abs(FSrc - FRef) < Epsilon;
96+
}
97+
5198
static bool compareFloatULP(const float &FSrc, const float &FRef,
5299
unsigned ULPTolerance, offloadtest::DenormMode DM) {
53100
if (FSrc == FRef)
@@ -67,6 +114,21 @@ static bool compareFloatULP(const float &FSrc, const float &FRef,
67114
return AbsDiff <= ULPTolerance;
68115
}
69116

117+
static bool compareFloat16Epsilon(const uint16_t &FSrc, const uint16_t &FRef,
118+
float Epsilon) {
119+
// Treat +0 and -0 as equal
120+
if ((FSrc & ~Float16BitSign) == 0 && (FRef & ~Float16BitSign) == 0)
121+
return true;
122+
if (FSrc == FRef)
123+
return true;
124+
if (isFloat16NAN(FSrc) || isFloat16NAN(FRef))
125+
return isFloat16NAN(FRef) && isFloat16NAN(FSrc);
126+
127+
const float FSrc32 = convertFloat16ToFloat(FSrc);
128+
const float FRef32 = convertFloat16ToFloat(FRef);
129+
return std::abs(FSrc32 - FRef32) < Epsilon;
130+
}
131+
70132
static bool compareFloat16ULP(const uint16_t &FSrc, const uint16_t &FRef,
71133
unsigned ULPTolerance) {
72134
// Treat +0 and -0 as equal
@@ -113,29 +175,25 @@ static bool testAll(std::function<bool(const T &, const T &)> ComparisonFn,
113175
return true;
114176
}
115177

116-
static bool testBufferFuzzy(offloadtest::Buffer *B1, offloadtest::Buffer *B2,
117-
unsigned ULPT, offloadtest::DenormMode DM) {
178+
template <typename T>
179+
static bool
180+
testBufferFloat(std::function<bool(const T &, const T &)> ComparisonFn,
181+
offloadtest::Buffer *B1, offloadtest::Buffer *B2) {
118182
assert(B1->Format == B2->Format && "Buffer types must be the same");
119183
switch (B1->Format) {
120184
case offloadtest::DataFormat::Float64: {
121185
const llvm::ArrayRef<double> Arr1(
122186
reinterpret_cast<double *>(B1->Data.get()), B1->Size / sizeof(double));
123187
const llvm::ArrayRef<double> Arr2(
124188
reinterpret_cast<double *>(B2->Data.get()), B2->Size / sizeof(double));
125-
auto Fn = [ULPT, DM](const double &FS, const double &FR) {
126-
return compareDoubleULP(FS, FR, ULPT, DM);
127-
};
128-
return testAll<double>(Fn, Arr1, Arr2);
189+
return testAll<double>(ComparisonFn, Arr1, Arr2);
129190
}
130191
case offloadtest::DataFormat::Float32: {
131192
const llvm::ArrayRef<float> Arr1(reinterpret_cast<float *>(B1->Data.get()),
132193
B1->Size / sizeof(float));
133194
const llvm::ArrayRef<float> Arr2(reinterpret_cast<float *>(B2->Data.get()),
134195
B2->Size / sizeof(float));
135-
auto Fn = [ULPT, DM](const float &FS, const float &FR) {
136-
return compareFloatULP(FS, FR, ULPT, DM);
137-
};
138-
return testAll<float>(Fn, Arr1, Arr2);
196+
return testAll<float>(ComparisonFn, Arr1, Arr2);
139197
}
140198
case offloadtest::DataFormat::Float16: {
141199
const llvm::ArrayRef<uint16_t> Arr1(
@@ -144,10 +202,64 @@ static bool testBufferFuzzy(offloadtest::Buffer *B1, offloadtest::Buffer *B2,
144202
const llvm::ArrayRef<uint16_t> Arr2(
145203
reinterpret_cast<uint16_t *>(B2->Data.get()),
146204
B2->Size / sizeof(uint16_t));
205+
return testAll<uint16_t>(ComparisonFn, Arr1, Arr2);
206+
}
207+
default:
208+
llvm_unreachable("Only float types are supported by the fuzzy test.");
209+
}
210+
return false;
211+
}
212+
213+
static bool testBufferFloatEpsilon(offloadtest::Buffer *B1,
214+
offloadtest::Buffer *B2, double Epsilon,
215+
offloadtest::DenormMode DM) {
216+
217+
switch (B1->Format) {
218+
case offloadtest::DataFormat::Float64: {
219+
auto Fn = [Epsilon, DM](const double &FS, const double &FR) {
220+
return compareDoubleEpsilon(FS, FR, Epsilon, DM);
221+
};
222+
return testBufferFloat<double>(Fn, B1, B2);
223+
}
224+
case offloadtest::DataFormat::Float32: {
225+
auto Fn = [Epsilon, DM](const float &FS, const float &FR) {
226+
return compareFloatEpsilon(FS, FR, (float)Epsilon, DM);
227+
};
228+
return testBufferFloat<float>(Fn, B1, B2);
229+
}
230+
case offloadtest::DataFormat::Float16: {
231+
auto Fn = [Epsilon](const uint16_t &FS, const uint16_t &FR) {
232+
return compareFloat16Epsilon(FS, FR, (float)Epsilon);
233+
};
234+
return testBufferFloat<uint16_t>(Fn, B1, B2);
235+
}
236+
default:
237+
llvm_unreachable("Only float types are supported by the fuzzy test.");
238+
}
239+
return false;
240+
}
241+
242+
static bool testBufferFloatULP(offloadtest::Buffer *B1, offloadtest::Buffer *B2,
243+
unsigned ULPT, offloadtest::DenormMode DM) {
244+
245+
switch (B1->Format) {
246+
case offloadtest::DataFormat::Float64: {
247+
auto Fn = [ULPT, DM](const double &FS, const double &FR) {
248+
return compareDoubleULP(FS, FR, ULPT, DM);
249+
};
250+
return testBufferFloat<double>(Fn, B1, B2);
251+
}
252+
case offloadtest::DataFormat::Float32: {
253+
auto Fn = [ULPT, DM](const float &FS, const float &FR) {
254+
return compareFloatULP(FS, FR, ULPT, DM);
255+
};
256+
return testBufferFloat<float>(Fn, B1, B2);
257+
}
258+
case offloadtest::DataFormat::Float16: {
147259
auto Fn = [ULPT](const uint16_t &FS, const uint16_t &FR) {
148260
return compareFloat16ULP(FS, FR, ULPT);
149261
};
150-
return testAll<uint16_t>(Fn, Arr1, Arr2);
262+
return testBufferFloat<uint16_t>(Fn, B1, B2);
151263
}
152264
default:
153265
llvm_unreachable("Only float types are supported by the fuzzy test.");
@@ -160,26 +272,25 @@ llvm::Error verifyResult(offloadtest::Result R) {
160272
case offloadtest::Rule::BufferExact: {
161273
if (testBufferExact(R.ActualPtr, R.ExpectedPtr))
162274
return llvm::Error::success();
163-
llvm::SmallString<256> Str;
164-
llvm::raw_svector_ostream OS(Str);
165-
OS << "Test failed: " << R.Name << "\nExpected:\n";
166-
llvm::yaml::Output YAMLOS(OS);
167-
YAMLOS << *R.ExpectedPtr;
168-
OS << "Got:\n";
169-
YAMLOS << *R.ActualPtr;
170-
return llvm::createStringError(Str.c_str());
171-
}
172-
case offloadtest::Rule::BufferFuzzy: {
173-
if (testBufferFuzzy(R.ActualPtr, R.ExpectedPtr, R.ULPT, R.DM))
275+
break;
276+
}
277+
case offloadtest::Rule::BufferFloatULP: {
278+
if (testBufferFloatULP(R.ActualPtr, R.ExpectedPtr, R.ULPT, R.DM))
279+
return llvm::Error::success();
280+
break;
281+
}
282+
case offloadtest::Rule::BufferFloatEpsilon: {
283+
if (testBufferFloatEpsilon(R.ActualPtr, R.ExpectedPtr, R.Epsilon, R.DM))
174284
return llvm::Error::success();
175-
llvm::SmallString<256> Str;
176-
llvm::raw_svector_ostream OS(Str);
177-
OS << "Test failed: " << R.Name << "\nExpected:\n";
178-
llvm::yaml::Output YAMLOS(OS);
179-
YAMLOS << *R.ExpectedPtr;
180-
OS << "Got:\n";
181-
YAMLOS << *R.ActualPtr;
182-
return llvm::createStringError(Str.c_str());
285+
break;
183286
}
184287
}
288+
llvm::SmallString<256> Str;
289+
llvm::raw_svector_ostream OS(Str);
290+
OS << "Test failed: " << R.Name << "\nExpected:\n";
291+
llvm::yaml::Output YAMLOS(OS);
292+
YAMLOS << *R.ExpectedPtr;
293+
OS << "Got:\n";
294+
YAMLOS << *R.ActualPtr;
295+
return llvm::createStringError(Str.c_str());
185296
}

lib/Support/Pipeline.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ void MappingTraits<offloadtest::Pipeline>::mapping(IO &I,
4646
R.ExpectedPtr = P.getBuffer(R.Expected);
4747
if (!R.ExpectedPtr)
4848
I.setError(Twine("Reference buffer ") + R.Expected + " not found!");
49-
if (R.Rule == Rule::BufferFuzzy) {
49+
if (R.Rule == Rule::BufferFloatULP ||
50+
R.Rule == Rule::BufferFloatEpsilon) {
5051
if (!isFloatingPointFormat(R.ActualPtr->Format) ||
5152
!isFloatingPointFormat(R.ExpectedPtr->Format))
52-
I.setError(Twine("BufferFuzzy only accepts Float buffers"));
53+
I.setError(Twine("BufferFloat only accepts Float buffers"));
5354
if (R.ActualPtr->Format != R.ExpectedPtr->Format)
5455
I.setError(Twine("Buffers must have the same type"));
5556
}
@@ -230,11 +231,16 @@ void MappingTraits<offloadtest::Result>::mapping(IO &I,
230231
I.mapRequired("Expected", R.Expected);
231232

232233
switch (R.Rule) {
233-
case Rule::BufferFuzzy: {
234+
case Rule::BufferFloatULP: {
234235
I.mapRequired("ULPT", R.ULPT);
235236
I.mapOptional("DenormMode", R.DM);
236237
break;
237238
}
239+
case Rule::BufferFloatEpsilon: {
240+
I.mapRequired("Epsilon", R.Epsilon);
241+
I.mapOptional("DenormMode", R.DM);
242+
break;
243+
}
238244
default:
239245
break;
240246
}

test/Tools/Offloader/BufferFuzzy-16bit.test renamed to test/Tools/Offloader/BufferFloat-16bit.test

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,30 +65,56 @@ Buffers:
6565
Data: [ 0, 0 ] # compare negative and positive zero
6666
Results:
6767
- Result: Test1 # Test two values are exactly the same
68-
Rule: BufferFuzzy
68+
Rule: BufferFloatULP
6969
ULPT: 0 # ulp shouldn't matter for this test
7070
Actual: Out1
7171
Expected: Expected1
7272
- Result: Test2 # Test both are NaN
73-
Rule: BufferFuzzy
73+
Rule: BufferFloatULP
7474
ULPT: 0 # ulp shouldn't matter for this test
7575
Actual: Out2
7676
Expected: Expected2
7777
- Result: Test3 # Actual and Expected are not the same but they are within the ULPT
78-
Rule: BufferFuzzy
78+
Rule: BufferFloatULP
7979
ULPT: 1
8080
Actual: Out3
8181
Expected: Expected3
8282
- Result: Test4 # Actual and Expected are not the same but they are within the ULPT
83-
Rule: BufferFuzzy
83+
Rule: BufferFloatULP
8484
ULPT: 2
8585
Actual: Out4
8686
Expected: Expected4
8787
- Result: Test5 # +0 and -0 should be equal
88-
Rule: BufferFuzzy
88+
Rule: BufferFloatULP
8989
ULPT: 0
9090
Actual: Out5
9191
Expected: Expected5
92+
# test BufferFloatEpsilon
93+
- Result: Test6 # Test two values are exactly the same
94+
Rule: BufferFloatEpsilon
95+
Epsilon: 0
96+
Actual: Out1
97+
Expected: Expected1
98+
- Result: Test7 # Test both are NaN
99+
Rule: BufferFloatEpsilon
100+
Epsilon: 0 # epsilon shouldn't matter for this test
101+
Actual: Out2
102+
Expected: Expected2
103+
- Result: Test8 # Actual and Expected are not the same but they are within the epsilon
104+
Rule: BufferFloatEpsilon
105+
Epsilon: 0.004
106+
Actual: Out3
107+
Expected: Expected3
108+
- Result: Test9 # Actual and Expected are not the same but they are within the epsilon
109+
Rule: BufferFloatEpsilon
110+
Epsilon: 0.008
111+
Actual: Out4
112+
Expected: Expected4
113+
- Result: Test10 # +0 and -0 should be equal
114+
Rule: BufferFloatEpsilon
115+
Epsilon: 0
116+
Actual: Out5
117+
Expected: Expected5
92118
DescriptorSets:
93119
- Resources:
94120
- Name: Out1

0 commit comments

Comments
 (0)