Skip to content

Optimize FP64 comparison functions on GPUs with slow FP64 performance - #12231

Open
lennartvoelz wants to merge 12 commits into
NVIDIA:mainfrom
lennartvoelz:libcudacxx/feat/fp64_comp_int_fast_path
Open

lennartvoelz wants to merge 12 commits into
NVIDIA:mainfrom
lennartvoelz:libcudacxx/feat/fp64_comp_int_fast_path

Conversation

@lennartvoelz

@lennartvoelz lennartvoelz commented Oct 23, 2026 •

Copy link
Copy Markdown

Description

Part of #11894

Comparison functions

  • isgreater, isless, isgreaterequal, islessequal and islessgreater compile to DSETP for double, which is slow on GPUs with low FP64 throughput -> idea to convert to int64 representation with the same ordering as double for the comparison
  • __fp64_to_ordered_int maps the bits of double to int64_t with the same ordering:
    I chose a selection form:
    key = x < 0 ? INT64_MIN - x : x -> -0 and +0 map to the same value
    The function is local to traits.h, as discussed in [FEA]: Optimize double-precision <cmath> functionalities with integer path #11894
  • NaN is handled by the existing double isnan fast path
  • the new path only runs on slow FP64 GPUs, guarded by the existing _CCCL_DISPATCH_SLOW_FP64_TARGET macro

Benchmarks:

op main this PR speedup
isless 724.6us 286.3us 2.53x
islessequal 725.0us 286.3us 2.53x
isgreater 725.2us 286.3us 2.53x
isgreaterequal 725.3us 286.5us 2.53x
isgreaterequal 725.2us 268.1us 2.70x

No FP64 instructions are emitted by the compiler on slow FP64 targets.

fmax/fmin

  • __fp64_to_total_order_int maps the bits of a double to int64_t in total order, i.e. -0 < +0. NaNs are placed beyond +-inf
  • __fp64_fmax_fmin_int adds function template that provides branch free min/max paths for doubles with integer instructions. -0 < +0, single NaN ignored and two NaNs return first argument quieted

Benchmarks:

op main this PR speedup
fmax 725.2us 395.8us 1.83x
fmin 725.4us 395.9us 1.83x

fmax / fmin also affect downstream paths in CUB via cuda::std::minimum/maximum:

Benchmark Path to fmin/fmax 2^16 2^20 2^24 2^28
CUB scan/applications/P1/running-min-max cuda::minimum/maximum −21.0% −20.4% −21.1% −21.6%
CUB scan/applications/P1/log-cdf-from-log-pdfs cuda::maximum −5.1% −4.8% −4.9% −4.6%
CUB reduce/min cuda::minimum −1.7% +2.0% −0.4% +0.3%
CUB reduce/deterministic (RFA sum) fmax(fabs(a), fabs(b)) 0.0% +1.4% +2.3% +2.5%

The only downstream benchmark negatively affected is using fabs, which is the reason for the slowdown. I'll address this in a separate PR.

Added tests for comparisons and fmin/fmax that cover edge cases

Checklist

  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

- adds __fp64_to_ordered_int helper that uses a selection form to convert the bit pattern to int64 with the same ordering as fp64
- adds double overloads for isgreater, isless, isgreaterequal, islessequal and islessgreater
- uses _CCCL_DISPATCH_SLOW_FP64_TARGET to dispatch between "slow" and "fast" fp64 arithmetic targets
@copy-pr-bot

copy-pr-bot Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-project-automation github-project-automation Bot moved this to Todo in CCCL Oct 23, 2026
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Progress in CCCL Oct 23, 2026
lennartvoelz and others added 8 commits October 23, 2026 22:35
quite -> quiet in min_max.h
- adds __fp64_to_total_order_int helper that maps the bit pattern to int64 in IEEE totalOrder (-0 < +0)
- adds branch-free __fp64_fmax_fmin_int helper that matches the hardware result bitwise (NaN handling, quiet bit)
- uses _CCCL_DISPATCH_SLOW_FP64_TARGET in fmax and fmin to dispatch to the integer path for double
- tests for fmin/fmax and the comparison functions isless, islessequal, isgreater, isgreaterequal, islessgreater
…argets the generic fallback (x < y ? y : x) returns either zero for ±0, while the integer path on slow targets orders -0 < +0

@miscco miscco left a comment

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.

LGTM but I want @fbusato or @davebayer to weigh in too

#include <cuda/std/__type_traits/is_extended_arithmetic.h>
#include <cuda/std/__type_traits/promote.h>
#include <cuda/std/cstdint>
#include <cuda/std/limits>

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.

