Skip to content

refactor(parquet): introduce string_size_scan_state for string size kernels - #23496

Merged
rapids-bot[bot] merged 4 commits into
NVIDIA:mainfrom
vyasr:parquet-substruct-redesign/pr5-output-string-size
Aug 10, 2026
Merged

refactor(parquet): introduce string_size_scan_state for string size kernels#23496
rapids-bot[bot] merged 4 commits into
NVIDIA:mainfrom
vyasr:parquet-substruct-redesign/pr5-output-string-size

Conversation

@vyasr

@vyasr vyasr commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #23479 to create a minimal shared state for the Parquet string-size scan kernels. It extracts page_decode_output_state from page_state_s, grouping the output conversion fields (dtype_len, dtype_len_in, and ts_scale) that are used while computing string output sizes.

The primary addition is string_size_scan_state, composed from page_decode_setup_state, page_decode_stream_state, and page_decode_output_state. The three string-size scan kernels (compute_page_string_sizes_kernel, compute_delta_page_string_sizes_kernel, and compute_delta_length_page_string_sizes_kernel) now use this smaller composed state instead of the full page_state_s. That removes unrelated nesting and progress fields from these kernels' shared-memory state while preserving the output-conversion metadata they need.

A fresh cuobjdump -res-usage comparison against upstream/main shows shared-memory reductions in all three migrated kernels. Register usage does not increase anywhere; it drops on a few newer architectures:

Kernel Arch summary shmem delta register changes
compute_page_string_sizes_kernel all generated arch variants -536 bytes unchanged except sm_100f: 32 -> 31
compute_delta_page_string_sizes_kernel all generated arch variants -536 bytes unchanged except sm_90a: 40 -> 32, sm_120a: 48 -> 40, sm_120: 48 -> 40
compute_delta_length_page_string_sizes_kernel all generated arch variants -544 bytes unchanged

Representative sm80 measurements:

Kernel shmem before shmem after delta regs before regs after
compute_page_string_sizes_kernel 1160 624 -536 40 40
compute_delta_page_string_sizes_kernel 3528 2992 -536 32 32
compute_delta_length_page_string_sizes_kernel 2144 1600 -544 62 62

This is the same incremental pattern as #23479: extract one coherent sub-state, migrate the kernels that can use the narrower composition, and keep the remaining full decode state for the follow-up integration PR.

Checklist

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

… size scan kernels via string_size_scan_state
@vyasr
vyasr requested a review from a team as a code owner July 31, 2026 04:52
@vyasr
vyasr requested review from PointKernel and mhaseeb123 July 31, 2026 04:52
@github-actions github-actions Bot added the libcudf Affects libcudf (C++/CUDA) code. label Jul 31, 2026
@vyasr vyasr added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c229b34d-cfbd-4f9f-9518-8997334b9915

📥 Commits

Reviewing files that changed from the base of the PR and between 2baed40 and 17b1a06.

📒 Files selected for processing (6)
  • cpp/src/io/parquet/decode_fixed.cu
  • cpp/src/io/parquet/page_data.cu
  • cpp/src/io/parquet/page_decode.cuh
  • cpp/src/io/parquet/page_delta_decode.cu
  • cpp/src/io/parquet/page_state_composed.cuh
  • cpp/src/io/parquet/page_string_decode.cu
🚧 Files skipped from review as they are similar to previous changes (6)
  • cpp/src/io/parquet/page_state_composed.cuh
  • cpp/src/io/parquet/page_delta_decode.cu
  • cpp/src/io/parquet/page_string_decode.cu
  • cpp/src/io/parquet/decode_fixed.cu
  • cpp/src/io/parquet/page_data.cu
  • cpp/src/io/parquet/page_decode.cuh

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved Parquet decoding reliability for fixed-width, byte-stream-split, delta, and string data.
    • Corrected conversions for decimals, timestamps, fixed-length strings, and other output formats.
    • Fixed null-value handling, including zero filling and destination sizing.
    • Strengthened validation of malformed byte-stream data.
    • Improved consistency when decoding nested data and converted outputs.

Walkthrough

Parquet page decoding now stores conversion metadata in output_cvt. Fixed-width, general, delta, timestamp, decimal, byte-stream-split, and string-size decoding paths use converted widths and timestamp scaling.

Changes

Parquet output conversion state

