Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
121bf9d
Add hybrid scan multifile reader basics
mhaseeb123 May 21, 2026
b763cdb
Minor
mhaseeb123 May 21, 2026
c9bf419
Clean up claude's comments
mhaseeb123 May 21, 2026
795f058
Add gtests
mhaseeb123 May 21, 2026
ec0b59e
Merge branch 'main' into fea/hybrid-scan-multifile-base
mhaseeb123 May 21, 2026
f8b90ea
Apply suggestions from code review
mhaseeb123 May 21, 2026
90aef61
Update cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp
mhaseeb123 May 21, 2026
8876f57
Apply suggestions from @PointKernel (thanks!)
mhaseeb123 May 21, 2026
a575276
Minor
mhaseeb123 May 21, 2026
c7d7cb6
Minor changes
mhaseeb123 May 21, 2026
467a628
Allow more than 2B rows
mhaseeb123 May 21, 2026
1909146
Minor bug fix
mhaseeb123 May 21, 2026
17fd247
Minor
mhaseeb123 May 21, 2026
0493395
Merge branch 'main' into fea/hybrid-scan-multifile-base
mhaseeb123 May 21, 2026
e0319ab
Merge branch 'main' into fea/hybrid-scan-multifile-base
mhaseeb123 May 28, 2026
bf25506
Merge branch 'main' into fea/hybrid-scan-multifile-base
mhaseeb123 May 29, 2026
e013fda
Add multifile row group filtering with stats and byte ranges
mhaseeb123 May 29, 2026
209a9e2
Merge branch 'main' into fea/hybrid-scan-multifile-row-group-filter-p…
mhaseeb123 Jun 2, 2026
c164897
Revert unneeded changes
mhaseeb123 Jun 2, 2026
7a35f6e
Style fix
mhaseeb123 Jun 2, 2026
8c7b183
Address comments
mhaseeb123 Jun 2, 2026
140e90d
Address review comments
mhaseeb123 Jun 2, 2026
9fcbb0a
Apply suggestions
mhaseeb123 Jun 3, 2026
a35f429
Style
mhaseeb123 Jun 3, 2026
269212c
Merge branch 'main' of https://github.com/rapidsai/cudf into fea/hybr…
mhaseeb123 Jun 4, 2026
0d404b1
Multifile hybrid scan APIs for row mask construction
mhaseeb123 Jun 4, 2026
db305f0
Simplify test
mhaseeb123 Jun 5, 2026
6b9d7af
Merge branch 'main' into fea/hybrid-scan-multifile-row-mask
mhaseeb123 Jun 5, 2026
48319ba
Multifile all column materializers for multifile hybrid scan
mhaseeb123 Jun 5, 2026
f9151ce
Doc updates
mhaseeb123 Jun 5, 2026
350047d
Merge branch 'main' into fea/column-chunk-byte-ranges
mhaseeb123 Jun 8, 2026
6cbf7ea
Merge branch 'main' into fea/column-chunk-byte-ranges
Matt711 Jun 9, 2026
344a2d5
Merge branch 'main' into fea/column-chunk-byte-ranges
vuule Jun 12, 2026
857dc5a
Merge branch 'main' into fea/column-chunk-byte-ranges
mhaseeb123 Jun 12, 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
61 changes: 61 additions & 0 deletions cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,67 @@ class hybrid_scan_multifile {
secondary_filters_byte_ranges(cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const;

/**
* @brief Builds a boolean survival column of size equal to the total number of rows in the row
* groups containing all `true` values
*
* @param row_group_indices Input per-source row group indices (one inner vector per source)
* @param stream CUDA stream used for device memory operations and kernel launches
* @param mr Device memory resource used to allocate the returned column's device memory
* @return An all-true boolean (survival) column spanning all selected rows across all sources
*/
[[nodiscard]] std::unique_ptr<cudf::column> build_all_true_row_mask(
cudf::host_span<std::vector<size_type> const> row_group_indices,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr) const;

/**
* @brief Builds a boolean column indicating surviving rows using page-level statistics in the
* page index
*
* @param row_group_indices Input per-source row group indices (one inner vector per source)
* @param options Parquet reader options
* @param stream CUDA stream used for device memory operations and kernel launches
* @param mr Device memory resource used to allocate the returned column's device memory
* @return A boolean column spanning all selected rows across all sources and indicating which
* filter column rows survive the statistics in the page index
*/
[[nodiscard]] std::unique_ptr<cudf::column> build_row_mask_with_page_index_stats(
cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr) const;

/**
* @brief Get byte ranges of column chunks of all (or selected) columns
*
* @param row_group_indices Input row group indices, one inner vector per source
* @param options Parquet reader options
* @return Pair of flattened byte ranges to column chunks of all (or selected) columns and their
* corresponding source indices
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<size_type>>
all_column_chunks_byte_ranges(cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options) const;

/**
* @brief Materializes all (or selected) columns and returns the final output table
*
* @param row_group_indices Input row group indices, one inner vector per source
* @param column_chunk_data Flattened device spans of column chunk data returned in the same order
* as `all_column_chunks_byte_ranges`
* @param options Parquet reader options
* @param stream CUDA stream used for device memory operations and kernel launches
* @param mr Device memory resource used to allocate the device memory for the output table
* @return Table of all materialized columns and metadata
*/
[[nodiscard]] table_with_metadata materialize_all_columns(
cudf::host_span<std::vector<size_type> const> row_group_indices,
cudf::host_span<cudf::device_span<uint8_t const> const> column_chunk_data,
parquet_reader_options const& options,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr) const;

private:
std::unique_ptr<detail::hybrid_scan_reader_impl> _impl;
};
Expand Down
13 changes: 4 additions & 9 deletions cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,15 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl {
rmm::cuda_stream_view stream);

/**
* @copydoc cudf::io::experimental::hybrid_scan::build_all_true_row_mask
* @copydoc cudf::io::experimental::hybrid_scan_multifile::build_all_true_row_mask
*/
[[nodiscard]] std::unique_ptr<cudf::column> build_all_true_row_mask(
cudf::host_span<std::vector<size_type> const> row_group_indices,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr);

/**
* @copydoc cudf::io::experimental::hybrid_scan::build_row_mask_with_page_index_stats
* @copydoc cudf::io::experimental::hybrid_scan_multifile::build_row_mask_with_page_index_stats
*/
[[nodiscard]] std::unique_ptr<cudf::column> build_row_mask_with_page_index_stats(
cudf::host_span<std::vector<size_type> const> row_group_indices,
Expand Down Expand Up @@ -196,19 +196,14 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl {
rmm::device_async_resource_ref mr);

/**
* @brief Fetches byte ranges for all (or selected) column chunks
*
* @param row_group_indices Input row groups indices
* @param options Parquet reader options
* @return Pair of a vector of byte ranges to column chunks of all (or selected) columns and a
* vector of their corresponding input source file indices
* @copydoc cudf::io::experimental::hybrid_scan_multifile::all_column_chunks_byte_ranges
*/
[[nodiscard]] std::pair<std::vector<byte_range_info>, std::vector<cudf::size_type>>
all_column_chunks_byte_ranges(cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options);

/**
* @copydoc cudf::io::experimental::hybrid_scan::materialize_all_columns
* @copydoc cudf::io::experimental::hybrid_scan_multifile::materialize_all_columns
*/
[[nodiscard]] table_with_metadata materialize_all_columns(
cudf::host_span<std::vector<size_type> const> row_group_indices,
Expand Down
39 changes: 39 additions & 0 deletions cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,43 @@ hybrid_scan_multifile::secondary_filters_byte_ranges(
return _impl->secondary_filters_byte_ranges(row_group_indices, options);
}

std::unique_ptr<cudf::column> hybrid_scan_multifile::build_all_true_row_mask(
cudf::host_span<std::vector<size_type> const> row_group_indices,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr) const
{
CUDF_FUNC_RANGE();
return _impl->build_all_true_row_mask(row_group_indices, stream, mr);
}

std::unique_ptr<cudf::column> hybrid_scan_multifile::build_row_mask_with_page_index_stats(
cudf::host_span<std::vector<size_type> const> row_group_indices,
parquet_reader_options const& options,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr) const
{
CUDF_FUNC_RANGE();
return _impl->build_row_mask_with_page_index_stats(row_group_indices, options, stream, mr);
}

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

table_with_metadata hybrid_scan_multifile::materialize_all_columns(
cudf::host_span<std::vector<size_type> const> row_group_indices,
cudf::host_span<cudf::device_span<uint8_t const> const> column_chunk_data,
parquet_reader_options const& options,
rmm::cuda_stream_view stream,
rmm::device_async_resource_ref mr) const
{
CUDF_FUNC_RANGE();
return _impl->materialize_all_columns(row_group_indices, column_chunk_data, options, stream, mr);
}

} // namespace cudf::io::parquet::experimental
1 change: 1 addition & 0 deletions cpp/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ ConfigureTest(
io/experimental/hybrid_scan_composer.cpp
io/experimental/hybrid_scan_filters_test.cpp
io/experimental/hybrid_scan_multifile_filters_test.cpp
io/experimental/hybrid_scan_multifile_test.cpp
io/experimental/hybrid_scan_test.cpp
io/parquet_common.cpp
io/parquet_test.cpp
Expand Down
52 changes: 52 additions & 0 deletions cpp/tests/io/experimental/hybrid_scan_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@

#include <cudf_test/column_wrapper.hpp>

#include <cudf/column/column_factories.hpp>
#include <cudf/io/parquet.hpp>
#include <cudf/table/table.hpp>
#include <cudf/types.hpp>
#include <cudf/utilities/default_stream.hpp>
#include <cudf/utilities/traits.hpp>

#include <rmm/device_buffer.hpp>

#include <cuda/iterator>

#include <format>
#include <random>
#include <string>
#include <utility>
#include <vector>

/**
* @brief Creates a strings column with a constant stringified value between 0 and 9999
Expand All @@ -35,6 +42,51 @@ cudf::test::strings_column_wrapper inline constant_strings(cudf::size_type value
return cudf::test::strings_column_wrapper(elements, elements + num_ordered_rows);
}

/**
* @brief Helper to construct a random list<str> column
*
* @param gen Random engine
* @param is_str_nullable Whether the string column should be nullable
* @param is_list_nullable Whether the list column should be nullable
*
* @return Unique pointer to the constructed list<str> column
*/
inline auto make_list_str_column(std::mt19937& gen, bool is_str_nullable, bool is_list_nullable)
{
auto constexpr num_rows = num_ordered_rows;
auto constexpr string_per_row = 3;
auto constexpr num_string_rows = num_rows * string_per_row;

std::vector<std::string> strings{
"abc", "x", "bananas", "gpu", "minty", "backspace", "", "cayenne", "turbine", "soft"};
std::uniform_int_distribution<int> uni(0, strings.size() - 1);
auto string_iter = cudf::detail::make_counting_transform_iterator(
0, [&](cudf::size_type idx) { return strings[uni(gen)]; });

std::bernoulli_distribution bn(0.7f);
auto string_valids = cudf::detail::make_counting_transform_iterator(
0, [&](int index) { return is_str_nullable ? bn(gen) : true; });
cudf::test::strings_column_wrapper string_col{
string_iter, string_iter + num_string_rows, string_valids};

auto offset_iter = cudf::detail::make_counting_transform_iterator(
0, [](cudf::size_type idx) { return idx * string_per_row; });
cudf::test::fixed_width_column_wrapper<cudf::size_type> offsets(offset_iter,
offset_iter + num_rows + 1);

auto list_valids =
cudf::detail::make_counting_transform_iterator(0, [&](int index) { return index % 100; });
auto [null_mask, null_count] = [&]() {
if (is_list_nullable) {
return cudf::test::detail::make_null_mask(list_valids, list_valids + num_rows);
} else {
return std::make_pair(rmm::device_buffer{}, 0);
}
}();
return cudf::make_lists_column(
num_rows, offsets.release(), string_col.release(), null_count, std::move(null_mask));
}

/**
* @brief Fail for types other than duration or timestamp
*/
Expand Down
79 changes: 79 additions & 0 deletions cpp/tests/io/experimental/hybrid_scan_multifile_common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cudf/io/experimental/hybrid_scan_multifile.hpp>
#include <cudf/io/parquet.hpp>
#include <cudf/io/parquet_io_utils.hpp>
#include <cudf/utilities/span.hpp>

#include <algorithm>
#include <functional>
#include <memory>
#include <vector>

/**
* @brief Struct to hold multifile datasources and footer buffers along with their byte spans
*/
struct multifile_inputs {
/**
* @brief Construct datasources, datasource refs, and footer byte spans from source info
*/
explicit multifile_inputs(cudf::io::source_info const& source_info)
: datasources{cudf::io::make_datasources(source_info)}
{
datasource_refs.reserve(datasources.size());
footer_buffers.reserve(datasources.size());
footer_byte_spans.reserve(datasources.size());

for (auto const& datasource : datasources) {
datasource_refs.emplace_back(*datasource);
footer_buffers.emplace_back(cudf::io::parquet::fetch_footer_to_host(datasource_refs.back()));
footer_byte_spans.emplace_back(*footer_buffers.back());
}
}

std::vector<std::unique_ptr<cudf::io::datasource>> datasources;
std::vector<std::reference_wrapper<cudf::io::datasource>> datasource_refs;
std::vector<std::unique_ptr<cudf::io::datasource::buffer>> footer_buffers;
std::vector<cudf::host_span<uint8_t const>> footer_byte_spans;
};

/**
* @brief Construct source info from host buffers
*/
template <typename Buffers>
cudf::io::source_info build_source_info(Buffers const& file_buffers)
{
std::vector<cudf::host_span<char const>> spans;
spans.reserve(file_buffers.size());
for (auto const& buf : file_buffers) {
spans.emplace_back(buf.data(), buf.size());
}
return cudf::io::source_info(cudf::host_span<cudf::host_span<char const>>{spans});
}

/**
* @brief Fetch and set up page indexes for all sources in a multifile reader
*/
inline void setup_page_indexes(cudf::io::parquet::experimental::hybrid_scan_multifile const& reader,
multifile_inputs const& inputs)
{
auto const page_index_byte_ranges = reader.page_index_byte_ranges();
std::vector<cudf::host_span<uint8_t const>> page_index_byte_spans;
page_index_byte_spans.reserve(page_index_byte_ranges.size());

auto const page_index_buffers = cudf::io::parquet::fetch_page_indexes_to_host(
cudf::host_span<std::reference_wrapper<cudf::io::datasource> const>{inputs.datasource_refs},
cudf::host_span<cudf::io::parquet::byte_range_info const>{page_index_byte_ranges});
std::transform(page_index_buffers.begin(),
page_index_buffers.end(),
std::back_inserter(page_index_byte_spans),
[](auto const& buffer) { return cudf::host_span<uint8_t const>{*buffer}; });

reader.setup_page_indexes(
cudf::host_span<cudf::host_span<uint8_t const> const>{page_index_byte_spans});
}
Loading
Loading