Nitpick: I believe we can just include the non-extended header here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Isn't this already the non-extended include?
Or perhaps I don't really understand what it means...

Comment on lines +212 to +220
_CCCL_DISPATCH_SLOW_FP64_TARGET(
const bool __is_less_equal = ::cuda::std::__fp64_to_ordered_int(__x) <= ::cuda::std::__fp64_to_ordered_int(__y);
return !(::cuda::std::isnan(__x) || ::cuda::std::isnan(__y)) && __is_less_equal;)
if (::cuda::std::isnan(__x) || ::cuda::std::isnan(__y))
{
return false;
}
return __x <= __y;
}

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 am curious as to whether this is something we want to do always, what happens if we do

    return !(::cuda::std::isnan(__x) || ::cuda::std::isnan(__y)) && __x <= __y;

below?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I had this thought too, but for the fast path targets (e.g. sm_80) the early return does not result in a branch

@lennartvoelz
lennartvoelz marked this pull request as ready for review October 26, 2026 11:43
@lennartvoelz
lennartvoelz requested review from a team as code owners October 26, 2026 11:43
@cccl-authenticator-app cccl-authenticator-app Bot moved this from In Progress to In Review in CCCL Oct 26, 2026
@lennartvoelz

Copy link
Copy Markdown
Author

Ready for review
One thing that came up with the new test cases:
The integer path reproduces the hardware double min/max behavior: one NaN is ignored and two return NaNs return the first argument with the quiet bit set. For fmax(x, x) and fmin(x, x) with a signaling NaN, the compiler folds this without executing max in hardware, so x is returned unchanged. The hardware path is not self-consistent logically because fmax(x, x) != fmax(x, y) with y == x. I think no integer implementation can match both?
How should this be handled?

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: 03c8d69
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: 0f3998b7-2cfa-4c99-8a36-d80a5412c479
Commits

Reviewing files that changed from the base of the PR and between 6f1865e and bb7584f.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: c4451c1
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: 031ef916-2ce3-4e72-a8e6-edb2ece79212
Commits

Reviewing files that changed from the base of the PR and between c4451c1 and 6f1865e.

Files selected for processing: 6
  • libcudacxx/benchmarks/bench/comparison/basic.cu
  • libcudacxx/benchmarks/bench/fmin_fmax/basic.cu
  • libcudacxx/include/cuda/std/__cmath/min_max.h
  • libcudacxx/include/cuda/std/__cmath/traits.h
  • libcudacxx/test/libcudacxx/std/numerics/c.math/fp_min_max.pass.cpp
  • libcudacxx/test/libcudacxx/std/numerics/c.math/fp_traits.pass.cpp

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved double-precision fmin and fmax behavior on supported devices, including handling of signed zero, infinities, denormal values, and NaNs.
    • Improved double-precision comparison behavior for ordering and NaN edge cases.
  • New Features
    • Added benchmarks for double-precision comparison, minimum, and maximum operations.

This PR adds integer-based double comparison and min/max paths for slow-FP64 targets. It also adds edge-case tests and CUDA benchmarks for the affected operations.

Changes: Slow-FP64 math paths

Layer / File(s) Summary
Double comparison predicates
libcudacxx/include/cuda/std/__cmath/traits.h, libcudacxx/test/libcudacxx/std/numerics/c.math/fp_traits.pass.cpp, libcudacxx/benchmarks/bench/comparison/basic.cu
Adds ordered-integer implementations for the five double comparison predicates on slow-FP64 targets. Tests cover signed zeros, NaNs, infinities, denormals, limits, and mixed arguments. Adds benchmarks for the predicates.
Double min/max functions
libcudacxx/include/cuda/std/__cmath/min_max.h, libcudacxx/test/libcudacxx/std/numerics/c.math/fp_min_max.pass.cpp, libcudacxx/benchmarks/bench/fmin_fmax/basic.cu
Adds integer-based double selection and slow-FP64 dispatch for fmin and fmax. Tests cover signed zeros, NaNs, infinities, denormals, and mixed arguments. Adds benchmarks for both functions.

Possibly related issues

  • NVIDIA/cccl issue 11894 requests integer-path optimizations for several double-precision <cmath> functions on slow-FP64 GPUs. This PR implements paths for comparison predicates and fmin/fmax; the issue also names functions not changed here.

Suggested reviewers: fbusato, miscco, davebayer

Priority: Normal

Change: Refactor

Merge Risk: Minimal · up to 6f186

No actionable issue remains; the new edge-case tests exercise the device paths, so this change is mergeable after normal checks.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants