Repository navigation
Conversation
Contributor
This was referenced Nov 13, 2026
rmlarsen
force-pushed
the
fpmp-cbrt-host-seed
branch
from
November 13, 2026 18:27
653ded7 to
dc58109
Compare
Contributor
Author
|
/ok to test dc58109 |
rmlarsen
added a commit
to rmlarsen/cccl
that referenced
this pull request
Nov 13, 2026
…n kernel, and handle their special values All four find the angle of a point (M, m), 0 <= m <= M, from a table of atan(i/16): atan(m/M) = atan(c) + atan(t), c = i/16, t = (m - c M) / (M + c m), with i chosen so that -1/64 <= t <= 3/64 and the leading words of m and c M cancel exactly. The octant is folded into the table (four rows), and a 4-coefficient kernel replaces the 13- and 19-term compensated Horner evaluations. atan and atan2 take t from one pair division. asin and acos enter as the unit-circle point (x, sqrt((1 - |x|)(1 + |x|))), where the division becomes a product by a tabulated cos(atan(c)) and the kernel is asin; for small x that is x itself, so asin(x) does not depend on the square root there. Special values follow C99 Annex F and are answered on entry. asin(-0) and atan(-0) are -0 (were +0); atan(+-inf) is +-pi/2 (was NaN); atan2 takes the sign of a zero y from its sign bit (atan2(-0, x) was +0 or +pi), accepts subnormal operands (was NaN) and scales its operands by a common power of two, so that tiny or, on the device, huge operands keep full accuracy (the relative error reached 1); asin and acos of a pair just outside [-1, 1] by its low limb are NaN. NaN results carry NaN in both limbs. fp64mp2: asin(+-0), atan(+-0) and atan2(+-0, x) keep the sign of zero, and asin and acos of |x| >= 1/2 form 1 - |x| from the limbs instead of summing them in binary128 first, which lost a low limb beyond its 113 bits: acos((1, -1e-30)) was off by 2e-5 and acos((1, -denorm_min)) was 0; both are now within 2^-106. Against the head of NVIDIA#12568, host, time per call: asin 1.6-2.2x, acos 1.9-2.1x, atan 2.9x and atan2 3.3x faster on x86-64 (2.4x in the geometric mean), 1.6-2.2x on a Cortex-A76 (1.9x). fp32 SASS instructions on sm_89: asin 420 -> 94, acos 432 -> 95, atan 264 -> 76, atan2 287 -> 86. Largest error against MPFR in units of u^2 = 2^-48, host / sm_89 device: asin 6.2 / 6.2 (was 10.9 / 14.1), acos 7.2 / 7.0 (6.2 / 9.6), atan 4.1 / 4.1 (6.4 / 6.9), atan2 5.9 / 5.9 (6.1 on moderate ranges). asin on |x| < 0.5 is less accurate than before (6.2 against 2.2). Host results are the same bits with GCC and Clang, with and without FMA, on x86-64, Cortex-A76 and Apple M4; the host takes the square root from sqrtf and the device from its approximate reciprocal square root, so about 9% of asin and acos results differ between host and device in the last bits. New test fpmp/math_invtrig.pass.cpp; the special-value tables of the four functions in fpmp_spec.rst are regenerated. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…set 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>
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>
…en exp pow(a, b) = exp(b ln|a|), and exp turns an absolute error in its argument into the same relative error in its result. ln|a| and b ln|a| were rounded to fp32mp2's own precision, so the error grew with |b ln|a||: up to 767u^2 (u = 2^-24) at 88. The special values also departed from C99 Annex F: -0 lost its sign, -inf with a non-integer exponent and +-FLT_MAX^+-FLT_MAX gave NaN, integer exponents above 2^24 were not recognized when the integer lived in the lo limb, and results below FLT_MIN flushed to zero. ln|a| is now formed to about 2^-58 as three floats: |a| = 2^k z, a 128-entry table of 1/c (at most 9 bits, so that fma(z_hi, 1/c, -1) is exact) and ln c, with c = 1 for the bucket around 1, and ln(1 + r) with r exact as three floats. b ln|a| comes from three exact products, is reduced by ln2/128, and exp uses a 128-entry table of 2^(j/128) and a degree-5 polynomial. The result is scaled in two steps so that it underflows gradually. Special values are handled explicitly before the main path, and overflow and underflow are decided from b_hi * ln|a| before the products, so no intermediate is infinite or NaN. Against binary128 the error is now at most 2.9u^2 (mean 0.29u^2) at every magnitude of b ln|a|, and all 685 special-value cases match glibc's pow. Results are bit-identical on the host and the device. pow is 2.4-3.8x faster on the tested CPUs and 1.5x faster on an RTX 4070 Laptop GPU when the inputs are read coalesced; where every input is itself a scattered gather, the two table lookups compete for L1 and it is about 15% slower. The pow special-values table of fpmp_spec.rst is regenerated; its accuracy and performance rows still describe the previous implementation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l bases The pow lookup tables were inline variables with default visibility, so a shared library that calls cuda::pow exported both as unique global symbols, and two libraries built against different versions of the tables would read one copy. They are now hidden, like the functions that read them. math_pow.pass.cpp gains two accuracy cases on the scaled path of ln|a|, a subnormal base and a tiny base whose lo limb is subnormal, and includes <cuda/std/limits> for numeric_limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The special-value test compared only the hi limb where pow returns exactly 1 or -1, such as pow(NaN, 0), pow(1, NaN) and pow(-1, +-inf), so a nonzero lo limb would have changed the value hi + lo and still passed. Those results must now have a zero lo limb, like the zero, infinite and subnormal ones. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
In a device pass without RDC, _CCCL_GLOBAL_CONSTANT has internal linkage, and nvcc with a Clang host rejects a visibility attribute there (error NVIDIA#1407-D), so every cuda/fp test failed to compile with CTK 13.4 and Clang 22. The tables now take _CCCL_VISIBILITY_HIDDEN outside the device pass only, which is where a shared library would export them. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
On a host whose fmaf is a hardware instruction, the single-precision seed of the cbrt / rcbrt Halley step is now a minimax polynomial in the reduced argument r in [1/4, 2), refined in single precision: one Halley step with an fma residual for cbrt, two division-free steps for rcbrt. It replaces exp2f(+-log2f(r) / 3), which stays on hosts where fmaf is a library call (the polynomial seeds are slower there). The device keeps the SFU lg2/ex2 pair, and its generated code is unchanged. The seeds err by at most 2^-23.98 (cbrt) and 2^-23.29 (rcbrt) over every float in [1/4, 2). Every multiply-add in them is an explicit fma, so those host results no longer depend on the C library or on the compiler's contraction. math_cbrt.pass.cpp checks cbrt and rcbrt against MPFR references across the argument reduction, on the host and on the device. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
rmlarsen
force-pushed
the
fpmp-cbrt-host-seed
branch
from
November 13, 2026 23:31
dc58109 to
395c0e6
Compare
2 tasks done
rmlarsen
added a commit
to rmlarsen/cccl
that referenced
this pull request
Nov 13, 2026
…n kernel, and handle their special values All four find the angle of a point (M, m), 0 <= m <= M, from a table of atan(i/16): atan(m/M) = atan(c) + atan(t), c = i/16, t = (m - c M) / (M + c m), with i chosen so that -1/64 <= t <= 3/64 and the leading words of m and c M cancel exactly. The octant is folded into the table (four rows), and a 4-coefficient kernel replaces the 13- and 19-term compensated Horner evaluations. atan and atan2 take t from one pair division. asin and acos enter as the unit-circle point (x, sqrt((1 - |x|)(1 + |x|))), where the division becomes a product by a tabulated cos(atan(c)) and the kernel is asin; for small x that is x itself, so asin(x) does not depend on the square root there. Special values follow C99 Annex F and are answered on entry. asin(-0) and atan(-0) are -0 (were +0); atan(+-inf) is +-pi/2 (was NaN); atan2 takes the sign of a zero y from its sign bit (atan2(-0, x) was +0 or +pi), accepts subnormal operands (was NaN) and scales its operands by a common power of two, so that tiny or, on the device, huge operands keep full accuracy (the relative error reached 1); asin and acos of a pair just outside [-1, 1] by its low limb are NaN. NaN results carry NaN in both limbs. fp64mp2: asin(+-0), atan(+-0) and atan2(+-0, x) keep the sign of zero, and asin and acos of |x| >= 1/2 form 1 - |x| from the limbs instead of summing them in binary128 first, which lost a low limb beyond its 113 bits: acos((1, -1e-30)) was off by 2e-5 and acos((1, -denorm_min)) was 0; both are now within 2^-106. Against the head of NVIDIA#12568, host, time per call: asin 1.6-2.2x, acos 1.9-2.1x, atan 2.9x and atan2 3.3x faster on x86-64 (2.4x in the geometric mean), 1.6-2.2x on a Cortex-A76 (1.9x). fp32 SASS instructions on sm_89: asin 420 -> 94, acos 432 -> 95, atan 264 -> 76, atan2 287 -> 86. Largest error against MPFR in units of u^2 = 2^-48, host / sm_89 device: asin 6.2 / 6.2 (was 10.9 / 14.1), acos 7.2 / 7.0 (6.2 / 9.6), atan 4.1 / 4.1 (6.4 / 6.9), atan2 5.9 / 5.9 (6.1 on moderate ranges). asin on |x| < 0.5 is less accurate than before (6.2 against 2.2). Host results are the same bits with GCC and Clang, with and without FMA, on x86-64, Cortex-A76 and Apple M4; the host takes the square root from sqrtf and the device from its approximate reciprocal square root, so about 9% of asin and acos results differ between host and device in the last bits. New test fpmp/math_invtrig.pass.cpp; the special-value tables of the four functions in fpmp_spec.rst are regenerated. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
mainOn a host with hardware FMA,
fp32mp2cbrtandrcbrtseed their Halley step with a refined polynomial instead ofexp2f(±log2f(r)/3). Against currentmain(07bbb7a):Performance. ns per call,
main→ this PR (speedup; in parentheses this PR's share over #12548):cbrtrcbrt-O3 -march=x86-64-v3-O3 -mcpu=cortex-a76-O3 -mcpu=apple-m4One core, 2048 arguments log-uniform in 2^±20 with full lo limbs, the fastest of 25 rounds (M4: 150), three alternating passes that agree within 1%. Two configurations do not change:
fmafa library call (x86-64 at-march=x86-64), the polynomial seeds madecbrt9% andrcbrt14% slower than [fpmp] Accurate, C99-conforming and faster fp32mp2 pow #12548 (44.8 against 41.1 ns, 63.3 against 55.7 ns), so they are compiled only whereFP_FAST_FMAFor__FMA__is defined.lg2/ex2pair; the SASS of acbrtand anrcbrtkernel is identical to [fpmp] Accurate, C99-conforming and faster fp32mp2 pow #12548's, instruction for instruction, on sm_75 and sm_89.Accuracy. Unchanged within the Halley step's own error. Maximum relative error on the host against MPFR, in u² (u = 2⁻²⁴):
maincbrtrcbrtEvery float in [1, 8) as the hi limb with a random lo limb (25.2M inputs: all significands for each exponent residue mod 3); the same hi limbs with lo = 0 and every 509th positive float, subnormals included, give lower maxima. The mean is 0.27u² (
cbrt) and 0.24u² (rcbrt) before and after.Platform independence. With the new seed, host results are the same bits everywhere: a hash of the results over 29.4M inputs is identical for GCC 15.2 at
-O3and-O0and Clang 21 on x86-64, GCC 14 on the Pi and Apple clang 21 on the M4. Onmainthe seed comes from the C library, and the M4's results differ from glibc's.Special values. Unchanged: ±0, ±inf and NaN return before the seed is computed.
Implementation
fpmp_math_impl_pow.hgains__internal_fpmp2_cbrt_seedand__internal_fpmp2_rcbrt_seed, which returnr^(1/3)andr^(-1/3)in single precision for the reduced argumentrin [1/4, 2). On the device, and on a host without hardware FMA, they are the expression used before. Otherwise:cbrt: a degree-4 minimax polynomial (relative error ≤ 1.87e-3) and one single-precision Halley step whose residual isfma(s2_hi, s, -r) + s2_lo·s, withs2_hi + s2_lo = s²exact. The seed errs by at most 2⁻²³·⁹⁸.rcbrt: a degree-3 polynomial (≤ 1.69e-2) and two division-free stepss(1 + u/3 + 2u²/9),u = 1 − r s³. The seed errs by at most 2⁻²³·²⁹.Both bounds are measured against MPFR over every float in [1/4, 2). The polynomials are Sollya
fpminimaxfits withsupnorm-certified errors; the commands are in the header. Every multiply-add is an explicitfma, so the result does not depend on the compiler's contraction.The comments of
cbrtandrcbrtgave the reduced range as roughly [2⁻¹, 2¹) and [2⁻², 2²); it is [1/4, 2) for both, and they now say so, since the polynomials rely on it.Testing
fpmp/math_cbrt.pass.cpp, host and device: 16 inputs covering every exponent residue mod 3, both signs, the ends of the normal range and subnormal hi limbs;cbrtandrcbrtmust be within 16u² of MPFR references held as double pairs.rcbrthad no host test before.math_cbrt,math,math_pow,math_reduction,accuracyandscalar_opspass with GCC 15.2 and Clang 21 at-march=x86-64-v3and-march=x86-64(the old seed), GCC at-O0, GCC 14 on the Pi and Apple clang 21 on the M4.cuda/fp/unitssuite passes on sm_89 (58 passed, 2 unsupported), CUDA 13.4 and GCC 15.2.-Wall -Wextra -Wshadow -Wconversion.Not run: MSVC (it defines neither macro, so it keeps the old seed) and GPUs other than sm_89.
Merge order
Stacked on #12548 (#12543, #12519 and #12530 have landed): the branch contains its commits, and this PR's change is 395c0e6. It carries #12548's CI override, which is reset before merging.
Checklist
🤖 Generated with Claude Code