Repository navigation
GH-533: Add ALP (Adaptive Lossless floating-Point) encoding specification - #557
Conversation
|
|
||
| ##### Header (7 bytes) | ||
|
|
||
| All multi-byte values are little-endian. |
There was a problem hiding this comment.
| All multi-byte values are little-endian. | |
| All multi-byte values are stored in little-endian order. |
| |--------|-------|------|------|-------------| | ||
| | 0 | compression_mode | 1 byte | uint8 | Compression mode (must be 0 = ALP) | | ||
| | 1 | integer_encoding | 1 byte | uint8 | Integer encoding (must be 0 = FOR + bit-packing) | | ||
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in \[3, 15\]. Default: 10 (vector size 1024) | |
There was a problem hiding this comment.
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in \[3, 15\]. Default: 10 (vector size 1024) | | |
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in \[3, 15\]. Recommended default: 10 (vector size 1024) | |
|
|
||
| | Offset | Field | Size | Type | Description | | ||
| |--------|-------|------|------|-------------| | ||
| | 0 | compression_mode | 1 byte | uint8 | Compression mode (must be 0 = ALP) | |
There was a problem hiding this comment.
we think ALP-RD fits in well here? I forget what the extension point is and why we were OK keeping this field, but not version.
There was a problem hiding this comment.
changing the code to read ALP's newer laid out bits would be difficult to apply across all implementations. but AlpRD is a completely newer implementations.
Added a note to the field description.
| |--------|-------|------|------|-------------| | ||
| | 0 | compression_mode | 1 byte | uint8 | Compression mode (must be 0 = ALP) | | ||
| | 1 | integer_encoding | 1 byte | uint8 | Integer encoding (must be 0 = FOR + bit-packing) | | ||
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in \[3, 15\]. Default: 10 (vector size 1024) | |
There was a problem hiding this comment.
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in \[3, 15\]. Default: 10 (vector size 1024) | | |
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in the inclusive range: \[3, 15\]. Default: 10 (vector size 1024) | |
| **Note:** The number of elements per vector and the packed data size are NOT stored | ||
| in the header. They are derived: | ||
| * Elements per vector: `vector_size` for all vectors except the last, which may be smaller. | ||
| * Packed data size: `ceil(num_elements_in_vector * bit_width / 8)`. |
There was a problem hiding this comment.
bit_width isn't in the header either, so it is a little strange to call ths out here?
There was a problem hiding this comment.
I also hihglighted this as confusing when reading the spec. I recommend removing this sentence as the packed data size is covered in the "Vector Format" section below
There was a problem hiding this comment.
comment regarding packed data size has been removed.
|
|
||
| **Note:** The number of elements per vector and the packed data size are NOT stored | ||
| in the header. They are derived: | ||
| * Elements per vector: `vector_size` for all vectors except the last, which may be smaller. |
There was a problem hiding this comment.
This seems like a little bit of a strange callout since it is covered on line 457 explicitly. and log_vector_size is stored in the header?
There was a problem hiding this comment.
I agree this is redundant -- I think leaving the note is valuable clarification
Note: The number of elements per vector and the packed data size are NOT stored
in the header, they are derived
However the other items are not
There was a problem hiding this comment.
Removed the packed data size bullet, kept the elements-per-vector clarification.
| values. Each offset gives the byte position of the corresponding vector's data, | ||
| measured from the start of the offset array itself. | ||
|
|
||
| The first offset equals `num_vectors * 4` (pointing just past the offset array). |
There was a problem hiding this comment.
| The first offset equals `num_vectors * 4` (pointing just past the offset array). | |
| The first offset always equals `num_vectors * 4` (pointing just past the offset array). |
Lets be explicitly here that we don't support padding.
| Data section sizes: | ||
| | Section | Size Formula | Description | | ||
| |---------------------|-----------------------------|------------------------------| | ||
| | PackedValues | ceil(N * bit\_width / 8) | Bit-packed delta values | |
There was a problem hiding this comment.
| | PackedValues | ceil(N * bit\_width / 8) | Bit-packed delta values | | |
| | PackedValues | ceil(`vector_size` * bit\_width / 8) | Bit-packed delta values | |
| |---------------------|-----------------------------|------------------------------| | ||
| | PackedValues | ceil(N * bit\_width / 8) | Bit-packed delta values | | ||
| | ExceptionPositions | num\_exceptions * 2 bytes | uint16 indices of exceptions | | ||
| | ExceptionValues | num\_exceptions * sizeof(T) | Original float/double values | |
There was a problem hiding this comment.
| | ExceptionValues | num\_exceptions * sizeof(T) | Original float/double values | | |
| | ExceptionValues | num\_exceptions * sizeof(encoded type) (float=4 and double=8) | Original float/double values | |
|
|
||
| The FOR-encoded deltas, bit-packed into `ceil(num_elements_in_vector * bit_width / 8)` bytes. | ||
| Values are packed from the least significant bit of each byte to the most significant bit, | ||
| in groups of 8 values, using the same bit-packing order as the |
There was a problem hiding this comment.
Where does the group of 8 values come in? Wouldn't this messup the number of bytes math?
There was a problem hiding this comment.
Good catch — removed 'groups of 8' phrasing. Now simply references the same LSB-first packing order as RLE/Bit-Packing Hybrid.
(I think RleBitPackHybrid was in my mind at that point :) )
| The encoding uses two separate multiplications (not a single multiplication by | ||
| `10^(e-f)`, and not division) to ensure that implementations produce identical | ||
| floating-point rounding across languages. The powers of 10 MUST be stored as | ||
| precomputed floating-point constants (i.e., literal values like `1e-3f`), not |
There was a problem hiding this comment.
Why can't they be precomputed at runtime?
There was a problem hiding this comment.
You're right — this shouldn't mandate literals vs runtime computation. Reworded to require that encoder and decoder use identical power-of-10 values, without prescribing how they're obtained.
| | Type | Magic Number | Formula | | ||
| |--------|-----------------------------------|----------------------------------| | ||
| | FLOAT | 2^22 + 2^23 = 12,582,912 | `(int)((value + magic) - magic)` | | ||
| | DOUBLE | 2^51 + 2^52 = 6,755,399,441,055,744 | `(long)((value + magic) - magic)` | |
There was a problem hiding this comment.
| | DOUBLE | 2^51 + 2^52 = 6,755,399,441,055,744 | `(long)((value + magic) - magic)` | | |
| | DOUBLE | 2^51 + 2^52 = 6,755,399,441,055,744 | `(int64_t)((value + magic) - magic)` | |
|
|
||
| | Type | Magic Number | Formula | | ||
| |--------|-----------------------------------|----------------------------------| | ||
| | FLOAT | 2^22 + 2^23 = 12,582,912 | `(int)((value + magic) - magic)` | |
There was a problem hiding this comment.
| | FLOAT | 2^22 + 2^23 = 12,582,912 | `(int)((value + magic) - magic)` | | |
| | FLOAT | 2^22 + 2^23 = 12,582,912 | `(int32_t)((value + magic) - magic)` | |
| ``` | ||
| +-------------------------------------------------------------------+ | ||
| | | | ||
| | encoded = round( value * 10^e * 10^(-f) ) | |
There was a problem hiding this comment.
| | encoded = round( value * 10^e * 10^(-f) ) | | |
| | encoded = fast_round( value * 10^e * 10^(-f) ) | |
alamb
left a comment
There was a problem hiding this comment.
Thank you @prtkgaur and @emkornfield -- I started going through this proposal as well. I haven't made it through the entire thing, but I left commands on what I have made it through first.
@prtkgaur how would you like to address feedback on this PR? Would you like to process the comments? I would also be happy to make a PR with proposed edits to your branch if that would be better. Please just let me know
| ALP works by converting floating-point values to integers using decimal scaling, | ||
| then applying Frame of Reference (FOR) encoding and bit-packing. Values that | ||
| cannot be losslessly converted are stored as exceptions. The encoding achieves | ||
| high compression for decimal-like floating-point data (e.g., monetary values, | ||
| sensor readings) while remaining fully lossless. |
There was a problem hiding this comment.
You have more summary at the end of the encoding, but I think a few more sentences in the intro would help people understand this more easily
| ALP works by converting floating-point values to integers using decimal scaling, | |
| then applying Frame of Reference (FOR) encoding and bit-packing. Values that | |
| cannot be losslessly converted are stored as exceptions. The encoding achieves | |
| high compression for decimal-like floating-point data (e.g., monetary values, | |
| sensor readings) while remaining fully lossless. | |
| ALP works by converting floating-point values to integers using decimal scaling, | |
| then applying Frame of Reference (FOR) encoding and bit-packing. Values that | |
| cannot be losslessly converted are stored as exceptions. The encoding achieves | |
| high compression for decimal-like floating-point data (e.g., monetary values, | |
| sensor readings) while remaining fully lossless. Values do not depend on | |
| each other, which enables quick random access and parallel encode/decode. |
There was a problem hiding this comment.
Agreed — added sentence about value independence enabling random access and parallel encode/decode.
|
|
||
| **Note:** The number of elements per vector and the packed data size are NOT stored | ||
| in the header. They are derived: | ||
| * Elements per vector: `vector_size` for all vectors except the last, which may be smaller. |
There was a problem hiding this comment.
I agree this is redundant -- I think leaving the note is valuable clarification
Note: The number of elements per vector and the packed data size are NOT stored
in the header, they are derived
However the other items are not
| **Note:** The number of elements per vector and the packed data size are NOT stored | ||
| in the header. They are derived: | ||
| * Elements per vector: `vector_size` for all vectors except the last, which may be smaller. | ||
| * Packed data size: `ceil(num_elements_in_vector * bit_width / 8)`. |
There was a problem hiding this comment.
I also hihglighted this as confusing when reading the spec. I recommend removing this sentence as the packed data size is covered in the "Vector Format" section below
Incorporate review comments from emkornfield and alamb on PR apache#557:
|
I expect to have a second round of feedback tomorrow |
alamb
left a comment
There was a problem hiding this comment.
Ok, I made it through the spec again. I think it is really quite close
Let me know what you think about the comments and what you think abotu the idea of starting to draft a Blog post.
| | ------------------------------------- | -------------- | | ||
| | [Bit-packed (Deprecated)](#BITPACKED) | BIT_PACKED = 4 | | ||
|
|
||
| | Encoding | ID | Supported Types | |
There was a problem hiding this comment.
Something seems strange to me about this chart. The version on main
Lines 28 to 40 in 96edf77
Doesn't appear in this document.
I would expect this PR that adds the new ALP encoding to add a new entry to the existing table, rather than add an entirely new table.
Maybe we need to merge up from main to this branch (I am happy to do this and I think I have the permissions to push to your branch, but I don't want to mess things up on you)
There was a problem hiding this comment.
You're right, this branch was forked before the summary table was added in #550. Rebased onto upstream/master and added ALP as a row to the existing table.
|
|
||
| ##### Fast Rounding | ||
|
|
||
| The rounding function uses a "magic number" technique for branchless rounding: |
There was a problem hiding this comment.
I think it would help to use consistent terminology here to above.
| The rounding function uses a "magic number" technique for branchless rounding: | |
| The `fast_round` function uses a "magic number" technique for branchless rounding. | |
| `fast_round(value)` is defined as follows: |
There was a problem hiding this comment.
Done. Updated to use fast_round consistently and added "is defined as follows".
| | FLOAT | 2^22 + 2^23 = 12,582,912 | `(int32_t)((value + magic) - magic)` | | ||
| | DOUBLE | 2^51 + 2^52 = 6,755,399,441,055,744 | `(int64_t)((value + magic) - magic)` | | ||
|
|
||
| For negative values, the signs are reversed: `(int32_t)((value - magic) + magic)` for FLOAT, `(int64_t)((value - magic) + magic)` for DOUBLE. |
There was a problem hiding this comment.
I don't understand this sentence. The C++ implementation does not seem to change sign order from what I can tell:
/// \brief Convert a float to an int without rounding
static inline auto FastRound(T n) -> SignedExactType {
n = n + Constants::kMagicNumber - Constants::kMagicNumber;
return static_cast<SignedExactType>(n);
}Also the "Fast Rounding" section of the ALP paper doesn't mention sign reversal that I can find
There was a problem hiding this comment.
You're right -- the sign reversal was incorrect. The C++ implementation uses a single formula for both positive and negative values (n + magic - magic). Removed the paragraph.
| (the encoded integer of the first non-exception value, or 0 if all values | ||
| are exceptions) before FOR encoding. This keeps the FOR range tight. | ||
|
|
||
| ##### Frame of Reference and Bit-Packing |
There was a problem hiding this comment.
This section has a worked example of FOR / bitpacking so perhaps we could write it like
| ##### Frame of Reference and Bit-Packing | |
| ##### Example: Frame of Reference and Bit-Packing |
|
|
||
| ##### Frame of Reference and Bit-Packing | ||
|
|
||
| After decimal encoding and exception substitution: |
There was a problem hiding this comment.
| After decimal encoding and exception substitution: | |
| Given the following data after decimal encoding and exception substitution: |
| + sum(vector_bytes for each vector) // all vectors | ||
| ``` | ||
|
|
||
| #### Constants Reference |
There was a problem hiding this comment.
This appears to be C/++ implementation details -- I am not sure it adds any value to the spec
| encoded size. However, ALP and Byte Stream Split can be complementary: ALP | ||
| exploits decimal structure while Byte Stream Split exploits byte-level correlation. | ||
|
|
||
| #### Size Calculations |
There was a problem hiding this comment.
I don't think we need to add add to the Parquet spec -- it isn't required to implement the encoder/decoder for ALP.
| converted are stored separately as *exceptions*. The encoding achieves high | ||
| compression for decimal-like floating-point data (e.g., monetary values, sensor | ||
| readings) while remaining fully lossless. Each value is encoded independently, | ||
| enabling random access to individual vectors and parallel encode/decode. |
There was a problem hiding this comment.
| enabling random access to individual vectors and parallel encode/decode. | |
| enabling random access to individual values and parallel encode/decode. |
| floating-point rounding across languages. Implementations must ensure that the | ||
| encoder and decoder use identical power-of-10 values for a given exponent. |
There was a problem hiding this comment.
I don't understand what "use identical power-of-10 values" means. ALL encoders and decoders must to use the exact same values (and floating point arithmetic) as I understand it.
There was a problem hiding this comment.
Yes, what you said. Reworded to: "All implementations MUST use the exact same floating-point arithmetic and power-of-10 constants to guarantee cross-language interoperability."
| To avoid the cost of exhaustive search on every vector, implementations | ||
| SHOULD use sampling to select up to 5 candidate (exponent, factor) | ||
| combinations (the "encoding preset") at the start of each column chunk. | ||
| Each vector then searches only those 5 candidates. |
There was a problem hiding this comment.
I think SHOULD is too strong a word here. Maybe we could soften it to a suggestion
| To avoid the cost of exhaustive search on every vector, implementations | |
| SHOULD use sampling to select up to 5 candidate (exponent, factor) | |
| combinations (the "encoding preset") at the start of each column chunk. | |
| Each vector then searches only those 5 candidates. | |
| To avoid the cost of exhaustive search on every vector, implementations | |
| can use a sampling approach. One such approach, described in the paper, is to | |
| select up to 5 candidate (exponent, factor) combinations (the "encoding preset") | |
| at the start of each column chunk, and when encoding each vector, | |
| test each of the 5 candidates for the fewest exceptions. |
| readings) while remaining fully lossless. Each value is encoded independently, | ||
| enabling random access to individual vectors and parallel encode/decode. | ||
|
|
||
| #### Overview |
There was a problem hiding this comment.
The algorithm description is so long that I think it should be moved to a separate file that we would link to here. In this file we would just keeping the description that is before the Overview.
There was a problem hiding this comment.
Yes this makes sense. I too thought it became long and having a separate file would be good.
Let me take a stab at it after I address the above comments and get approval. Else the comment threads on github will get lost.
There was a problem hiding this comment.
Maybe similar to how it is done for bloom filter: https://github.com/apache/parquet-format/blob/master/BloomFilter.md
There was a problem hiding this comment.
I also think it would be ok to if we moved the content to a separate file as a follow on PR (after the spec change is approved) as in my mind moving the content to a separate file does not affect the content of the spec
There was a problem hiding this comment.
It seems we should do this as a follow on PR. I can do so after this PR is merged
There was a problem hiding this comment.
Got it. Let's address it in a follow up PR
There was a problem hiding this comment.
Filed a ticket to track
There was a problem hiding this comment.
PR to move the content:
Add the encoding specification for ALP (encoding value 10) to Encodings.md. ALP compresses FLOAT and DOUBLE columns by converting values to integers via decimal scaling, then applying Frame of Reference encoding and bit-packing. Values that cannot be losslessly round-tripped are stored as exceptions. The spec covers: - Page layout: 7-byte header, offset array, compressed vectors - Vector format: AlpInfo, ForInfo, packed values, exception data - Encoding math: two-step multiplication for cross-language consistency - Parameter selection, exception detection, and decoding steps Based on the paper "ALP: Adaptive Lossless floating-Point Compression" (Afroozeh and Boncz, SIGMOD 2024). Wire format matches the C++ Arrow and Java parquet-java implementations.
Incorporate review comments from emkornfield and alamb on PR apache#557:
| | [Delta-length byte array](#DELTALENGTH) | DELTA_LENGTH_BYTE_ARRAY = 6 | BYTE_ARRAY | | ||
| | [Delta Strings](#DELTASTRING) | DELTA_BYTE_ARRAY = 7 | BYTE_ARRAY, FIXED_LEN_BYTE_ARRAY | | ||
| | [Byte Stream Split](#BYTESTREAMSPLIT) | BYTE_STREAM_SPLIT = 9 | INT32, INT64, FLOAT, DOUBLE, FIXED_LEN_BYTE_ARRAY | | ||
| | [ALP](#ALP) | ALP = 10 | FLOAT, DOUBLE | |
There was a problem hiding this comment.
I've raised the same question in the google doc earlier. FP16 and FP8 have fewer bits so the benefit may not be that significant compared to regular float and double. But I agree that we can discuss these in the future to not expand and thus block current spec design.
| converted are stored separately as *exceptions*. The encoding achieves high | ||
| compression for decimal-like floating-point data (e.g., monetary values, sensor | ||
| readings) while remaining fully lossless. Each value is encoded independently, | ||
| enabling random access to individual values and parallel encode/decode. |
There was a problem hiding this comment.
| enabling random access to individual values and parallel encode/decode. | |
| enabling random access to individual values and parallel encoding/decoding. |
| +-------------+------+------+-----+---------+----------+----------+-----+----------+ | ||
| ``` | ||
|
|
||
| The compression pipeline below describes *one* way to produce a conforming |
There was a problem hiding this comment.
IIUC, the informative compression description should be move to a separate doc later in the followup PR because here we just need necessary description for decoding a ALP-encoded page (i.e. the Page Layout section).
cc @alamb
There was a problem hiding this comment.
Ack. Makes sense. We can do this in a follow up PR
There was a problem hiding this comment.
| Byte: 0 1 2 3 4 5 6 | ||
| +----------------+---------------+--------------+----+----+----+----+ |
There was a problem hiding this comment.
| Byte: 0 1 2 3 4 5 6 | |
| +----------------+---------------+--------------+----+----+----+----+ | |
| Byte: 0 1 2 3 4 5 6 | |
| +----------------+---------------+--------------+----+----+----+----+ |
Let's adjust the alignment here.
| |--------|-------|------|------|-------------| | ||
| | 0 | compression_mode | 1 byte | uint8 | Compression mode (0 = ALP). Reserved for future variants (e.g., ALP-RD). | | ||
| | 1 | integer_encoding | 1 byte | uint8 | Integer encoding (must be 0 = FOR + bit-packing) | | ||
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in the inclusive range \[3, 15\]. Recommended default: 10 (vector size 1024) | |
There was a problem hiding this comment.
If the minimum value is 3, why not defining it as log_vector_size_minus_three so it can start with 0? Not a strong opinion since this anyway consume a byte which is large enough.
There was a problem hiding this comment.
I'd like to keep log_vector_size, maps directly to 1 << log_vector_size.
| v | ||
| +----------------------------------------------------------+ | ||
| | 1. CHOOSE PARAMETERS | | ||
| | Select (exponent, factor) pair for this vector | |
There was a problem hiding this comment.
| | Select (exponent, factor) pair for this vector | | |
| | Select (exponent, factor) pair for this array | |
Be consistent with Input: float/double array above.
| Each subsequent offset equals the previous offset plus the stored size of the | ||
| previous vector. No padding is inserted between vectors. | ||
|
|
||
| A vector's absolute byte position within the page is |
There was a problem hiding this comment.
This is incorrect when pages are compressed or have rep/def levels. I would suggest removing this sentence. If we want to emphasize random access of ALP, perhaps we should replace page_data_start with a more precise start point after decompressed.
| | 0 | compression_mode | 1 byte | uint8 | Compression mode (0 = ALP). Reserved for future variants (e.g., ALP-RD). | | ||
| | 1 | integer_encoding | 1 byte | uint8 | Integer encoding (must be 0 = FOR + bit-packing) | | ||
| | 2 | log_vector_size | 1 byte | uint8 | log2(vector\_size). Must be in the inclusive range \[3, 15\]. Recommended default: 10 (vector size 1024) | | ||
| | 3 | num_elements | 4 bytes | int32 | Total number of floating-point values in the page | |
There was a problem hiding this comment.
| | 3 | num_elements | 4 bytes | int32 | Total number of floating-point values in the page | | |
| | 3 | num_elements | 4 bytes | int32 | Total number of non-null floating-point values in the page | |
| values. Each offset gives the byte position of the corresponding vector's data, | ||
| measured from the start of the offset array itself. | ||
|
|
||
| The first offset always equals `num_vectors * 4` (pointing just past the offset array). |
There was a problem hiding this comment.
Why num_vectors * 4 but not num_vectors * 4 + 7 to include the header? Usually the whole buffer is stored including header, so implementations need to manually add 7 each time to get an absolute offset to each vector.
There was a problem hiding this comment.
I don't see any particular reason for one or the other. Given that we have used the num_vectors * 4 in the prototype implementation I think it is ok to leave this as is
|
|
||
| This encoding is adapted from the paper | ||
| ["ALP: Adaptive Lossless floating-Point Compression"](https://dl.acm.org/doi/10.1145/3626717) | ||
| by Afroozeh and Boncz (SIGMOD 2024). |
There was a problem hiding this comment.
It seems that the paper has THREE authors but we have only listed TWO.
| +----------------------------------------------------------+ | ||
| | 3. FRAME OF REFERENCE (FOR) | | ||
| | min_val = min(encoded[:]) | | ||
| | delta[i] = encoded[i] - min_val | |
There was a problem hiding this comment.
We need to clarify that delta[i] is an unsigned integer to avoid signed overflow.
State explicitly that the magic-number add/subtract are floating-point operations (only the final cast is integer), and that the constants are written as integers merely because they are exact. Add a MUST requiring FLOAT fast_round to be evaluated in single precision and DOUBLE in double precision, since a promotion of the FLOAT path to double drops the value into a binade with ULP below 1.0, defeating the rounding entirely.
The 1.5*2^n midpoint constant exists to make the magic-number rounding branchless, covering a symmetric range in a single formula. Since this spec branches on sign, the midpoint is the wrong choice: with a branch, 2^n doubles the usable domain (to +/-2^23 for FLOAT and +/-2^52 for DOUBLE) and makes the branch actually necessary, whereas with the midpoint the branch was redundant. Update the constants, the domain description, and the prose (the technique is no longer branchless).
Resolve open review comments on PR apache#557 (apacheGH-533): - parquet.thrift: add missing Encoding.ALP = 10 (thread 50). The enum value was never added; prose and the summary table said "ALP = 10" but the machine-readable contract lacked it. - Fix paper authorship to three authors: Afroozeh, Kuffo, Boncz (62). - "For each data page..." intro, drop misleading "page-level header" (16/49). - num_elements is the non-null value count (56). - Correct vector offset base for compressed / rep-def pages: use alp_data_start (decoded page data) instead of page_data_start+7 (55). - Note Vector Format applies to compression_mode=0 / integer_encoding=0 (59). - Label vector header vs data section; forward-ref num_exceptions/bit_width (60/61). - Clarify deltas are non-negative/unsigned with no sign extension (64). - Exceptions stored as raw IEEE 754 bits, MUST NOT canonicalize NaN (63). - Header byte-diagram alignment and "parallel encoding/decoding" wording (49/52). Leaves the contested fast_round sign-branching (threads 31/47/48) untouched pending reviewer consensus.
Follow-up to review comments on PR apache#557 (apacheGH-533): - Pipeline step 1 wording: "for this array" to match the diagram's "Input: float/double array" (thread 54). - Clarify FOR deltas are computed and stored as unsigned integers, explicitly to avoid signed overflow when max-min exceeds the encoded type's signed maximum; no sign extension on unpack (thread 64). - Annotate the Vector Format diagram with a bracket row marking the vector header vs the data section (thread 61).
|
SInce we have had a successful vote, I suggest we merge this PR and make additional changes as follow on PRs: From what I can see, it looks like @prtkgaur resolved all blocking comments from @wgtmac 's most recent review (e.g. Thus, @wgtmac could you confirm you are happy with merging this PR as is (perhaps by approving the PR) and if so I'll merge this PR in and we can continue to iterate in follow on PRs |
| readings) while remaining fully lossless. Each value is encoded independently, | ||
| enabling random access to individual vectors and parallel encode/decode. | ||
|
|
||
| #### Overview |
There was a problem hiding this comment.
Got it. Let's address it in a follow up PR
| +-------------+------+------+-----+---------+----------+----------+-----+----------+ | ||
| ``` | ||
|
|
||
| The compression pipeline below describes *one* way to produce a conforming |
There was a problem hiding this comment.
Ack. Makes sense. We can do this in a follow up PR
|
|
||
| This encoding is adapted from the paper | ||
| ["ALP: Adaptive Lossless floating-Point Compression"](https://dl.acm.org/doi/10.1145/3626717) | ||
| by Afroozeh, Kuffo, and Boncz (SIGMOD 2024). |
| +----------------------------------------------------------+ | ||
| | 3. FRAME OF REFERENCE (FOR) | | ||
| | min_val = min(encoded[:]) | | ||
| | delta[i] = encoded[i] - min_val | |
| | FLOAT | 2^22 + 2^23 = 12,582,912 | `(int32_t)((value + magic) - magic)` | `(int32_t)((value - magic) + magic)` | | ||
| | DOUBLE | 2^51 + 2^52 = 6,755,399,441,055,744 | `(int64_t)((value + magic) - magic)` | `(int64_t)((value - magic) + magic)` | | ||
|
|
||
| The sign branching is necessary because the technique relies on `value ± magic` |
There was a problem hiding this comment.
I over indexed on Ryan's example and updated the wording, thanks Andrew for the correction.
Given the examples that Andrew has brought up i'll keep the wording but instead of "encoder must do it", I can change it to "one of the ways of doing it is..."
| ratio and exception count, never correctness or what a reader decodes. The | ||
| `fast_round` technique below is one recommended implementation. | ||
|
|
||
| ##### Fast Rounding (informative) |
There was a problem hiding this comment.
@alamb do you recommend I simplify the language here or remove part of the section?
There was a problem hiding this comment.
I'm going for simplification for now
|
I just had a brief sync with @prtkgaur - I think he plans to simplify a few things and then we'll merge this one in and iterate in follow on PRs |
alamb
left a comment
There was a problem hiding this comment.
I took a look at the most recent edits and I think they look good. I will merge this in and we can continue iterating as follow on PRs
| example, places `8,388,608` in a binade whose ULP is far below 1.0 and no rounding | ||
| occurs. | ||
|
|
||
| Each form has a limited valid domain — roughly (-2^23, 2^23) for floats and |
# Which issue does this PR close? <!-- We generally require a GitHub issue to be filed for all bug fixes and enhancements and this helps us generate change logs for our releases. You can link an issue to this PR using the GitHub syntax. --> - Part of #10541 - Closes #8748 . - Related spec PR: apache/parquet-format#557 # Rationale for this change check issue <!-- Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed. Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes. --> # What changes are included in this PR? <!-- There is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR. --> # Are these changes tested? <!-- We typically require tests for all PRs in order to: 1. Prevent the code from being accidentally broken by subsequent changes 2. Serve as another way to document the expected behavior of the code If tests are not included in your PR, please explain why (for example, are they covered by existing tests)? --> # Are there any user-facing changes? <!-- If there are user-facing changes then we may require documentation to be updated before approving the PR. If there are any breaking changes to public APIs, please call them out. --> --------- Co-authored-by: Andrew Lamb <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
# Which issue does this PR close? <!-- We generally require a GitHub issue to be filed for all bug fixes and enhancements and this helps us generate change logs for our releases. You can link an issue to this PR using the GitHub syntax. --> - Part of apache#10541 - Closes apache#8748 . - Related spec PR: apache/parquet-format#557 # Rationale for this change check issue <!-- Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed. Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes. --> # What changes are included in this PR? <!-- There is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR. --> # Are these changes tested? <!-- We typically require tests for all PRs in order to: 1. Prevent the code from being accidentally broken by subsequent changes 2. Serve as another way to document the expected behavior of the code If tests are not included in your PR, please explain why (for example, are they covered by existing tests)? --> # Are there any user-facing changes? <!-- If there are user-facing changes then we may require documentation to be updated before approving the PR. If there are any breaking changes to public APIs, please call them out. --> --------- Co-authored-by: Andrew Lamb <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
# Which issue does this PR close? <!-- We generally require a GitHub issue to be filed for all bug fixes and enhancements and this helps us generate change logs for our releases. You can link an issue to this PR using the GitHub syntax. --> - Part of apache#10541 - Closes apache#8748 . - Related spec PR: apache/parquet-format#557 # Rationale for this change check issue <!-- Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed. Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes. --> # What changes are included in this PR? <!-- There is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR. --> # Are these changes tested? <!-- We typically require tests for all PRs in order to: 1. Prevent the code from being accidentally broken by subsequent changes 2. Serve as another way to document the expected behavior of the code If tests are not included in your PR, please explain why (for example, are they covered by existing tests)? --> # Are there any user-facing changes? <!-- If there are user-facing changes then we may require documentation to be updated before approving the PR. If there are any breaking changes to public APIs, please call them out. --> --------- Co-authored-by: Andrew Lamb <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Add the encoding specification for ALP (encoding value 10) to Encodings.md. ALP compresses FLOAT and DOUBLE columns by converting values to integers via decimal scaling, then applying Frame of Reference encoding and bit-packing. Values that cannot be losslessly round-tripped are stored as exceptions.
See rendered preview here: https://github.com/prtkgaur/parquet-format/blob/alpEncoding/Encodings.md#adaptive-lossless-floating-point-alp--10
The spec covers:
Based on the paper "ALP: Adaptive Lossless floating-Point Compression" (Afroozeh and Boncz, SIGMOD 2024). Wire format matches the C++ Arrow and Java parquet-java implementations.
Rationale for this change
What changes are included in this PR?
Do these changes have PoC implementations?