Repository navigation
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Keep upstream checked-arithmetic fixes and the updated JIT transform calling convention while retaining AST cast and rescale support. Preserve target scale metadata when normalizing Parquet expressions.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Summary
Merge Risk: ⚪ Minimal · up to
|
| template <typename T> | ||
| __device__ static inline T eval(T a) | ||
| requires(cudf::is_fixed_point<T>()) | ||
| { | ||
| return a; | ||
| } | ||
|
|
There was a problem hiding this comment.
This eval overload is a silent no-op for is_valid_unary_op. Let's drop it and update the unary dispatch in expression_evaluator.cuh (L727/L758) on the two-arg form for RESCALE instead, e.g.:
template <ast_operator op, typename T>
constexpr bool is_valid_ast_unary_op =
op == ast_operator::RESCALE ? requires(operator_functor<op> f, T v) { f(v, int32_t{}); }
: is_valid_unary_op<operator_functor<op>, T>;| template <typename T> | |
| __device__ static inline T eval(T a) | |
| requires(cudf::is_fixed_point<T>()) | |
| { | |
| return a; | |
| } |
| auto const op = plan.operators[operator_index]; | ||
| auto const arity = plan.operator_arities[operator_index]; | ||
| auto const op = plan.operators[operator_index]; | ||
| auto const target_scale = plan.operator_target_scales[operator_index]; |
There was a problem hiding this comment.
AI's nit: plan is in global memory so this adds a load per operator per row, though only RESCALE needs it.
| auto const target_scale = plan.operator_target_scales[operator_index]; | |
| auto const target_scale = | |
| op == ast_operator::RESCALE ? plan.operator_target_scales[operator_index] : 0; |
| }); | ||
| // Resolve expression type | ||
| auto const data_type = cudf::ast::detail::ast_operator_return_type(op, operand_types); | ||
| auto data_type = cudf::ast::detail::ast_operator_return_type(op, operand_types); |
There was a problem hiding this comment.
AI pointed out that CAST_TO_DECIMAL128 is unusable here as it throws at root (L275). As an intermediate with nulls, it passes the size check at L286, then resolve_output memcpys 16 bytes into an int64_t and resolve_input truncates the rep. Let’s reject it upfront, and ideally L286 should compare against
| auto data_type = cudf::ast::detail::ast_operator_return_type(op, operand_types); | |
| CUDF_EXPECTS(op != ast_operator::CAST_TO_DECIMAL128, | |
| "CAST_TO_DECIMAL128 is not supported by the AST interpreter.", | |
| cudf::data_type_error); | |
| auto data_type = cudf::ast::detail::ast_operator_return_type(op, operand_types); |
| CUDF_EXPECTS(operand_types.size() == 1 && cudf::is_fixed_point(operand_types.front()), | ||
| "RESCALE requires one fixed-point operand.", | ||
| cudf::data_type_error); | ||
| return operand_types.front(); |
There was a problem hiding this comment.
This returns the input scale, not the target scale (the parser patches it).
| return operand_types.front(); | |
| // Scale is the input's; callers must apply the operation's target scale | |
| return operand_types.front(); |
| * @param input Input fixed-point expression | ||
| * @param target_scale Desired output scale | ||
| */ | ||
| operation(ast_operator op, expression const& input, int32_t target_scale); |
There was a problem hiding this comment.
Use numeric::scale_type to match cudf::data_type (same for the deleted overload, member and get_target_scale()).
| operation(ast_operator op, expression const& input, int32_t target_scale); | |
| operation(ast_operator op, expression const& input, numeric::scale_type target_scale); |
| CAST_TO_DECIMAL128, ///< Cast a decimal value to decimal128, preserving its scale | ||
| RESCALE ///< Rescale a fixed-point value to operation target scale |
There was a problem hiding this comment.
nit: Document the AST limitation and rounding behavior.
| CAST_TO_DECIMAL128, ///< Cast a decimal value to decimal128, preserving its scale | |
| RESCALE ///< Rescale a fixed-point value to operation target scale | |
| CAST_TO_DECIMAL128, ///< Cast a decimal value to decimal128, preserving its scale (JIT only) | |
| RESCALE ///< Rescale a fixed-point value to the target scale (truncates toward zero) |
| auto rescale_expected = cudf::test::fixed_point_column_wrapper<int32_t>( | ||
| {1200, -4500, 7800}, {1, 0, 1}, numeric::scale_type{-3}); | ||
| auto rescale_result = Executor::compute_column(table, rescale); | ||
| CUDF_TEST_EXPECT_COLUMNS_EQUAL(rescale_expected, rescale_result->view(), verbosity); |
There was a problem hiding this comment.
Optional: Let's also cover rescaling to a coarser scale and RESCALE as an intermediate (the main use case, and the only path where the target scale reaches a non-root operator).
| CUDF_TEST_EXPECT_COLUMNS_EQUAL(rescale_expected, rescale_result->view(), verbosity); | |
| CUDF_TEST_EXPECT_COLUMNS_EQUAL(rescale_expected, rescale_result->view(), verbosity); | |
| auto rescale_coarse = cudf::ast::operation{cudf::ast::ast_operator::RESCALE, ref, -1}; | |
| auto rescale_coarse_expected = cudf::test::fixed_point_column_wrapper<int32_t>( | |
| {12, -45, 78}, {1, 0, 1}, numeric::scale_type{-1}); | |
| auto rescale_coarse_result = Executor::compute_column(table, rescale_coarse); | |
| CUDF_TEST_EXPECT_COLUMNS_EQUAL(rescale_coarse_expected, rescale_coarse_result->view(), verbosity); | |
| auto literal_value = cudf::fixed_point_scalar<numeric::decimal32>(7800, numeric::scale_type{-3}); | |
| auto literal = cudf::ast::literal{literal_value}; | |
| auto less = cudf::ast::operation{cudf::ast::ast_operator::LESS, rescale, literal}; | |
| auto less_expected = column_wrapper<bool>{{true, false, false}, {1, 0, 1}}; | |
| auto less_result = Executor::compute_column(table, less); | |
| CUDF_TEST_EXPECT_COLUMNS_EQUAL(less_expected, less_result->view(), verbosity); |
| { | ||
| using Executor = TypeParam; | ||
|
|
||
| auto input = column_wrapper<int32_t>{{0, 1, 2, 3}, {1, 0, 1, 1}}; |
There was a problem hiding this comment.
Optional: Only INT32 input is covered. Consider adding float, bool and decimal inputs (float -> int truncation, decimal -> int/float scale handling).
| CAST_TO_DECIMAL32, ///< Cast a decimal value to decimal32, preserving its scale | ||
| CAST_TO_DECIMAL64, ///< Cast a decimal value to decimal64, preserving its scale | ||
| CAST_TO_DECIMAL128, ///< Cast a decimal value to decimal128, preserving its scale |
There was a problem hiding this comment.
CAST_TO_DECIMAL* only changes the width and RESCALE only changes the scale, so a full decimal cast needs two chained ops whose order matters for overflow. Can CAST_TO_DECIMAL* take an optional target scale instead of a separate RESCALE?
| private: | ||
| ast_operator op; | ||
| std::vector<std::reference_wrapper<expression const>> operands; | ||
| std::optional<int32_t> target_scale; |
There was a problem hiding this comment.
Same here (along with the getter and the constructor in expressions.cpp)
| std::optional<int32_t> target_scale; | |
| std::optional<numeric::scale_type> target_scale; |
Description
AST expressions currently expose only int64, uint64 and float64 casts. This PR adds casts to bool, the remaining integer widths, float32, and decimal32/64/128, plus a
RESCALEoperation with an explicit target scale. Expressions can perform these conversions within AST evaluation and JIT transforms without materializing a separate cast column.Checklist