Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
80d92b7
Prefetch parquet metadata for scan tasks
TomAugspurger May 22, 2026
109ad43
Use FilepathSource
TomAugspurger Jun 2, 2026
047d478
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 2, 2026
52340ba
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 2, 2026
1b28cbd
Revert "Use FilepathSource"
TomAugspurger Jun 2, 2026
3bff8b1
fixes
TomAugspurger Jun 2, 2026
5e47b6c
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 4, 2026
f3fa17f
use streamingscan
TomAugspurger Jun 4, 2026
7f19690
use streamingscan
TomAugspurger Jun 4, 2026
cf23d61
Fix the test
TomAugspurger Jun 5, 2026
f80f5fd
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 5, 2026
0533d4f
Review
TomAugspurger Jun 5, 2026
ac0d799
Tests
TomAugspurger Jun 5, 2026
5d10839
tests
TomAugspurger Jun 5, 2026
2decc39
test coverage
TomAugspurger Jun 5, 2026
46bf9e2
Test revamp
TomAugspurger Jun 5, 2026
55a84d2
remove unused code
TomAugspurger Jun 5, 2026
020a959
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 5, 2026
d000c01
conditional prefetch
TomAugspurger Jun 5, 2026
58c9f38
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 5, 2026
5eb05b5
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 8, 2026
6cc42a0
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 9, 2026
c3b4f76
review
TomAugspurger Jun 9, 2026
bc0bde7
combine tests
TomAugspurger Jun 9, 2026
504334f
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 9, 2026
ec64ce2
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 12, 2026
f82f7a9
fusedscan
TomAugspurger Jun 12, 2026
3ad4638
Merge branch 'main' into tom/cudf-polars-prefetch-metadata
TomAugspurger Jun 15, 2026
2b234dd
Reuse parquet metadata
TomAugspurger Jun 15, 2026
759fb37
disable with 0
TomAugspurger Jun 15, 2026
5c85fce
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 15, 2026
419e793
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 22, 2026
1d9e214
lint
TomAugspurger Jun 22, 2026
8d0bc3a
fixes
TomAugspurger Jun 22, 2026
580c5a1
Error if both are provided
TomAugspurger Jun 23, 2026
bb10333
remove redundant isinstance
TomAugspurger Jun 23, 2026
1f9be6b
error message
TomAugspurger Jun 23, 2026
02a37aa
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 23, 2026
01a9812
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 23, 2026
6117014
Update the mock
TomAugspurger Jun 23, 2026
b66af9f
Switch to per-path caching
TomAugspurger Jun 23, 2026
daa4078
add a todo
TomAugspurger Jun 23, 2026
0bd3a1d
Merge branch 'main' into tom/cudf-polars-prefetch-metadata
TomAugspurger Jun 29, 2026
674209f
Followup to tom/cudf-sourceinfo-size
TomAugspurger Jun 9, 2026
0446983
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 29, 2026
bb7a9a6
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 30, 2026
73a1040
doc cachedparquetinfo
TomAugspurger Jun 30, 2026
ab9d500
WIP: POC for eliminating IRExecutionContext cache
TomAugspurger Jun 30, 2026
6a62e71
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jun 30, 2026
d6c125d
self review
TomAugspurger Jun 30, 2026
36cb181
move
TomAugspurger Jun 30, 2026
ef8a87e
One more
TomAugspurger Jun 30, 2026
479036e
fixup
TomAugspurger Jun 30, 2026
c4a6fe0
fixup
TomAugspurger Jun 30, 2026
44f0eb7
cleanup
TomAugspurger Jul 1, 2026
018af7c
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 1, 2026
fbdfa0b
Fix fast_count
TomAugspurger Jul 1, 2026
95430fd
hash bug fix
TomAugspurger Jul 1, 2026
2a71cff
Merge branch 'main' into tom/cudf-polars-prefetch-metadata
TomAugspurger Jul 1, 2026
d01be74
Truncate error message
TomAugspurger Jul 1, 2026
4e7f4dc
Coverage, simplification
TomAugspurger Jul 1, 2026
6f62dd2
kvikio pragmas
TomAugspurger Jul 1, 2026
848b48d
coverage
TomAugspurger Jul 1, 2026
2e6e1da
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 1, 2026
2734181
Workaround Projection(Cache(StreamingScan)) issue
TomAugspurger Jul 2, 2026
3d2cb95
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 2, 2026
e8f8534
coverage
TomAugspurger Jul 2, 2026
bb6cbe5
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 6, 2026
bd02450
revert replace
TomAugspurger Jul 6, 2026
805bba3
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 6, 2026
c8e77a0
Update hashing, equality of prefetched metadata
TomAugspurger Jul 6, 2026
cd1b8cc
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 6, 2026
d22d7cc
Coverage
TomAugspurger Jul 6, 2026
90659c7
Coverage
TomAugspurger Jul 6, 2026
6a97719
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 6, 2026
db078d3
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 6, 2026
677b6f2
Link to blocker
TomAugspurger Jul 6, 2026
0b7898b
Inline
TomAugspurger Jul 6, 2026
ef5bb7a
Remove uninformative comments
TomAugspurger Jul 6, 2026
7a5f86b
Merge remote-tracking branch 'upstream/main' into tom/cudf-polars-pre…
TomAugspurger Jul 6, 2026
c50c207
Merge branch 'main' into tom/cudf-polars-prefetch-metadata
TomAugspurger Jul 7, 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
4 changes: 3 additions & 1 deletion python/cudf_polars/cudf_polars/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
from rmm._cuda import gpu

