Skip to content

Support Parquet DELTA encodings with more than 64 values per mini-block - #23314

Open
pramodsatya wants to merge 15 commits into
rapidsai:mainfrom
pramodsatya:parquet-delta-large-mini-blocks
Open

Support Parquet DELTA encodings with more than 64 values per mini-block#23314
pramodsatya wants to merge 15 commits into
rapidsai:mainfrom
pramodsatya:parquet-delta-large-mini-blocks

Conversation

@pramodsatya

Copy link
Copy Markdown
Contributor

Description

The GPU Parquet reader rejects DELTA-encoded pages whose mini-blocks hold more than 64 values
with DELTA_PARAMS_UNSUPPORTED, although the format allows any multiple of 32 and other
readers accept such files. This PR removes the mini-block size limit from the
DELTA_BINARY_PACKED, DELTA_BYTE_ARRAY and DELTA_LENGTH_BYTE_ARRAY decoders.

The limit came from the decoder's rolling value buffer, which had to hold two whole
mini-blocks. Instead of growing it, decode and consume mini-blocks one warp-size pass at a
time, so every buffer is sized by the decode pipeline (values in flight per iteration) rather
than by any mini-block geometry:

  • The decode kernels' producer warps decode one 32-value pass per call
    (delta_binary_decoder::decode_next_pass()); each page still produces
    min(values_per_mb, 64) values per main-loop iteration, so previously-readable pages keep
    their exact iteration schedule. Shared memory for the value buffers is unchanged.
  • The skip paths (skip_values, skip_values_and_sum, delta_byte_array_decoder::skip) also
    advance pass by pass and resume at a pass boundary, instead of requiring a whole mini-block
    to stay resident; pages resuming after a skip produce one pass per iteration since larger
    batches could overwrite the up-to-31 not-yet-consumed values the skip leaves behind.
  • The string-size prepasses read decoded lengths back per pass; whole-mini-block read-back
    silently mis-computes str_bytes once a mini-block no longer fits the buffer.
  • init_binary_block now validates values_per_mb % 32 == 0; malformed headers previously
    decoded garbage instead of erroring.

This also fixes two latent bugs reachable on nested pages with the old code:

  • The nz_idx ring (leaf-ordinal to output-row map) shares its size with the value buffers,
    but on nested pages the level decoder overshoots its target by up to a warp of values and
    wraps onto entries the value consumer is reading when a page decodes 64 values per iteration
    (values_per_mb == 64, e.g. arrow-rs INT64 lists; confirmed with compute-sanitizer
    racecheck). The ring now has its own, larger size.
  • delta_byte_array_decoder::skip saved the string needed for the next batch's front
    compression at a stale offset inside the scratch it was about to overwrite; the last decoded
    string now lives in a reserved slot past the scratch area.

Since no stock writer emits more than 64 values per mini-block (cudf and parquet-mr write 32,
pyarrow and arrow-rs write 64 for INT64), tests build single-page files in memory from plain
value vectors with new test utilities (parquet_delta_test_utils.hpp; the builders' output was
cross-checked against pyarrow for every geometry the tests use): flat INT64, LIST, and
both string encodings flat and as LIST, at 64/96/128/256 values per mini-block, with
full, num_rows-trimmed and skip_rows reads. Also adds
parquet_read_delta_binary/parquet_read_delta_string benchmarks (the DELTA decode kernels
had no reader benchmark; results are within ~1% of the previous code for DELTA_BINARY_PACKED
and DELTA_BYTE_ARRAY, ~2% for DELTA_LENGTH_BYTE_ARRAY).

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@copy-pr-bot

copy-pr-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added libcudf Affects libcudf (C++/CUDA) code. CMake CMake build issue labels Jul 17, 2026
@mhaseeb123

Copy link
Copy Markdown
Member

/ok to test aa8b101

@pramodsatya
pramodsatya marked this pull request as ready for review July 21, 2026 01:35
@pramodsatya
pramodsatya requested review from a team as code owners July 21, 2026 01:35
@pramodsatya
pramodsatya requested a review from vuule July 21, 2026 01:35
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR changes GPU DELTA decoding to warp-sized pass processing, updates CUDA string and skip handling, adds compact-protocol page writers and DELTA-encoded Parquet fixtures, expands reader coverage, and registers NVBench benchmarks.

Changes

Parquet DELTA decoder

Layer / File(s) Summary
Warp-pass decoder implementation
cpp/src/io/parquet/delta_binary.cuh
DELTA decoding, skipping, and length summation now operate through validated warp-sized passes.
CUDA kernel integration
cpp/src/io/parquet/page_delta_decode.cu, cpp/src/io/parquet/page_string_decode.cu
Binary and string kernels use expanded ring buffers, resume-page scheduling, explicit error handling, preserved prefix state, and cooperative-groups traversal.
Compact protocol page-header writers
cpp/src/io/parquet/compact_protocol_writer.hpp, cpp/src/io/parquet/compact_protocol_writer.cpp
Adds serialization overloads for Parquet page-header types.
Fixture builders and reader tests
cpp/tests/io/parquet_delta_test_utils.hpp, cpp/tests/io/parquet_reader_test.cpp
Adds flat and nested DELTA Parquet builders and tests large mini-blocks, row ranges, nulls, and nested skip behavior.
Reader benchmarks
cpp/benchmarks/CMakeLists.txt, cpp/benchmarks/io/parquet/parquet_reader_encoding.cpp
Registers integer and string DELTA reader benchmarks across encoding, I/O, and data-size parameters.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: cuIO, improvement, Performance

Suggested reviewers: vuule, pmattione-nvidia, mhaseeb123

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: lifting the Parquet DELTA mini-block value limit beyond 64.
Description check ✅ Passed The description is detailed and directly matches the Parquet DELTA decoding, testing, and benchmark changes in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
cpp/benchmarks/io/parquet/parquet_reader_encoding.cpp (1)

6-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct standard-library includes.

This file directly uses std::string_view, std::string, std::vector, and std::move on Lines [23], [36], and [59], but does not include their defining headers. Avoid relying on transitive includes.

Proposed include additions
 `#include` "reader_common.hpp"
 
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
 `#include` <benchmarks/common/generate_input.hpp>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/benchmarks/io/parquet/parquet_reader_encoding.cpp` around lines 6 - 15,
Add the direct standard-library headers defining the symbols used by this
benchmark: string_view, string, vector, and move. Update the include section of
parquet_reader_encoding.cpp without changing the benchmark implementation or
relying on transitive includes.

Source: Coding guidelines

cpp/src/io/parquet/delta_binary.cuh (1)

32-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Constants and the warp-divides-mini-block invariant are sound (32 % warp_size == 0 guarantees warp_size divides every spec-valid mini-block size), and the delta_rolling_buf_size derivation matches the documented "two batches in flight + header slot".

Minor: the static_assert on Line 36 has no diagnostic message. Adding one aids diagnosis if the warp-size assumption is ever violated on a new arch.

Proposed message
-static_assert(delta_mini_block_size_multiple % cudf::detail::warp_size == 0);
+static_assert(delta_mini_block_size_multiple % cudf::detail::warp_size == 0,
+              "warp_size must divide the DELTA mini-block size multiple; the pass-based "
+              "decoders assume warp_size divides every spec-valid mini-block size");
As per coding guidelines: "use clear `static_assert` messages for template misuse".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/io/parquet/delta_binary.cuh` around lines 32 - 49, Add a clear
diagnostic message to the static_assert enforcing the
delta_mini_block_size_multiple and cudf::detail::warp_size divisibility
invariant. Keep the existing condition and surrounding constants unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tests/io/parquet_delta_test_utils.hpp`:
- Around line 354-372: Update delta_test_strings to generate a mixture of ASCII
and valid non-ASCII UTF-8 sequences instead of using only the current
alphanumeric alphabet. Preserve the existing length, shared-prefix, and
deterministic random-generation behavior so DELTA_BYTE_ARRAY and related string
tests exercise byte-wise handling of multi-byte code points.

---

Nitpick comments:
In `@cpp/benchmarks/io/parquet/parquet_reader_encoding.cpp`:
- Around line 6-15: Add the direct standard-library headers defining the symbols
used by this benchmark: string_view, string, vector, and move. Update the
include section of parquet_reader_encoding.cpp without changing the benchmark
implementation or relying on transitive includes.

In `@cpp/src/io/parquet/delta_binary.cuh`:
- Around line 32-49: Add a clear diagnostic message to the static_assert
enforcing the delta_mini_block_size_multiple and cudf::detail::warp_size
divisibility invariant. Keep the existing condition and surrounding constants
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 25563803-0815-484a-9274-f4f50ab926e4

📥 Commits

Reviewing files that changed from the base of the PR and between 08bd466 and e732a18.

📒 Files selected for processing (7)
  • cpp/benchmarks/CMakeLists.txt
  • cpp/benchmarks/io/parquet/parquet_reader_encoding.cpp
  • cpp/src/io/parquet/delta_binary.cuh
  • cpp/src/io/parquet/page_delta_decode.cu
  • cpp/src/io/parquet/page_string_decode.cu
  • cpp/tests/io/parquet_delta_test_utils.hpp
  • cpp/tests/io/parquet_reader_test.cpp

Comment thread cpp/tests/io/parquet_delta_test_utils.hpp
@mhaseeb123 mhaseeb123 added feature request New feature or request 3 - Ready for Review Ready for review by team non-breaking Non-breaking change Velox Functionality that helps Velox-cudf labels Jul 21, 2026
Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated

// index just past the values decode_next_pass() has produced so far (0 before the first pass,
// even though the header value already occupies index 0)
__device__ constexpr uint32_t next_pass_start_idx()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not usable in a constexpr context as the member variables are runtime. don't make this constexpr, it is misleading.


void BM_parquet_read_delta_string(nvbench::state& state)
{
bench_read_encoding(state, {cudf::type_id::STRING});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

post benchmark results in the pr conversation page for before/after adding the decoding changes.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
// that is safe today because a 32-lane WarpScan over int64_t is shuffle-based and never
// touches its (empty) TempStorage, but a cub change or a wider scan type could turn this
// into a race.
__shared__ cub::WarpScan<int64_t>::TempStorage temp_storage;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks for finding this issue. let's just fix this now by declaring an array of size 2: temp_storage[2], plus a cudf_assert that we're not calling with 3+ warps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this array is now removed with use of cooperative_groups, is that alright?

// the column values plus the DELTA block geometry (block_size, mini_block_count) and get back
// the complete file bytes.

// ---------------------------------------------------------------------------------------------

@mhaseeb123 mhaseeb123 Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use our compact_protocol_writer here instead of a reimpl?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes this was AI generated, page headers are now serialized with compact_protocol_writer, thanks for pointing it out.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
Comment on lines +32 to +34
// The DELTA_BINARY_PACKED spec requires the number of values in a mini-block to be a multiple of
// 32. That this equals the warp size is a coincidence the decoders below depend on: they produce
// values in warp_size-wide passes, so warp_size must divide every spec-valid mini-block size.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// The DELTA_BINARY_PACKED spec requires the number of values in a mini-block to be a multiple of
// 32. That this equals the warp size is a coincidence the decoders below depend on: they produce
// values in warp_size-wide passes, so warp_size must divide every spec-valid mini-block size.
// The DELTA_BINARY_PACKED spec requires the number of values in a mini-block to be a multiple of
// 32. The decoders rely on the coincidence that this also equals warp size; they produce values in warp_size-wide passes, so it must divide every spec-valid mini-block size.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
Comment on lines +38 to +42
// The decoders produce values in warp_size-wide passes (see decode_next_pass), so mini-blocks of
// any size decode with a fixed-size rolling buffer. The decode loops produce up to two passes per
// iteration: pages whose mini-blocks hold at least two passes keep the two-pass batch the loops
// have always used, and running several passes back to back amortizes the per-iteration
// synchronization.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// The decoders produce values in warp_size-wide passes (see decode_next_pass), so mini-blocks of
// any size decode with a fixed-size rolling buffer. The decode loops produce up to two passes per
// iteration: pages whose mini-blocks hold at least two passes keep the two-pass batch the loops
// have always used, and running several passes back to back amortizes the per-iteration
// synchronization.
// The decode loops produce up to two (warp_size-wide) passes per iteration: pages whose mini-blocks
// hold at least two passes keep the two-pass batch the loops have always used, and running several
// passes back to back amortizes the per-iteration synchronization.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
Comment on lines +97 to +98
uint32_t cur_pass; // current warp_size-wide pass within the mini-block, used by
// decode_next_pass for pipelined single-pass decoding

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
uint32_t cur_pass; // current warp_size-wide pass within the mini-block, used by
// decode_next_pass for pipelined single-pass decoding
uint32_t cur_pass; // current pass within the mini-block

Comment on lines +266 to +288
// position at the end of this pass's values since the following calculates negative indexes
auto const d_start = cur_mb_start + (pass + 1) * (warp_size * mb_bits / 8);

// unpack deltas. modified from version in decode_dictionary_indices(), but
// that one only unpacks up to bitwidths of 24. simplified some since this
// will always do batches of 32.
// NOTE: because this needs to handle up to 64 bits, the branching used in the other
// implementation has been replaced with a loop. While this uses more registers, the
// looping version is just as fast and easier to read.
zigzag128_t delta = 0;
if (lane_id + current_value_idx < value_count) {
int32_t ofs = (lane_id - warp_size) * mb_bits;
uint8_t const* p = d_start + (ofs >> 3);
ofs &= 7;
if (p < block_end) {
uint32_t c = 8 - ofs; // 0 - 7 bits
delta = (*p++) >> ofs;

while (c < mb_bits && p < block_end) {
delta |= static_cast<zigzag128_t>(*p++) << c;
c += 8;
}
delta &= (static_cast<zigzag128_t>(1) << mb_bits) - 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to get rid of some of these magic numbers if possible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

bits_per_byte=8 reduces use of magic-numbers here.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
Comment on lines +295 to +302
// do inclusive scan to get value - first_value at each position
// NOTE: this function-scope shared TempStorage is shared by all warps that call this method
// concurrently (e.g. the prefix and suffix decoder warps of the DELTA_BYTE_ARRAY kernels).
// that is safe today because a 32-lane WarpScan over int64_t is shuffle-based and never
// touches its (empty) TempStorage, but a cub change or a wider scan type could turn this
// into a race.
__shared__ cub::WarpScan<int64_t>::TempStorage temp_storage;
cub::WarpScan<int64_t>(temp_storage).InclusiveSum(delta, delta);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional: We should use (warp) cooperative_group in here for reductions and syncs. Easier and extensible in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion, refactored accordingly.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
using cudf::detail::warp_size;
int const t = threadIdx.x;
int const lane_id = t % warp_size;
int const t = threadIdx.x;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same nudge regarding use of cg here. Then we don't need to branch out for warp 0 as

if (t < warp_size)

and can directly do:

// Branch by warp id directly
if (warp.meta_thread_rank() == 0)

@pramodsatya

Copy link
Copy Markdown
Contributor Author

Parquet DELTA reader decode benchmark comparison, obtained using NVBench on L40S, io_type=DEVICE_BUFFER, data_size=512 MiB, cold GPU time.
Δ% negative = faster; baseline = branch without these changes.

Encoding cardinality run_length Before (ms) After (ms) Δ%
DELTA_BINARY_PACKED 0 1 6.974 7.027 +0.77%
DELTA_BINARY_PACKED 0 32 6.635 6.664 +0.44%
DELTA_BINARY_PACKED 1000 1 7.009 7.042 +0.48%
DELTA_BINARY_PACKED 1000 32 6.816 6.815 −0.01%
DELTA_LENGTH_BYTE_ARRAY 0 1 6.321 6.449 +2.03%
DELTA_LENGTH_BYTE_ARRAY 0 32 6.321 6.459 +2.19%
DELTA_LENGTH_BYTE_ARRAY 1000 1 6.375 6.452 +1.22%
DELTA_LENGTH_BYTE_ARRAY 1000 32 6.251 6.363 +1.80%
DELTA_BYTE_ARRAY 0 1 8.925 8.497 −4.80%
DELTA_BYTE_ARRAY 0 32 8.872 8.501 −4.17%
DELTA_BYTE_ARRAY 1000 1 8.904 8.558 −3.90%
DELTA_BYTE_ARRAY 1000 32 8.634 8.328 −3.54%
PLAIN (control) 0 1 6.056 6.064 +0.14%
PLAIN (control) 0 32 5.656 5.564 −1.62%
PLAIN (control) 1000 1 6.048 6.044 −0.06%
PLAIN (control) 1000 32 5.570 5.568 −0.04%

DELTA states: mean −0.62%, best −4.80% (DELTA_BYTE_ARRAY), worst +2.19% (DELTA_LENGTH_BYTE_ARRAY, within the ~4% NVBench noise seen on the baseline run). string axis also has PLAIN rows (~8.4 ms, flat) omitted here for brevity.

@pramodsatya

Copy link
Copy Markdown
Contributor Author

@pmattione-nvidia , @mhaseeb123 , thanks for the feedback, updated the PR per your suggestions. Could you please take another look?

return c.value();
}

size_t CompactProtocolWriter::write(DataPageHeader const& d)

@mhaseeb123 mhaseeb123 Jul 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional nit: If possible, can we replace single letter variable names in here. I know other functions here also use them so you can disregard this too if you don't feel like it. For example:

Suggested change
size_t CompactProtocolWriter::write(DataPageHeader const& d)
size_t CompactProtocolWriter::write(DataPageHeader const& pg_hdr)


auto const block = cg::this_thread_block();
auto const warp = cg::tiled_partition<cudf::detail::warp_size>(block);
int const t = threadIdx.x;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
int const t = threadIdx.x;
int const t = block.thread_rank();

block.sync();

// two warps will traverse the prefixes and suffixes and sum them up
auto const db = t < warp_size ? &prefixes : t < 2 * warp_size ? &suffixes : nullptr;

@mhaseeb123 mhaseeb123 Jul 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
auto const db = t < warp_size ? &prefixes : t < 2 * warp_size ? &suffixes : nullptr;
auto const warp_id = warp.meta_group_rank();
auto const db = (warp_id == 0) ? &prefixes : warp_id == 1 ? &suffixes : nullptr;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out.

Comment thread cpp/tests/io/parquet_reader_test.cpp Outdated
Comment on lines +1212 to +1215
// block_size=128, mini_block_count=1 -> 128 values/mini-block: the reader previously rejected
// mini-blocks over 64 values with DELTA_PARAMS_UNSUPPORTED (0x100).
TEST_F(ParquetReaderTest, DeltaBinaryLargeMiniBlock128)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit (style): Please put the comments inside the tests like

Suggested change
// block_size=128, mini_block_count=1 -> 128 values/mini-block: the reader previously rejected
// mini-blocks over 64 values with DELTA_PARAMS_UNSUPPORTED (0x100).
TEST_F(ParquetReaderTest, DeltaBinaryLargeMiniBlock128)
{
TEST_F(ParquetReaderTest, DeltaBinaryLargeMiniBlock128)
{
// block_size=128, mini_block_count=1 -> 128 values/mini-block: the reader previously rejected
// mini-blocks over 64 values with DELTA_PARAMS_UNSUPPORTED (0x100).

// block_size=384, mini_block_count=4 -> 96 values/mini-block: exercises multiple
// mini-blocks per block (both the within-block and next-block advance paths) and a
// non-power-of-two pass count (3).
TEST_F(ParquetReaderTest, DeltaBinaryLargeMiniBlock96)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we also add a malformed-geometry case (e.g. block_size=96, mini_block_count=2) expecting DELTA_PARAMS_UNSUPPORTED, and a case with null leaf values? The new values_per_mb % 32 validation and null handling at these mini-block sizes are otherwise uncovered.

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
// implementation has been replaced with a loop. While this uses more registers, the
// looping version is just as fast and easier to read.
zigzag128_t delta = 0;
if (lane_id + current_value_idx < value_count) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The guard omits pass, so it stops filtering anything past pass 0 (harmless today since p < block_end bounds the reads, but misleading now that a mini-block can hold 8 passes).

Suggested change
if (lane_id + current_value_idx < value_count) {
if (current_value_idx + pass * warp_size + lane_id < value_count) {

Comment thread cpp/src/io/parquet/delta_binary.cuh Outdated
constexpr int delta_rolling_buf_size = (2 * max_delta_mini_block_size) + 1;
// Parquet serializes the bit-packed mini-block deltas as a stream of 8-bit bytes, so a bit count is
// converted to a byte count by dividing by this.
constexpr int bits_per_byte = 8;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's use the macro CHAR_BIT (directly where needed) instead of defining this in the header here which is pulled by every TU

Suggested change
constexpr int bits_per_byte = 8;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion, was uncertain if CHAR_BITS is the right invariant here so had gone with a new const to be safe, updated to CHAR_BITS.

int block_size,
int mini_block_count)
{
assert(block_size % mini_block_count == 0 && (block_size / mini_block_count) % 32 == 0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use:

Suggested change
assert(block_size % mini_block_count == 0 && (block_size / mini_block_count) % 32 == 0);
CUDF_EXPECTS(block_size % mini_block_count == 0 && (block_size / mini_block_count) % 32 == 0, "your one liner message here");

Comment thread cpp/src/io/parquet/page_delta_decode.cu Outdated
Comment on lines +856 to +858
uint32_t const batch_size =
is_skip_resume ? cudf::detail::warp_size
: min(db->values_per_mb, static_cast<uint32_t>(delta_max_batch_size));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
uint32_t const batch_size =
is_skip_resume ? cudf::detail::warp_size
: min(db->values_per_mb, static_cast<uint32_t>(delta_max_batch_size));
// only nested pages resume the decoder mid-page; flat pages re-init it below and can keep the full batch
bool const resumes_mid_page = is_skip_resume and has_repetition;
uint32_t const batch_size =
resumes_mid_page ? cudf::detail::warp_size
: min(db->values_per_mb, static_cast<uint32_t>(delta_max_batch_size));

@pramodsatya
pramodsatya requested a review from mhaseeb123 July 29, 2026 21:03
@mhaseeb123

Copy link
Copy Markdown
Member

@pmattione-nvidia Looks like there are some merge conflicts. Could you please coordinate with @vyasr to get them resolved

# Conflicts:
#	cpp/src/io/parquet/page_delta_decode.cu
#	cpp/src/io/parquet/page_string_decode.cu
@vyasr

vyasr commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

/ok to test 6822b1b

@vyasr

vyasr commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@pmattione-nvidia Looks like there are some merge conflicts. Could you please coordinate with @vyasr to get them resolved

I took care of the conflicts. They were just the struct field changes from my PRs.

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

Labels

3 - Ready for Review Ready for review by team CMake CMake build issue feature request New feature or request libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change Velox Functionality that helps Velox-cudf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants