Skip to content

[fp] fp32mp2 math: native float-float implementations to 45-48 bits, exact pair rounding and remainder, C99 special values - #12641

Draft
akolesov-nvidia wants to merge 24 commits into
NVIDIA:mainfrom
akolesov-nvidia:dev/akolesov/fp32mp2_math_update
Draft

akolesov-nvidia wants to merge 24 commits into
NVIDIA:mainfrom
akolesov-nvidia:dev/akolesov/fp32mp2_math_update

Conversation

@akolesov-nvidia

Copy link
Copy Markdown
Contributor

Summary

The fp32mp2 math functions are reimplemented in float-float arithmetic, with no FP64 operation on the fast paths. On the normal dataset they reach 45-47 correct bits (of 48) where the previous implementations delivered 41-45, and on the pattern dataset (powers of two, extreme exponents, special structure) the trigonometric functions go from 25-28 bits to 44-45. Functions that were double or binary128 fallbacks become native (erfcx, normcdf, erfinv, erfcinv, hypot, rhypot, 5-30x the throughput), the rounding and remainder families become exact on the whole pair, and the functions return the C99 Annex F values for infinities, NaN and arguments outside their domain.

Highlights. Geomean throughput gain over the 40 changed fp32mp2 cells: 1.58x. normcdf 30x, hypot 13.7x, rhypot 10.6x, erfcinv 10x, erfcx 7.5x, erfinv 4.9x, frexp 2.7x, rcbrt 1.4x, pow 1.4x, exp10 1.35x, sinh 1.2x, atan 1.16x; sin, cos and tan match main's throughput at 5 more bits (41 to 45-46), and the rest of the math is 0.95-1.2x at 1-3 more bits. normcdfinv (0.92x) carries its new domain checks. The exact fpmp2_accuracy::high level of fmod and remainder is new (on main that level ran the default code), so it has no comparison. sqrt gains 2 bits and is 1.13x faster; rsqrt is unchanged.

The table compares this branch with main (778da2d, with #12519, #12530 and #12543) on an RTX A6000 (sm_86, CUDA 13.4), measured with the cccl-fpdev suite at the default accuracy level. All rows are fp32mp2 at the default level. Columns: correct bits on the normal and the pattern dataset; throughput in evaluations per clock per SM (one run each on the same GPU in the same order; run-to-run noise on this setup measured at 3.6% or less); SASS instructions of the function's own code (cuobjdump --dump-sass of the bare function compiled with -O2 for sm_86, out-of-line helpers included, CUDA's division and square-root slow-path subroutines excluded); ptxas registers. A dash marks a level that had no implementation of its own on main.

function level bits old (normal / pattern) bits new (normal / pattern) ev/clk/SM old ev/clk/SM new gain SASS old SASS new regs old / new
acos def 44 / 44 46 / 46 0.46 0.48 1.04x 472 464 21 / 21
acosh def 43 / 43 45 / 45 0.42 0.44 1.05x 544 504 16 / 17
asin def 44 / 44 46 / 46 0.43 0.42 0.98x 440 448 21 / 21
asinh def 43 / 43 45 / 45 0.40 0.43 1.07x 552 512 16 / 17
atan def 45 / 45 47 / 47 0.37 0.43 1.16x 280 248 22 / 20
atan2 def 45 / 0 46 / 22 0.33 0.38 1.15x 360 312 25 / 22
atanh def 44 / 44 46 / 46 0.53 0.55 1.04x 496 464 16 / 16
boys_f0 def 43 / 43 44 / 44 0.36 0.44 1.22x 752 824 21 / 21
cbrt def 45 / 44 47 / 47 1.19 1.32 1.11x 104 96 21 / 20
cos def 41 / 31 46 / 45 0.48 0.48 1.00x 448 592 25 / 34
cosh def 43 / 40 46 / 46 0.63 0.64 1.02x 184 176 17 / 18
cospi def 41 / 41 46 / 45 0.45 0.47 1.04x 320 304 23 / 21
erfc def 41 / 23 45 / 23 0.18 0.20 1.11x 528 672 32 / 24
erfcinv def 48 / 48 46 / 46 0.04 0.40 10.00x 408 (+3 slow-path calls) 2928 (+6 slow-path calls) 23 / 33
erfcx def 48 / 26 45 / 23 0.04 0.30 7.50x 216 (+1 slow-path calls) 1320 18 / 28
erfinv def 48 / 24 46 / 23 0.13 0.64 4.92x 232 (+1 slow-path calls) 2912 (+6 slow-path calls) 20 / 32
exp def 43 / 25 46 / 33 0.73 0.82 1.12x 152 144 20 / 18
exp10 def 45 / 30 46 / 30 0.82 1.11 1.35x 200 144 14 / 16
exp2 def 44 / 25 46 / 33 0.75 0.92 1.23x 152 128 16 / 16
expm1 def 46 / 40 46 / 45 0.63 0.70 1.11x 312 272 17 / 20
fmod def 48 / 48 48 / 48 2.92 2.92 1.00x 288 (+2 slow-path calls) 296 (+2 slow-path calls) 22 / 22
frexp def 48 / 48 48 / 48 0.99 2.66 2.69x 56 48 10 / 9
hypot def 48 / 29 46 / 29 0.12 1.64 13.67x 160 88 18 / 14
log def 44 / 44 45 / 45 0.78 0.83 1.06x 152 152 16 / 18
log10 def 44 / 44 44 / 45 0.72 0.80 1.11x 160 152 16 / 16
log1p def 43 / 43 45 / 45 0.61 0.66 1.08x 328 280 16 / 16
log2 def 44 / 44 45 / 44 0.73 0.82 1.12x 160 152 16 / 16
normcdf def 48 / 33 45 / 23 0.02 0.60 30.00x 192 728 34 / 24
normcdfinv def 41 / 41 45 / 45 0.25 0.23 0.92x 736 840 20 / 19
pow def 41 / 28 46 / 36 0.32 0.44 1.38x 352 328 17 / 27
rcbrt def 46 / 46 47 / 47 0.97 1.36 1.40x 136 104 18 / 15
remainder def 48 / 48 48 / 48 2.25 2.25 1.00x 352 (+2 slow-path calls) 360 (+2 slow-path calls) 26 / 26
rhypot def 48 / 23 44 / 22 0.15 1.59 10.60x 160 96 18 / 16
round def 48 / 48 48 / 48 3.33 3.61 1.08x 112 88 10 / 9
sin def 41 / 33 46 / 45 0.49 0.48 0.98x 440 584 25 / 34
sinh def 43 / 40 46 / 46 0.63 0.76 1.21x 320 288 17 / 18
sinpi def 41 / 23 46 / 25 0.43 0.45 1.05x 328 360 26 / 24
sqrt def 44 / 25 46 / 25 5.22 5.92 1.13x 32 32 12 / 8
tan def 41 / 31 45 / 44 0.44 0.43 0.98x 456 600 25 / 34
tanh def 45 / 45 46 / 46 0.60 0.57 0.95x 336 328 17 / 18
fmod high - 48 / 48 - 3.10 - - 1096 (+2 slow-path calls) - / 32
remainder high - 48 / 48 - 1.98 - - 2152 (+2 slow-path calls) - / 33

Cells not listed are unchanged in bits and SASS size, with throughput within noise (8%). The pattern dataset is sign-sensitive in places where fpmp2 does not track the sign of zero (documented in this PR), which is what the low pattern bits of atan2, sinpi, erfc and the fallback-replacing functions reflect on both sides; their normal-dataset bits are the meaningful comparison. erfinv and erfcinv call a shared inverse-CDF kernel, which is why their SASS counts grow while the throughput rises 5-10x.

The high rows of fmod and remainder are a new level rather than a replacement: on main the high level ran the default code, so the old-side cells are left empty. On dense pairs the exact path costs remainder 1.98 against the default level's 2.25 evaluations per clock per SM (its wide-reduction block is present whether or not it is taken, 26 to 33 registers); fmod high runs at 3.10 against 2.92. What the level buys shows with sparse pairs in the dataset (one pair in 1024 with its tail 1-36 binades below the head):

cell main 778da2d this branch
fmod high, sparse pairs 0 bits, FAIL (the high level ran the default code) 48 bits
remainder high, sparse pairs 0 bits, FAIL 48 bits
fmod / remainder def, sparse pairs 48 bits dense, sparse operands reduced as their 53-bit rounding (by contract, a warning) same

What changes, per area

  • Exponentials and logarithms. exp to 46 bits with gradual underflow; exp2, exp10, expm1, log, log2, log1p to 45-46 bits (log10 stays at 44 on the normal dataset, 45 on the pattern dataset). The exp kernel is split so that erfcx and the other users scale the result themselves.
  • Trigonometric and inverse trigonometric. sin, cos, tan, sincos, sinpi, cospi to 45-46 bits; asin, acos, atan, atan2 to 46-47 bits. The argument reduction keeps [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530's fast-path accumulation in fp32mp2_high, which is what holds the error at a few u² within 2^-40 of a multiple of pi/2 (math_reduction.pass.cpp); the Payne-Hanek path and the kernels are new.
  • Hyperbolic. sinh, tanh, atanh to 46 bits and asinh, acosh to 45, with the overflow and domain fixes; cosh uses the clamped 0.5/e helper from [fpmp] Faster, more accurate fpmp2 arithmetic with single-limb scalars and a shorter pair product #12519.
  • Powers, roots, distances. pow is the triple-float implementation of [fpmp] Accurate, C99-conforming and faster fp32mp2 pow #12548 (a triple-float ln|a| and a table-driven exp), taken verbatim with its unit test so that the two branches do not diverge; cbrt, rcbrt to 47 bits; native fp32mp2 hypot (46 bits) and rhypot (44 bits, through rsqrt) instead of the FP64 wrappers.
  • Error and probability functions. erfc and normcdfinv to 45 bits, boys_f0 to 44; native erfcx, normcdf, erfinv, erfcinv instead of the double and binary128 fallbacks (5-30x the throughput, at 45-46 bits where the fallbacks read 48).
  • Rounding, remainder and conversions. ceil, floor, trunc, round, rint, nearbyint, fdim, remquo, lrint, lround, llrint, llround are exact on the pair (the previous round misrounded half-way pairs such as (9969168, 0.49999997)); fmod, remainder and remquo are exact on sparse pairs (a tail far below the head) in fpmp2_accuracy::high, and compute on the pair rounded to 53 bits in the other levels, as before; the default level is unchanged in code path, accuracy and throughput.
  • Classification. isfinite, isinf, isnan, signbit and the fpmp_ spellings look at both limbs.
  • sqrt. A new form for fp32mp2, 46 bits at 1.13x the throughput; rsqrt is unchanged. Both take no branch for special arguments, which is now documented.
  • Special values. log, log2, log10, atan, the six rounding functions, normcdfinv and boys_f0 return the C99 Annex F values at zero, negative arguments, the infinities and NaN, through branch-free selects (cost measured at 0-7% on the affected functions). The sign of a zero result is not tracked by fpmp2, documented next to the sqrt/rsqrt note.
  • Kernels take scalars as scalars (x * 2.0f rather than x * fp32mp2(2.0f)), so the single-limb operator overloads of [fpmp] Faster, more accurate fpmp2 arithmetic with single-limb scalars and a shorter pair product #12519 apply inside the math as well.

Tests

Validation

With the cccl-fpdev suite against this branch: unit matrix (host, device, NVRTC, each at C++17 and C++20) all passing; the CI accuracy set (171 cells, pattern dataset, all levels) passes with 0 FAIL and 103 OK / 68 WARN cells, against 86 OK / 85 WARN on main (the warnings are the by-design special-input classes); the special-value probe (fp32mp2, 41 functions x 19 arguments, host and device) reports no failure, only the documented warnings (sign of zero, sqrt/rsqrt, subnormal results).

akolesov-nvidia and others added 22 commits November 13, 2026 21:35
exp: 41 -> 46 bits on the working range, 43/41 -> 46 on the normal and
special datasets, same throughput, latency 328 -> 308 clk/ev. Results
below FLT_MIN now underflow to subnormals instead of zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
exp2: 41 -> 46 bits. exp10: 44 -> 46 bits, +37 % throughput.
log, log1p, log2, log10: 43-44 -> 45 bits near 1, 45 -> 46-48
elsewhere, 10-26 % faster.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bits on the work/normal/special/pattern datasets (L4):
sin 37/41/41/25 -> 46/46/46/45, cos 38/41/41/28 -> 46/46/46/45,
tan 37/41/41/25 -> 45/45/45/44, sinpi 41/41/41/23 -> 46/46/46/25,
cospi 41/41/41/41 -> 46/46/46/45; throughput +8-26 %. The exp/log
kernels give bit-identical results at 10-23 % more throughput.

Fixes: atan2 returned 0 for operands above 2^126, atan(+-inf) returned
NaN instead of +-pi/2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
asin, acos: 44 -> 46 bits. atan: 45 -> 47 bits, +17 % throughput.
atan2: 45 -> 46 bits, +11 %. sqrt: fp32mp2 9.5 -> 2.8 u^2 (44 -> 46
bits), now the same on host and device. The error-bounds page is
updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fp64mp2 sqrt returns to its original form (106 bits, 25 % faster than
the previous commit's). fp32mp2 rsqrt: 44 -> 47 bits (13.4 -> 2.0 u^2),
the same on host and device, at 2.12 instead of 4.97 ev/clk/SM on the
L4. The error-bounds page is updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
No functional change: exp, expm1, sinh, cosh, tanh and the log family
give identical SASS, accuracy and throughput.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pow: normal dataset 41 -> 44 bits, special 39 -> 41, work 30 -> 33,
pattern 27 -> 32 (the last three limited by results below 2^-102).
Throughput 0.30 -> 0.25 ev/clk/SM, accepted for the accuracy gain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cbrt: 45 -> 47 bits, +17 % throughput. rcbrt: 46 -> 47 bits, +58 %
throughput. hypot, rhypot: native fp32mp2 implementations instead of
the double fallback, 46 bits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sinh: 45-46 -> 46 bits (4.4 -> 3.6 u^2). tanh: 45 -> 46 bits (6.9 ->
3.1 u^2). atanh: 44-45 -> 46 bits (8.6 -> 3.6 u^2), throughput 0.53 ->
0.44 ev/clk/SM.

Fixes: sinh and cosh returned NaN above |x| = 88.72, now +-inf; atanh
returned NaN for arguments just inside +-1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…air; round fix

fdim, remquo and the four integer roundings no longer go through
double: exact on the limb pair (remquo over remainder's reduction, the
roundings over rint / round). The predicates, copysign, frexp, modf,
ilogb and logb test the float limbs instead of widening to double.

Fix: round was off by one on integer-valued pairs with |lo| >= 2^23;
now 0 error on the ts, 190 -> 166 clk/ev.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
isfinite, isinf, isnan and signbit classify both limbs and combine the
answers by logic alone, so a loose pair with an infinity or a NaN in lo,
or (+inf, -inf), is reported correctly; no arithmetic involved. The
function list no longer labels them as placeholders.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mcdf, erfinv, erfcinv

erfc: 40-41 -> 45 bits (136 -> 4.8 u^2). boys_f0: 43 -> 46 bits, +27 %
throughput. normcdfinv: 41 -> 45 bits at the old size and throughput.
erfcx, normcdf, erfinv, erfcinv: native fp32mp2 implementations instead
of the device-only double fallbacks, 45-46 bits, 1.3-3x lower latency.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
normcdfinv(1 - 2^-53), (1 - 2^-40) and 2^-40 against CUDA's double
normcdfinv, and +-inf at 1 and 0.
… fpmp2_accuracy::high

The remainder family of the former dev/akolesov/fp32mp2_remainder_fix line, replayed
as one commit on the rebase onto main (its three commits built on an older base of
the math line and no longer apply one by one):

- fpmp: fp32mp2 fmod exact on sparse pairs; remainder exact in high (3ee7818)
- fpmp: fp32mp2 fmod exact in fpmp2_accuracy::high only (0652edc)
- fpmp: fp32mp2 remquo on remainder's reduction, exact in fpmp2_accuracy::high (35e2622)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Twenty expression sites in exp, the hyperbolic functions, the special functions and
asin/acos promoted a float that one limb holds exactly to a pair first (__ffloat(0.5f),
__ffloat(1.0f), __ffloat(__y0), ...) or doubled a pair as x + x. They now pass the scalar
to the mixed-type operators, or multiply by 2.0f. Today the operators promote the scalar
themselves, so values and code are unchanged (verified: identical maximum errors on the
accuracy suite); once NVIDIA/cccl PR NVIDIA#12519 lands, the same sites take its single-limb
kernels (6 operations and a 2u^2 bound for the product), measured at 1.053x throughput
over the fp32mp2 math set. Coefficient tables and pairs built from two limbs are unchanged.

(cherry picked from commit 47c0752)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rounding functions, normcdfinv and boys_f0

After NVIDIA/cccl issue NVIDIA#12550. Wrong results for special arguments, now C99 Annex F:
- log, log2, log10 returned finite numbers for x <= 0, +inf and NaN (log(-2) = 0.693,
  log(0) = -104.7, log(+inf) = 88.7): +-0 -> -inf, +inf -> +inf, negative or NaN -> NaN.
- atan turned NaN into pi/2 in its reciprocal range: NaN propagates.
- floor, ceil, trunc, round, rint returned NaN for +-inf (inf - inf in the lo-limb accumulate):
  the infinity itself.
- normcdfinv returned +-inf outside [0, 1]: NaN; exactly 0 and 1 still give -inf and +inf.
- boys_f0 returned NaN below about -94, where F0 exceeds FLT_MAX: +inf.

All guards are branch-free: the core runs and a few selects pick the special value afterwards
(a uniform branch to a cold path cost two to three times more in these throughput-bound kernels,
its convergence barrier more than its comparisons). Measured on an RTX A6000 against the
previous tip: atan, floor, ceil, trunc, round within 1%, boys_f0 -2%, rint/nearbyint -3%,
log/log2 -3.5%, normcdfinv -4.5%, log10 -7%; bit counts unchanged on every cell. sqrt and rsqrt
are deliberately left without special-value handling (see the docs); signed-zero results of the
odd functions are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
State what the two core-header roots do for zero, infinity, negative, NaN and (on the device)
subnormal arguments, and the scaling workaround, since they are deliberately kept free of the
special-value handling the math functions have.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 806124c)
Adds libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/math_special_values.pass.cpp,
which checks the class and the sign of the result at the arguments of C99 Annex F:
zero, a negative argument, the largest finite values, the infinities and NaN, for
log/log2/log10, atan, the six rounding functions (fp32mp2 and fp64mp2), normcdfinv
and boys_f0. sqrt and rsqrt are left out (documented behavior), and so is the sign of
a zero result. The test runs on the host and in a kernel through force_include.h and
fails on the code before the special-value fixes (log(0) was the first assertion to
trip).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Documents, next to the sqrt/rsqrt note, that fpmp2 arithmetic and math do not track
the sign of zero: renormalization adds the two limbs, so every zero result of +, -, *
and fma is +0 even where IEEE 754 gives -0, while a zero from / or from a math
function carries whichever sign its evaluation leaves (and fp32mp2 floor(-0) differs
between host and device). Only negation, fabs, copysign and construction from a
built-in -0.0 keep the sign. Measured on host and device for fp32mp2 and fp64mp2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…2548

Replaces the pow section of fpmp_math_impl_pow.h with the one from NVIDIA/cccl
PR NVIDIA#12548 at 6490471 (Rasmus Munk Larsen: a triple-float ln|a| and a
table-driven exp, tables with hidden visibility in host code), byte for byte, and
adds its unit test math_pow.pass.cpp. The cbrt, rcbrt, hypot and norm code of
this header stays as it is on this branch.

Measured on an RTX A6000 with the cccl-fpdev suite, fp32mp2 pow at the default
level, this implementation against the one it replaces: work 34 vs 33 bits,
normal 46 vs 44, special 46 vs 41, pattern 36 vs 32 (warnings 6e-04% vs 16.85%),
and 1.45x the throughput (0.45 vs 0.31 ev/clk/SM, 422 vs 672 clk/ev). cbrt,
rcbrt, hypot and rhypot unchanged.

When NVIDIA#12548 lands upstream, its version of the pow section is the one to keep on
the next rebase of this branch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The new fp32mp2 rsqrt form of this branch read 47 bits instead of 44 but at 0.44x the
throughput (2.30 vs 5.28 evaluations per clock per SM on an RTX A6000), too high a price
for three bits in a core operation. __fpmp2_rsqrt goes back to the form on main; the new
sqrt form (46 bits, 1.14x) stays. The special-argument behavior documented in fpmp.rst is
the same for both forms (NaN for +inf, a negative argument, NaN and, on the device, a
subnormal; rsqrt(0) is NaN).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…A#12530

Our Cody-Waite fast path (|x| < 2^20) folded the second- and third-order terms with
error-free sums but closed with float additions whose rounding is relative to those
terms, about 2^-79 in absolute value; at an argument within 2^-40 of a multiple of
pi/2 that is 300 u^2 relative to the reduced argument (main's math_reduction test, sin
of (0x1.b751ep+16, 0x1.a9937cp-12)). NVIDIA#12530 accumulates the corrections in
fp32mp2_high, whose error is relative to the running result, and passes its test at
<= 6 u^2. This takes that block as it is on main; the Payne-Hanek slow path and the
sin/cos/tan kernels stay ours. Measured on an RTX A6000: bits unchanged (sin 46/45,
cos 46/45, tan 45/44 on normal/pattern), sin and cos 0.49 instead of 0.54 and tan 0.45
instead of 0.48 evaluations per clock per SM.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

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

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-project-automation github-project-automation Bot moved this to Todo in CCCL Nov 13, 2026
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Progress in CCCL Nov 13, 2026
Two lines the pre-commit.ci clang-format check reformats: the nested conditional in
__internal_fpmp2_log_special_value and an assignment alignment in normcdfinv.

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

This branch has not been deployed

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

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant