KDA 3D FFTs on Packed Hermitian Half-Spectra
CuTe-DSL forward (real-to-complex) and inverse (complex-to-real) unnormalized 3D
FFTs of one real grid, in fp32 and fp64, for the NVIDIA RTX 5090. The spectrum
is the packed half-spectrum that structured-grid spectral solvers use: x
innermost, interleaved (re, im), an x-row stride of exactly nx // 2 + 1.
The results are those of torch.fft.rfftn and
torch.fft.irfftn(..., norm="forward").
Against cuFFT's own 3D plans, geometric mean over eight grids from 32^3 to
256^3: r2c 1.99x and c2r 1.85x in fp32, r2c 2.62x and c2r 2.63x in fp64.
Against torch.fft, which runs a 3D transform as two cuFFT plans and a copy,
about 3x.
Requirements
- NVIDIA GPU with compute capability 12.0; tuned and tested on the RTX 5090 only
- CUDA 13 compatible driver
- PyTorch 2.12.1 with CUDA 13.0 (tested)
nvidia-cutlass-dsl[cu13]==4.6.0(tested)
Usage
import torch
from kernels import get_kernel
fft3d = get_kernel("humanfia-lab/fft3d", version=1, trust_remote_code=True)
u = torch.randn(96, 96, 144, device="cuda") # [nz, ny, nx], fp32 or fp64
spectrum = fft3d.r2c(u) # [96, 96, 73, 2] == torch.view_as_real(torch.fft.rfftn(u))
back = fft3d.c2r(spectrum) # [96, 96, 144], unnormalized: 96 * 96 * 144 * u
launch = fft3d.prepare_r2c(u) # plan once for this buffer...
u.copy_(torch.randn_like(u))
spectrum = launch() # ...then transform its current contents
r2c(x)takes a contiguous[nz, ny, nx]CUDA tensor, float32 or float64.c2r(X)takes a[nz, ny, nf, 2]half-spectrum of the same dtypes and returns[nz, ny, 2 * (nf - 1)]. Each call returns a new tensor and leaves its input unchanged. Other dtypes, devices and shapes raiseTypeErrororValueError.prepare_r2c(x)andprepare_c2r(X)plan for that input buffer and returnlaunch(), which transforms the buffer's current contents into one output tensor that it reuses on every call. Calls of onelaunchmust be ordered (one stream). Keeplaunchalive as long as a CUDA graph captured from it. fp64 r2c has no two-phase form; itslaunch()callsr2c(x).- Calls on different CUDA streams may run concurrently, including calls of the same shape.
- The first call for a new shape compiles its kernels (CuTe DSL JIT), typically in 1 to 30 s, up to about 4 minutes for a very long z axis over a tiny y-x plane (such as 4096x8x16). Make that first call outside CUDA graph capture; later calls can be captured.
c2rexpects a Hermitian-consistent half-spectrum, such as the output ofr2cor Fourier-space data that keeps its symmetry. On other input the result depends on the order of the 1D passes, as with cuFFT; these kernels then matchtorch.fft.irfftn.- Tuned on the eight grids below and checked on dozens of others per kernel,
including odd and prime extents and up to 1024x1024x512. fp64 r2c raises
NotImplementedErrorwhen a y or z axis needs a direct DFT longer than 6336 points (prime factors above 13, or smooth lengths above 1536); fp64 c2r raises it for nz from 1280 to 4096, ny from 3072 to 4096 or nx / 2 from 3072 to 4096.
Accuracy
fp32 kernels compute in fp32 from twiddle tables computed in float64 on the host: no reduced precision, no tensor cores, no approximate math. Against the float64 result the relative L2 error is 1.4e-7 to 1.8e-7 on the grids below, where cuFFT's is 2.0e-7 to 2.8e-7. Every element is within 1e-2, or within 1e-3 of its own magnitude.
fp64 kernels are IEEE float64 throughout, within 3.5e-16 to 5.9e-16 relative L2
of torch.fft's float64 result. The RTX 5090 runs FP64 at 1/64 of the FP32
rate, so both fp64 kernels compute part of their float64 additions with a
software IEEE add (round to nearest even) on the integer units, which run
alongside the FP64 unit. It gives bit-identical results to the FP64 unit for
finite normal or zero operands and results; only the sign of some zero results
differs. Outside that domain it is wrong, silently: data below about 1e-300 in
magnitude, subnormal values, results that overflow, and Inf or NaN inputs give
wrong outputs instead of propagating. Data between about 1e-300 and 1e300 in
magnitude is unaffected.
Repeated calls give bitwise identical results.
Performance
Speedup over cuFFT's 3D plan:
| grid (nz x ny x nx) | r2c fp32 | c2r fp32 | r2c fp64 | c2r fp64 |
|---|---|---|---|---|
| 32x32x32 | 3.33x | 2.60x | 3.89x | 2.28x |
| 64x64x64 | 2.13x | 2.00x | 2.48x | 2.44x |
| 80x80x112 | 2.22x | 1.85x | 2.76x | 2.77x |
| 96x96x96 | 2.24x | 2.14x | 2.37x | 2.64x |
| 96x96x144 | 1.92x | 1.77x | 2.12x | 2.53x |
| 128x128x128 | 1.80x | 1.65x | 2.50x | 2.99x |
| 192x192x192 | 1.39x | 1.44x | 2.72x | 2.94x |
| 256x256x256 | 1.42x | 1.59x | 2.45x | 2.56x |
| geomean | 1.99x | 1.85x | 2.62x | 2.63x |
| geomean, 128^3 and up | 1.53x | 1.56x | 2.55x | 2.82x |
Times in microseconds (torch.fft / cuFFT 3D plan / kernel):
| grid | r2c fp32 | c2r fp32 | r2c fp64 | c2r fp64 |
|---|---|---|---|---|
| 32x32x32 | 10.4 / 8.6 / 2.6 | 9.3 / 7.0 / 2.7 | 24.8 / 23.5 / 6.0 | 18.0 / 16.6 / 7.3 |
| 64x64x64 | 11.6 / 9.4 / 4.4 | 14.3 / 9.0 / 4.5 | 32.7 / 31.5 / 12.7 | 36.3 / 32.6 / 13.4 |
| 80x80x112 | 21.1 / 15.1 / 6.8 | 20.1 / 13.9 / 7.5 | 89.5 / 78.9 / 28.5 | 72.5 / 76.6 / 27.7 |
| 96x96x96 | 23.4 / 16.5 / 7.4 | 25.6 / 16.7 / 7.8 | 87.4 / 75.6 / 32.0 | 83.1 / 76.2 / 28.8 |
| 96x96x144 | 31.4 / 18.5 / 9.6 | 31.4 / 18.7 / 10.6 | 117.7 / 98.5 / 46.5 | 125.9 / 99.0 / 39.1 |
| 128x128x128 | 42.0 / 24.2 / 13.4 | 56.8 / 23.4 / 14.2 | 182.8 / 164.0 / 65.5 | 202.1 / 173.7 / 58.1 |
| 192x192x192 | 123.2 / 66.9 / 48.1 | 126.4 / 71.2 / 49.3 | 647.9 / 542.5 / 199.5 | 590.8 / 538.5 / 182.9 |
| 256x256x256 | 346.8 / 163.0 / 114.5 | 300.2 / 187.4 / 117.5 | 1625.0 / 1116.0 / 456.0 | 1445.3 / 1130.0 / 441.8 |
On small grids cuFFT's several kernels are launch-bound, and one or two fused kernels win most. On large fp32 grids the kernels make fewer passes over memory. In fp64 the FP64 unit is the limit, and the integer-unit adder adds throughput beside it.
Measurements use one RTX 5090 (driver 580.82.07) with CUPTI cold-L2 kernel time
over CUDA graph replay, 3 warmup and 30 measured iterations, median of 3 trials,
each trial timing cuFFT before and after the kernel. cuFFT is the cuFFT 12.0
that PyTorch 2.12.1+cu130 ships, as one cufftPlan3d per grid made before
timing and executed out of place; its C2R overwrites its input and is given a
copy. torch.fft is rfftn / irfftn on the same library. Speeds differ by a
few percent between RTX 5090 cards.
Source and licensing
Written by Claude Opus 5.5 in four 12-hour runs of KDA (kernel design agents),
one per KDA task (fft3d_r2c, fft3d_c2r, fft3d_r2c_f64, fft3d_c2r_f64),
built from the requests in NVlabs/kda#7,
and accepted by the KDA judge. Published from commit e539498 of
humanfia/kda-kernels-internal, directory rtx5090/fft3d/, which also holds
its tests and the fixes made after the judge's checks. MIT-licensed.
Available functions
c2rprepare_c2rprepare_r2cr2c
- Downloads last month
- -
- Kernel Builder
- 375561f


