Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1cb92fc
Refactor hybrid scan multifile API to enhance bloom filter functionality
qbacpey Jun 11, 2026
815ca02
Enhance hybrid scan multifile API with improved bloom filter handling
qbacpey Jun 14, 2026
51e4c0e
Update comments in hybrid scan multifile filters test for clarity
qbacpey Jun 14, 2026
18e492d
Merge branch 'main' into hybrid/api-split-t1
mhaseeb123 Jun 15, 2026
dbb6284
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Jun 15, 2026
b8baad7
Refactor bloom filter functions in hybrid scan implementation
qbacpey Jun 15, 2026
a750d47
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Jun 22, 2026
2077626
Merge branch 'main' into hybrid/api-split-t1
qbacpey Jun 24, 2026
28177c9
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Jun 25, 2026
aff5c7f
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Jul 24, 2026
25c6be0
Format
qbacpey Jul 24, 2026
3dcdeb5
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Jul 27, 2026
2484b20
Enhance bloom filter validation in hybrid scan implementation
qbacpey Jul 27, 2026
4d4bbc9
Refactor bloom filter parameter types to use std::span
qbacpey Jul 27, 2026
df86dc8
Add tests for parquet bloom filter behavior with absent and mixed pre…
qbacpey Jul 28, 2026
d9c3bed
Style
qbacpey Jul 28, 2026
9f497b9
Merge branch 'main' into hybrid/api-split-t1
qbacpey Jul 28, 2026
99eebce
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Aug 3, 2026
03c9df3
Update bloom filter parameter description and fix comment in hybrid s…
qbacpey Aug 3, 2026
a2ff9e3
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Aug 4, 2026
95388b0
Add dictionary page filtering and update bloom filter documentation
qbacpey Aug 5, 2026
b0567a4
Merge branch 'main' into hybrid/api-split-t1
qbacpey Aug 5, 2026
969cbac
Enhance Parquet testing with bloom filter alignment and pruning
qbacpey Aug 6, 2026
59219e1
Merge branch 'main' into hybrid/api-split-t1
qbacpey Aug 6, 2026
c31aa10
Merge branch 'main' into hybrid/api-split-t1
qbacpey Aug 7, 2026
eae0894
Let `test_parquet` use `read_parquet` only
qbacpey Aug 11, 2026
3ea856c
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Aug 11, 2026
12bd65b
Merge remote-tracking branch 'upstream/main' into hybrid/api-split-t1
qbacpey Aug 17, 2026
cf1fe4e
Merge branch 'main' into hybrid/api-split-t1
qbacpey Aug 18, 2026
e8ecae7
Merge branch 'main' into hybrid/api-split-t1
qbacpey Aug 18, 2026
6dc0260
Merge branch 'main' into hybrid/api-split-t1
qbacpey Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 53 additions & 36 deletions cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <cuda/stream>

#include <memory>
#include <span>
#include <utility>
#include <vector>

/**
Expand Down Expand Up @@ -157,20 +159,64 @@ class hybrid_scan_multifile {
cuda::stream_ref stream) const;

/**
* @brief Get byte ranges of bloom filters and dictionary pages (secondary filters) for row group
* pruning
* @brief Get byte ranges of bloom filters for row group pruning
*
Comment thread
qbacpey marked this conversation as resolved.
* @note Device buffers for bloom filter byte ranges must be allocated using a 32 byte
* aligned memory resource
*
* @param row_group_indices Span of vectors of input row group indices, one per source
* @param options Parquet reader options
* @return Pair of vectors of byte ranges of column chunk with bloom filters and dictionary
* pages subject to filter predicate
* @return A pair of vectors containing bloom filter byte ranges and corresponding source indices
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<byte_range_info>>
secondary_filters_byte_ranges(cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const;
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<size_type>>
bloom_filters_byte_ranges(std::span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const;

/**
* @brief Filter the row groups using column chunk bloom filters
*
* @param bloom_filter_data Device spans of header-stripped bloom filter bitsets of column
* chunks with an equality predicate, ordered to match the bloom filter
* byte ranges returned by `bloom_filters_byte_ranges`
* @param row_group_indices Input row group indices
* @param options Parquet reader options
* @param stream CUDA stream used for device memory operations and kernel launches
* @return Vectors of filtered per-source row group indices, one per source
*/
[[nodiscard]] std::vector<std::vector<size_type>> filter_row_groups_with_bloom_filters(
std::span<cudf::device_span<uint8_t const> const> bloom_filter_data,
std::span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options,
cuda::stream_ref stream) const;

/**
* @brief Get byte ranges of column chunk dictionary pages for row group pruning
*
* @param row_group_indices Span of vectors of input row group indices, one per source
* @param options Parquet reader options
* @return Pair of flattened byte ranges to column chunk dictionary pages subject to the filter
* predicate and their corresponding source indices
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<size_type>>
dictionary_pages_byte_ranges(cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const;

/**
* @brief Filter the row groups using column chunk dictionary pages
*
* @param dictionary_page_data Device spans of dictionary page data of column chunks with an
* (in)equality predicate, in the same order as the byte ranges returned by
* `dictionary_pages_byte_ranges` including empty spans against empty byte ranges
* @param row_group_indices Span of vectors of input row group indices, one per source
* @param options Parquet reader options
* @param stream CUDA stream used for device memory operations and kernel launches
* @return Vector of vectors of filtered row group indices, one per source
*/
[[nodiscard]] std::vector<std::vector<size_type>> filter_row_groups_with_dictionary_pages(
cudf::host_span<cudf::device_span<uint8_t const> const> dictionary_page_data,
cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options,
cuda::stream_ref stream) const;

/**
* @brief Builds a boolean survival column of size equal to the total number of rows in the row
Expand Down Expand Up @@ -455,35 +501,6 @@ class hybrid_scan_multifile {
*/
[[nodiscard]] bool has_next_table_chunk() const;

/**
* @brief Get byte ranges of column chunk dictionary pages for row group pruning
*
* @param row_group_indices Span of vectors of input row group indices, one per source
* @param options Parquet reader options
* @return Pair of flattened byte ranges to column chunk dictionary pages subject to the filter
* predicate and their corresponding source indices
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<size_type>>
dictionary_pages_byte_ranges(cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const;

/**
* @brief Filter the row groups using column chunk dictionary pages
*
* @param dictionary_page_data Device spans of dictionary page data of column chunks with an
* (in)equality predicate, in the same order as the byte ranges returned by
* `dictionary_pages_byte_ranges` including empty spans against empty byte ranges
* @param row_group_indices Span of vectors of input row group indices, one per source
* @param options Parquet reader options
* @param stream CUDA stream used for device memory operations and kernel launches
* @return Vector of vectors of filtered row group indices, one per source
*/
[[nodiscard]] std::vector<std::vector<size_type>> filter_row_groups_with_dictionary_pages(
cudf::host_span<cudf::device_span<uint8_t const> const> dictionary_page_data,
cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options,
cuda::stream_ref stream) const;

private:
std::unique_ptr<detail::hybrid_scan_reader_impl> _impl;
};
Expand Down
64 changes: 39 additions & 25 deletions cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ std::vector<std::vector<cudf::size_type>> aggregate_reader_metadata::filter_row_
return stats_filtered_row_group_indices.value_or(all_row_group_indices(row_group_indices));
}

