Skip to content

Commit da11548

Browse files
authored
Fix scale handling of fixed-point types in minmax aggregation for cudf::reduce (#23752)
Fixes the logic in `cudf::reduce` for the minmax aggregation handling fixed-point types with non-zero scale. Also adds new gtests with non-zero fixed-point types. Closes: #23752 Authors: - David Wendt (https://github.com/davidwendt) Approvers: - Muhammad Haseeb (https://github.com/mhaseeb123) - Vukasin Milovanovic (https://github.com/vuule) URL: #23752
1 parent 8c3955b commit da11548

2 files changed

Lines changed: 133 additions & 7 deletions

File tree

cpp/src/reductions/minmax.cu

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <cudf/scalar/scalar_factories.hpp>
1818
#include <cudf/utilities/default_stream.hpp>
1919
#include <cudf/utilities/memory_resource.hpp>
20+
#include <cudf/utilities/traits.hpp>
2021

2122
#include <cuda/iterator>
2223
#include <cuda/std/functional>
@@ -144,6 +145,18 @@ struct assign_min_max {
144145
T* max_data;
145146
};
146147

148+
template <typename T>
149+
std::unique_ptr<cudf::scalar_type_t<T>> make_minmax_scalar(cudf::data_type type,
150+
cuda::stream_ref stream,
151+
rmm::device_async_resource_ref mr)
152+
{
153+
if constexpr (cudf::is_fixed_point<T>()) {
154+
return std::make_unique<cudf::scalar_type_t<T>>(
155+
device_storage_type_t<T>{}, numeric::scale_type{type.scale()}, true, stream, mr);
156+
}
157+
return std::make_unique<cudf::scalar_type_t<T>>(T{}, true, stream, mr);
158+
}
159+
147160
/**
148161
* @brief Computes a minmax_pair<T> reduction directly over a dictionary column's decoded key
149162
* values, i.e. `keys[indices[i]]` for each row `i`.
@@ -185,10 +198,9 @@ struct minmax_dictionary_functor {
185198
{
186199
using storage_type = device_storage_type_t<T>;
187200
auto dev_result = reduce_dictionary<storage_type>(col, stream);
188-
using ScalarType = cudf::scalar_type_t<T>;
189201
auto const key_type = dictionary_column_view(col).keys().type();
190-
auto minimum = std::make_unique<ScalarType>(T{}, true, stream, mr);
191-
auto maximum = std::make_unique<ScalarType>(T{}, true, stream, mr);
202+
auto minimum = make_minmax_scalar<T>(key_type, stream, mr);
203+
auto maximum = make_minmax_scalar<T>(key_type, stream, mr);
192204
cudf::detail::device_single_thread(
193205
assign_min_max<storage_type>{dev_result.data(), minimum->data(), maximum->data()}, stream);
194206
return {std::move(minimum), std::move(maximum)};
@@ -255,13 +267,12 @@ struct minmax_functor {
255267
// compute minimum and maximum values
256268
auto dev_result = reduce<storage_type>(col, stream);
257269
// create output scalars
258-
using ScalarType = cudf::scalar_type_t<T>;
259-
auto minimum = new ScalarType(T{}, true, stream, mr);
260-
auto maximum = new ScalarType(T{}, true, stream, mr);
270+
auto minimum = make_minmax_scalar<T>(col.type(), stream, mr);
271+
auto maximum = make_minmax_scalar<T>(col.type(), stream, mr);
261272
// copy dev_result to the output scalars
262273
cudf::detail::device_single_thread(
263274
assign_min_max<storage_type>{dev_result.data(), minimum->data(), maximum->data()}, stream);
264-
return {std::unique_ptr<scalar>(minimum), std::unique_ptr<scalar>(maximum)};
275+
return {std::move(minimum), std::move(maximum)};
265276
}
266277

267278
/**

cpp/tests/reductions/reduction_tests.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <cudf/copying.hpp>
1414
#include <cudf/detail/iterator.cuh>
1515
#include <cudf/dictionary/dictionary_column_view.hpp>
16+
#include <cudf/dictionary/encode.hpp>
1617
#include <cudf/dictionary/update_keys.hpp>
1718
#include <cudf/fixed_point/fixed_point.hpp>
1819
#include <cudf/reduction.hpp>
@@ -2179,6 +2180,61 @@ TYPED_TEST(FixedPointTestAllReps, FixedPointReductionMaxLarge)
21792180
}
21802181
}
21812182

2183+
TYPED_TEST(FixedPointTestAllReps, FixedPointMinMax)
2184+
{
2185+
using namespace numeric;
2186+
using decimalXX = TypeParam;
2187+
using RepType = cudf::device_storage_type_t<decimalXX>;
2188+
using fp_wrapper = cudf::test::fixed_point_column_wrapper<RepType>;
2189+
2190+
for (auto const i : {0, -1, -2, -3}) {
2191+
auto const scale = scale_type{i};
2192+
auto const column = fp_wrapper{{2, 3, 1, 4}, scale};
2193+
2194+
auto const expected_min = decimalXX{scaled_integer<RepType>{1, scale}};
2195+
auto const expected_max = decimalXX{scaled_integer<RepType>{4, scale}};
2196+
2197+
auto const result = cudf::minmax(column);
2198+
auto const min_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.first.get());
2199+
auto const max_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.second.get());
2200+
2201+
// Scale must be preserved in the output scalars.
2202+
EXPECT_EQ(min_scalar->type().scale(), i);
2203+
EXPECT_EQ(max_scalar->type().scale(), i);
2204+
2205+
EXPECT_EQ(min_scalar->fixed_point_value(), expected_min);
2206+
EXPECT_EQ(max_scalar->fixed_point_value(), expected_max);
2207+
}
2208+
}
2209+
2210+
TYPED_TEST(FixedPointTestAllReps, FixedPointMinMaxWithNulls)
2211+
{
2212+
using namespace numeric;
2213+
using decimalXX = TypeParam;
2214+
using RepType = cudf::device_storage_type_t<decimalXX>;
2215+
using fp_wrapper = cudf::test::fixed_point_column_wrapper<RepType>;
2216+
2217+
for (auto const i : {0, -1, -2, -3}) {
2218+
auto const scale = scale_type{i};
2219+
// valid: {2, null, 1, null, 4} — min=1, max=4
2220+
auto const column = fp_wrapper{{2, 3, 1, 5, 4}, {true, false, true, false, true}, scale};
2221+
2222+
auto const expected_min = decimalXX{scaled_integer<RepType>{1, scale}};
2223+
auto const expected_max = decimalXX{scaled_integer<RepType>{4, scale}};
2224+
2225+
auto const result = cudf::minmax(column);
2226+
auto const min_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.first.get());
2227+
auto const max_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.second.get());
2228+
2229+
// Scale must be preserved in the output scalars.
2230+
EXPECT_EQ(min_scalar->type().scale(), i);
2231+
EXPECT_EQ(max_scalar->type().scale(), i);
2232+
2233+
EXPECT_EQ(min_scalar->fixed_point_value(), expected_min);
2234+
EXPECT_EQ(max_scalar->fixed_point_value(), expected_max);
2235+
}
2236+
}
2237+
21822238
TYPED_TEST(FixedPointTestAllReps, FixedPointReductionNUnique)
21832239
{
21842240
using namespace numeric;
@@ -3098,6 +3154,65 @@ TYPED_TEST(DictionaryReductionTest, Quantile)
30983154
45.0);
30993155
}
31003156

3157+
template <typename T>
3158+
struct FixedPointDictionaryReductionTest : public cudf::test::BaseFixture {};
3159+
3160+
TYPED_TEST_SUITE(FixedPointDictionaryReductionTest, cudf::test::FixedPointTypes);
3161+
3162+
TYPED_TEST(FixedPointDictionaryReductionTest, FixedPointDictionaryMinMax)
3163+
{
3164+
using namespace numeric;
3165+
using decimalXX = TypeParam;
3166+
using RepType = cudf::device_storage_type_t<decimalXX>;
3167+
using fp_wrapper = cudf::test::fixed_point_column_wrapper<RepType>;
3168+
3169+
for (auto const i : {0, -1, -2, -3}) {
3170+
auto const scale = scale_type{i};
3171+
auto const col = fp_wrapper{{1, 2, 3, 4}, scale};
3172+
auto const dict = cudf::dictionary::encode(col);
3173+
auto const expected_min = decimalXX{scaled_integer<RepType>{1, scale}};
3174+
auto const expected_max = decimalXX{scaled_integer<RepType>{4, scale}};
3175+
3176+
auto const result = cudf::minmax(dict->view());
3177+
auto const min_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.first.get());
3178+
auto const max_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.second.get());
3179+
3180+
EXPECT_EQ(min_scalar->type().scale(), i);
3181+
EXPECT_EQ(max_scalar->type().scale(), i);
3182+
EXPECT_EQ(min_scalar->fixed_point_value(), expected_min);
3183+
EXPECT_EQ(max_scalar->fixed_point_value(), expected_max);
3184+
EXPECT_TRUE(min_scalar->is_valid());
3185+
EXPECT_TRUE(max_scalar->is_valid());
3186+
}
3187+
}
3188+
3189+
TYPED_TEST(FixedPointDictionaryReductionTest, FixedPointDictionaryMinMaxWithNulls)
3190+
{
3191+
using namespace numeric;
3192+
using decimalXX = TypeParam;
3193+
using RepType = cudf::device_storage_type_t<decimalXX>;
3194+
using fp_wrapper = cudf::test::fixed_point_column_wrapper<RepType>;
3195+
3196+
for (auto const i : {0, -1, -2, -3}) {
3197+
auto const scale = scale_type{i};
3198+
auto const col = fp_wrapper{{1, 2, 3, 4, 5}, {true, false, true, false, true}, scale};
3199+
auto const dict = cudf::dictionary::encode(col);
3200+
auto const expected_min = decimalXX{scaled_integer<RepType>{1, scale}};
3201+
auto const expected_max = decimalXX{scaled_integer<RepType>{5, scale}};
3202+
3203+
auto const result = cudf::minmax(dict->view());
3204+
auto const min_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.first.get());
3205+
auto const max_scalar = static_cast<cudf::scalar_type_t<decimalXX>*>(result.second.get());
3206+
3207+
EXPECT_EQ(min_scalar->type().scale(), i);
3208+
EXPECT_EQ(max_scalar->type().scale(), i);
3209+
EXPECT_EQ(min_scalar->fixed_point_value(), expected_min);
3210+
EXPECT_EQ(max_scalar->fixed_point_value(), expected_max);
3211+
EXPECT_TRUE(min_scalar->is_valid());
3212+
EXPECT_TRUE(max_scalar->is_valid());
3213+
}
3214+
}
3215+
31013216
struct ListReductionTest : public cudf::test::BaseFixture {
31023217
void reduction_test(cudf::column_view const& input_data,
31033218
cudf::column_view const& expected_value,

0 commit comments

Comments
 (0)