Skip to content

Commit aed30cc

Browse files
authored
Merge branch 'main' into docs-libcudf-introspect-overflow
2 parents 8c1ad71 + 35e7cd6 commit aed30cc

10 files changed

Lines changed: 415 additions & 158 deletions

File tree

cpp/libcudf_streaming/src/detail/device_bloom_filter.cu

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,16 @@ using BloomFilterRefType =
5858
cuco::bloom_filter_ref<KeyType,
5959
cuco::extent<std::size_t>,
6060
cuco::thread_scope_device,
61-
cuco::arrow_filter_policy<KeyType, cuco::identity_hash>>;
61+
cuco::parametric_filter_policy<cuco::identity_hash<KeyType>,
62+
std::uint32_t,
63+
8,
64+
8,
65+
8,
66+
1,
67+
1,
68+
8,
69+
false,
70+
false>>;
6271
using StorageType = BloomFilterRefType::filter_block_type;
6372

6473
} // namespace

cpp/src/io/parquet/arrow_filter_policy.cuh

Lines changed: 0 additions & 118 deletions
This file was deleted.

cpp/src/io/parquet/bloom_filter_reader.cu

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
#include "arrow_filter_policy.cuh"
76
#include "compact_protocol_reader.hpp"
87
#include "expression_transform_helpers.hpp"
98
#include "io/utilities/time_utils.hpp"
@@ -25,6 +24,7 @@
2524
#include <rmm/device_buffer.hpp>
2625
#include <rmm/exec_policy.hpp>
2726

27+
#include <cuco/bloom_filter_policies.cuh>
2828
#include <cuco/bloom_filter_ref.cuh>
2929
#include <cuda/iterator>
3030
#include <thrust/tabulate.h>
@@ -36,6 +36,30 @@
3636
namespace cudf::io::parquet::detail {
3737
namespace {
3838

39+
/**
40+
* @brief Policy describing the Apache Arrow Block-Split Bloom Filter, hashing keys with cudf's
41+
* `XXHash_64` (so that `cudf::string_view` and other cudf types are hashed by content, matching the
42+
* Apache Parquet/Arrow bloom filter specification).
43+
*
44+
* Uses cuco's `parametric_filter_policy` with the Apache Arrow layout: 256-bit blocks (8 x
45+
* `uint32_t`), 8 fingerprint bits per key, fully horizontal add (Theta=8) and fully vertical
46+
* contains (Phi=8). This layout is bit-compatible with Apache Arrow, as verified by cuCollections
47+
* `tests/bloom_filter/arrow_compat_test.cu`.
48+
*
49+
* @tparam Key The type of the values to generate a fingerprint for.
50+
*/
51+
template <class Key>
52+
using arrow_filter_policy = cuco::parametric_filter_policy<cudf::hashing::detail::XXHash_64<Key>,
53+
std::uint32_t,
54+
8,
55+
8,
56+
8,
57+
1,
58+
1,
59+
8,
60+
false,
61+
false>;
62+
3963
/**
4064
* @brief Converts bloom filter membership results (for each column chunk) to a device column.
4165
*

cpp/src/join/mark_join.cuh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
#pragma once
@@ -165,8 +165,7 @@ using storage_ref_type =
165165
cuco::bucket_storage_ref<mark_key_type, mark_join_bucket_size, cuco::extent<std::size_t>>;
166166
using right_key_type = cuco::pair<hash_value_type, rhs_index_type>;
167167

168-
using bloom_filter_policy_type =
169-
cuco::default_filter_policy<cuco::detail::identity_hash<hash_value_type>, hash_value_type, 2U>;
168+
using bloom_filter_policy_type = cuco::default_filter_policy<hash_value_type>;
170169
using bloom_filter_allocator_type = rmm::mr::polymorphic_allocator<cuda::std::byte>;
171170
using bloom_filter_type = cuco::bloom_filter<hash_value_type,
172171
cuco::extent<std::size_t>,

cpp/tests/io/parquet_bloom_filter_test.cu

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
#include "src/io/parquet/arrow_filter_policy.cuh"
7-
86
#include <cudf_test/base_fixture.hpp>
97
#include <cudf_test/column_utilities.hpp>
108
#include <cudf_test/column_wrapper.hpp>
@@ -18,15 +16,29 @@
1816
#include <rmm/mr/polymorphic_allocator.hpp>
1917

2018
#include <cuco/bloom_filter.cuh>
19+
#include <cuco/bloom_filter_policies.cuh>
20+
21+
#include <cstdint>
2122

2223
using StringType = cudf::string_view;
2324

2425
class ParquetBloomFilterTest : public cudf::test::BaseFixture {};
2526

2627
TEST_F(ParquetBloomFilterTest, TestStrings)
2728
{
28-
using key_type = StringType;
29-
using policy_type = cudf::io::parquet::detail::arrow_filter_policy<key_type>;
29+
using key_type = StringType;
30+
// Apache Arrow Block-Split Bloom Filter layout, hashing keys with cudf's `XXHash_64` (matching
31+
// `cudf::io::parquet::detail::arrow_filter_policy`).
32+
using policy_type = cuco::parametric_filter_policy<cudf::hashing::detail::XXHash_64<key_type>,
33+
std::uint32_t,
34+
8,
35+
8,
36+
8,
37+
1,
38+
1,
39+
8,
40+
false,
41+
false>;
3042
using word_type = policy_type::word_type;
3143

3244
std::size_t constexpr num_filter_blocks = 4;

python/cudf/cudf/core/udf/mlir_backend/masked_lowering.py

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from __future__ import annotations
44

5+
import operator
6+
from functools import partial
57
from typing import TYPE_CHECKING
68

79
from numba_cuda_mlir import types
810
from numba_cuda_mlir._mlir import ir as mlir_ir
9-
from numba_cuda_mlir._mlir.dialects import llvm
10-
from numba_cuda_mlir.extending import lowering_registry
11+
from numba_cuda_mlir._mlir.dialects import arith, llvm
12+
from numba_cuda_mlir.extending import lower_cast, lowering_registry
1113
from numba_cuda_mlir.lowering_utilities import convert
1214
from numba_cuda_mlir.models import PrimitiveModel, register_model
1315

1416
from cudf.core.udf.api import Masked
15-
from cudf.core.udf.mlir_backend.masked_typing import MaskedType
17+
from cudf.core.udf.mlir_backend.masked_typing import (
18+
MaskedType,
19+
NAType,
20+
na_type,
21+
)
1622

1723
if TYPE_CHECKING:
1824
from numba_cuda_mlir.mlir_lowering import MLIRLower
@@ -62,6 +68,21 @@ def __init__(
6268
super().__init__(data_model_manager, masked_type, struct_type)
6369

6470

71+
def _extract_masked_value_valid(struct_val, value_mlir_ty, valid_ty):
72+
"""Pull the ``(value, valid)`` SSA values out of a ``Masked`` struct."""
73+
v = llvm.extractvalue(
74+
res=value_mlir_ty,
75+
container=struct_val,
76+
position=mlir_ir.DenseI64ArrayAttr.get([0]),
77+
)
78+
valid = llvm.extractvalue(
79+
res=valid_ty,
80+
container=struct_val,
81+
position=mlir_ir.DenseI64ArrayAttr.get([1]),
82+
)
83+
return v, valid
84+
85+
6586
def _lower_masked_constructor(
6687
builder: MLIRLower, target: Var, args: list[Var], kwargs: list
6788
) -> None:
@@ -105,6 +126,63 @@ def _lower_masked_getattr(
105126
builder.store_var(target, convert(field_value, target_mlir_ty))
106127

107128

129+
# ``cast(NA -> Masked)`` and ``cast(scalar -> Masked)``. Both build a Masked
130+
# struct for the target's value type; they differ only in the payload (NA has
131+
# none, so use undef) and the validity bit (NA -> invalid, scalar -> valid).
132+
# Triggered by branch unification, e.g. ``return x if cond else cudf.NA`` or
133+
# ``return 5``.
134+
def _cast_to_masked(context, builder, from_ty, to_ty, val):
135+
value_mlir_ty = builder.get_mlir_type(to_ty.value_type)
136+
if isinstance(from_ty, NAType):
137+
value = llvm.UndefOp(value_mlir_ty)
138+
valid = 0
139+
else:
140+
value = convert(val, value_mlir_ty)
141+
valid = 1
142+
valid_const = arith.constant(
143+
result=builder.get_mlir_type(types.boolean), value=valid
144+
)
145+
return _pack_masked(builder, to_ty, value, valid_const)
146+
147+
148+
# ``cast(Masked -> Masked)``: branch unification across different
149+
# inner widths (e.g. one branch returns Masked(int32), another
150+
# returns Masked(float64); Numba unifies to Masked(float64)).
151+
# Promote the payload, preserve the validity bit.
152+
def _cast_masked_to_masked(context, builder, from_ty, to_ty, val):
153+
if from_ty.value_type == to_ty.value_type:
154+
return val
155+
st = llvm.StructType(val.type)
156+
m_val, m_valid = _extract_masked_value_valid(val, st.body[0], st.body[1])
157+
value_mlir_ty = builder.get_mlir_type(to_ty.value_type)
158+
casted = convert(m_val, value_mlir_ty)
159+
return _pack_masked(builder, to_ty, casted, m_valid)
160+
161+
162+
# ``is``/``is not`` against NA are registered for both operand orders
163+
# (``m is NA`` and ``NA is m``), so find the MaskedType operand rather than
164+
# assuming which position it is in.
165+
def _masked_operand(builder, args):
166+
"""Return the ``MaskedType`` operand of a ``Masked``/``NA`` comparison."""
167+
for arg in args:
168+
if isinstance(builder.get_numba_type(arg.name), MaskedType):
169+
return arg
170+
raise TypeError("expected a MaskedType operand")
171+
172+
173+
# ``is``/``is not`` against NA both reduce to the validity bit: ``m is NA`` ->
174+
# ``not m.valid`` and ``m is not NA`` -> ``m.valid``. Registered for both
175+
# operators (and operand orders) via partials below.
176+
def _lower_masked_na_compare(builder, target, args, kwargs, *, is_null):
177+
m = builder.load_var(_masked_operand(builder, args))
178+
st = llvm.StructType(m.type)
179+
_, valid = _extract_masked_value_valid(m, st.body[0], st.body[1])
180+
if is_null:
181+
one = arith.constant(valid.type, 1)
182+
valid = arith.xori(valid, one)
183+
builder.store_var(target, valid)
184+
185+
108186
def _register() -> None:
109187
"""Register the data model and lowerings with ``numba_cuda_mlir``.
110188
@@ -123,5 +201,17 @@ def _register() -> None:
123201

124202
lowering_registry.lower_getattr_generic(MaskedType)(_lower_masked_getattr)
125203

204+
lower_cast(na_type, MaskedType)(_cast_to_masked)
205+
for _scalar_cls in (types.Integer, types.Float, types.Boolean):
206+
lower_cast(_scalar_cls, MaskedType)(_cast_to_masked)
207+
lower_cast(MaskedType, MaskedType)(_cast_masked_to_masked)
208+
209+
is_na = partial(_lower_masked_na_compare, is_null=True)
210+
is_not_na = partial(_lower_masked_na_compare, is_null=False)
211+
lower(operator.is_, MaskedType, NAType)(is_na)
212+
lower(operator.is_, NAType, MaskedType)(is_na)
213+
lower(operator.is_not, MaskedType, NAType)(is_not_na)
214+
lower(operator.is_not, NAType, MaskedType)(is_not_na)
215+
126216

127217
_register()

0 commit comments

Comments
 (0)