std::vector<byte_range_info> aggregate_reader_metadata::get_bloom_filter_bytes(
std::pair<std::vector<byte_range_info>, std::vector<cudf::size_type>>
aggregate_reader_metadata::bloom_filters_byte_ranges(
std::span<std::vector<cudf::size_type> const> row_group_indices,
std::span<data_type const> output_dtypes,
std::span<cudf::size_type const> output_column_schemas,
Expand All @@ -463,7 +464,7 @@ std::vector<byte_range_info> aggregate_reader_metadata::get_bloom_filter_bytes(
std::back_inserter(bloom_filter_col_schemas),
[](auto& bloom_filter_literals) { return not bloom_filter_literals.empty(); });

// No equality literals found, return empty vector
// No equality literals found, return empty pair
if (bloom_filter_col_schemas.empty()) { return {}; }

// Compute total number of input row groups
Expand All @@ -476,36 +477,42 @@ std::vector<byte_range_info> aggregate_reader_metadata::get_bloom_filter_bytes(
std::vector<byte_range_info> bloom_filter_bytes;
bloom_filter_bytes.reserve(num_chunks);

// Parallel map identifying the source each emitted byte range must be fetched from
std::vector<cudf::size_type> bloom_filter_source_map;
bloom_filter_source_map.reserve(num_chunks);

// Flag to check if we have at least one valid bloom filter offset
auto have_bloom_filters = false;

// For all sources
std::for_each(cuda::counting_iterator<std::size_t>{0},
cuda::counting_iterator{row_group_indices.size()},
[&](auto const src_index) {
// Get all row group indices in the data source
auto const& rg_indices = row_group_indices[src_index];
// For all row groups
std::for_each(rg_indices.cbegin(), rg_indices.cend(), [&](auto const rg_index) {
// For all column chunks
std::for_each(
bloom_filter_col_schemas.begin(),
bloom_filter_col_schemas.end(),
[&](auto const schema_idx) {
auto& col_meta = get_column_metadata(rg_index, src_index, schema_idx);
// Get bloom filter offsets and sizes
bloom_filter_bytes.emplace_back(col_meta.bloom_filter_offset.value_or(0),
col_meta.bloom_filter_length.value_or(0));

// Set `have_bloom_filters` if `bloom_filter_offset` is valid
if (col_meta.bloom_filter_offset.has_value()) { have_bloom_filters = true; }
});
});
});
std::for_each(
cuda::counting_iterator<std::size_t>{0},
cuda::counting_iterator{row_group_indices.size()},
[&](auto const src_index) {
// Get all row group indices in the data source
auto const& rg_indices = row_group_indices[src_index];
// For all row groups
std::for_each(rg_indices.cbegin(), rg_indices.cend(), [&](auto const rg_index) {
// For all column chunks
std::for_each(
bloom_filter_col_schemas.begin(),
bloom_filter_col_schemas.end(),
[&](auto const schema_idx) {
auto& col_meta = get_column_metadata(rg_index, src_index, schema_idx);
// Get bloom filter offsets and sizes
bloom_filter_bytes.emplace_back(col_meta.bloom_filter_offset.value_or(0),
col_meta.bloom_filter_length.value_or(0));
bloom_filter_source_map.emplace_back(static_cast<cudf::size_type>(src_index));

// Set `have_bloom_filters` if `bloom_filter_offset` is valid
if (col_meta.bloom_filter_offset.has_value()) { have_bloom_filters = true; }
});
});
});

if (not have_bloom_filters) { return {}; }

return bloom_filter_bytes;
return {std::move(bloom_filter_bytes), std::move(bloom_filter_source_map)};
}

std::pair<std::vector<byte_range_info>, std::vector<cudf::size_type>>
Expand Down Expand Up @@ -699,6 +706,13 @@ aggregate_reader_metadata::filter_row_groups_with_bloom_filters(
// Compute total number of input row groups
auto const total_row_groups = compute_total_row_groups(row_group_indices);

// Ensure there is one bloom filter data span per eligible column in each row group

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.

CUDF_EXPECTS(bloom_filter_data.size() ==
static_cast<std::size_t>(total_row_groups) * bloom_filter_col_schemas.size(),
"Bloom filter data size must match the number of row groups times the number of "
"columns with bloom filters and an equality predicate",
std::invalid_argument);

// Transform bloom filter data to cuda::std::byte type for apply_bloom_filters
std::vector<cudf::device_span<cuda::std::byte const>> transformed_bloom_filter_data;
transformed_bloom_filter_data.reserve(bloom_filter_data.size());
Expand Down
13 changes: 7 additions & 6 deletions cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,14 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base {
* @param output_column_schemas schema indices of output columns
* @param filter AST expression to filter row groups based on bloom filters
*
* @return Byte ranges of bloom filters, one per column chunk with equality predicate
* @return A pair of vectors containing bloom filter byte ranges and corresponding source indices
*/
[[nodiscard]] std::vector<cudf::io::text::byte_range_info> get_bloom_filter_bytes(
std::span<std::vector<size_type> const> row_group_indices,
std::span<data_type const> output_dtypes,
std::span<cudf::size_type const> output_column_schemas,
std::reference_wrapper<ast::expression const> filter);
[[nodiscard]] std::pair<std::vector<cudf::io::text::byte_range_info>,
std::vector<cudf::size_type>>
bloom_filters_byte_ranges(std::span<std::vector<size_type> const> row_group_indices,
std::span<data_type const> output_dtypes,
std::span<cudf::size_type const> output_column_schemas,
std::reference_wrapper<ast::expression const> filter);

/**
* @brief Get the dictionary page byte ranges, one per column chunk with (in)equality predicate
Expand Down
24 changes: 20 additions & 4 deletions cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,14 @@ hybrid_scan_reader_impl::secondary_filters_byte_ranges(
CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered");
auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options);

// Single source: keep only the bloom filter byte ranges, not the source map
auto const bloom_filter_bytes =
_extended_metadata->get_bloom_filter_bytes(row_group_indices,
output_dtypes,
_output_column_schemas,
expr_conv.get_converted_expr().value());
_extended_metadata
->bloom_filters_byte_ranges(row_group_indices,
output_dtypes,
_output_column_schemas,
expr_conv.get_converted_expr().value())
.first;
auto const dictionary_page_bytes =
_extended_metadata
->dictionary_pages_byte_ranges(row_group_indices,
Expand All @@ -314,6 +317,19 @@ hybrid_scan_reader_impl::dictionary_pages_byte_ranges(
expr_conv.get_converted_expr().value());
}

std::pair<std::vector<byte_range_info>, std::vector<size_type>>
hybrid_scan_reader_impl::bloom_filters_byte_ranges(
std::span<std::vector<size_type> const> row_group_indices, parquet_reader_options const& options)
{
CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered");
auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options);

return _extended_metadata->bloom_filters_byte_ranges(row_group_indices,
output_dtypes,
_output_column_schemas,
expr_conv.get_converted_expr().value());
}

std::vector<std::vector<size_type>>
hybrid_scan_reader_impl::filter_row_groups_with_dictionary_pages(
std::span<cudf::device_span<uint8_t const> const> dictionary_page_data,
Expand Down
9 changes: 8 additions & 1 deletion cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,19 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl {
cuda::stream_ref stream);

/**
* @copydoc cudf::io::parquet::experimental::hybrid_scan_multifile::secondary_filters_byte_ranges
* @copydoc cudf::io::parquet::experimental::hybrid_scan_reader::secondary_filters_byte_ranges
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<byte_range_info>>
secondary_filters_byte_ranges(std::span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options);

/**
* @copydoc cudf::io::parquet::experimental::hybrid_scan_multifile::bloom_filters_byte_ranges
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<size_type>>
bloom_filters_byte_ranges(std::span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options);

/**
* @copydoc cudf::io::parquet::experimental::hybrid_scan_multifile::dictionary_pages_byte_ranges
*/
Expand Down
19 changes: 15 additions & 4 deletions cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,24 @@ std::vector<std::vector<size_type>> hybrid_scan_multifile::filter_row_groups_wit
return _impl->filter_row_groups_with_stats(row_group_indices, options, stream);
}

std::pair<std::vector<text::byte_range_info>, std::vector<text::byte_range_info>>
hybrid_scan_multifile::secondary_filters_byte_ranges(
cudf::host_span<std::vector<size_type> const> row_group_indices,
std::pair<std::vector<text::byte_range_info>, std::vector<size_type>>
hybrid_scan_multifile::bloom_filters_byte_ranges(
std::span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const
{
CUDF_FUNC_RANGE();
return _impl->secondary_filters_byte_ranges(row_group_indices, options);
return _impl->bloom_filters_byte_ranges(row_group_indices, options);
}

std::vector<std::vector<size_type>> hybrid_scan_multifile::filter_row_groups_with_bloom_filters(
std::span<cudf::device_span<uint8_t const> const> bloom_filter_data,
std::span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options,
cuda::stream_ref stream) const
{
CUDF_FUNC_RANGE();
return _impl->filter_row_groups_with_bloom_filters(
bloom_filter_data, row_group_indices, options, stream);
}

std::unique_ptr<cudf::column> hybrid_scan_multifile::build_all_true_row_mask(
Expand Down
Loading
Loading