import cudf_polars.dsl.tracing
from cudf_polars.dsl.ir import IRExecutionContext
from cudf_polars.dsl.ir import (
IRExecutionContext,
)
from cudf_polars.dsl.tracing import CUDF_POLARS_NVTX_DOMAIN
from cudf_polars.dsl.translate import Translator
from cudf_polars.utils.config import (
Expand Down
104 changes: 91 additions & 13 deletions python/cudf_polars/cudf_polars/dsl/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import itertools
import json
import random
import reprlib
import time
import uuid
from collections.abc import Sized
Expand Down Expand Up @@ -66,6 +67,7 @@
)

if TYPE_CHECKING:
import concurrent.futures
from collections.abc import (
Callable,
Generator,
Expand All @@ -74,14 +76,14 @@
Iterator,
Sequence,
)
from concurrent.futures import ThreadPoolExecutor
Comment thread
Matt711 marked this conversation as resolved.
from typing import Literal, Self

from polars import polars # type: ignore[attr-defined]

from rmm.pylibrmm.stream import Stream

from cudf_polars.containers.dataframe import NamedColumn
from cudf_polars.dsl.utils.io import CachedParquetInfo
from cudf_polars.streaming.rank_aware_source import RankAwareSource
from cudf_polars.typing import CSECache, ClosedInterval, Schema, Slice as Zlice
from cudf_polars.utils.config import ParquetOptions
Expand Down Expand Up @@ -138,7 +140,7 @@ class IRExecutionContext:
Identifier for the query being executed.
"""

py_executor: ThreadPoolExecutor | None = field(default=None)
py_executor: concurrent.futures.ThreadPoolExecutor | None = field(default=None)
get_cuda_stream: Callable[[], Stream] = field(default=get_cuda_stream)
query_id: uuid.UUID = field(default_factory=uuid.uuid4)

Expand Down Expand Up @@ -570,6 +572,8 @@ def do_evaluate(
def _parquet_physical_types(
paths: list[str], columns: list[str] | None
) -> dict[str, plc.DataType]:
# TODO: Use prefetched metadata
# https://github.com/rapidsai/cudf/issues/22940
metadata = plc.io.parquet_metadata.read_parquet_metadata(plc.io.SourceInfo(paths))
column_types = metadata.schema().column_types()

Expand Down Expand Up @@ -637,6 +641,7 @@ class Scan(IR):
"""Input from files."""

__slots__ = (
"cached_parquet_info",
"cloud_options",
"include_file_paths",
"n_rows",
Expand All @@ -663,7 +668,7 @@ class Scan(IR):
"predicate",
"parquet_options",
)
_n_non_child_args = 11
_n_non_child_args = 12
typ: str
"""What type of file are we reading? Parquet, CSV, etc..."""
reader_options: dict[str, Any]
Expand All @@ -686,6 +691,8 @@ class Scan(IR):
"""Mask to apply to the read dataframe."""
parquet_options: ParquetOptions
"""Parquet-specific options."""
cached_parquet_info: list[CachedParquetInfo] | None
"""Cached parquet file metadata."""

PARQUET_DEFAULT_CHUNK_SIZE: int = 0 # unlimited
PARQUET_DEFAULT_PASS_LIMIT: int = 16 * 1024**3 # 16GiB
Expand All @@ -704,6 +711,7 @@ def __init__(
include_file_paths: str | None,
predicate: expr.NamedExpr | None,
parquet_options: ParquetOptions,
cached_parquet_info: list[CachedParquetInfo] | None = None,
):
self.schema = schema
self.typ = typ
Expand All @@ -728,9 +736,14 @@ def __init__(
include_file_paths,
predicate,
parquet_options,
cached_parquet_info,
)
self.children = ()
self.parquet_options = parquet_options
self.cached_parquet_info = cached_parquet_info

Scan._validate_cached_parquet_info(self.paths, self.cached_parquet_info)

if self.typ not in ("csv", "parquet", "ndjson"): # pragma: no cover
# This line is unhittable ATM since IPC/Anonymous scan raise
# on the polars side
Expand Down Expand Up @@ -810,6 +823,21 @@ def __init__(
"Reading only parquet metadata to produce row index."
)

@staticmethod
def _validate_cached_parquet_info(
paths: list[str],
cached_parquet_info: list[CachedParquetInfo] | None,
) -> None:
if cached_parquet_info is not None and paths != [
info.path for info in cached_parquet_info
]:
missing = reprlib.repr(
set(paths) - {info.path for info in cached_parquet_info}
)
raise AssertionError(
f"Paths do not match cached parquet info. Missing paths: {missing}"
)

def get_hashable(self) -> Hashable:
"""
Hashable representation of the node.
Expand Down Expand Up @@ -866,12 +894,34 @@ def add_file_paths(
@staticmethod
@nvtx_annotate_cudf_polars(message="Scan._get_parquet_row_count_from_metadata")
def _get_parquet_row_count_from_metadata(
paths: list[str], skip_rows: int, n_rows: int
paths: list[str],
Comment thread
TomAugspurger marked this conversation as resolved.
skip_rows: int,
n_rows: int,
parquet_options: ParquetOptions,
cached_parquet_info: list[CachedParquetInfo] | None,
) -> int:
# Zero-width parquet files lose their row count when read through
# pylibcudf. See https://github.com/rapidsai/cudf/issues/21428
meta = plc.io.parquet_metadata.read_parquet_metadata(plc.io.SourceInfo(paths))
num_rows = meta.num_rows() - skip_rows
if parquet_options.prefetch_file_metadata:
if cached_parquet_info is None:
raise AssertionError(
"Cached parquet info is required when prefetching file metadata is enabled"
)

Scan._validate_cached_parquet_info(paths, cached_parquet_info)
parquet_metadatas = [
info.file_metadata for info in cached_parquet_info
] # pragma: no cover
num_rows = sum(
metadata.num_rows for metadata in parquet_metadatas
) # pragma: no cover
else:
meta = plc.io.parquet_metadata.read_parquet_metadata(
plc.io.SourceInfo(paths)
)
num_rows = meta.num_rows()

num_rows -= skip_rows
if n_rows != -1:
num_rows = min(num_rows, n_rows)
return max(num_rows, 0)
Expand All @@ -892,6 +942,7 @@ def do_evaluate(
include_file_paths: str | None,
predicate: expr.NamedExpr | None,
parquet_options: ParquetOptions,
cached_parquet_info: list[CachedParquetInfo] | None,
*,
context: IRExecutionContext,
) -> DataFrame:
Expand Down Expand Up @@ -1007,6 +1058,24 @@ def read_csv_header(
df,
)
elif typ == "parquet":
if parquet_options.prefetch_file_metadata:
if cached_parquet_info is None:
raise AssertionError(
"Cached parquet info is required when prefetching file metadata is enabled"
)
Scan._validate_cached_parquet_info(paths, cached_parquet_info)
filepath_sources = []
parquet_metadatas = []
for info in cached_parquet_info:
filepath_sources.append(
plc.io.types.FilepathSource(info.path, info.size)
)
parquet_metadatas.append(info.file_metadata)
source_info = plc.io.SourceInfo(filepath_sources)
else:
parquet_metadatas = None
source_info = plc.io.SourceInfo(paths)

filters = None
if predicate is not None and row_index is None:
# Can't apply filters during read if we have a row index.
Expand All @@ -1016,9 +1085,7 @@ def read_csv_header(
),
stream=stream,
)
builder = plc.io.parquet.ParquetReaderOptions.builder(
plc.io.SourceInfo(paths)
)
builder = plc.io.parquet.ParquetReaderOptions.builder(source_info)
if filters is not None and parquet_options.use_jit_filter:
builder.use_jit_filter(use_jit_filter=True)
parquet_reader_options = builder.decimal_width(
Expand All @@ -1038,6 +1105,7 @@ def read_csv_header(
parquet_reader_options,
chunk_read_limit=parquet_options.chunk_read_limit,
pass_read_limit=parquet_options.pass_read_limit,
parquet_metadatas=parquet_metadatas,
stream=stream,
)
chunk = reader.read_chunk()
Expand All @@ -1053,7 +1121,9 @@ def read_csv_header(
[concatenated_columns[i], columns.pop()], stream=stream
)
num_rows = (
cls._get_parquet_row_count_from_metadata(paths, skip_rows, n_rows)
cls._get_parquet_row_count_from_metadata(
paths, skip_rows, n_rows, parquet_options, cached_parquet_info
)
if not names
else None
)
Expand All @@ -1070,12 +1140,16 @@ def read_csv_header(
)
else:
tbl_w_meta = plc.io.parquet.read_parquet(
parquet_reader_options, stream=stream
parquet_reader_options,
parquet_metadatas=parquet_metadatas,
stream=stream,
)
# TODO: consider nested column names?
col_names = tbl_w_meta.column_names(include_children=False)
num_rows = (
cls._get_parquet_row_count_from_metadata(paths, skip_rows, n_rows)
cls._get_parquet_row_count_from_metadata(
paths, skip_rows, n_rows, parquet_options, cached_parquet_info
)
if not col_names
else None
)
Expand Down Expand Up @@ -1747,7 +1821,11 @@ def evaluate(
stream = context.get_cuda_stream()
scan = self.children[0]
effective_rows = Scan._get_parquet_row_count_from_metadata(
scan.paths, scan.skip_rows, scan.n_rows
scan.paths,
scan.skip_rows,
scan.n_rows,
scan.parquet_options,
None,
)
dtype = DataType(pl.UInt32())
col = Column(
Expand Down
1 change: 1 addition & 0 deletions python/cudf_polars/cudf_polars/dsl/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ def _(node: plrs._ir_nodes.Scan, translator: Translator, schema: Schema) -> ir.I
)
),
parquet_options,
cached_parquet_info=None,

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.

note: I gave up on prefetching for the in-memory engine. We can revisit that if we want (it'd need a similar replace as happens in engine/core.py)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FYI I also did not do hybrid scan for the in-memory engine

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.

Seems fine to focus on streaming.

)


Expand Down
Loading
Loading