Let's build the fastest accurate sinf / cosf in the world.
Not the fastest — CUDA already has __sinf, one hardware instruction, and
nothing will beat it. Not the most accurate either — that is what sinf is
for. The interesting target is the corner nobody occupies: sinf's accuracy
at a speed close to the intrinsic's, across the whole float range.
Today, on six GPUs across four architectures, fastsin::sin is 6–8× faster
than sinf at |x| = 10⁶ with identical error. That is a starting point, not
a finish line. Contributions that push either axis are what this repo is for.
#include "fastsin.cuh"
__global__ void k(const float* x, float* y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) y[i] = fastsin::sin(x[i]); // drop-in for sinf
}Header-only, MIT, C++17, sm_75 and up. Copy include/ and include the header.
# -arch must match your GPU: it selects the tuning constants, not just the SASS
ARCH=sm_$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | head -1 | tr -d '.')
nvcc -O3 -arch=$ARCH -std=c++17 -Iinclude your_code.cu
# check what your build actually did
nvcc -O3 -arch=$ARCH -std=c++17 -Iinclude -o config bench/config.cu && ./config| valid |x| | abs error | ||
|---|---|---|---|
sin cos sincos |
all | ~1.1e−07 | start here — dispatches on |x| |
sin_warp cos_warp sincos_warp |
all | ~1.1e−07 | warp-uniform; needs a full converged warp |
sin_small cos_small sincos_small |
< 5e6 | ~9e−08 | fastest accurate path. Silently wrong past 5e6 |
sin_big cos_big sincos_big |
all | ~1.1e−07 | branch-free, no dispatch |
sin_rough cos_rough sincos_rough |
< 1e3 | ~4e−07 | __sinf-class accuracy, 1000× wider range |
-use_fast_mathsilently rewrites everysinfin your code into__sinf— error goes from 8.7e−08 to 0.14 at |x| = 10⁶ with no diagnostic.fastsinis unaffected, so it also works as a way to keep accurate trig in a build that needs fast-math elsewhere.bench/config.cudetects the flag.
The algorithms are not new. These are the sources they come from.
Argument reduction — used directly
- Payne, M. H. and Hanek, R. N. Radian Reduction for Trigonometric
Functions. ACM SIGNUM Newsletter 18(1), 1983, 19–24. — the infinite-precision
reduction
sin_bigimplements. - Cody, W. J. and Waite, W. Software Manual for the Elementary Functions.
Prentice-Hall, 1980. — the splitting of π/2 into few-significant-bit pieces
that
sin_smalluses. - Ng, K. C. Argument Reduction for Huge Arguments: Good to the Last Bit.
SunPro technical report, 1992. — the practical treatment of how many bits of
2/π you actually need, which
bench/exhaustive.cure-derives by brute force for binary32.
Background and error analysis
- Muller, J.-M. Elementary Functions: Algorithms and Implementation. Birkhäuser, 3rd ed., 2016. — the standard reference for everything here.
- Brisebarre, N., Defour, D., Kornerup, P., Muller, J.-M. and Revol, N. A New Range-Reduction Algorithm. IEEE Transactions on Computers 54(3), 2005, 331–339.
- Boldo, S., Daumas, M. and Li, R.-C. Formally Verified Argument Reduction with a Fused Multiply-Add. IEEE Transactions on Computers 58(8), 2009, 1139–1145.
- Daumas, M., Mazenc, C., Merrheim, X. and Muller, J.-M. Modular Range Reduction: A New Algorithm for Fast and Accurate Computation of the Elementary Functions. Journal of Universal Computer Science 1(3), 1995.
- Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), 1991, 5–48.
Vendor documentation
- CUDA C++ Programming Guide, Appendix: Mathematical Functions — the
documented 2 ULP bound for
sinfthat everything here is measured against.
The ask is simple: show me a measurement, and I will merge it.
A contribution here is not "I think this is faster." It is: at this range of |x|, this function beats the current one by this much on speed or accuracy, and here is the run that proves it. Bring that and it goes in.
- Which entry point, or a new one.
- Which range of |x| it wins in. Everything in this library is a trade that holds over some interval and fails outside it; saying where is not a caveat, it is the result.
- How much, on which axis — throughput, absolute error, or both.
- The output of the harness, not a summary of it.
ARCH=sm_$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | head -1 | tr -d '.')
# throughput and error across twenty decades of |x| -- this is the evidence
nvcc -O3 -arch=$ARCH -std=c++17 -Iinclude -o sweep bench/sweep.cu
./sweep > results/sweep/YourGPU.csv
python bench/plot.py # regenerates docs/curves.svg
# correctness gates -- both must pass, no exceptions
nvcc -O3 -arch=$ARCH -std=c++17 -Iinclude -o exhaustive bench/exhaustive.cu
./exhaustive # all 2^31 positive floats vs double
nvcc -O3 -arch=$ARCH -std=c++17 -Iinclude -o test_all bench/test_all.cu
./test_all # 96M samples + inf, NaN, subnormals
# optional: the same inputs judged in ULP instead, which is the metric this
# library deliberately does not hold. Diagnostic, not a gate.
nvcc -O3 -arch=$ARCH -std=c++17 -Iinclude -o ulp bench/ulp.cu && ./ulpexhaustivereportsPASSandtest_allreportsSPECIAL_SUMMARY mismatches=0 verdict=PASS.- No existing entry point gets slower or less accurate. If a change trades one for the other, it becomes a new entry point rather than replacing one.
- A tuning constant that differs per architecture goes in
include/fastsin_tuning.cuhwith the measurement in a comment. That file has no unsourced numbers in it and should stay that way. - Negative results are welcome as issues. Several things that obviously should have worked did not, and knowing which is worth as much as the code.
Concretely, what I would most like to see:
- Blackwell.
sm_100/sm_120compile and are correct, but fall through to conservative defaults. Someone with the hardware runningbench/frontier.cuand adding a measured branch would close the last gap in the architecture table. sin_bigpast 154 Gop/s. It is still 1.7× behindsin_smallon an RTX 4060.bench/ablate.cuprices each remaining piece.- A cheaper dispatcher. nvcc if-converts the
|x|branch and runs both paths;__noinline__measured worse. There may be a third way. - Other precisions.
__half,__nv_bfloat16, and double all have the same shape of problem and none of them are here. - Other vendors. HIP and SYCL ports.
RTX 4060. Top: throughput. Bottom: absolute error. The amber field is
|x| ≥ 105615, where CUDA's sinf leaves its fast path — a constant measured to
one ULP and identical on every GPU tested. The grey field is worse than 2 ULP,
the bound CUDA documents for sinf.
At |x| = 10⁶, same error in every row (8.45e−08):
| GPU | arch | sinf |
fastsin::sin |
|
|---|---|---|---|---|
| Tesla T4 | sm_75 | 22.9 | 138.1 | 6.02× |
| A100-40GB | sm_80 | 65.2 | 441.7 | 6.77× |
| A10 | sm_86 | 46.9 | 335.6 | 7.16× |
| L4 | sm_89 | 43.9 | 295.7 | 6.73× |
| RTX 4060 | sm_89 | 27.4 | 219.1 | 8.01× |
| H100 | sm_90 | 112.6 | 882.5 | 7.84× |
Gop/s, one evaluation per op. This is not a speed-for-accuracy trade — the error columns are identical to three significant figures.
Verified exhaustively, not sampled. bench/exhaustive.cu evaluates every
one of the 2³¹ positive floats against a double-precision reference; negatives
mirror exactly, since sin is odd and cos is even.
SCAN sin_big max_abs_err=1.1042e-07 PASS
SCAN cos_big max_abs_err=1.0948e-07 PASS
SPECIAL_SUMMARY mismatches=0 verdict=PASS on all six GPUs above.
-
Blackwell is untuned.
-
Accuracy is absolute, not ULP — and the gap is not small. Measured over 96M inputs by
bench/ulp.cu:max abs error max ULP sinf8.81e−08 1.49 sin_big1.09e−07 3,079,907 Near a zero of sin the correctly rounded result is tiny, so its ULP is tiny, and a reduction that is comfortably within 1e−07 absolute is millions of ULP out in relative terms.
sinfholds its documented bound there and this does not. If you need relative accuracy near multiples of π, usesinf. -
sin_smallreturns a wrong number past |x| = 5e6 with no warning. Usefastsin::sinunless you know your range. -
Only NVIDIA, only
float.
If this saved you time, buy me a coffee.
Every hour of GPU rental for the cross-architecture sweeps comes out of pocket — the numbers in the table above are six cloud GPUs, re-run from scratch on every change to the reduction. Coffee keeps the sweeps running and the next architecture measured instead of guessed.
MIT. See LICENSE.