Layer / File(s) Summary
Conversion state and initialization
cpp/src/io/parquet/page_decode.cuh
Adds page_decode_output_state and moves output widths and timestamp scaling into page_state_s::output_cvt.
Fixed-width and page decoding
cpp/src/io/parquet/decode_fixed.cu, cpp/src/io/parquet/page_data.cu, cpp/src/io/parquet/page_data.cuh
Uses output_cvt for destination sizing, input widths, decimal and timestamp conversion, byte-stream-split validation, and null filling.
Delta binary output sizing
cpp/src/io/parquet/page_delta_decode.cu
Uses the converted output width for value writes and null filling.
String-size scan state and widths
cpp/src/io/parquet/page_state_composed.cuh, cpp/src/io/parquet/page_string_decode.cu, cpp/src/io/parquet/page_string_utils.cuh
Adds shared string-scan state and uses converted input widths for fixed-length string calculations.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: cuIO

Suggested reviewers: pointkernel, vuule, bdice

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the introduction of string_size_scan_state for Parquet string-size kernels, which is the primary objective of the pull request.
Description check ✅ Passed The description is directly related to the changes and explains the new composed state, migrated kernels, and shared-memory improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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/page_data.cu (1)

84-95: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Move the malformed-page check before the division.

Line 85 computes num_values by dividing data_len by s->output_cvt.dtype_len_in before the guard at line 88 checks s->output_cvt.dtype_len_in <= 0. If dtype_len_in is 0 for a malformed BYTE_STREAM_SPLIT page, this division executes first and causes an integer division by zero, which is undefined behavior in device code. The guard below is meant to catch exactly this case, but it runs too late.

The sibling function decode_fixed_width_split_values in decode_fixed.cu performs the check before the division. Apply the same order here.

🐛 Proposed fix: check before dividing
   auto const data_len   = cuda::std::distance(s->stream.data_start, s->stream.data_end);
-  auto const num_values = data_len / s->output_cvt.dtype_len_in;
-
-  // Check malformed BYTE_STREAM_SPLIT pages
-  if (s->output_cvt.dtype_len_in <= 0 or data_len <= 0) {
+
+  // Check malformed BYTE_STREAM_SPLIT pages
+  if (s->output_cvt.dtype_len_in <= 0 or data_len <= 0) {
     cg::invoke_one(block, [&]() {
       set_error(static_cast<kernel_error::value_type>(decode_error::INVALID_BYTE_STREAM_SPLIT_SIZE),
                 error_code);
     });
     return;
   }
+
+  auto const num_values = data_len / s->output_cvt.dtype_len_in;
🤖 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/page_data.cu` around lines 84 - 95, Move the malformed
BYTE_STREAM_SPLIT validation in the surrounding decode function before
calculating num_values, checking dtype_len_in and data_len before dividing
data_len by dtype_len_in. Preserve the existing set_error and return behavior,
matching the ordering used by decode_fixed_width_split_values.
🤖 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/page_data.cu`:
- Around line 84-95: Move the malformed BYTE_STREAM_SPLIT validation in the
surrounding decode function before calculating num_values, checking dtype_len_in
and data_len before dividing data_len by dtype_len_in. Preserve the existing
set_error and return behavior, matching the ordering used by
decode_fixed_width_split_values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5843b27c-4bfd-4769-ae37-5ae10d8035ef

📥 Commits

Reviewing files that changed from the base of the PR and between 7f58752 and ad71ecf.

📒 Files selected for processing (8)
  • cpp/src/io/parquet/decode_fixed.cu
  • cpp/src/io/parquet/page_data.cu
  • cpp/src/io/parquet/page_data.cuh
  • cpp/src/io/parquet/page_decode.cuh
  • cpp/src/io/parquet/page_delta_decode.cu
  • cpp/src/io/parquet/page_state_composed.cuh
  • cpp/src/io/parquet/page_string_decode.cu
  • cpp/src/io/parquet/page_string_utils.cuh

@bdice bdice left a comment

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.

Everything seems fine except this comment block, which is a little strange.

Comment thread cpp/src/io/parquet/page_state_composed.cuh Outdated
vyasr added 2 commits August 10, 2026 15:58
…edesign/pr5-output-string-size

# Conflicts:
#	cpp/src/io/parquet/decode_fixed.cu
#	cpp/src/io/parquet/page_decode.cuh
#	cpp/src/io/parquet/page_state_composed.cuh
…_assert

Addresses review comment on NVIDIA#23495: the sizeof(string_offset_scan_state) <
sizeof(page_state_s) static_assert was verification scaffold from the initial
struct extraction. Neither level_scan_state nor string_size_scan_state have an
equivalent assert, and the shmem win is already covered by the resource-usage
measurements in the PR description. Drop it for consistency.
@vyasr

vyasr commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 7ec9b08 into NVIDIA:main Aug 10, 2026
140 checks passed
@vyasr
vyasr deleted the parquet-substruct-redesign/pr5-output-string-size branch August 10, 2026 17:16
rapids-bot Bot pushed a commit that referenced this pull request Aug 10, 2026
…_page_decode_state replacing page_state_s (#23610)

This PR completes the incremental narrowing pattern from #23471, #23479, #23495, and #23496 by retiring `page_state_s` entirely. It extracts `page_decode_nesting_state` (the nesting decode cache and pointer) and composes `full_page_decode_state` from `page_decode_setup_state`, `page_decode_stream_state`, `page_decode_nesting_state`, `page_decode_progress_state`, and `page_decode_output_state`. Every full-decode kernel (`decode_page_data`, `decode_split_page_data_kernel`, `decode_page_data_generic`, `decode_delta_binary`, `decode_delta_byte_array`, `decode_delta_length_byte_array`, `compute_string_page_bounds`, `compute_page_sizes`) now takes this composed state instead of the ad-hoc `page_state_s`, and the old struct is deleted.

The composition also folds `page_state_s`'s stand-alone level-decoding fields (`lvl_start[2]`, `lvl_end`) into the already-existing `page_decode_stream_state::abs_lvl_start` / `abs_lvl_end` arrays, and drops the unused `first_output_value` field. Together with the composition's tighter layout this removes 32 bytes of shared memory from every full-decode kernel on every generated architecture, with no register-count regressions.

A fresh `cuobjdump -res-usage` comparison against `upstream/main` shows the same 32-byte shmem reduction on all eight migrated kernels on every generated architecture. Register usage does not increase anywhere; one variant of `decode_split_page_data_kernel` on `sm_86` drops by 8 registers:

| Kernel | Arch summary | shmem delta | register changes |
|---|---|---:|---|
| `decode_page_data` | all generated arch variants | -32 bytes | unchanged |
| `decode_split_page_data_kernel` | all generated arch variants | -32 bytes | unchanged except `sm_86`: one template variant 48 -> 40 |
| `decode_page_data_generic` | all generated arch variants (44 template variants) | -32 bytes | unchanged |
| `compute_page_sizes` | all generated arch variants | -32 bytes | unchanged |
| `compute_string_page_bounds` | all generated arch variants | -32 bytes | unchanged |
| `decode_delta_binary` | all generated arch variants | -32 bytes | unchanged |
| `decode_delta_byte_array` | all generated arch variants | -32 bytes | unchanged |
| `decode_delta_length_byte_array` | all generated arch variants | -32 bytes | unchanged |

Representative sm_80 measurements:

| Kernel | shmem before | shmem after | delta | regs before | regs after |
|---|---:|---:|---:|---:|---:|
| `decode_page_data` | 4112 | 4080 | -32 | 56 | 56 |
| `decode_split_page_data_kernel` | 4112 | 4080 | -32 | 56 | 56 |
| `decode_page_data_generic` (min variant) | 2104 | 2072 | -32 | 62 | 62 |
| `decode_page_data_generic` (max variant) | 13024 | 12992 | -32 | 64 | 64 |
| `compute_page_sizes` | 3240 | 3208 | -32 | 32 | 32 |
| `compute_string_page_bounds` | 3256 | 3224 | -32 | 32 | 32 |
| `decode_delta_binary` | 2700 | 2668 | -32 | 56 | 56 |
| `decode_delta_byte_array` | 5068 | 5036 | -32 | 72 | 72 |
| `decode_delta_length_byte_array` | 3392 | 3360 | -32 | 64 | 64 |

With this PR the `page_state_s` type is fully removed. All Parquet decode and preprocess kernels now use one of four purpose-built shared-memory states (`level_scan_state`, `string_size_scan_state`, `string_offset_scan_state`, `full_page_decode_state`), each holding only the substructs it actually needs.

Authors:
  - Vyas Ramasubramani (https://github.com/vyasr)

Approvers:
  - Bradley Dice (https://github.com/bdice)

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

Labels

improvement Improvement / enhancement to an existing function libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants