Skip to content

Commit c2c4e85

Browse files
Fix dictionary pruning for column chunks with no dictionary page (#23746)
A chunk with no dictionary page contributes no values to the hash set built for its row group, but cuco rounds every capacity up to at least one bucket, so the set still has slots. Testing emptiness by slot count therefore missed it, and probing a set that was never built reported the literal as absent and pruned the row group. Test the value count instead. Also add the missing group.sync() between zeroing the per-row-group results and decoding the dictionary page, without which a thread could overwrite a result another thread had already written. Authors: - Paul Mattione (https://github.com/pmattione-nvidia) - Muhammad Haseeb (https://github.com/mhaseeb123) Approvers: - Muhammad Haseeb (https://github.com/mhaseeb123) - Vukasin Milovanovic (https://github.com/vuule) URL: #23746
1 parent 9e8e799 commit c2c4e85

2 files changed

Lines changed: 60 additions & 4 deletions

File tree

cpp/src/io/parquet/experimental/dictionary_page_filter.cu

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,14 @@ CUDF_KERNEL void query_dictionaries(cudf::device_span<T> decoded_data,
293293

294294
// Evaluate the scalar against all cuco hash sets of this column
295295
for (auto set_idx = group.thread_rank(); set_idx < total_row_groups; set_idx += group.size()) {
296-
// If the set is empty (no dictionary page data), then skip the dictionary page filter
297-
if (set_offsets[set_idx + 1] - set_offsets[set_idx] == 0) {
296+
// Number of values in this hash set
297+
auto const num_set_values = value_offsets[set_idx + 1] - value_offsets[set_idx];
298+
299+
// Skip the dictionary page filter for a column chunk with no dictionary page. Emptiness must be
300+
// read from the value count and not from the number of slots, because cuco rounds every
301+
// capacity up to at least one bucket, so an empty dictionary still has slots. Its set was never
302+
// built, so probing it would report the literal as absent and prune the row group.
303+
if (num_set_values == 0) {
298304
result[set_idx] = operators[scalar_idx] == ast::ast_operator::EQUAL;
299305
continue;
300306
}
@@ -311,8 +317,6 @@ CUDF_KERNEL void query_dictionaries(cudf::device_span<T> decoded_data,
311317
storage_ref};
312318
auto set_find_ref = hash_set_ref.rebind_operators(cuco::contains);
313319

314-
// Number of values in this hash set
315-
auto const num_set_values = value_offsets[set_idx + 1] - value_offsets[set_idx];
316320
// Literal value to find in this hash set
317321
auto const literal_value = scalar.value<T>();
318322

@@ -901,6 +905,8 @@ CUDF_KERNEL void __launch_bounds__(DECODE_BLOCK_SIZE)
901905
results[i][row_group_idx] = false;
902906
}
903907

908+
group.sync();
909+
904910
// Decode values from the current dictionary page with the current thread block
905911
for (auto value_idx = group.thread_rank(); value_idx < page.num_input_values;
906912
value_idx += group.num_threads()) {

cpp/tests/io/experimental/hybrid_scan_filters_test.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,56 @@ TEST_P(DictionaryFilterGapTest, FilterRowGroupsWithMissingDictPages)
19681968
EXPECT_EQ(filter_row_groups_with_dictionaries(datasource_ref, reader_ref, options, stream, mr),
19691969
expected);
19701970
}
1971+
1972+
// The cases below give a column more than `MAX_INLINE_LITERALS` literals, which builds a hash set
1973+
// per dictionary instead of evaluating the literals inline. A row group with no dictionary page
1974+
// has no hash set built for it, so that path has to recognize it and keep the row group.
1975+
1976+
// Filtering - col0 equals any of three plain values: row group 0 is pruned as its dictionary
1977+
// holds none of them, row group 1 cannot be pruned
1978+
{
1979+
auto literal_value0 = cudf::string_scalar("plain_value_5", true, stream);
1980+
auto literal_value1 = cudf::string_scalar("plain_value_6", true, stream);
1981+
auto literal_value2 = cudf::string_scalar("plain_value_7", true, stream);
1982+
auto literal0 = cudf::ast::literal(literal_value0);
1983+
auto literal1 = cudf::ast::literal(literal_value1);
1984+
auto literal2 = cudf::ast::literal(literal_value2);
1985+
auto const equal0 = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col0_ref, literal0);
1986+
auto const equal1 = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col0_ref, literal1);
1987+
auto const equal2 = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col0_ref, literal2);
1988+
auto const either = cudf::ast::operation(cudf::ast::ast_operator::LOGICAL_OR, equal0, equal1);
1989+
auto const filter_expression =
1990+
cudf::ast::operation(cudf::ast::ast_operator::LOGICAL_OR, either, equal2);
1991+
auto const options =
1992+
cudf::io::parquet_reader_options::builder().filter(filter_expression).build();
1993+
1994+
auto const expected = std::vector<cudf::size_type>{1};
1995+
EXPECT_EQ(filter_row_groups_with_dictionaries(datasource_ref, reader_ref, options, stream, mr),
1996+
expected);
1997+
}
1998+
1999+
// Filtering - col0 equals any of three values, one of which is in row group 0's dictionary: both
2000+
// row groups survive
2001+
{
2002+
auto literal_value0 = cudf::string_scalar("dict_value", true, stream);
2003+
auto literal_value1 = cudf::string_scalar("plain_value_5", true, stream);
2004+
auto literal_value2 = cudf::string_scalar("plain_value_6", true, stream);
2005+
auto literal0 = cudf::ast::literal(literal_value0);
2006+
auto literal1 = cudf::ast::literal(literal_value1);
2007+
auto literal2 = cudf::ast::literal(literal_value2);
2008+
auto const equal0 = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col0_ref, literal0);
2009+
auto const equal1 = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col0_ref, literal1);
2010+
auto const equal2 = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col0_ref, literal2);
2011+
auto const either = cudf::ast::operation(cudf::ast::ast_operator::LOGICAL_OR, equal0, equal1);
2012+
auto const filter_expression =
2013+
cudf::ast::operation(cudf::ast::ast_operator::LOGICAL_OR, either, equal2);
2014+
auto const options =
2015+
cudf::io::parquet_reader_options::builder().filter(filter_expression).build();
2016+
2017+
auto const expected = std::vector<cudf::size_type>{0, 1};
2018+
EXPECT_EQ(filter_row_groups_with_dictionaries(datasource_ref, reader_ref, options, stream, mr),
2019+
expected);
2020+
}
19712021
}
19722022

19732023
INSTANTIATE_TEST_SUITE_P(Compression,

0 commit comments

Comments
 (0)