Skip to content

[fpmp] Force-inline the fpmp2 operators for faster fp32mp2 math on the CPU - #12543

Merged
miscco merged 7 commits into
NVIDIA:mainfrom
rmlarsen:fpmp-math-inline
Nov 13, 2026
Merged

miscco merged 7 commits into
NVIDIA:mainfrom
rmlarsen:fpmp-math-inline

Conversation

@rmlarsen

@rmlarsen rmlarsen commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Results, compared with main

This PR force-inlines the fpmp2 arithmetic and comparison operators, the polynomial evaluators and the public math functions. Results do not change: it only stops host compilers from calling those functions out of line, inside the fp32mp2 math functions or in their callers. It is based on main and lands first: #12519, #12530 and #12548 are stacked on it.

Performance. Speedup (old time / new time), as the geometric mean over 30 fp32mp2 math functions, with each function called through one non-inlined call ("out of line") or inlined into the benchmark loop ("inlined"):

Platform Over Out of line Inlined
aarch64: DGX Spark Cortex-X925, GCC 13.3 -O3 -mcpu=neoverse-v2 main 2.251 1.492
x86-64: AMD Ryzen Threadripper PRO 7965WX (Zen 4), GCC 15.2 -O3 -march=native main 1.207 1.030
aarch64: Raspberry Pi 5 (Cortex-A76), GCC 14 -O3 -mcpu=cortex-a76 #12530 1.092 1.078
x86-64: Intel Core i5-11500T, GCC 15.2 -O3 -march=x86-64-v3 #12530 1.044 1.027
aarch64: Apple M4 (Mac mini), Apple clang 21 -O3 -mcpu=apple-m4 #12530 1.071 1.060
GPU: sm_75 to sm_121 (RTX 2070, H100, B200, B300, Rubin, DGX Spark GB10) main 1.00 (identical SASS) 1.00

Accuracy. Unchanged.

Special values. Unchanged. Only the sign of a few NaN results changes on the host, which IEEE 754 leaves unspecified, measured on top of #12530:

  • boys_f0(NaN) on x86-64 (GCC 15.2, with and without -ffp-contract=off);
  • cos(±inf), cos(NaN), erfc(NaN) and boys_f0(inf) on the Pi (GCC 14).

There, inlining lets GCC contract differently.

Implementation

The fpmp2 arithmetic and comparison operators, add/sub/mul/div, the __fpmp2_*_scalar helpers and the three polynomial evaluators do nothing but dispatch to kernels that are already force-inlined (_CCCL_FPMP_CORE_API). They were plain inline functions, though, and GCC 14 for aarch64 calls several of them out of line in the fp32mp2 math functions:

  • the low-accuracy pair product and quotient;
  • the pair-by-float product;
  • three __fpmp_poly_horner_mixed instances.

