-
Notifications
You must be signed in to change notification settings - Fork 178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Floating point compressor: write tests that test special floating point cases #3384
base: dev
Are you sure you want to change the base?
Floating point compressor: write tests that test special floating point cases #3384
Conversation
This pull request has been linked to Shortcut Story #19397: Floating point compressor: write tests that test special floating point cases. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think subnormals should be allowed. We can discuss this further if needed.
double scale = 2.53; | ||
double offset = 0.138; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why test with just these specific values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried playing with random values just now, and found out that the compiler optimizes the result out when the values are too large, causing a test failure that is not actually my float scaling filter codes fault, but the tests fault. I wish there was a way to compile tests with less than O3 but alas...
BTW, this is only problematic in the CPP API test suite, not the tile one.
I have submitted a change where the values are between -64 and 64, and this seems to pass. Perhaps we should investigate this further. I think I talked to Isaiah about this already and he just said to note that this is a lossy compressor...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the compiler optimizes the result out when the values are too large
That really doesn't sound right. Do you have specific values that were generated that we causing failures? (If not, it illustrates why using non-repeatable random numbers is bad for testing and using seedable, repeatable pseudorandom sequences is good for testing.)
"Too large" with scaling could mean that you're getting overflow, in which case the testing is correctly identifying a defect in the compressor, failing to recognize overflow and failing on compression, which is better than writing out bad data without a failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked the original PR #3243. It has an overflow/underflow defect. There's no checking at all for either condition. The reason the error is not showing up with fixed test parameters is that the data is within the realm of validity for the filter parameters.
A simple example. With 56-bit mantissas (double
), scale = 1 and offset = 2^11, then data max_float - (2^10)
will have an addition overflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it better that I check for overflow/underflow with https://en.cppreference.com/w/cpp/numeric/fenv/feclearexcept or manually?
…lekim/sc-19397/floating-point-compressor-write-tests-that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few implicit integer to floating-point compiler warnings that block compiling on clang when warnings-as-errors is on.
@@ -87,6 +90,19 @@ Status FloatScalingFilter::run_forward( | |||
const T* part_data = static_cast<const T*>(i.data()); | |||
for (uint32_t j = 0; j < num_elems_in_part; ++j) { | |||
T elem = part_data[j]; | |||
|
|||
// We should only handle numbers that are either normalized or 0. | |||
switch (std::fpclassify(elem)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abigalekim this check needs to happen after the computation, and we need to check that the classification of the input matches the classification of the output (nan -> nan, infinite -> infinite)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(per @eric-hughes-tiledb -- we also need to check for integer overflow, if not done already)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are addressing overflow in a different PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, also how? We shouldn't store the input data, and since we're converting and storing as integers, it doesn't really make sense to support infinite/NaN numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check fpclassify(elem) == fpclassify( [the floating point expression inside round ] )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because of this change, zeros and denormalized array tests fail. is this expected? should I make the change in this PR? There are other PRs coming up to address this specific change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There remain major problems with this PR. The following are all blocking problems for me. I will continue to request changes on this PR until they're fixed.
- There's no documentation about which tests do what. These tests are subtle enough that we need documentation, both for the tests and the test support classes.
- The documentation needs to include inequalities that show that the generated data won't overflow if it's not supposed to overflow (or will overflow if that's what's being tested).
- There's a large amount of copypasta. Even some of the comments are copypasta. It's a bad idea to make a maintenance burden in fresh code. Where there's copypasta, it needs to be turned into support functions or classes.
- The constructor of a PRNG class will need a seed value as an argument.
- For tests with slightly different tests inside basically the same loop, a lambda is appropriate to instantiate the difference.
- Problems with random numbers to generate test cases:
- Most importantly, there are tests that use non-repeatable seeds for PRNG. These are unacceptable in unit tests, exactly because they are non-repeatable. One of the consequences of this is that any reference to
std::random_device
needs to be eliminated.- Printing out a seed is not an acceptable substitute by itself. Perhaps in concert with other mechanisms to make replication reliable and straightforward it would be; I'd need to see it.
- Continued use of
std::mt19937
, a 32-bit generator, to generate 64-length numbers. Usestd::mt19937_64
.
- Most importantly, there are tests that use non-repeatable seeds for PRNG. These are unacceptable in unit tests, exactly because they are non-repeatable. One of the consequences of this is that any reference to
- Non-C.41 use of
set_option
. The filter class already has a good constructor; use it.
|
||
f.set_option(TILEDB_SCALE_FLOAT_BYTEWIDTH, &byte_width) | ||
.set_option(TILEDB_SCALE_FLOAT_FACTOR, &scale) | ||
.set_option(TILEDB_SCALE_FLOAT_OFFSET, &offset); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use non-C.41 functions like set_option
when a C.41 constructor is available. Generate PRNG-originated test values first and construct the filter object (with the full constructor) second.
f.set_option(TILEDB_SCALE_FLOAT_OFFSET, &offset); | ||
INFO( | ||
"Scale: " + std::to_string(scale) + ", Offset: " + | ||
std::to_string(offset) + ", Byte Width: " + std::to_string(byte_width)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Printing out the seeds is not a substitute for using a fixed seed. Replication of an error would require manual intervention in a debugger or custom code, neither of which counts as "actually repeatable".
T dis_max = std::min( | ||
std::numeric_limits<T>::max(), | ||
static_cast<T>(std::numeric_limits<W>::max())); | ||
std::uniform_real_distribution<T> dis(dis_min, dis_max); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This distribution has two problems.
- It's going to overflow on generated values in all cases. The floating scale is in the interval [-64, 64], so value within [max/64,max] will overflow on multiplication. The offset adds to the complication. This all needs documentation with an inequality estimate showing that the generated values won't generate an overflow.
- It's going to overflow on generated values for certain type arguments
T
andW
, for exampledouble
andint8_t
.
If you're not seeing failures, it's that you're getting lucky and not generating enough values. int dim_hi = 10
and that means only 100 values are being generated, which isn't enough to see problems.
test/src/unit-filter-pipeline.cc
Outdated
@@ -3963,7 +3964,7 @@ TEST_CASE("Filter: Test encryption", "[filter][encryption]") { | |||
} | |||
|
|||
template <typename FloatingType, typename IntType> | |||
void testing_float_scaling_filter() { | |||
void testing_float_scaling_filter(bool negative) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding an argument without using it?
test/src/unit-filter-pipeline.cc
Outdated
@@ -3963,7 +3964,7 @@ TEST_CASE("Filter: Test encryption", "[filter][encryption]") { | |||
} | |||
|
|||
template <typename FloatingType, typename IntType> | |||
void testing_float_scaling_filter() { | |||
void testing_float_scaling_filter(bool negative) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is extremely poorly named. What's it testing?
It needs both a new name and documentation.
} | ||
} | ||
|
||
TEMPLATE_TEST_CASE( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these tests belong in unit-filter-pipeline
. They're not testing FilterPipeline
test/src/unit-filter-pipeline.cc
Outdated
|
||
Tile tile; | ||
Datatype t = Datatype::FLOAT32; | ||
switch (sizeof(FloatingType)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This switch statement is copypasta. Figure out how to get rid of it.
test/src/unit-filter-pipeline.cc
Outdated
FilterPipeline pipeline; | ||
ThreadPool tp(4); | ||
CHECK(pipeline.add_filter(FloatScalingFilter()).ok()); | ||
pipeline.get_filter<FloatScalingFilter>()->set_option( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More non-C.41
There's more. That's all I have time for this morning. |
Here, I write extra test cases that test special types of floating point numbers for the float scaling filter.
Test cases to write:
TYPE: IMPROVEMENT
DESC: Floating point compressor additional testing