perf(parquet): decode def and rep level streams concurrently via 2D grid - #23637
perf(parquet): decode def and rep level streams concurrently via 2D grid#23637vyasr wants to merge 4 commits into
Conversation
Each page's level-decode kernel is now dispatched with a 2D grid: dim_grid = (pages.size(), 2) blockIdx.y selects the level stream (DEFINITION=0, REPETITION=1). Blocks for absent streams (non-null pages, non-list pages) return immediately, so occupancy is preserved. The two streams decode concurrently on separate SMs instead of sequentially within one block.
Assert that the level-stream range is non-negative on entry to rle_stream::init(). Catches callers that pass an inverted range early rather than silently producing garbage output. Suggested by reviewer r3760481306.
📝 WalkthroughSummary by CodeRabbit
WalkthroughParquet level preprocessing now launches separate blocks for repetition and definition levels. Each block selects and decodes one stream. RLE stream initialization now checks that its staging range is nonnegative. ChangesParquet level preprocessing
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cpp/src/io/parquet/rle_stream.cuh (1)
293-298: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winValidate the range for non-staged streams.
Line 297 runs only when
_smem_stageis not null.cpp/src/io/parquet/decode_fixed.cu:1158-1179callsrle_stream::initwithout a staging buffer, so those streams bypass the new validation. Compute and validate the range before the staging branch.Proposed fix
+ auto const len = cuda::std::distance(_start, _end); + cudf_assert(len >= 0 && "rle_stream::init: _end must be >= _start"); + if (_smem_stage != nullptr) { auto* const smem_stage = static_cast<uint8_t const*>(cuda::std::assume_aligned<16>(_smem_stage)); - auto const len = static_cast<int>(cuda::std::distance(_start, _end)); - cudf_assert(len >= 0 && "rle_stream::init: _end must be >= _start"); if (len > 0 && len <= stage_capacity) {🤖 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/rle_stream.cuh` around lines 293 - 298, Move the _start-to-_end distance calculation and the len >= 0 assertion in rle_stream::init before the _smem_stage != nullptr branch, so non-staged streams from decode_fixed also validate the range. Reuse the validated len inside the staging path without changing its existing capacity handling.
🤖 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.
Outside diff comments:
In `@cpp/src/io/parquet/rle_stream.cuh`:
- Around line 293-298: Move the _start-to-_end distance calculation and the len
>= 0 assertion in rle_stream::init before the _smem_stage != nullptr branch, so
non-staged streams from decode_fixed also validate the range. Reuse the
validated len inside the staging path without changing its existing capacity
handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: fd9800bd-8288-4994-a730-a023c6b21cf0
📒 Files selected for processing (2)
cpp/src/io/parquet/decode_preprocess.cucpp/src/io/parquet/rle_stream.cuh
|
This might be a naive question. Why use a 2D grid rather than a 1D grid with |
No real reason, the two are structurally equivalent and the only differences are semantics. |
Description
preprocess_levels_kerneldecodes the definition and repetition level streams for each Parquet page. Prior to this change the kernel dispatched one block per page, which decoded both streams sequentially — repetition first, then definition. Because these streams are independent, there is no reason to serialize them.This PR changes the kernel to a 2D grid with 2 blocks per page:
blockIdx.yselects which stream the block owns, so the two decoders run concurrently on separate SMs. Blocks whose stream is absent for a given page (non-list columns have no repetition stream; non-nullable columns have no definition stream) return immediately, so there is no wasted work.Performance (A100 80GB,
parquet_read_decode, 512 MiB,DEVICE_BUFFER, no compression)13 of 16 configs improved; 0 regressions.
parquet_read_wide_tablesandparquet_read_long_strings/parquet_read_file_shapewere within benchmark noise (non-nullable DECIMAL wide tables have no level streams).Checklist