You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[fpmp] Force-inline the fpmp2 operators for faster fp32mp2 math on the CPU - #12543
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"):
Largest gains. Out of line on the Cortex-X925, where GCC 13 called the operators out of line in most math functions: atanh 3.8×, sin, cos and cospi 3.7×, sinh 3.4×, expm1 3.3× and pow 3.1×. On Zen 4: tanh, sinh and expm1 1.55×, pow 1.53×. On the Pi (over [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530), pow 1.76×, atanh 1.65×, acosh 1.47× and tanh 1.41×.
Out of line, no function is slower than 0.98× on any of these platforms.
Loops that call the math functions. Force-inlining the operators alone made the math functions look larger to GCC, which then stopped inlining exp, log, log2, atan and cosh into the loops that call them, where main inlines and vectorizes them: y[i] = cuda::log(x[i]) over an array took 22 ns per element instead of 2.2 on Zen 4. The public math functions and their cuda::std forwarders are therefore force-inlined too, and such loops are now as fast as on main or faster, within 3% (atan on Zen 4):
On the hosts the values are the same, and host/device agreement is unchanged for every function.
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
Bit-exact comparisons. On top of [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530, the 31 fp32mp2 math functions × 2¹⁸ inputs were bit-identical on sm_89 and value-identical on the x86-64 and Pi hosts, as described under Special values. Force-inlining the public functions changes the bits of four special-input results on the hosts, not their values: one erfc result on Zen 4 (GCC 15.2), three cos and one normcdfinv result on the Cortex-X925 (GCC 13.3).
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.
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
… 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>
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
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>
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.
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.
@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.
@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.
@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.
…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>
… π/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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Results, compared with
mainThis PR force-inlines the
fpmp2arithmetic 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 thefp32mp2math functions or in their callers. It is based onmainand lands first: #12519, #12530 and #12548 are stacked on it.Performance. Speedup (old time / new time), as the geometric mean over 30
fp32mp2math functions, with each function called through one non-inlined call ("out of line") or inlined into the benchmark loop ("inlined"):-O3 -mcpu=neoverse-v2main-O3 -march=nativemain-O3 -mcpu=cortex-a76-O3 -march=x86-64-v3-O3 -mcpu=apple-m4mainThe rows. The DGX Spark and Zen 4 rows measure this PR on
main, with [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530's second harness. The Pi, i5-11500T and M4 rows were measured earlier, when this PR sat on top of [fpmp] Faster, more accurate fpmp2 arithmetic with single-limb scalars and a shorter pair product #12519 and [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530 and did not yet force-inline the public math functions; "inlined" there is [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530's own harness.Largest gains. Out of line on the Cortex-X925, where GCC 13 called the operators out of line in most math functions: atanh 3.8×, sin, cos and cospi 3.7×, sinh 3.4×, expm1 3.3× and pow 3.1×. On Zen 4: tanh, sinh and expm1 1.55×, pow 1.53×. On the Pi (over [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530), pow 1.76×, atanh 1.65×, acosh 1.47× and tanh 1.41×.
Out of line, no function is slower than 0.98× on any of these platforms.
Why it lands first. Without it, [fpmp] Faster, more accurate fpmp2 arithmetic with single-limb scalars and a shorter pair product #12519 and [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530 make some functions slower on aarch64 with GCC, which calls their new operators out of line: tanh ran at 0.73× of
mainon the Pi, and at 0.64× on the Cortex-X925 (out of line), with sinh and expm1 at 0.79–0.85×. With this PR underneath, no function in [fpmp] Faster, more accurate fpmp2 arithmetic with single-limb scalars and a shorter pair product #12519 or [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530 is slower than 0.97× out of line, on the Cortex-X925 or on Zen 4.Loops that call the math functions. Force-inlining the operators alone made the math functions look larger to GCC, which then stopped inlining exp, log, log2, atan and cosh into the loops that call them, where
maininlines and vectorizes them:y[i] = cuda::log(x[i])over an array took 22 ns per element instead of 2.2 on Zen 4. The public math functions and theircuda::stdforwarders are therefore force-inlined too, and such loops are now as fast as onmainor faster, within 3% (atan on Zen 4):y[i] = f(x[i]), 4096 valuesmainsin, tanh, expm1, atanh and pow are faster than on
mainin the same loops, on both CPUs.Still slower: a math function inside the caller's own non-inline helper, as in the "inlined" mode of [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530's second harness. GCC sizes the helper by the fully expanded function and may not inline it into the loop, which then is not vectorized across its chains: atan and cosh run at 0.23–0.24× of
mainthere on Zen 4 (and log at 0.26× with [fpmp] Faster, more accurate fpmp2 arithmetic with single-limb scalars and a shorter pair product #12519 on top), and log at 0.38× on the Cortex-X925. Force-inlining the helper, or calling the function in the loop directly, avoids it. With [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530's harness, the Pi showed sin and cosh at 0.85× and 0.84× of [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530 for the same reason, and the i5-11500T sin, cospi and cosh at 0.88–0.93×.Accuracy. Unchanged.
main's (sm_89 and sm_90; measured on top of [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530 also on sm_100, sm_103, sm_107, sm_120 and sm_121), so device results are bit-identical.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:
-ffp-contract=off);There, inlining lets GCC contract differently.
Implementation
The
fpmp2arithmetic and comparison operators,add/sub/mul/div, the__fpmp2_*_scalarhelpers and the three polynomial evaluators do nothing but dispatch to kernels that are already force-inlined (_CCCL_FPMP_CORE_API). They were plaininlinefunctions, though, and GCC 14 for aarch64 calls several of them out of line in thefp32mp2math functions:low-accuracy pair product and quotient;floatproduct;__fpmp_poly_horner_mixedinstances.This PR declares those functions in
fpmp.h(39 onmain, 44 once #12519 adds its scalar operators), and the evaluators infpmp_math_impl.h,_CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE, as libcudacxx does incuda/__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, thevolatilemembers and the named functions offpmp.h(sqrt,fma, ...) are unchanged. CUB already force-inlinesfriendoperators the same way (cub/detail/null_type.cuh).It also declares the 90 public math functions of
fpmp_math.hand their 13cuda::stdforwarders_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 offpmp.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
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
mainon the DGX Spark's Cortex-X925 and on Zen 4: out of line / inlinedTesting
main's on sm_89 and sm_90, because nvcc already inlined all of these functions. On top of [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530, it was also identical on sm_100, sm_103, sm_107, sm_120 and sm_121 (CUDA 13.4).fp32mp2math functions × 2¹⁸ inputs were bit-identical on sm_89 and value-identical on the x86-64 and Pi hosts, as described under Special values. Force-inlining the public functions changes the bits of four special-input results on the hosts, not their values: one erfc result on Zen 4 (GCC 15.2), three cos and one normcdfinv result on the Cortex-X925 (GCC 13.3).cuda/fp/unitssuite passes on sm_89 onmainwith this PR (53 passed;fpemu/float64andfpmp/atomic_ddunsupported), with CUDA 13.4 and GCC 15.2. On top of [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530 it passed on sm_89 (56 passed) and on sm_120, where onlyfpemu/charbool.pass.cppfails, as onmain.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/unitslit suite on a T4 and an H100, MSVC and arm64 precompiles) is reset by 627abd3, so this PR now runs the full CI.Checklist
🤖 Generated with Claude Code