Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions cpp/include/cudf/detail/stream_compaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ enum class mask_type : bool {
/**
* @brief Filters @p input using @p boolean_mask.
*
* @note An empty mask returns an empty table for @p mask_kind `RETENTION` and a copy of @p input
* for @p mask_kind `DELETION`.
*
* @param input The input table to filter
* @param boolean_mask A nullable BOOL8 column used to filter @p input
* @param mask_kind Specifies how the boolean mask is treated (retentions or deletions)
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/cudf/stream_compaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ std::unique_ptr<table> apply_retention_mask(
* if the corresponding element `i` in the mask is non-null and `false`.
* This operation is stable: the input order is preserved.
*
* @note If @p deletion_mask is empty, or @p input has zero rows, an empty table is returned.
* @note If @p deletion_mask is empty, a copy of @p input is returned. If @p input has zero rows,
* an empty table is returned.
Comment thread
igorpeshansky marked this conversation as resolved.
*
* @throws cudf::logic_error if non-empty @p input has different number of rows than @p
* deletion_mask.
Expand Down
7 changes: 5 additions & 2 deletions cpp/src/stream_compaction/apply_mask.cu
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,18 @@ std::unique_ptr<table> apply_mask(table_view const& input,
cuda::stream_ref stream,
rmm::device_async_resource_ref mr)
{
if (boolean_mask.is_empty()) { return empty_like(input); }
auto const is_retention = (mask_kind == mask_type::RETENTION);

if (boolean_mask.is_empty()) {
return is_retention ? empty_like(input) : std::make_unique<table>(input, stream, mr);

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.

[Optional] This is the right behavior if you want to special-case the empty mask for deletion, but I'm also wondering if there's a need to special-case it in the first place. Since we're changing behavior here anyway, and there's no backward compatibility with apply_boolean_mask to maintain, we could also just fall through to the mask size check below and let the empty mask trigger that…

@mhaseeb123 mhaseeb123 Aug 29, 2026

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.

I think this new behavior makes sense. Empty mask should just mean nothing retained or deleted or we should just strongly enforce CUDF_EXPECTS(input.size() == mask.size()) like we do for the lists version + what you mentioned above.

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.

Since we're changing behavior here anyway, and there's no backward compatibility with apply_boolean_mask to maintain, we could also just fall through to the mask size check below and let the empty mask trigger that…

I think apply_boolean_mask retains the same behavior -> Empty output if the mask is empty which is the same with RETENTIONS.

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.

I was only talking about the deletion branch. Retention would have to keep empty_like for apply_boolean_mask compatibility, but deletion has no such prior implementation, and thus is free to fall through to the size check instead.

Since this was optional, we can just leave it at that, but just note that the lists and the table versions still disagree on how they treat an empty mask.

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

CUDF_EXPECTS(boolean_mask.type().id() == type_id::BOOL8, "Mask must be Boolean type");
CUDF_EXPECTS(input.num_rows() == 0 || input.num_rows() == boolean_mask.size(),
"Column size mismatch");

auto device_boolean_mask = cudf::column_device_view::create(boolean_mask, stream);

auto const is_retention = (mask_kind == mask_type::RETENTION);
if (boolean_mask.has_nulls()) {
if (is_retention) {
return detail::copy_if(input, retention_mask_filter<true>{*device_boolean_mask}, stream, mr);
Expand Down
6 changes: 1 addition & 5 deletions cpp/tests/stream_compaction/apply_mask_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,14 +415,10 @@ TEST_F(ApplyDeletionMask, EmptyMask)
cudf::test::fixed_width_column_wrapper<double> col3{{10, 40, 70, 5, 2, 10}, {1, 1, 0, 1, 1, 0}};
cudf::table_view input{{col1, col2, col3}};
cudf::test::fixed_width_column_wrapper<bool> deletion_mask{};
cudf::test::fixed_width_column_wrapper<int16_t> col1_expected{};
cudf::test::fixed_width_column_wrapper<int32_t> col2_expected{};
cudf::test::fixed_width_column_wrapper<double> col3_expected{};
cudf::table_view expected{{col1_expected, col2_expected, col3_expected}};

auto got = cudf::apply_deletion_mask(input, deletion_mask);

CUDF_TEST_EXPECT_TABLES_EQUAL(expected, got->view());
CUDF_TEST_EXPECT_TABLES_EQUAL(input, got->view());
}

TEST_F(ApplyDeletionMask, WrongMaskType)
Expand Down
6 changes: 3 additions & 3 deletions python/pylibcudf/pylibcudf/lists.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -860,14 +860,14 @@ cpdef Column apply_retention_mask(
input : Column
The input column.
retention_mask : Column
The boolean mask whose true values retain input elements.
A lists-of-bools column used as a retention mask.
stream : Stream | None
CUDA stream on which to perform the operation.

Returns
-------
Column
A Column of filtered elements based upon the retention mask.
Lists column with elements kept where retention mask is valid and true.
"""
cdef unique_ptr[column] c_result
cdef ListsColumnView list_view = input.list_view()
Expand Down Expand Up @@ -924,7 +924,7 @@ cpdef Column apply_deletion_mask(
Returns
-------
Column
Lists column with elements removed where deletion_mask is true.
Lists column with elements removed where deletion mask is valid and true.
"""
cdef unique_ptr[column] c_result
cdef ListsColumnView list_view = input.list_view()
Expand Down
6 changes: 3 additions & 3 deletions python/pylibcudf/pylibcudf/stream_compaction.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ cpdef Table apply_retention_mask(
source_table : Table
The input table to filter.
retention_mask : Column
The boolean mask whose true values retain input rows.
A boolean column used as a retention mask.

Returns
-------
Table
A new table with rows removed based on the boolean mask.
A new table with rows kept where retention mask is valid and true.
"""
cdef unique_ptr[table] c_result

Expand Down Expand Up @@ -200,7 +200,7 @@ cpdef Table apply_deletion_mask(
Returns
-------
Table
Table with rows removed where deletion_mask is true.
Table with rows removed where deletion mask is valid and true.
"""
cdef unique_ptr[table] c_result

Expand Down
Loading