From 9b3d166c87d1f41fcc201db9f57f6b701db24f50 Mon Sep 17 00:00:00 2001 From: Yunsong Wang Date: Tue, 14 Jul 2026 18:47:53 +0000 Subject: [PATCH 01/12] Use iterative seeding for multi-column row hashing --- .../cudf/detail/row_operator/hashing.cuh | 22 +++++++++++++------ .../row_operator/preprocessed_table.cuh | 9 +++++--- cpp/src/row_operator/row_operators.cu | 9 +++++--- cpp/tests/hashing/murmurhash3_x86_32_test.cpp | 14 ++++++++++++ 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/cpp/include/cudf/detail/row_operator/hashing.cuh b/cpp/include/cudf/detail/row_operator/hashing.cuh index e71fc5213483..0f2c170a5c81 100644 --- a/cpp/include/cudf/detail/row_operator/hashing.cuh +++ b/cpp/include/cudf/detail/row_operator/hashing.cuh @@ -22,9 +22,9 @@ #include #include +#include #include #include -#include #include @@ -117,16 +117,22 @@ class device_row_hasher { */ __device__ result_type operator()(size_type row_index) const noexcept { - auto const hasher = [row_index, this](auto const& column) { + auto const hasher = [row_index, this](auto seed, auto const& column) { return cudf::type_dispatcher( - column.type(), element_hasher_adapter{_check_nulls, _seed}, column, row_index); + column.type(), element_hasher_adapter{_check_nulls, seed}, column, row_index); }; + if (_num_input_columns > 1) { + return detail::accumulate(_table.begin(), _table.end(), _seed, hasher); + } + auto const has_columns = _table.num_columns() > 0; - auto const init = has_columns ? hasher(_table.column(0)) : _seed; + auto const init = has_columns ? hasher(_seed, _table.column(0)) : _seed; auto const start_col = static_cast(has_columns); - auto it = thrust::make_transform_iterator(_table.begin() + start_col, hasher); + auto it = cuda::transform_iterator{ + _table.begin() + start_col, + [hasher, seed = _seed](auto const& column) { return hasher(seed, column); }}; return detail::accumulate( it, it + (_table.num_columns() - start_col), init, [](auto hash, auto h) { return cudf::hashing::detail::hash_combine(hash, h); @@ -217,13 +223,15 @@ class device_row_hasher { CUDF_HOST_DEVICE device_row_hasher(Nullate check_nulls, table_device_view t, + size_type num_input_columns, result_type seed = DEFAULT_HASH_SEED) noexcept - : _check_nulls{check_nulls}, _table{t}, _seed(seed) + : _check_nulls{check_nulls}, _table{t}, _num_input_columns{num_input_columns}, _seed(seed) { } Nullate const _check_nulls; table_device_view const _table; + size_type const _num_input_columns; // Assumes seeds are the same as the result type of the hash function result_type const _seed; }; @@ -278,7 +286,7 @@ class row_hasher { Nullate nullate = {}, cuda::std::invoke_result_t, int32_t> seed = DEFAULT_HASH_SEED) const { - return DeviceRowHasher(nullate, *d_t, seed); + return DeviceRowHasher(nullate, *d_t, d_t->_num_input_columns, seed); } private: diff --git a/cpp/include/cudf/detail/row_operator/preprocessed_table.cuh b/cpp/include/cudf/detail/row_operator/preprocessed_table.cuh index 3490b9b826e8..c004891afd1e 100644 --- a/cpp/include/cudf/detail/row_operator/preprocessed_table.cuh +++ b/cpp/include/cudf/detail/row_operator/preprocessed_table.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -73,16 +73,19 @@ struct preprocessed_table { preprocessed_table(table_device_view_owner&& table, std::vector&& null_buffers, - std::vector>&& tmp_columns) + std::vector>&& tmp_columns, + size_type num_input_columns) : _t(std::move(table)), _null_buffers(std::move(null_buffers)), - _tmp_columns(std::move(tmp_columns)) + _tmp_columns(std::move(tmp_columns)), + _num_input_columns(num_input_columns) { } table_device_view_owner _t; std::vector _null_buffers; std::vector> _tmp_columns; + size_type _num_input_columns; }; } // namespace equality diff --git a/cpp/src/row_operator/row_operators.cu b/cpp/src/row_operator/row_operators.cu index 4b21d9c980cb..4400bdfa1eb2 100644 --- a/cpp/src/row_operator/row_operators.cu +++ b/cpp/src/row_operator/row_operators.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -854,8 +854,11 @@ std::shared_ptr preprocessed_table::create(table_view const& std::get<0>(decompose_structs(struct_offset_removed_table, decompose_lists_column::YES)); auto d_t = table_device_view_owner(table_device_view::create(verticalized_t, stream)); - return std::shared_ptr(new preprocessed_table( - std::move(d_t), std::move(nullable_data.new_null_masks), std::move(nullable_data.new_columns))); + return std::shared_ptr( + new preprocessed_table(std::move(d_t), + std::move(nullable_data.new_null_masks), + std::move(nullable_data.new_columns), + t.num_columns())); } two_table_comparator::two_table_comparator(table_view const& left, diff --git a/cpp/tests/hashing/murmurhash3_x86_32_test.cpp b/cpp/tests/hashing/murmurhash3_x86_32_test.cpp index bb0b5cc41efa..897b586008ec 100644 --- a/cpp/tests/hashing/murmurhash3_x86_32_test.cpp +++ b/cpp/tests/hashing/murmurhash3_x86_32_test.cpp @@ -50,6 +50,20 @@ TEST_F(MurmurHashTest, MultiValue) CUDF_TEST_EXPECT_COLUMNS_EQUAL(output1->view(), output2->view()); } +TEST_F(MurmurHashTest, SparkCompatibleIterativeSeeding) +{ + cudf::test::fixed_width_column_wrapper const first({0, 1, -1, 42, 123456789}); + cudf::test::fixed_width_column_wrapper const second({10, 20, 30, -40, -987654321}); + auto const input = cudf::table_view({first, second}); + + auto const output = cudf::hashing::murmurhash3_x86_32(input, 42); + + // Generated with Apache Spark's Murmur3Hash using seed 42. + cudf::test::fixed_width_column_wrapper const expected( + {2573243963u, 1151116018u, 1549484878u, 3007216400u, 2314233967u}); + CUDF_TEST_EXPECT_COLUMNS_EQUAL(output->view(), expected); +} + TEST_F(MurmurHashTest, MultiValueNulls) { // Nulls with different values should be equal From 51cd9a21c377bbb3ec2ee319db8dd19e3aa525d4 Mon Sep 17 00:00:00 2001 From: Yunsong Wang Date: Tue, 14 Jul 2026 20:12:16 +0000 Subject: [PATCH 02/12] Preserve custom row hasher compatibility --- .../cudf/detail/row_operator/hashing.cuh | 8 ++++++- cpp/tests/row_operator/row_operator_tests.cu | 24 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cpp/include/cudf/detail/row_operator/hashing.cuh b/cpp/include/cudf/detail/row_operator/hashing.cuh index 0f2c170a5c81..b2eb62a0db04 100644 --- a/cpp/include/cudf/detail/row_operator/hashing.cuh +++ b/cpp/include/cudf/detail/row_operator/hashing.cuh @@ -286,7 +286,13 @@ class row_hasher { Nullate nullate = {}, cuda::std::invoke_result_t, int32_t> seed = DEFAULT_HASH_SEED) const { - return DeviceRowHasher(nullate, *d_t, d_t->_num_input_columns, seed); + using device_hasher_type = DeviceRowHasher; + if constexpr (cuda::std::is_same_v>) { + return device_hasher_type(nullate, *d_t, d_t->_num_input_columns, seed); + } else { + return device_hasher_type(nullate, *d_t, seed); + } } private: diff --git a/cpp/tests/row_operator/row_operator_tests.cu b/cpp/tests/row_operator/row_operator_tests.cu index 08046c5e3128..8d0e64a8b677 100644 --- a/cpp/tests/row_operator/row_operator_tests.cu +++ b/cpp/tests/row_operator/row_operator_tests.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -289,6 +289,28 @@ TYPED_TEST(NaNTableViewTest, TestEqualityComparatorTwoTableNaNCase) struct RowOperatorTest : public cudf::test::BaseFixture {}; +template