Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion cpp/src/io/parquet/io_utils/parquet_io_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,12 @@ fetch_byte_ranges_to_device_async_impl(
auto const& byte_ranges = byte_ranges_per_source[source_idx];

// Total buffer size required for column chunks of this source
auto const source_size = datasources[source_idx].get().size();
auto const buffer_size = std::accumulate(
byte_ranges.begin(), byte_ranges.end(), std::size_t{0}, [](auto acc, auto const& range) {
byte_ranges.begin(), byte_ranges.end(), std::size_t{0}, [&](auto acc, auto const& range) {
CUDF_EXPECTS(
static_cast<size_t>(range.offset()) + static_cast<size_t>(range.size()) <= source_size,
"Byte range exceeds datasource size");
return acc + range.size();
});

Expand Down
39 changes: 39 additions & 0 deletions cpp/tests/io/experimental/hybrid_scan_filters_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,45 @@ TEST_F(HybridScanFiltersTest, RowGroupPasses)
}
}

TEST_F(HybridScanFiltersTest, FetchByteRangesInvalidRanges)
{
std::vector<std::byte> data(1024);
auto const datasource =
cudf::io::datasource::create(cudf::host_span<std::byte const>(data.data(), data.size()));
auto const stream = cudf::get_default_stream();
auto const mr = cudf::get_current_device_resource_ref();

EXPECT_THROW(
cudf::io::parquet::fetch_byte_ranges_to_device_async(
*datasource,
std::vector<cudf::io::text::byte_range_info>{cudf::io::text::byte_range_info{-1, 16}},
stream,
mr),
cudf::logic_error);

EXPECT_THROW(
cudf::io::parquet::fetch_byte_ranges_to_device_async(
*datasource,
std::vector<cudf::io::text::byte_range_info>{cudf::io::text::byte_range_info{512, 1024}},
stream,
mr),
cudf::logic_error);

EXPECT_THROW(
cudf::io::parquet::fetch_byte_ranges_to_device_async(
*datasource,
std::vector<cudf::io::text::byte_range_info>{cudf::io::text::byte_range_info{0, -1}},
stream,
mr),
cudf::logic_error);

EXPECT_NO_THROW(cudf::io::parquet::fetch_byte_ranges_to_device_async(
*datasource,
std::vector<cudf::io::text::byte_range_info>{cudf::io::text::byte_range_info{1023, 1}},
stream,
mr));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

class DictionaryFilterGapTest : public HybridScanFiltersTest,
public ::testing::WithParamInterface<cudf::io::compression_type> {};

Expand Down
8 changes: 8 additions & 0 deletions docs/cudf/source/pylibcudf/api_docs/io/experimental.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
============
Experimental
============

APIs in this namespace are experimental and may change without warning in the future.

.. automodule:: pylibcudf.io.experimental
:members:
2 changes: 2 additions & 0 deletions docs/cudf/source/pylibcudf/api_docs/io/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ I/O Functions

avro
csv
experimental
json
orc
parquet
parquet_io_utils
parquet_metadata
text
timezone
6 changes: 6 additions & 0 deletions docs/cudf/source/pylibcudf/api_docs/io/parquet_io_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
================
Parquet IO Utils
================

.. automodule:: pylibcudf.io.parquet_io_utils
:members:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4 changes: 2 additions & 2 deletions python/pylibcudf/pylibcudf/io/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# =============================================================================
# cmake-format: off
# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# cmake-format: on
# =============================================================================

set(cython_sources avro.pyx csv.pyx datasource.pyx json.pyx orc.pyx parquet.pyx
parquet_metadata.pyx text.pyx timezone.pyx types.pyx
parquet_io_utils.pyx parquet_metadata.pyx text.pyx timezone.pyx types.pyx
)

set(linked_libraries cudf::cudf)
Expand Down
2 changes: 2 additions & 0 deletions python/pylibcudf/pylibcudf/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
json,
orc,
parquet,
parquet_io_utils,
parquet_metadata,
text,
timezone,
Expand All @@ -30,6 +31,7 @@
"json",
"orc",
"parquet",
"parquet_io_utils",
"parquet_metadata",
"text",
"timezone",
Expand Down
18 changes: 18 additions & 0 deletions python/pylibcudf/pylibcudf/io/parquet_io_utils.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from pylibcudf.io.text cimport ByteRangeInfo
from pylibcudf.io.types cimport SourceInfo
from rmm.pylibrmm.memory_resource cimport DeviceMemoryResource

cpdef list fetch_byte_ranges_to_device(
SourceInfo source_info,
list byte_ranges,
object stream=*,
DeviceMemoryResource mr=*,
)

cpdef bytes fetch_page_index_to_host(
SourceInfo source_info,
ByteRangeInfo page_index_range,
)
Comment thread
Matt711 marked this conversation as resolved.
22 changes: 22 additions & 0 deletions python/pylibcudf/pylibcudf/io/parquet_io_utils.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from rmm.pylibrmm.memory_resource import DeviceMemoryResource

from pylibcudf.gpumemoryview import gpumemoryview
from pylibcudf.io.text import ByteRangeInfo
from pylibcudf.io.types import SourceInfo
from pylibcudf.utils import CudaStreamLike

__all__ = ["fetch_byte_ranges_to_device", "fetch_page_index_to_host"]

def fetch_byte_ranges_to_device(
source_info: SourceInfo,
byte_ranges: list[ByteRangeInfo],
stream: CudaStreamLike | None = None,
mr: DeviceMemoryResource | None = None,
) -> list[gpumemoryview]: ...
def fetch_page_index_to_host(
source_info: SourceInfo,
page_index_range: ByteRangeInfo,
) -> bytes: ...
157 changes: 157 additions & 0 deletions python/pylibcudf/pylibcudf/io/parquet_io_utils.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""IO utilities for Parquet."""

from libc.stddef cimport size_t
from libc.stdint cimport uint8_t, uintptr_t
from libcpp.memory cimport make_unique, unique_ptr
from libcpp.pair cimport pair
from libcpp.utility cimport move
from libcpp.vector cimport vector
from cython.operator cimport dereference

from rmm.librmm.device_buffer cimport device_buffer
from rmm.pylibrmm.device_buffer cimport DeviceBuffer
from rmm.pylibrmm.memory_resource cimport DeviceMemoryResource
from rmm.pylibrmm.stream cimport Stream

from pylibcudf.gpumemoryview cimport gpumemoryview
from pylibcudf.io.text cimport ByteRangeInfo
from pylibcudf.io.types cimport SourceInfo
from pylibcudf.libcudf.io.datasource cimport datasource, make_datasources
from pylibcudf.libcudf.io.parquet_io_utils cimport (
const_byte_range_info,
const_uint8_t,
cpp_fetch_byte_ranges_to_device,
fetch_page_index_to_host as cpp_fetch_page_index_to_host,
)

from pylibcudf.libcudf.io.text cimport byte_range_info
from pylibcudf.libcudf.utilities.span cimport device_span, host_span
from pylibcudf.utils cimport _get_memory_resource, _get_stream

__all__ = ["fetch_byte_ranges_to_device", "fetch_page_index_to_host"]


cpdef list fetch_byte_ranges_to_device(
SourceInfo source_info,
list byte_ranges,
object stream=None,
DeviceMemoryResource mr=None,
):
"""Fetch byte ranges from a Parquet source into device memory.

Parameters
----------
source_info : SourceInfo
Source describing a single Parquet file.
byte_ranges : list[ByteRangeInfo]
Byte ranges to fetch, as returned by
:meth:`~pylibcudf.io.experimental.HybridScanReader.filter_column_chunks_byte_ranges`,
:meth:`~pylibcudf.io.experimental.HybridScanReader.payload_column_chunks_byte_ranges`,
or
:meth:`~pylibcudf.io.experimental.HybridScanReader.all_column_chunks_byte_ranges`.
stream : Stream, optional
CUDA stream.
mr : DeviceMemoryResource, optional
Device memory resource.

Returns
-------
list[gpumemoryview]
One view per byte range. Each view holds a reference to the
:class:`~rmm.DeviceBuffer` that owns its memory, keeping the
allocation alive for as long as the view is referenced.

Raises
------
ValueError
If ``source_info`` does not describe exactly one source.
"""
cdef Stream _stream = _get_stream(stream)
cdef DeviceMemoryResource _mr = _get_memory_resource(mr)
cdef vector[unique_ptr[datasource]] sources = make_datasources(source_info.c_obj)
if sources.size() != 1:
raise ValueError(
f"fetch_byte_ranges_to_device requires exactly one source, "
f"got {sources.size()}"
)

cdef vector[byte_range_info] ranges_vec
cdef ByteRangeInfo bri
for bri in byte_ranges:
ranges_vec.push_back(bri.c_obj)

cdef pair[vector[device_buffer], vector[device_span[const_uint8_t]]] fetched
with nogil:
fetched = cpp_fetch_byte_ranges_to_device(

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.

Maybe add a note here that this ensures the C++ future is complete?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added here 4544758

dereference(sources[0]),
host_span[const_byte_range_info](ranges_vec.data(), ranges_vec.size()),
_stream.view(),
_mr.get_mr(),
)

if fetched.first.size() != 1:
raise RuntimeError(
f"Expected exactly one device buffer, got {fetched.first.size()}"
)
cdef DeviceBuffer owner = DeviceBuffer.c_from_unique_ptr(
make_unique[device_buffer](move(fetched.first[0])),
_stream,
_mr,
)
cdef gpumemoryview owner_gv = gpumemoryview(owner)
cdef uintptr_t base = owner_gv.ptr
cdef uintptr_t ptr
cdef size_t n
result = []
for i in range(fetched.second.size()):
ptr = <uintptr_t>fetched.second[i].data()
n = fetched.second[i].size()
result.append(owner_gv.byte_slice(slice(ptr - base, ptr - base + n)))
return result
Comment thread
coderabbitai[bot] marked this conversation as resolved.


cpdef bytes fetch_page_index_to_host(
SourceInfo source_info,
ByteRangeInfo page_index_range,
):
"""Fetch parquet page index bytes to host memory.

Parameters
----------
source_info : SourceInfo
Source describing a single Parquet file.
page_index_range : ByteRangeInfo
Byte range of the page index, as returned by
:meth:`~pylibcudf.io.experimental.HybridScanReader.page_index_byte_range`.

Returns
-------
bytes
Raw page index bytes copied to Python host memory.

Raises
------
ValueError
If ``source_info`` does not describe exactly one source.
"""
cdef vector[unique_ptr[datasource]] sources = make_datasources(source_info.c_obj)
if sources.size() != 1:
raise ValueError(
f"fetch_page_index_to_host requires exactly one source, "
f"got {sources.size()}"
)

cdef unique_ptr[datasource.buffer] buf
with nogil:
buf = move(cpp_fetch_page_index_to_host(
dereference(sources[0]),
(<ByteRangeInfo>page_index_range).c_obj,
))

if buf.get() is NULL:
raise RuntimeError("fetch_page_index_to_host returned no buffer")
cdef const uint8_t* ptr = buf.get().data()
cdef size_t n = buf.get().size()
return bytes(ptr[:n])
Comment thread
coderabbitai[bot] marked this conversation as resolved.
2 changes: 1 addition & 1 deletion python/pylibcudf/pylibcudf/io/parquet_metadata.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ cdef class FileMetaData:

See Also
--------
read_parquet_footers
pylibcudf.io.parquet_metadata.read_parquet_footers
Read one ``FileMetaData`` per source directly from
:class:`pylibcudf.io.types.SourceInfo`.
"""
Expand Down
7 changes: 5 additions & 2 deletions python/pylibcudf/pylibcudf/libcudf/io/datasource.pxd
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
from libc.stddef cimport size_t
from libc.stdint cimport uint8_t
from libcpp.memory cimport unique_ptr
from libcpp.vector cimport vector
from pylibcudf.libcudf.io.types cimport source_info
Expand All @@ -11,7 +12,9 @@ cdef extern from "cudf/io/datasource.hpp" \
namespace "cudf::io" nogil:

cdef cppclass datasource:
pass
cppclass buffer:
const uint8_t* data() except +libcudf_exception_handler const
size_t size() except +libcudf_exception_handler const

cdef vector[unique_ptr[datasource]] make_datasources(
source_info info
Expand Down
Loading
Loading