This PR declares those functions in fpmp.h (39 on main, 44 once #12519 adds its scalar operators), and the evaluators in fpmp_math_impl.h, _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE, as libcudacxx does in cuda/__stream/invalid_stream.h. Unlike _CCCL_TRIVIAL_HOST_DEVICE_API, this does not mark them nodebug or artificial, so a debugger can still step into them; the generated code is the same. The constructors, the conversion and assignment operators, the volatile members and the named functions of fpmp.h (sqrt, fma, ...) are unchanged. CUB already force-inlines friend operators the same way (cub/detail/null_type.cuh).

It also declares the 90 public math functions of fpmp_math.h and their 13 cuda::std forwarders _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE. Their implementations were already force-inlined (_CCCL_FPMP_CORE_API), so these wrappers were the only level left to the compiler. The named functions of fpmp.h (sqrt, fma, ...) stay as they are: GCC inlines them into such loops anyway.

Measurements

The Pi, i5-11500T and M4 figures come from #12530's harness, measured on top of #12530: each function runs on one core, and the figure is the fastest of at least 30 rounds, with a 3 s warm-up on the M4. The DGX Spark and Zen 4 figures come from #12530's second harness on main: the five variants of the stack interleaved, the median of 15 rounds, best of three passes, on one core.

Per-function speedups over #12530: out-of-line call / plain
Function Pi 5 i5-11500T Apple M4
exp 1.00 / 1.01 1.00 / 0.99 1.00 / 1.01
exp2 1.00 / 1.00 1.00 / 1.00 1.00 / 1.01
exp10 1.00 / 1.00 1.00 / 1.00 1.00 / 1.00
expm1 1.31 / 1.02 1.11 / 1.00 1.09 / 1.10
log 1.00 / 1.00 1.00 / 1.01 1.00 / 1.00
log2 1.00 / 1.00 1.00 / 1.01 1.00 / 1.00
log10 1.00 / 1.00 1.00 / 0.97 1.00 / 1.00
log1p 1.27 / 1.02 1.09 / 0.99 1.18 / 1.07
sin 1.01 / 0.85 1.01 / 0.93 1.07 / 1.01
cos 1.00 / 1.00 1.00 / 1.00 1.05 / 1.00
tan 1.00 / 1.00 1.00 / 1.00 1.00 / 1.01
sinpi 1.00 / 1.00 1.00 / 1.00 0.98 / 1.00
cospi 1.00 / 0.99 1.00 / 0.93 1.00 / 1.06
asin 0.99 / 1.00 1.00 / 1.00 1.11 / 1.14
acos 1.00 / 1.00 1.00 / 1.00 1.14 / 1.15
atan 1.00 / 1.00 1.00 / 1.00 1.19 / 1.17
erf 1.01 / 1.00 0.99 / 1.01 0.99 / 0.99
erfc 1.00 / 1.00 1.00 / 1.00 1.24 / 1.25
normcdfinv 1.00 / 1.00 1.00 / 1.00 1.36 / 1.30
boys_f0 1.02 / 1.02 1.00 / 1.00 1.04 / 1.07
tanh 1.41 / 1.41 1.16 / 1.00 1.08 / 1.05
sinh 1.00 / 1.00 1.01 / 1.01 1.08 / 1.04
cosh 0.99 / 0.84 1.01 / 0.88 1.00 / 1.02
atanh 1.65 / 1.83 1.33 / 1.34 1.17 / 1.17
asinh 1.37 / 1.53 1.15 / 1.20 1.10 / 1.16
acosh 1.47 / 1.59 1.22 / 1.28 1.12 / 1.11
cbrt 1.00 / 1.00 1.00 / 0.98 1.01 / 1.00
rcbrt 1.00 / 1.05 1.02 / 1.02 1.01 / 0.93
pow 1.76 / 1.90 1.34 / 1.36 1.24 / 1.07
sincos 1.00 / 1.00 1.00 / 1.01 0.99 / 1.00
geomean 1.092 / 1.078 1.044 / 1.027 1.071 / 1.060

The M4's plain rcbrt (0.93×) comes from one fast round of #12530: 15.1 ns, against 16.2 ns in its other three rounds and in all four of this PR's.

Per-function speedups of this PR over main on the DGX Spark's Cortex-X925 and on Zen 4: out of line / inlined
Function DGX Spark Cortex-X925 Zen 4
exp 2.15 / 0.94 1.00 / 0.94
exp2 1.45 / 1.00 1.00 / 1.00
exp10 1.86 / 1.00 1.09 / 1.00
expm1 3.34 / 1.00 1.55 / 1.00
log 1.69 / 0.38 1.00 / 1.00
log2 1.84 / 0.93 1.04 / 1.00
log10 1.84 / 0.93 1.04 / 1.00
log1p 2.44 / 0.99 1.46 / 1.00
sin 3.74 / 3.70 1.21 / 1.21
cos 3.74 / 3.70 1.21 / 1.21
tan 2.63 / 2.61 1.16 / 1.16
sincos 2.60 / 2.61 1.16 / 1.15
sinpi 3.13 / 3.10 1.34 / 1.34
cospi 3.72 / 3.68 1.40 / 1.41
asin 1.06 / 1.06 1.05 / 1.03
acos 1.07 / 1.07 1.05 / 1.03
atan 1.00 / 1.00 1.00 / 0.23
erf 1.89 / 1.85 1.03 / 1.01
erfc 2.78 / 2.78 1.26 / 1.26
normcdfinv 1.87 / 1.87 1.22 / 1.22
boys_f0 1.90 / 1.94 1.22 / 1.22
tanh 3.01 / 0.99 1.55 / 1.54
sinh 3.41 / 0.98 1.55 / 1.55
cosh 3.01 / 1.00 1.31 / 0.24
atanh 3.78 / 3.75 1.49 / 1.47
asinh 2.37 / 2.40 1.39 / 1.39
acosh 2.88 / 2.88 1.39 / 1.40
cbrt 1.46 / 1.00 1.00 / 1.00
rcbrt 1.62 / 0.96 1.00 / 0.95
pow 3.08 / 1.00 1.53 / 0.99
geomean 2.251 / 1.492 1.207 / 1.030

Testing

Merge order

Based on main; it should merge first. #12519, #12530 and #12548 are stacked on it and merge its branch.

The CI override of ab86603 (the cuda/fp/units lit suite on a T4 and an H100, MSVC and arm64 precompiles) is reset by 627abd3, so this PR now runs the full CI.

Checklist

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

🤖 Generated with Claude Code

@copy-pr-bot

copy-pr-bot Bot commented Nov 9, 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.

@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Progress in CCCL Nov 9, 2026
@rmlarsen rmlarsen changed the title [fpmp] Force-inline the fpmp2 arithmetic operators and the polynomial evaluators [fpmp] Force-inline the fpmp2 operators for faster fp32mp2 math on the CPU Nov 9, 2026

@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.

Looks fine by me, note that this also prevents the debugger to step into those functions, which might not be what you want.

In that case you really want to just add _CCCL_FORCEINLINE

rmlarsen and others added 3 commits November 9, 2026 12:55
… evaluators

The fpmp2 arithmetic and comparison operators, add/sub/mul/div, the __fpmp2_*_scalar
helpers and the three polynomial evaluators only dispatch to kernels that are already
force-inlined, but they were plain inline functions. GCC 14 for aarch64 calls several
of them out of line in the fp32mp2 math functions: the low-accuracy pair product and
quotient, the pair-by-float product and three __fpmp_poly_horner_mixed instances.
tanh, atanh, asinh, acosh and pow pay for those calls, and tanh became 0.72x on a
Raspberry Pi 5 when the single-limb kernels changed which operator it calls.

Declare those 44 functions in fpmp.h and the evaluators in fpmp_math_impl.h
_CCCL_TRIVIAL_HOST_DEVICE_API. The constructors, conversion and assignment operators,
volatile members and the named math functions (sqrt, fma, ...) are left as they are.

Timed with each function behind one out-of-line call, so that whether the compiler
inlines the math function into the loop does not enter, the geometric mean over 30
fp32mp2 math functions is 1.092x faster on a Raspberry Pi 5 (GCC 14; tanh 1.41x,
atanh 1.65x, pow 1.76x), 1.044x on an Intel Core i5-11500T (GCC 15.2) and 1.071x on
an Apple M4 (Apple clang 21), and no function is slower than 0.98x. When the math
function may be inlined into the loop, the larger sin and cosh no longer are, so the
plain benchmark shows sin 0.85x and cosh 0.84x on the Pi; its geometric means are
1.078x, 1.027x and 1.060x.

The device SASS is unchanged. Over 2^18 inputs per function, results are
bit-identical on sm_89; on the hosts (x86-64 with GCC 15.2, with and without
-ffp-contract=off, and the Pi) only the sign of a NaN result differs, for a few
special inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the operators force-inlined, the bodies of the public math functions look larger
to GCC's inliner, and it stops inlining exp, log, log2, atan and cosh into their
callers. A loop such as `for (...) y[i] = cuda::log(x[i]);`, which GCC inlined and
vectorized on main, then calls the function for every element.

Their implementations are already force-inlined (_CCCL_FPMP_CORE_API); the 90 public
wrappers in fpmp_math.h and the 13 cuda::std forwarders were the only levels left to
the compiler. Declare them _CCCL_TRIVIAL_HOST_DEVICE_API as well.

ns per element of y[i] = f(x[i]) over 4096 fp32mp2 values, measured when this change sat on
top of NVIDIA#12519 and NVIDIA#12530: main / NVIDIA#12530 / NVIDIA#12530 with the operators force-inlined / with
this commit too:

  AMD Ryzen Threadripper PRO 7965WX, GCC 15.2 -O3 -march=native
    exp   1.93 / 1.84 / 23.6 / 1.82     atan  3.77 / 3.77 / 35.7 / 3.81
    log   2.19 / 2.08 / 22.3 / 2.08     cosh  2.64 / 2.50 / 29.2 / 2.51
    log2  27.2 / 2.25 / 24.4 / 2.23
  Cortex-X925 (DGX Spark), GCC 13.3 -O3 -mcpu=neoverse-v2
    exp   5.87 / 4.97 / 17.8 / 5.03     cosh  7.87 / 7.66 / 22.6 / 7.28
    log   17.8 / 16.4 / 16.3 / 5.31     log2  19.8 / 18.4 / 18.5 / 6.69

sin, tanh, expm1, atanh and pow are equal or faster in the same loops. Called through
cuda::std, the wrappers alone are not enough (exp 22.2 ns, atan 33.6 ns on the
Threadripper); with the forwarders forced too, exp takes 1.83 ns and atan 3.77 ns.
Behind one out-of-line call, the geometric mean over 30 functions became 1.139x faster than
NVIDIA#12530 instead of 1.124x on the Threadripper, and 2.22x instead of 2.18x on the X925.

The device SASS is unchanged (sm_89, sm_90, sm_100, sm_120). On the host, results keep
their values; on x86-64 with GCC 15.2, one special-input result of erfc changes its
bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecompile on arm64 [reset before merging]

This PR changes only fpmp headers, so limit its CI to the cuda/fp lit suite on a T4
(gcc and clang, CTK 12.X and 13.X) and an H100 (gcc, CTK 13.X), plus a precompile of
the same suite with MSVC and on an arm64 runner with gcc, the compiler this change is
for. The override must be reset to empty before the PR can merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmlarsen
rmlarsen marked this pull request as ready for review November 9, 2026 23:15
@rmlarsen
rmlarsen requested review from a team as code owners November 9, 2026 23:15
@rmlarsen
rmlarsen requested a review from jrhemstad November 9, 2026 23:15
@cccl-authenticator-app cccl-authenticator-app Bot moved this from In Progress to In Review in CCCL Nov 9, 2026
Empties the override matrix again, so this PR runs the full CI before merging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • Refactor
    • Updated the implementation of multi-precision floating-point arithmetic and math operations, including comparisons, increment and decrement, and math functions used in host and device execution.
    • Supported operations, calculations, accuracy selection, and results remain unchanged. This update does not add or remove user-facing capabilities.

The fpmp2 operators, arithmetic overloads, math wrappers, cuda::std forwarding overloads, and polynomial helpers now use _CCCL_FORCEINLINE. Their signatures, calculations, dispatch, and forwarding behavior remain unchanged.

Changes: fpmp2 annotations

Layer / File(s) Summary
Operators and arithmetic overloads
libcudacxx/include/cuda/__fp/fpmp.h
fpmp2 operators and accuracy-explicit add, sub, mul, and div overloads use _CCCL_FORCEINLINE. Their logic and delegation remain unchanged.
Math APIs and polynomial helpers
libcudacxx/include/cuda/__fp/fpmp_math.h, libcudacxx/include/cuda/__fp/fpmp_math_impl.h
fpmp2 math APIs, cuda::std forwarding wrappers, and polynomial helpers use _CCCL_FORCEINLINE. Their calls and helper bodies remain unchanged.

Possibly related PRs

  • NVIDIA/cccl#12548: It changes the same operator and mixed-scalar overload declarations in fpmp.h and is stated to depend on this force-inline change landing first.
  • NVIDIA/cccl#12530: It builds on the force-inlined declarations and math paths changed here, and the supplied summary states a direct stack relationship.

Suggested reviewers: akolesov-nvidia

Change: Refactor

Merge Risk: Minimal · up to 91455

This change force-inlines fpmp2 operators and math functions to speed up host code. No actionable merge-blocking risk was found.

Architecture risk: Low · up to 91455

The change affects 1 system.

Changed systems: libcudacxx

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — libcudacxx (service) was modified; 3 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in libcudacxx/include/cuda/__fp/fpmp_math_impl.h: Updates __fpmp_poly_horner_mixed to use _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE instead of _CCCL_HOST_DEVICE_API inline; the function interface and body stay the same.
  • observed — Modified behavior in libcudacxx/include/cuda/__fp/fpmp_math_impl.h: Updates __fpmp_poly_horner_comp to use _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE instead of _CCCL_HOST_DEVICE_API inline; the function interface and body stay the same.
  • observed — Modified behavior in libcudacxx/include/cuda/__fp/fpmp_math_impl.h: Updates __fpmp_poly_eval to use _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE instead of _CCCL_HOST_DEVICE_API inline; the function interface and body stay the same.
  • observed — Modified behavior in libcudacxx/include/cuda/__fp/fpmp_math.h: The fpmp2 wrappers for exponential, logarithmic, power, trigonometric, hyperbolic, error, Boys, and inverse-normal functions replace inline with _CCCL_FORCEINLINE; signatures and calls to the corresponding __fpmp2_* functions are unchanged.
- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: d0d9d68
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: 9b0e1acc-c1a5-4a7e-9f93-a71374bc0e68
Commits

Reviewing files that changed from the base of the PR and between 3057237 and 914553f.

Files selected for processing: 1

  • libcudacxx/include/cuda/__fp/fpmp.h

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

…p in

_CCCL_TRIVIAL_HOST_DEVICE_API also marks a function nodebug and artificial, so a
debugger cannot step into it. The operators, polynomial evaluators and math functions
that this PR force-inlines are user-facing, so declare them
`_CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE` instead, as libcudacxx does elsewhere
(cuda/__stream/invalid_stream.h). The fpmp kernels behind them keep
_CCCL_FPMP_CORE_API.

The generated host code (GCC 15.2) and the sm_89 SASS are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Additional comments: 1

libcudacxx/include/cuda/__fp/fpmp.h: 1

1008-1030: Trivial · Maintainability & Code Quality · Low value

Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

suggestion: The retrieved learning says to flag inlining hints that lack profiling data. The PR reports host benchmarks, so the learning is satisfied for the arithmetic operators. Add a short comment near the first _CCCL_TRIVIAL_HOST_DEVICE_API use on the operators. The comment should say why force-inlining is needed (host compilers emit out-of-line calls). This prevents a later cleanup from reverting the annotation. Increment, decrement and compound-assignment operators now also force-inline. They add no new logic, only a call to an already force-inlined operator. If code size or compile time matters, drop _CCCL_TRIVIAL_HOST_DEVICE_API from these wrappers and keep it on the core operators.

Also applies to: 1032-1047

Source: Learnings

Review info

Run configuration

  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: 26e9c14
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: 75f6fa8a-3c41-49db-8643-438e0807864e
Commits

Reviewing files that changed from the base of the PR and between 26e9c14 and 627abd3.

Files selected for processing: 3

  • libcudacxx/include/cuda/__fp/fpmp.h
  • libcudacxx/include/cuda/__fp/fpmp_math.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl.h

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

@rmlarsen

rmlarsen commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

@miscco Thanks, that was not intended. 2fad1ca declares everything this PR force-inlines _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE instead, as libcudacxx does in cuda/__stream/invalid_stream.h. The operators, the polynomial evaluators and the math functions stay force-inlined, but a debugger can step into them. The generated host code (GCC 15.2) and the sm_89 SASS are unchanged. #12519, which adds more scalar operators on top of this PR, does the same for them in b8b4f8b.

…ined

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmlarsen

Copy link
Copy Markdown
Contributor Author

@coderabbitai I added the comment in 3057237: in fpmp.h above the operators, and in fpmp_math.h above the math functions. The increment, decrement and compound-assignment operators stay force-inlined. They are one-line wrappers, so it costs nothing, and leaving them to GCC would bring back the out-of-line calls this PR removes.

@coderabbitai

coderabbitai Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

Analysis chain

Script executed:

#!/bin/bash
set -u
for name in fpmp.h fpmp_math.h; do
  echo "=== Locate $name ==="
  fd -t f -F "$name" .
done
for file in libcudacxx/include/cuda/__fp/fpmp.h libcudacxx/include/cuda/__fp/fpmp_math.h; do
  echo "=== Outline $file ==="
  ast-grep outline "$file" --items all --match 'operator|fpmp' 2>/dev/null | head -100 || true
  echo "=== Force-inline and rationale context: $file ==="
  rg -n -C 4 'force.inline|force.inline|out.of.line|_CCCL_FORCEINLINE|operator\+\+|operator--|operator\+=' "$file" | head -250 || true
done
echo "=== Commit changes ==="
git show --format=fuller --no-ext-diff 3057237ff9 -- libcudacxx/include/cuda/__fp/fpmp.h libcudacxx/include/cuda/__fp/fpmp_math.h | head -300 || true
echo "=== Relevant review conversations ==="
gh api graphql -f query='
query {
  repository(owner: "NVIDIA", name: "cccl") {
    pullRequest(number: 12543) {
      reviewThreads(first: 100) {
        nodes {
          path
          isResolved
          comments(first: 30) { nodes { author { login } body url } }
        }
        pageInfo { hasNextPage }
      }
    }
  }
}' --jq '.data.repository.pullRequest.reviewThreads | {hasNextPage: .pageInfo.hasNextPage, matchingThreads: [.nodes[] | select(any(.comments.nodes[]; (.body | ascii_downcase | test("3057237ff9|increment|compound.assignment|force.inline"))))]}' || true

Length of output: 28762


@rmlarsen suggestion: The comments are present in both headers. I accept the rationale for keeping the increment, decrement, and compound-assignment operators force-inlined. No further change is needed.

You are interacting with an AI system.

@miscco

miscco commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

/ok to test 3057237

@miscco
miscco enabled auto-merge (squash) November 11, 2026 07:28
@github-actions

This comment has been minimized.

@rmlarsen

Copy link
Copy Markdown
Contributor Author

/ok to test 914553f

@miscco

miscco commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

/ok to test 914553f

@github-actions

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 1h 28m: Pass: 100%/230 | Total: 2d 01h | Max: 1h 25m | Hits: 93%/696198

See results here.

@miscco
miscco merged commit 994988b into NVIDIA:main Nov 13, 2026
250 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in CCCL Nov 13, 2026
akolesov-nvidia pushed a commit that referenced this pull request Nov 13, 2026
…s and a shorter pair product (#12519)

* [fpmp] Add single-limb kernels for scalar operands and shorten the pair product

Arithmetic between an fpmp2 and a scalar that one limb holds exactly (the limb
type, float for fp64mp2, integers up to the limb's significand width) no longer
promotes the scalar to a pair:

- x + c, x - c, c - x use DWPlusFP (the existing accumulate): the same value as
  the pair addition, at 10 instead of 20 instructions at high.
- x * c is TwoProd plus one fma and a Fast2Sum, 6 instead of 9 instructions at
  mid, with error below 2u^2 to first order where the promoted product reaches
  3u^2; high uses JMP17 Algorithm 7 (measured 1.5u^2).
- x / c drops the term on the divisor's zero low limb (same value).
- The compound forms +=, -=, *=, /= and the accuracy-selecting add/sub/mul/div
  take the same kernels; fpmp2_stat forwards to them to stay bit-identical.

The default pair product folds both cross terms into the exact residual with
two fmas (7 instead of 9 instructions, first-order bound 5u^2, the bound of
Algorithm 10); high is the low product plus renormalization (8 instructions,
measured 3.9u^2 against 4.9u^2). __fpmp2_high_acc drops a second Fast2Sum that
never changed its already normalized result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Make fpmp2_stat's compound scalar operators accept what fpmp2 accepts

fpmp2_stat's +=, -=, *= and /= took a plain _FpType, so a scalar that one
limb cannot hold was narrowed first: fp32mp2_stat *= 16777217 multiplied by
16777216, where fp32mp2 converts the integer to an exact pair. With explicit
casts, a double also reached the fpmp2_stat overload through the implicit
single-limb constructor, which fp32mp2 rejects.

The four operators now take any arithmetic scalar the wrapped type's own
compound operator accepts and forward it unconverted, and are deleted for the
others. stat.pass.cpp checks the integer case and that both types accept the
same scalars; stat_implicit_casts.pass.cpp checks a double under
CCCL_FPMP_EXPLICIT_CASTS=0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Review: brace-initialize limb constants, drop inline from new function templates

Use _FpType{1} and _FpType{0} for the limb constants in the increment and
decrement operators (fpmp2 and fpmp2_stat) and in the single-limb division,
drop the redundant inline from the new __fpmp2_*_scalar function templates,
and mark the element-type aliases in the accuracy-selecting mixed functions
_CCCL_NODEBUG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Shorten the pair product's dependency chain; make the high product more accurate

The two-fma default product saved two instructions but made the residual of
x_hi * y_hi feed two dependent fmas. In a chain of products on the CPU that
made it 9% slower than the old code (whose p1 + p2 GCC fuses). The default
product now sums the cross terms with one fma off the residual's path, so
x_lo, which arrives last, enters only that fma: 8 instructions instead of 9,
1.19x lower latency on the host than before, 1.10-1.13x GPU throughput, and
the same measured error as Algorithm 10 (5.0u^2, 4.0u^2 for squares).

The high product (low product plus renormalization) turned out to reach
5.0u^2 on float squares, no better than mid. It is now x_lo * y_lo and the
cross terms, smallest first, summed by two fmas off the residual's path:
9 instructions, measured 4.0u^2 (3.5u^2 for squares).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Keep the high scalar product bit-identical between host and device

In the high x * c (Algorithm 7) the separately rounded x_lo * c fed an add,
which GCC contracts on the host by default: host and device disagreed on 4%
of products. Rounding it through an fma with a zero addend gives the same
value and leaves nothing to contract; on the device it is still one FFMA.
Every product now agrees bit for bit between host and device on 2^20 random
operands, where the old default product disagreed on 20%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Test scalar trace counts and scalar-first subtraction independently

The fpmp2_stat counting kernel now also runs s *= 2 (a scalar that takes the
pair path), s /= 2.0f and s * 0.5f, and checks that each is traced once.
scalar_ops checks x - c and c - x against an exact expansion instead of
against the addition they share code with, and checks c - x on fixed
operands with hand-computed results, including a pair whose low limb is not
normalized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Update poly_horner_mixed comments: the transition step no longer promotes

The mixed operator+ now adds the float product with the single-limb kernel
instead of converting it to a pair first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Form the low quotient's lo word with an fma so host compilers cannot contract it

__fpmp2_low_div and the low branch of __fpmp2_div_limb returned their lo word as a
plain product. GCC, under its default -ffp-contract=fast on FMA hardware, fuses such a
product into whatever adds the caller does next, which gives different bits from the
device, where __fmul_rn is never contracted.

Add __fpmp_mul_rn_nocontract, x * y as an fma with a zero addend, and use it for those
lo words and for the x_lo * c term of the high scalar product, which spelled the same
construct out by hand.

Device results are unchanged, except that an exactly zero lo word is now +0. Over the
fp32mp2 math functions (2^18 inputs each), results are bit-identical on sm_89 and on
the host with -ffp-contract=off; with GCC 15.2 -march=x86-64-v3 and default
contraction, cosh's results that differ from the device drop from 21135 to 4344.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Review: comment fixes; host-device deleted scalar operators in fpmp2_stat

- fptool_stat.h: mark the four deleted scalar compound operators _CCCL_HOST_DEVICE_API,
  as every other deleted member is. Under clang-CUDA a host-only deleted overload is not
  viable in device code, so the call would fall through to operator+=(const fpmp2_stat&)
  via the narrowing single-limb constructor that the deletion exists to block. nvcc and
  host compilers are unaffected.
- fpmp.h: the mixed operators and the mixed add/sub/mul/div<Acc> convert a scalar
  dividend to a pair; say so in both comments. Rewrap the compound-assignment comment.
- fpmp_impl_muladd.h: the default product's dependent path ends in a Fast2Sum, not a
  TwoSum, and in the scalar product |x_lo * c + residual| is below 4u, not 3u, when
  x_hi * c lies in [2, 4); the 2u^2 rounding bound is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Skip the negligible 1/e term of sinh and cosh where its low limb is subnormal

dd2262e made the low quotient's lo word a separate product so that host compilers
cannot fuse it into the next add. In the 0.5/e of fp32mp2 sinh and cosh, that product
is subnormal once e > 2^101 (|x| > 70), and Intel cores take a microcode assist for
each product with a subnormal result; fused into the add, it was normal. On an Intel
Core i5-11500T, where 18% of the benchmark's cosh inputs have |x| > 70, cosh went
from 44.5 to 51.8 ns per call. With FTZ/DAZ set, both versions run alike, and an AMD
Ryzen 5 3500U shows no difference.

For e > 2^32, 0.5/e is under 2^-63 of either result. The new
__internal_fpmp2_half_inv_exp returns 0 there, after dividing by 2^32 instead of e so
that no subnormal is formed, without a branch. On the i5-11500T, cosh now takes 39.6
ns and sinh 82.6 ns, against 51.8 and 94.9 before and 44.5 and 91.6 on main; no other
function changes.

Over 2^18 inputs each, sinh and cosh are bit-identical to before on sm_89 and on the
host with GCC 15.2, with and without -ffp-contract=off. Results can differ only where
the dropped term would have reached the low limb, by under 2^-63 relative. fp64mp2
sinh and cosh are binary128 wrappers and are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Force-inline the fpmp2 arithmetic operators and the polynomial evaluators

The fpmp2 arithmetic and comparison operators, add/sub/mul/div, the __fpmp2_*_scalar
helpers and the three polynomial evaluators only dispatch to kernels that are already
force-inlined, but they were plain inline functions. GCC 14 for aarch64 calls several
of them out of line in the fp32mp2 math functions: the low-accuracy pair product and
quotient, the pair-by-float product and three __fpmp_poly_horner_mixed instances.
tanh, atanh, asinh, acosh and pow pay for those calls, and tanh became 0.72x on a
Raspberry Pi 5 when the single-limb kernels changed which operator it calls.

Declare those 44 functions in fpmp.h and the evaluators in fpmp_math_impl.h
_CCCL_TRIVIAL_HOST_DEVICE_API. The constructors, conversion and assignment operators,
volatile members and the named math functions (sqrt, fma, ...) are left as they are.

Timed with each function behind one out-of-line call, so that whether the compiler
inlines the math function into the loop does not enter, the geometric mean over 30
fp32mp2 math functions is 1.092x faster on a Raspberry Pi 5 (GCC 14; tanh 1.41x,
atanh 1.65x, pow 1.76x), 1.044x on an Intel Core i5-11500T (GCC 15.2) and 1.071x on
an Apple M4 (Apple clang 21), and no function is slower than 0.98x. When the math
function may be inlined into the loop, the larger sin and cosh no longer are, so the
plain benchmark shows sin 0.85x and cosh 0.84x on the Pi; its geometric means are
1.078x, 1.027x and 1.060x.

The device SASS is unchanged. Over 2^18 inputs per function, results are
bit-identical on sm_89; on the hosts (x86-64 with GCC 15.2, with and without
-ffp-contract=off, and the Pi) only the sign of a NaN result differs, for a few
special inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Force-inline the fpmp2 math function wrappers too

With the operators force-inlined, the bodies of the public math functions look larger
to GCC's inliner, and it stops inlining exp, log, log2, atan and cosh into their
callers. A loop such as `for (...) y[i] = cuda::log(x[i]);`, which GCC inlined and
vectorized on main, then calls the function for every element.

Their implementations are already force-inlined (_CCCL_FPMP_CORE_API); the 90 public
wrappers in fpmp_math.h and the 13 cuda::std forwarders were the only levels left to
the compiler. Declare them _CCCL_TRIVIAL_HOST_DEVICE_API as well.

ns per element of y[i] = f(x[i]) over 4096 fp32mp2 values, measured when this change sat on
top of #12519 and #12530: main / #12530 / #12530 with the operators force-inlined / with
this commit too:

  AMD Ryzen Threadripper PRO 7965WX, GCC 15.2 -O3 -march=native
    exp   1.93 / 1.84 / 23.6 / 1.82     atan  3.77 / 3.77 / 35.7 / 3.81
    log   2.19 / 2.08 / 22.3 / 2.08     cosh  2.64 / 2.50 / 29.2 / 2.51
    log2  27.2 / 2.25 / 24.4 / 2.23
  Cortex-X925 (DGX Spark), GCC 13.3 -O3 -mcpu=neoverse-v2
    exp   5.87 / 4.97 / 17.8 / 5.03     cosh  7.87 / 7.66 / 22.6 / 7.28
    log   17.8 / 16.4 / 16.3 / 5.31     log2  19.8 / 18.4 / 18.5 / 6.69

sin, tanh, expm1, atanh and pow are equal or faster in the same loops. Called through
cuda::std, the wrappers alone are not enough (exp 22.2 ns, atan 33.6 ns on the
Threadripper); with the forwarders forced too, exp takes 1.83 ns and atan 3.77 ns.
Behind one out-of-line call, the geometric mean over 30 functions became 1.139x faster than
#12530 instead of 1.124x on the Threadripper, and 2.22x instead of 2.18x on the X925.

The device SASS is unchanged (sm_89, sm_90, sm_100, sm_120). On the host, results keep
their values; on x86-64 with GCC 15.2, one special-input result of erfc changes its
bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] CI override: run the cuda/fp unit tests on sm_75 and sm_90, precompile on arm64 [reset before merging]

This PR changes only fpmp headers, so limit its CI to the cuda/fp lit suite on a T4
(gcc and clang, CTK 12.X and 13.X) and an H100 (gcc, CTK 13.X), plus a precompile of
the same suite with MSVC and on an arm64 runner with gcc, the compiler this change is
for. The override must be reset to empty before the PR can merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Reset the CI override

Empties the override matrix again, so this PR runs the full CI before merging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Force-inline with _CCCL_FORCEINLINE so debuggers can still step in

_CCCL_TRIVIAL_HOST_DEVICE_API also marks a function nodebug and artificial, so a
debugger cannot step into it. The operators, polynomial evaluators and math functions
that this PR force-inlines are user-facing, so declare them
`_CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE` instead, as libcudacxx does elsewhere
(cuda/__stream/invalid_stream.h). The fpmp kernels behind them keep
_CCCL_FPMP_CORE_API.

The generated host code (GCC 15.2) and the sm_89 SASS are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Force-inline the scalar operators and helpers with _CCCL_FORCEINLINE too

Matches #12543: the scalar *= and /= and the __fpmp2_*_scalar helpers stay
force-inlined but are no longer nodebug or artificial, so a debugger can step in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Review: say why the operators and math functions are force-inlined

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Review: update the remaining instruction counts; check the low product against its bound

- fpmp.rst: with the shorter pair product, a converted scalar costs 8 instructions
  at mid, not 9.
- fp_optimization_guide.rst: multiplication is 8 at mid and 9 at high; the one-limb
  accumulate is 10 against 20 at high; mad is 16 against 19 at mid; fp64mp2 spends 8
  FP64 instructions per mid multiplication.
- fpmp_spec.rst: the SASS Instructions Summary table now matches the per-function
  tables (mul at def 8, acc at high 10, mad at high 28).
- scalar_ops.pass.cpp: x * y at low is checked against its documented 6u^2 bound, not
  the 5.0u^2 measured maximum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Review: force-inline the new kernels with _CCCL_FORCEINLINE too

__fpmp2_add_limb, __fpmp2_mul_limb, __fpmp2_high_mul, __fpmp2_div_limb and
__fpmp_mul_rn_nocontract were declared _CCCL_TRIVIAL_HOST_DEVICE_API, which also
marks them nodebug and artificial. They are now
_CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE, like the operators that call them, so
they stay force-inlined and a debugger can step into them.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Michael Schellenberger Costa <miscco@nvidia.com>
rmlarsen added a commit to rmlarsen/cccl that referenced this pull request Nov 13, 2026
NVIDIA#12543 and NVIDIA#12519 have landed and this branch now sits directly on main, so the
override that limited CI to the cuda/fp unit tests is emptied and the full PR matrix
runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
miscco pushed a commit that referenced this pull request Nov 13, 2026
… π/2 and erfc at large x (#12530)

* [fpmp] Use the single-limb kernels in the fp32mp2 math functions

Where a math kernel promoted a scalar that one limb holds exactly to a pair before an
add, subtract or multiply (__ffloat(c), __afloat(c), __ffloat(c, 0.0f), a double literal,
or a pair built from a float variable), pass the float scalar instead, so the operation
takes the single-limb kernel. Doublings x + x become x * 2.0f; cbrt and rcbrt square
their float seed with one TwoProd; erf forms 1 - 2^n with one Fast2Sum.

The products that now reach a single-limb add directly, n * C3 in the sin/cos/tan and
exp10 reductions and f * pi2, f * pi3 in sincospi, go through __fpmp_mul_rn_nocontract,
so a host compiler that contracts gives the same bits as before.

Results are bit-identical on the device (31 fp32mp2 functions, 2^18 inputs each, sm_89)
and on the host with -ffp-contract=off, and unchanged on the host under GCC's default
contraction. FP operations saved per call, estimated from the kernels' operation counts:
30 in the sin/cos/sincos/tan Cody-Waite reduction, 30 in exp10's reduction, 20 in
sincospi, 17 in atanh, 15 in erfc, about 13 in cbrt and 8 in rcbrt, 10 in log1p, expm1
and boys_f0, 9 in erf, 7 in log and tanh, and 1 to 6 in exp, exp2, asin, acos, sinh,
cosh and normcdfinv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Extend the trig Cody-Waite reduction to a four-piece pi/2 with an exact n*C3

Near a multiple of pi/2 the reduced argument r is far smaller than n*C3, and two errors
that are negligible elsewhere dominate it: rounding n*C3 to one float, up to
2^-24 * n*C3, and the 9.5e-23 by which C1 + C2 + C3 misses pi/2, which adds
n * 9.5e-23. For cos(28781.701171875 - 7.6e-5), with n = 18323, each contributes about
1.7e-18 to r, a relative error of 3.8e-7 in all.

Compute n*C3 exactly with two_mult_fma, add n*C4 (C4 = -0x1.cceba4p-74; the four pieces
miss pi/2 by 8.5e-32) to its low word with one fma, and subtract the pair. Relative
error against binary128 over 2^18 inputs per function, 30% of them near a multiple of
pi/2, on sm_89 (the host gives the same figures), in u^2 = 2^-48:

  function  median          99.9th percentile  max
  sin       0.149 -> 0.115  8.3e8 -> 52        2.3e11 -> 9.3e3
  cos       0.100 -> 0.077  8.5e8 -> 56        1.7e11 -> 8.5e3
  tan       0.608 -> 0.31   1.6e9 -> 64        2.8e11 -> 6.7e3

The last correction costs 12 more FP operations than the single-limb version; the
reduction path as a whole is 73 operations, against 91 before the single-limb kernels.
sincospi keeps the three-piece pi, since pi * f does not cancel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Correct erfc's third ln 2 term

erfc reduces -x^2 by i * ln 2 with ln 2 as the pair __ln2_hi plus
__LN2_LO = ln 2 - 6.9314718055994529e-1. The pair holds that double only to 48 bits and
misses ln 2 by +8.78e-17, so adding __LN2_LO (+2.32e-17) left the term's error at
1.11e-16 * |i| rather than reducing it. Use ln 2 minus the pair, -0x1.950d88p-54 (within
3e-24), as a float, multiplied by i once and subtracted with the single-limb kernel:
8 FP operations instead of 11.

The accuracy gain is below noise. erfc's error is dominated by the pair rounding of -x^2
and by the low-accuracy reduction at magnitude x^2, both about u^2 * x^2. Against
binary128 over 2^18 inputs (sm_89), the median relative error goes from 5.54 to 5.53 u^2,
the 99.9th percentile from 80 to 79.6 u^2, and the maximum stays at 212 u^2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Reduce erfc's exp(-x^2) from an exact x^2

erfc(x) = erfcx(a) * exp(-a^2) with a = |x|. The exponent was rounded to a pair,
-a^2 ~ xx, and reduced by i * ln 2 in low-accuracy pair arithmetic at magnitude a^2;
each step erred by about u^2 * a^2 in the exponent, which is the relative error of the
result. The correction meant to restore -a^2 - xx recomputed -a * a as the same pair,
so it was always zero.

Take a^2 exactly instead: a_hi^2 and 2 a_hi a_lo by two_mult_fma, plus a_lo^2. Split
ln 2 as L1 + L2 + L3 with L1 to 12 bits, so that i * L1 is exact for |i| <= 1091 and
-a_hi^2 - i * L1 is exact by Sterbenz's lemma, sum the remaining small terms as a pair,
and add the leading term last. The reduced argument then errs by about u^2, and the
always-zero correction is dropped, so the exponent path also takes fewer operations.

Relative error against binary128 over 2^18 inputs (sm_89), in u^2 = 2^-48: median
5.53 -> 1.33, 99.9th percentile 79.6 -> 57.9, maximum 212 -> 71.1. For x >= 2 the
largest error in each interval of width 0.5 drops from 17..212, growing with x^2, to
5..8. The remaining maximum, about 70, is at |x| < 0.5 and comes from the erfcx kernel;
it is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Correct boys_f0's expansion point in its comments

The 4 <= a < 11.46 branch evaluates its minimax polynomial in 3.46 - a (0x1.baf1a8p1);
its leading coefficient is F0(3.4605). The comments said 6.92 - a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Make the polynomial evaluators round alike on the host and the device

__fpmp_poly_horner_mixed's float phase and __fpmp_poly_horner_comp's Phase 0 computed
v * x_hi + c as a plain expression, which nvcc contracts into an FMA by default and a
host compiler may or may not; they now call __fpmp_fma_rn. horner_mixed's transition
step added the plain product v * x_hi to a pair through the single-limb kernel, and GCC
under its default -ffp-contract=fast fused it into that kernel's TwoSum; the product
now goes through __fpmp_mul_rn_nocontract.

Device results are bit-identical (31 fp32mp2 math functions, 2^18 inputs each, sm_89),
and no function differs from the device on the host more often than before. With GCC
15.2 -march=x86-64-v3 and default contraction, exp, exp2, exp10, expm1 and erf now agree
with the device bit for bit (exp10 differed in 25991 of 262144 results), erfc's
differences drop from 46035 to 6996, and normcdfinv's from 76727 to 21881. With
-ffp-contract=off, exp, exp2, exp10 and erf agree bit for bit, and erfc's differences
drop from 11947 to 4863.

Also correct horner_mixed's comments: its float phase takes M - 1 steps, not M.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Test the trig and erfc reductions where the reduced argument cancels

New math_reduction.pass.cpp checks fp32mp2 sin, cos, sincos and tan near a multiple of
pi/2, and erfc at large x, against binary128 references held as double pairs, to within
32u^2 (u = 2^-24), on the host and the device.

The inputs come from a sweep of 2^18 arguments per function. On them the earlier
reductions erred by 1.8e9 to 2.1e10 u^2 (trig) and 94 to 113 u^2 (erfc); the current
ones err by at most 6u^2 on sm_89 and on the host, with and without -ffp-contract=off.
The test aborts on the earlier headers. One extra cos case is the example from the
reduction commit, cos(28781.701171875 - 7.6e-5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] CI override: run the cuda/fp unit tests on sm_75 and sm_90 [reset before merging]

This PR changes only the fpmp headers and their tests, so limit its CI to the cuda/fp
lit suite on a T4 (gcc and clang, CTK 12.X and 13.X) and an H100 (gcc, CTK 13.X), plus
an MSVC precompile of the same suite. The override must be reset to empty before the
PR can merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] Keep the trig reduction's pair adds on sm_90

The single-limb `(s, e) - pl` and `+ x_lo` of the Cody-Waite reduction return the same
values as the pair adds they replaced, in fewer operations, and make sin, cos, sincos
and tan 5-10% faster on sm_100 and sm_120. On sm_90, nvcc schedules them worse, with
the same register use and occupancy: on an H100, sin and cos ran at 0.96x and 0.97x of
#12519, which is 0.97x and 0.98x of main.

sm_90 now keeps the pair adds (NV_IF_ELSE_TARGET(NV_IS_EXACTLY_SM_90, ...)); every other
target, the host included, compiles the same code as before. On an H100 (8 chains per
thread, median of 15 interleaved launches), over #12519:

  sin     0.963 -> 0.996      sincos  0.989 -> 1.011
  cos     0.966 -> 1.001      tan     1.043 -> 1.003

Against main, sin and cos are now 1.006x and 1.013x, and tan stays 1.029x faster. An
H200 measures the same. Results are bit-identical to before on sm_89, sm_90 and sm_120
over 2^18 inputs per function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [fpmp] CI override: name CTK 12.X's compilers

The override paired CTK 12.X with the bare 'gcc', 'clang' and 'msvc', which
expand to GCC 16, Clang 22 and MSVC 14.50. CTK 12.9 supports none of them, so
those three jobs failed before building anything. CTK 12.X now runs with gcc14,
clang19 and msvc2022, as the weekly matrix does.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>

* [fpmp] Reset the CI override before merging

#12543 and #12519 have landed and this branch now sits directly on main, so the
override that limited CI to the cuda/fp unit tests is emptied and the full PR matrix
runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants