Skip to content

Commit 2416411

Browse files
authored
Add ULP comparison support for Float16 (#183)
* finished implementation and tests * rename half to float16 * change ZeroInitSize to 4 to fix failing D3D12-Intel-DXC test * add float16 in readme * address PR comments * change readme float16 data
1 parent e3697bc commit 2416411

File tree

5 files changed

+287
-11
lines changed

5 files changed

+287
-11
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ Buffers:
6464
Format: Float32
6565
Stride: 4
6666
Data: [ 0.0, 1.0 ]
67+
- Name: Out2 # Buffer where our output will go
68+
Format: Float16
69+
Stride: 2
70+
ZeroInitSize: 4 # ZeroInitSize needs to be 4 bytes minimum
71+
- Name: Expected2 # Buffer which stores the expected result of our test
72+
Format: Float16
73+
Stride: 2
74+
Data: [ 0x1, 0x2 ]
6775
Results: # Using Result can verify test values without filecheck
6876
- Result: Test1
6977
Rule: BufferFuzzy # Rule which can be used to compare Float Buffers; They are compared within a ULP range

lib/Support/Check.cpp

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515

1616
static bool isDenorm(float F) { return std::fpclassify(F) == FP_SUBNORMAL; }
1717

18+
static bool isFloat16NAN(uint16_t Val) {
19+
return (Val & 0x7c00) == 0x7c00 && (Val & 0x03ff) != 0;
20+
}
21+
1822
static bool compareFloatULP(const float &FSrc, const float &FRef,
1923
unsigned ULPTolerance, offloadtest::DenormMode DM) {
2024
if (FSrc == FRef)
2125
return true;
22-
if (std::isnan(FSrc))
23-
return std::isnan(FRef);
26+
if (std::isnan(FSrc) || std::isnan(FRef))
27+
return std::isnan(FRef) && std::isnan(FSrc);
2428
if (DM == offloadtest::DenormMode::Any) {
2529
// If denorm expected, output can be sign preserved zero. Otherwise output
2630
// should pass the regular ulp testing.
@@ -34,6 +38,18 @@ static bool compareFloatULP(const float &FSrc, const float &FRef,
3438
return AbsDiff <= ULPTolerance;
3539
}
3640

41+
static bool compareFloat16ULP(const uint16_t &FSrc, const uint16_t &FRef,
42+
unsigned ULPTolerance) {
43+
if (FSrc == FRef)
44+
return true;
45+
if (isFloat16NAN(FSrc) || isFloat16NAN(FRef))
46+
return isFloat16NAN(FRef) && isFloat16NAN(FSrc);
47+
// 16-bit floating point numbers must preserve denorms
48+
int Diff = FSrc - FRef;
49+
unsigned int AbsDiff = Diff < 0 ? -Diff : Diff;
50+
return AbsDiff <= ULPTolerance;
51+
}
52+
3753
static bool testBufferExact(offloadtest::Buffer *B1, offloadtest::Buffer *B2) {
3854
if (B1->size() != B2->size())
3955
return false;
@@ -50,18 +66,33 @@ static bool testBufferFuzzy(offloadtest::Buffer *B1, offloadtest::Buffer *B2,
5066
case offloadtest::DataFormat::Float32: {
5167
if (B1->Size != B2->Size)
5268
return false;
53-
llvm::MutableArrayRef<float> Arr1(reinterpret_cast<float *>(B1->Data.get()),
54-
B1->Size / sizeof(float));
69+
llvm::ArrayRef<float> Arr1(reinterpret_cast<float *>(B1->Data.get()),
70+
B1->Size / sizeof(float));
5571
assert(B2->Format == offloadtest::DataFormat::Float32 &&
5672
"Buffer types must be the same");
57-
llvm::MutableArrayRef<float> Arr2(reinterpret_cast<float *>(B2->Data.get()),
58-
B2->Size / sizeof(float));
59-
for (unsigned I = 0; I < Arr1.size(); ++I) {
73+
llvm::ArrayRef<float> Arr2(reinterpret_cast<float *>(B2->Data.get()),
74+
B2->Size / sizeof(float));
75+
for (unsigned I = 0, E = Arr1.size(); I < E; ++I) {
6076
if (!compareFloatULP(Arr1[I], Arr2[I], ULPT, DM))
6177
return false;
6278
}
6379
return true;
6480
}
81+
case offloadtest::DataFormat::Float16: {
82+
if (B1->Size != B2->Size)
83+
return false;
84+
llvm::ArrayRef<uint16_t> Arr1(reinterpret_cast<uint16_t *>(B1->Data.get()),
85+
B1->Size / sizeof(uint16_t));
86+
assert(B2->Format == offloadtest::DataFormat::Float16 &&
87+
"Buffer types must be the same");
88+
llvm::ArrayRef<uint16_t> Arr2(reinterpret_cast<uint16_t *>(B2->Data.get()),
89+
B2->Size / sizeof(uint16_t));
90+
for (unsigned I = 0, E = Arr1.size(); I < E; ++I) {
91+
if (!compareFloat16ULP(Arr1[I], Arr2[I], ULPT))
92+
return false;
93+
}
94+
return true;
95+
}
6596
default:
6697
llvm_unreachable("Only float types are supported by the fuzzy test.");
6798
}

lib/Support/Pipeline.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
using namespace offloadtest;
1515

16+
bool isFloatingPointFormat(DataFormat Format) {
17+
return Format == DataFormat::Float16 || Format == DataFormat::Float32;
18+
}
19+
1620
namespace llvm {
1721
namespace yaml {
1822
void MappingTraits<offloadtest::Pipeline>::mapping(IO &I,
@@ -41,9 +45,9 @@ void MappingTraits<offloadtest::Pipeline>::mapping(IO &I,
4145
R.ExpectedPtr = P.getBuffer(R.Expected);
4246
if (!R.ExpectedPtr)
4347
I.setError(Twine("Reference buffer ") + R.Expected + " not found!");
44-
if (R.Rule == offloadtest::Rule::BufferFuzzy) {
45-
if (R.ActualPtr->Format != offloadtest::DataFormat::Float32 ||
46-
R.ExpectedPtr->Format != offloadtest::DataFormat::Float32)
48+
if (R.Rule == Rule::BufferFuzzy) {
49+
if (!isFloatingPointFormat(R.ActualPtr->Format) ||
50+
!isFloatingPointFormat(R.ExpectedPtr->Format))
4751
I.setError(Twine("BufferFuzzy only accepts Float buffers"));
4852
}
4953
}
@@ -127,7 +131,7 @@ void MappingTraits<offloadtest::Buffer>::mapping(IO &I,
127131
DATA_CASE(Int16, int16_t)
128132
DATA_CASE(Int32, int32_t)
129133
DATA_CASE(Int64, int64_t)
130-
DATA_CASE(Float16, uint16_t)
134+
DATA_CASE(Float16, llvm::yaml::Hex16)
131135
DATA_CASE(Float32, float)
132136
DATA_CASE(Float64, double)
133137
DATA_CASE(Bool, uint32_t) // Because sizeof(bool) is 1 but HLSL represents a bool using 4 bytes.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#--- source.hlsl
2+
3+
RWStructuredBuffer<half> Out1 : register(u0);
4+
RWStructuredBuffer<half> Out2 : register(u1);
5+
RWStructuredBuffer<half> Out3 : register(u2);
6+
RWStructuredBuffer<half> Out4 : register(u3);
7+
8+
[numthreads(1,1,1)]
9+
void main() {
10+
Out1[0] = (half)3.14159265;
11+
Out2[0] = (half)(0.0 / 0.0); // Should be NaN
12+
Out3[0] = (half)5.40234375;
13+
Out4[0] = (half)6.40234375;
14+
}
15+
16+
//--- pipeline.yaml
17+
18+
---
19+
Shaders:
20+
- Stage: Compute
21+
Entry: main
22+
DispatchSize: [1, 1, 1]
23+
Buffers:
24+
- Name: Out1
25+
Format: Float16
26+
Stride: 2
27+
ZeroInitSize: 4
28+
- Name: Expected1
29+
Format: Float16
30+
Stride: 2
31+
Data: [ 0x4248, 0 ]
32+
- Name: Out2
33+
Format: Float16
34+
Stride: 2
35+
ZeroInitSize: 4
36+
- Name: Expected2
37+
Format: Float16
38+
Stride: 2
39+
Data: [ 0x7E00, 0 ] # Should be NaN
40+
- Name: Out3
41+
Format: Float16
42+
Stride: 2
43+
ZeroInitSize: 4
44+
- Name: Expected3
45+
Format: Float16
46+
Stride: 2
47+
Data: [ 0x4566, 0 ] # Should be 1 ulp away
48+
- Name: Out4
49+
Format: Float16
50+
Stride: 2
51+
ZeroInitSize: 4
52+
- Name: Expected4
53+
Format: Float16
54+
Stride: 2
55+
Data: [ 0x4665, 0 ] # Should be 2 ulp away
56+
Results:
57+
- Result: Test1 # Test two values are exactly the same
58+
Rule: BufferFuzzy
59+
ULPT: 0 # ulp shouldn't matter for this test
60+
Actual: Out1
61+
Expected: Expected1
62+
- Result: Test2 # Test both are NaN
63+
Rule: BufferFuzzy
64+
ULPT: 0 # ulp shouldn't matter for this test
65+
Actual: Out2
66+
Expected: Expected2
67+
- Result: Test3 # Actual and Expected are not the same but they are within the ULPT
68+
Rule: BufferFuzzy
69+
ULPT: 1
70+
Actual: Out3
71+
Expected: Expected3
72+
- Result: Test4 # Actual and Expected are not the same but they are within the ULPT
73+
Rule: BufferFuzzy
74+
ULPT: 2
75+
Actual: Out4
76+
Expected: Expected4
77+
DescriptorSets:
78+
- Resources:
79+
- Name: Out1
80+
Kind: RWStructuredBuffer
81+
DirectXBinding:
82+
Register: 0
83+
Space: 0
84+
VulkanBinding:
85+
Binding: 0
86+
- Name: Out2
87+
Kind: RWStructuredBuffer
88+
DirectXBinding:
89+
Register: 1
90+
Space: 0
91+
VulkanBinding:
92+
Binding: 1
93+
- Name: Out3
94+
Kind: RWStructuredBuffer
95+
DirectXBinding:
96+
Register: 2
97+
Space: 0
98+
VulkanBinding:
99+
Binding: 2
100+
- Name: Out4
101+
Kind: RWStructuredBuffer
102+
DirectXBinding:
103+
Register: 3
104+
Space: 0
105+
VulkanBinding:
106+
Binding: 3
107+
...
108+
#--- end
109+
110+
# REQUIRES: Half
111+
112+
# UNSUPPORTED: Clang-Vulkan
113+
# RUN: split-file %s %t
114+
# RUN: %dxc_target -enable-16bit-types -T cs_6_5 -Fo %t.o %t/source.hlsl
115+
# RUN: %offloader %t/pipeline.yaml %t.o
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#--- source.hlsl
2+
3+
RWStructuredBuffer<half> Out1 : register(u0);
4+
RWStructuredBuffer<half> Out2 : register(u1);
5+
6+
[numthreads(1,1,1)]
7+
void main() {
8+
Out1[0] = (half)20.3;
9+
Out1[1] = (half)5.0;
10+
Out2[0] = (half)0.0;
11+
}
12+
13+
//--- pipeline.yaml
14+
15+
---
16+
Shaders:
17+
- Stage: Compute
18+
Entry: main
19+
DispatchSize: [1, 1, 1]
20+
Buffers:
21+
- Name: Out1
22+
Format: Float16
23+
Stride: 2
24+
ZeroInitSize: 4
25+
- Name: Expected1
26+
Format: Float16
27+
Stride: 2
28+
Data: [ 0x3E00, 0x4100 ]
29+
- Name: Out2
30+
Format: Float16
31+
Stride: 2
32+
ZeroInitSize: 4
33+
- Name: Expected2
34+
Format: Float16
35+
Stride: 2
36+
Data: [ 0x0001, 0 ]
37+
Results:
38+
- Result: Test1
39+
Rule: BufferFuzzy
40+
ULPT: 1
41+
Actual: Out1
42+
Expected: Expected1
43+
- Result: Test2 # Denorm value
44+
Rule: BufferFuzzy
45+
ULPT: 0
46+
Actual: Out2
47+
Expected: Expected2
48+
DescriptorSets:
49+
- Resources:
50+
- Name: Out1
51+
Kind: RWStructuredBuffer
52+
DirectXBinding:
53+
Register: 0
54+
Space: 0
55+
VulkanBinding:
56+
Binding: 0
57+
- Name: Out2
58+
Kind: RWStructuredBuffer
59+
DirectXBinding:
60+
Register: 1
61+
Space: 0
62+
VulkanBinding:
63+
Binding: 1
64+
...
65+
#--- end
66+
67+
# REQUIRES: Half
68+
69+
# UNSUPPORTED: Clang-Vulkan
70+
# RUN: split-file %s %t
71+
# RUN: %dxc_target -enable-16bit-types -T cs_6_5 -Fo %t.o %t/source.hlsl
72+
# RUN: not %offloader %t/pipeline.yaml %t.o 2>&1 | FileCheck %s
73+
74+
# CHECK: Test failed: Test1
75+
# CHECK: Expected:
76+
# CHECK: ---
77+
# CHECK: Name: Expected1
78+
# CHECK: Format: Float16
79+
# CHECK: Stride: 2
80+
# CHECK: Data: [ 0x3E00, 0x4100 ]
81+
# CHECK: OutputProps:
82+
# CHECK: Height: 0
83+
# CHECK: Width: 0
84+
# CHECK: Depth: 0
85+
# CHECK: ...
86+
# CHECK: Got:
87+
# CHECK: ---
88+
# CHECK: Name: Out1
89+
# CHECK: Format: Float16
90+
# CHECK: Stride: 2
91+
# CHECK: Data: [ 0x4D13, 0x4500 ]
92+
# CHECK: OutputProps:
93+
# CHECK: Height: 0
94+
# CHECK: Width: 0
95+
# CHECK: Depth: 0
96+
97+
# CHECK: Test failed: Test2
98+
# CHECK: Expected:
99+
# CHECK: ---
100+
# CHECK: Name: Expected2
101+
# CHECK: Format: Float16
102+
# CHECK: Stride: 2
103+
# CHECK: Data: [ 0x1, 0x0 ]
104+
# CHECK: OutputProps:
105+
# CHECK: Height: 0
106+
# CHECK: Width: 0
107+
# CHECK: Depth: 0
108+
# CHECK: ...
109+
# CHECK: Got:
110+
# CHECK: ---
111+
# CHECK: Name: Out2
112+
# CHECK: Format: Float16
113+
# CHECK: Stride: 2
114+
# CHECK: Data: [ 0x0, 0x0 ]
115+
# CHECK: OutputProps:
116+
# CHECK: Height: 0
117+
# CHECK: Width: 0
118+
# CHECK: Depth: 0

0 commit comments

Comments
 (0)