Skip to content
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

[ntuple] add Real32Quant column type #16390

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

silverweed
Copy link
Contributor

@silverweed silverweed commented Sep 9, 2024

This Pull request:

adds the Real32Quant column type to RNTuple. This column type stores floating point values on disk as integers with a user-defined precision (from 3 to 32 bits) and a user-defined value range. This allows to reduce the storage space required to save floats with a well-defined range with more precision than a simple truncation.

The conversion is defined as (pseudocode):

def quantize(value, min, max, n_precision_bits)
{
  quantized_max = (1 << n_precision_bits) - 1;
  scale = quantized_max / (max - min);
  quantized = round((value - min) * scale);
  return quantized;
}

This change requires adding metadata to the on-disk information, more specifically in the Field Description (see specifications.md for more details).

Checklist:

  • tested changes locally
  • updated the docs (if necessary)

@silverweed silverweed self-assigned this Sep 9, 2024
@silverweed silverweed force-pushed the ntuple_quantfloat_2 branch 3 times, most recently from cde7f5a to ee5a38d Compare September 9, 2024 13:52
@silverweed silverweed marked this pull request as draft September 10, 2024 07:58
@silverweed silverweed force-pushed the ntuple_quantfloat_2 branch 3 times, most recently from eadb32d to 3c1d6f6 Compare September 10, 2024 12:43
Copy link

github-actions bot commented Sep 10, 2024

Test Results

    13 files      13 suites   3d 3h 43m 58s ⏱️
 2 697 tests  2 697 ✅ 0 💤 0 ❌
32 953 runs  32 953 ✅ 0 💤 0 ❌

Results for commit 64700ac.

♻️ This comment has been updated with latest results.

@silverweed silverweed force-pushed the ntuple_quantfloat_2 branch 2 times, most recently from ae7a8ee to 5519b91 Compare September 11, 2024 06:40
@silverweed silverweed marked this pull request as ready for review September 11, 2024 07:34
@hahnjo hahnjo added this to the 6.34.00 milestone Sep 11, 2024
Copy link
Contributor

@jblomer jblomer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! In principle looks good to me.

tree/ntuple/v7/doc/specifications.md Outdated Show resolved Hide resolved
tree/ntuple/v7/doc/specifications.md Outdated Show resolved Hide resolved
tree/ntuple/v7/doc/specifications.md Outdated Show resolved Hide resolved
void SetTruncated(std::size_t nBits);
/// Sets this field to use a quantized integer representation using `nBits` per value.
/// This call promises that this field will only contain values contained in `[minValue, maxValue]` inclusive.
/// If a value outside this range is assigned to this field, the behavior is undefined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we define it? I.e. test and throw?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep the specification as vague as possible to give us room for implementing it in the most performant way possible. Maybe this is not necessary, but I think we should have a discussion about it before settling for anything other than UB - we can always specify more in the future, but not go back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine for the specification. But in our current implementation, should we / will we throw?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 6526106

tree/ntuple/v7/src/RColumnElement.hxx Show resolved Hide resolved
@silverweed silverweed force-pushed the ntuple_quantfloat_2 branch 2 times, most recently from ee0dc36 to 3a9c90c Compare September 16, 2024 08:44
@pcanal
Copy link
Member

pcanal commented Sep 16, 2024

This column type stores floating point values on disk as integers with a user-defined precision (from 3 to 32 bits) and a user-defined value range.

This sounds very similar to Double32 ... We will need to explain clearly the differences and advantages ....

Copy link
Contributor

@jblomer jblomer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! I'll leave the question where to throw in the Unpack() call stack up to you.

Copy link
Member

@pcanal pcanal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend holding off on merging until we have documented/understood the differences (if any) between this implementation and the related Double32_t implementation (capability and resulting onfile precision)

@silverweed
Copy link
Contributor Author

@pcanal The implementation is, logically speaking, exactly the same (see TBufferFile.cxx:Read/WriteDouble32.
The main difference is design-wise, for the fact that RNTuple's quantization is:

  • not bound to the type of the variable (you can quantize any float or double, not just a Double32_t
  • not statically chosen (you can set the value range and the bit width at runtime rather than deciding once and for all via the variable comments).

We can discuss in more details, but in my opinion those are the two main points of difference. The implementation itself is trivial and it's akin to Read/WriteFastArrayDouble32 (but slightly more performant in principle, as it doesn't have to check the min/max/scale factor for each element - they are all the same within a call to Pack/Unpack).

As a last divergence point, Double32_t silently clamps the values that fall our of range, while Real32Quant will throw in that situation.

@pcanal
Copy link
Member

pcanal commented Sep 19, 2024

(but slightly more performant in principle, as it doesn't have to check the min/max/scale factor for each element - they are all the same within a call to Pack/Unpack).

Isn't it the already same in the in Read/WriteFastArrayDouble32 ? (Furthermore for reading the common case is to call TBufferFile::ReadWithFactor/ReadWithNbits)

The implementation is, logically speaking, exactly the same

Thank you for clarifying :)

As a last divergence point, Double32_t silently clamps the values that fall our of range, while Real32Quant will throw in that situation.

(If not already done) this should be called out in the doc.

@silverweed
Copy link
Contributor Author

silverweed commented Sep 20, 2024

Isn't it the already same in the in Read/WriteFastArrayDouble32?

Yes, I had misread the function the first time, you are right. But anyway performance is not really our concern as regards our motivation for Real32Quant :)

(If not already done) this should be called out in the doc.

Our idea is, at least for now, to say that the behavior in case of out-of-range is undefined, because we don't want to preclude ourselves to change our mind or our implementation in the future. At the moment we throw an exception but we might decide something different in the future, e.g. for performance reasons.
You can see the exact wording in this very commit (in the specifications.md file), so if you have some specific suggestion feel free to comment on it.

Edit: I was wrong, we're currently not specifying the behavior in the specifications.md but only in the code. Should we also explicitly say the behavior is undefined in the specs? I'm not sure it makes sense because the specs in principle only refer to the binary format; should they also say how a writer should behave when receiving wrong input from the user? @jblomer thoughts about this?

@jblomer
Copy link
Contributor

jblomer commented Sep 20, 2024

Edit: I was wrong, we're currently not specifying the behavior in the specifications.md but only in the code. Should we also explicitly say the behavior is undefined in the specs? I'm not sure it makes sense because the specs in principle only refer to the binary format; should they also say how a writer should behave when receiving wrong input from the user? @jblomer thoughts about this?

I think that the specification is not the ideal place for that sort of error behavior documentation. I'd suggest a brief section in the architecture.md that summarizes the low-precision float options in RNTuple and highlights the differences and similarities to the already existing Double32_t and Float16_t.

@jblomer
Copy link
Contributor

jblomer commented Sep 23, 2024

@pcanal is there still anything blocking this PR?

Copy link
Member

@pcanal pcanal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants