Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change nullable() to has_nulls() in cudf::detail::gather #14363

Merged
merged 18 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 12 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
19 changes: 13 additions & 6 deletions cpp/include/cudf/detail/gather.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -674,13 +674,20 @@ std::unique_ptr<table> gather(table_view const& source_table,
}

auto const nullable = bounds_policy == out_of_bounds_policy::NULLIFY ||
std::any_of(source_table.begin(), source_table.end(), [](auto const& col) {
return col.nullable();
});
cudf::has_nested_nullable_columns(source_table);
if (nullable) {
auto const op = bounds_policy == out_of_bounds_policy::NULLIFY ? gather_bitmask_op::NULLIFY
: gather_bitmask_op::DONT_CHECK;
gather_bitmask(source_table, gather_map_begin, destination_columns, op, stream, mr);
auto const has_nulls =
bounds_policy == out_of_bounds_policy::NULLIFY || cudf::has_nested_nulls(source_table);
Copy link
Contributor

@ttnghia ttnghia Nov 15, 2023

Choose a reason for hiding this comment

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

Should this use && instead? Because if we indeed don't have any nulls here then we don't need to call gather_bitmask.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably I misunderstood the usage of this variable. So this variable should be called need_new_bitmask or so. It should not be has_nulls.

Copy link
Member Author

Choose a reason for hiding this comment

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

But we need to call gather_bitmask if out_of_bounds_policy::NULLIFY. gather_bitmask will help nullify any OOB accesses.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, so || is indeed needed, but please rename that variable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

if (has_nulls) {
auto const op = bounds_policy == out_of_bounds_policy::NULLIFY
? gather_bitmask_op::NULLIFY
: gather_bitmask_op::DONT_CHECK;
gather_bitmask(source_table, gather_map_begin, destination_columns, op, stream, mr);
} else {
for (size_type i = 0; i < source_table.num_columns(); ++i) {
set_all_valid_null_masks(source_table.column(i), *destination_columns[i], stream, mr);
}
}
}

return std::make_unique<table>(std::move(destination_columns));
Expand Down
17 changes: 17 additions & 0 deletions cpp/include/cudf/detail/null_mask.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
#pragma once

#include <cudf/column/column.hpp>
#include <cudf/types.hpp>
#include <cudf/utilities/default_stream.hpp>
#include <cudf/utilities/span.hpp>
Expand Down Expand Up @@ -259,6 +260,22 @@ cudf::size_type inplace_bitmask_and(device_span<bitmask_type> dest_mask,
size_type mask_size_bits,
rmm::cuda_stream_view stream);

/**
* @brief Recursively set valid null masks for all children.
*
* This function applies all valid null masks to the output column if input column satisfies
* `nullable() == true` condition
*
* @param input input column
* @param output output column
* @param stream
* @param mr
divyegala marked this conversation as resolved.
Show resolved Hide resolved
*/
void set_all_valid_null_masks(column_view const& input,
column& output,
rmm::cuda_stream_view stream,
rmm::mr::device_memory_resource* mr);

} // namespace detail

} // namespace cudf
17 changes: 17 additions & 0 deletions cpp/include/cudf/table/table_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,23 @@ inline bool has_nested_nulls(table_view const& input)
});
}

/**
* @brief Returns True if the table has a nullable column at any level of the column hierarchy
*
* @param input The table to check for nullable columns
* @return True if the table has nullable columns at any level of the column hierarchy, false
* otherwise
*/
inline bool has_nested_nullable_columns(table_view const& input)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same with this implementation. This could be moved to https://github.com/rapidsai/cudf/blob/branch-23.12/cpp/src/table/table.cpp
This applies to the other non-templated inline functions in this header as well so I would be ok if this was done in a follow up PR -- in 24.02 too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make an issue for this.

Copy link
Member Author

Choose a reason for hiding this comment

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

{
return std::any_of(input.begin(), input.end(), [](auto const& col) {
return col.nullable() ||
std::any_of(col.child_begin(), col.child_end(), [](auto const& child_col) {
return has_nested_nullable_columns(table_view{{child_col}});
});
});
}

/**
* @brief The function to collect all nullable columns at all nested levels in a given table.
*
Expand Down
15 changes: 15 additions & 0 deletions cpp/src/bitmask/null_mask.cu
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,21 @@ std::pair<rmm::device_buffer, size_type> bitmask_or(table_view const& view,
return std::pair(std::move(null_mask), 0);
}

void set_all_valid_null_masks(column_view const& input,
column& output,
rmm::cuda_stream_view stream,
rmm::mr::device_memory_resource* mr)
{
if (input.nullable()) {
auto mask = detail::create_null_mask(output.size(), mask_state::ALL_VALID, stream, mr);
output.set_null_mask(std::move(mask), 0);

for (size_type i = 0; i < input.num_children(); ++i) {
set_all_valid_null_masks(input.child(i), output.child(i), stream, mr);
}
}
}

} // namespace detail

// Create a bitmask from a specific range
Expand Down
17 changes: 12 additions & 5 deletions cpp/src/groupby/sort/group_quantiles.cu
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct calculate_quantile_fn {
double const* d_quantiles;
size_type num_quantiles;
interpolation interpolation;
size_type* null_count;
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be an unrelated change to this PR so ideally it should be in a separate PR. But I'm fine to keep this here but please clarify that in the PR description.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done


__device__ void operator()(size_type i)
{
Expand All @@ -68,11 +69,13 @@ struct calculate_quantile_fn {
thrust::for_each_n(thrust::seq,
thrust::make_counting_iterator(0),
num_quantiles,
[d_result = d_result, segment_size, offset](size_type j) {
if (segment_size == 0)
[d_result = d_result, segment_size, offset, this](size_type j) {
if (segment_size == 0) {
d_result.set_null(offset + j);
else
atomicAdd(this->null_count, 1);
bdice marked this conversation as resolved.
Show resolved Hide resolved
} else {
d_result.set_valid(offset + j);
}
});
}
};
Expand Down Expand Up @@ -104,6 +107,7 @@ struct quantiles_functor {
auto values_view = column_device_view::create(values, stream);
auto group_size_view = column_device_view::create(group_sizes, stream);
auto result_view = mutable_column_device_view::create(result->mutable_view(), stream);
auto null_count = rmm::device_scalar<cudf::size_type>(0, stream, mr);

// For each group, calculate quantile
if (!cudf::is_dictionary(values.type())) {
Expand All @@ -118,7 +122,8 @@ struct quantiles_functor {
group_offsets.data(),
quantile.data(),
static_cast<size_type>(quantile.size()),
interpolation});
interpolation,
null_count.data()});
} else {
auto values_iter = cudf::dictionary::detail::make_dictionary_iterator<T>(*values_view);
thrust::for_each_n(rmm::exec_policy(stream),
Expand All @@ -131,9 +136,11 @@ struct quantiles_functor {
group_offsets.data(),
quantile.data(),
static_cast<size_type>(quantile.size()),
interpolation});
interpolation,
null_count.data()});
}

result->set_null_count(null_count.value(stream));
return result;
}

Expand Down
56 changes: 0 additions & 56 deletions cpp/tests/join/join_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1941,62 +1941,6 @@ TEST_F(JoinTest, FullJoinWithStructsAndNulls)
CUDF_TEST_EXPECT_TABLES_EQUIVALENT(*sorted_gold, *sorted_result);
}

TEST_F(JoinTest, Repro_StructsWithoutNullsPushedDown)
{
// When joining on a STRUCT column, if the parent nulls are not reflected in
// the children, the join might produce incorrect results.
//
// In this test, a fact table of structs is joined against a dimension table.
// Both tables must match (only) on the NULL row. This will fail if the fact table's
// nulls are not pushed down into its children.
using ints = column_wrapper<int32_t>;
using structs = cudf::test::structs_column_wrapper;
using namespace cudf::test::iterators;

auto make_table = [](auto&& col) {
auto columns = CVector{};
columns.push_back(std::move(col));
return cudf::table{std::move(columns)};
};

auto const fact_table = [make_table] {
auto fact_ints = ints{0, 1, 2, 3, 4};
auto fact_structs = structs{{fact_ints}, no_nulls()}.release();
// Now set struct validity to invalidate index#3.
cudf::detail::set_null_mask(
fact_structs->mutable_view().null_mask(), 3, 4, false, cudf::get_default_stream());
// Struct row#3 is null, but Struct.child has a non-null value.
return make_table(std::move(fact_structs));
}();

auto const dimension_table = [make_table] {
auto dim_ints = ints{999};
auto dim_structs = structs{{dim_ints}, null_at(0)};
return make_table(dim_structs.release());
}();

auto const result = inner_join(fact_table.view(), dimension_table.view(), {0}, {0});
EXPECT_EQ(result->num_rows(), 1); // The null STRUCT rows should match.

// Note: Join result might not have nulls pushed down, since it's an output of gather().
// Must superimpose parent nulls before comparisons.
auto [superimposed_results, _] = cudf::structs::detail::push_down_nulls(
*result, cudf::get_default_stream(), rmm::mr::get_current_device_resource());

auto const expected = [] {
auto fact_ints = ints{0};
auto fact_structs = structs{{fact_ints}, null_at(0)};
auto dim_ints = ints{0};
auto dim_structs = structs{{dim_ints}, null_at(0)};
auto columns = CVector{};
columns.push_back(fact_structs.release());
columns.push_back(dim_structs.release());
return cudf::table{std::move(columns)};
}();

CUDF_TEST_EXPECT_TABLES_EQUIVALENT(superimposed_results, expected);
}

using lcw = cudf::test::lists_column_wrapper<int32_t>;
using cudf::test::iterators::null_at;

Expand Down
Loading