NVIDIA / NVIDIA/cudf

[FEA] Support NaN-aware operators in libcudf that conform to ETL engine expectations

Open
#18,930 0 comments 4 reactions 0 assignees View on GitHub
feature request Java libcudf Python Spark
Dominant language
C++
Stars
9.8k
Forks
1.1k
Avg merge
3d 6m
Merged PRs (30d)
278

Description

**Is your feature request related to a problem? Please describe.**
In https://github.com/rapidsai/cudf/issues/4760 we discussed the degree to which libcudf would adhere to IEEE 754's comparison predicates for defining the behavior of NaNs. The issue discussed there is that Spark follows a different convention from most programming languages (which follow IEEE 754) by having $\mathrm{NaN} >= x, \forall x$, and the Spark RAPIDS plugin needed a way to support that behavior. In that issue, we determined that the cost of making libcudf IEEE 754-compliant everywhere would would be too high in cases like sorts, aggregations, or joins where Spark would effectively have to reimplement them from scratch. As a result, most of these operations in libcudf natively support NaN-aware computations that support the Spark convention, either by using [a custom comparator](https://github.com/rapidsai/cudf/blob/branch-25.06/cpp/include/cudf/table/experimental/row_operators.cuh#L211) or by specifying an enum like [`nan_equality`](https://github.com/rapidsai/cudf/blob/branch-25.06/cpp/include/cudf/types.hpp#L143). However, as part of that discussion it was also decided that in cases where the cost was lower, e.g. an extra one or two kernel invocations for checking NaNs on the part of the caller, libcudf would not provide extra facilities and would instead devolve the responsibility onto consumers. IEEE 754 defines [a separate total ordering predicate](https://en.wikipedia.org/wiki/IEEE_754#Total-ordering_predicate) that deviates from the comparison predicate specifically for NaN functionality, and that is effectively the behavior that we want.

The most common case that is not currently supported is in binary operations i.e. direct comparisons with NaN. As a result of this, spark-rapids has adapted multiple parts of its code base to handle its NaN conventions. Some examples include:
- https://github.com/NVIDIA/spark-rapids/blob/1a8e5ae0f2297fa1f2dd396b0d6100df9873ba52/delta-lake/common/src/main/scala/com/nvidia/spark/rapids/delta/GpuStatisticsCollection.scala#L374-L402
- https://github.com/NVIDIA/spark-rapids/blob/1a8e5ae0f2297fa1f2dd396b0d6100df9873ba52/sql-plugin/src/main/scala/org/apache/spark/sql/rapids/predicates.scala#L169-L189
- https://github.com/NVIDIA/spark-rapids/blob/1a8e5ae0f2297fa1f2dd396b0d6100df9873ba52/sql-plugin/src/main/scala/org/apache/spark/sql/rapids/aggregate/aggregateFunctions.scala#L123-L172

More recent work has led us to reconsider this position. While it is true that most programming languages follow IEEE 754, most ETL engines do not. In fact, Spark's convention is a common one amongst such engines, including PostgreSQL. Most relevant to us now, [Polars also follows this convention](https://docs.pola.rs/user-guide/concepts/data-types-and-structures/#floating-point-numbers), as does [Velox](https://github.com/facebookincubator/velox/blob/4277ae8d8239c8eb4d38ff3f89827543510cb204/velox/docs/develop/types.rst?plain=1#L85). That means that libcudf's choice is now having a negative performance impact for most ways in which it is or will be used.

**Describe the solution you'd like**
We should reconsider offering native support for NaN-aware comparison operators that conform to the conventions of these tools. These operations will not only improve performance for these engines, they will also reduce memory pressure by saving them from having to materialize extra intermediate columns containing NaN checks.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.