Add compact B200 FFT3D support to the shared kernel package
#1
by ubospica - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
- README.md +113 -10
- build/torch-cuda/LICENSE.txt +21 -0
- build/torch-cuda/__init__.py +60 -0
- build/torch-cuda/_b200/__init__.py +151 -0
- build/torch-cuda/_b200/c2r_cuda_z_inverse_cuda_kernel.py +63 -0
- build/torch-cuda/_b200/c2r_cuda_z_inverse_fft_axis.cu +128 -0
- build/torch-cuda/_b200/c2r_dense_half_inverse_wide_redirect.cu +85 -0
- build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_cuda_kernel.py +63 -0
- build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_fft_axis.cu +128 -0
- build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_lto.py +110 -0
- build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_native_dispatch.py +100 -0
- build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_wide_redirect.cu +65 -0
- build/torch-cuda/_b200/c2r_f64_dense_half_inverse_lto.py +110 -0
- build/torch-cuda/_b200/c2r_f64_dense_half_inverse_wide_redirect.cu +67 -0
- build/torch-cuda/_b200/c2r_f64_half_vector_inverse_wide_redirect.cu +65 -0
- build/torch-cuda/_b200/c2r_f64_narrow_index_inverse_scalar_output_reconstruct.cu +45 -0
- build/torch-cuda/_b200/c2r_f64_paired_x_inverse_cuda_kernel.py +64 -0
- build/torch-cuda/_b200/c2r_f64_paired_x_inverse_fft_axis.cu +270 -0
- build/torch-cuda/_b200/c2r_f64_paired_x_inverse_wide_redirect.cu +29 -0
- build/torch-cuda/_b200/c2r_f64_paired_y_direct_inverse_lto.py +116 -0
- build/torch-cuda/_b200/c2r_f64_paired_y_direct_inverse_wide_redirect.cu +114 -0
- build/torch-cuda/_b200/c2r_f64_partial_y_inverse_wide_redirect.cu +114 -0
- build/torch-cuda/_b200/c2r_f64_scalar_input_inverse_scalar_io.cu +26 -0
- build/torch-cuda/_b200/c2r_paired_y_direct_inverse_wide_redirect.cu +109 -0
- build/torch-cuda/_b200/c2r_paired_y_inverse_wide_redirect.cu +87 -0
- build/torch-cuda/_b200/c2r_pitched_half_inverse_wide_redirect.cu +86 -0
- build/torch-cuda/_b200/cuda_kernel.py +64 -0
- build/torch-cuda/_b200/cuda_twiddle_forward_lto.py +113 -0
- build/torch-cuda/_b200/cuda_twiddle_forward_native_dispatch.c +42 -0
- build/torch-cuda/_b200/cuda_twiddle_forward_native_dispatch.py +101 -0
- build/torch-cuda/_b200/cuda_z_inverse_cufft_support.py +105 -0
- build/torch-cuda/_b200/cuda_z_inverse_lto.py +110 -0
- build/torch-cuda/_b200/cuda_z_inverse_native_dispatch.c +42 -0
- build/torch-cuda/_b200/cuda_z_inverse_native_dispatch.py +101 -0
- build/torch-cuda/_b200/cuda_z_inverse_schedule.py +71 -0
- build/torch-cuda/_b200/dense_half_inverse_abi.py +30 -0
- build/torch-cuda/_b200/dense_half_inverse_kernels.py +10 -0
- build/torch-cuda/_b200/dense_half_inverse_lto.py +110 -0
- build/torch-cuda/_b200/dense_half_inverse_native_dispatch.c +43 -0
- build/torch-cuda/_b200/dense_half_inverse_native_dispatch.py +104 -0
- build/torch-cuda/_b200/dense_half_inverse_schedule.py +73 -0
- build/torch-cuda/_b200/direct_dispatch.c +53 -0
- build/torch-cuda/_b200/direct_dispatch.py +98 -0
- build/torch-cuda/_b200/fft_axis.cu +169 -0
- build/torch-cuda/_b200/forward_cuda_f32.py +151 -0
- build/torch-cuda/_b200/forward_half.py +149 -0
- build/torch-cuda/_b200/forward_native.py +182 -0
- build/torch-cuda/_b200/forward_paired_y.py +153 -0
- build/torch-cuda/_b200/forward_scalar.py +63 -0
- build/torch-cuda/_b200/forward_small.py +195 -0
README.md
CHANGED
|
@@ -10,10 +10,17 @@ tags:
|
|
| 10 |
- fp64
|
| 11 |
- blackwell
|
| 12 |
- rtx-5090
|
|
|
|
| 13 |
---
|
| 14 |
|
| 15 |
# KDA 3D FFTs on Packed Hermitian Half-Spectra
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
CuTe-DSL forward (real-to-complex) and inverse (complex-to-real) unnormalized 3D
|
| 18 |
FFTs of one real grid, in fp32 and fp64, for the NVIDIA RTX 5090. The spectrum
|
| 19 |
is the packed half-spectrum that structured-grid spectral solvers use: x
|
|
@@ -28,6 +35,8 @@ about 3x.**
|
|
| 28 |
|
| 29 |
## Requirements
|
| 30 |
|
|
|
|
|
|
|
| 31 |
- NVIDIA GPU with compute capability 12.0; tuned and tested on the RTX 5090
|
| 32 |
only
|
| 33 |
- CUDA 13 compatible driver
|
|
@@ -36,11 +45,14 @@ about 3x.**
|
|
| 36 |
|
| 37 |
## Usage
|
| 38 |
|
|
|
|
|
|
|
|
|
|
| 39 |
```python
|
| 40 |
import torch
|
| 41 |
from kernels import get_kernel
|
| 42 |
|
| 43 |
-
fft3d = get_kernel("humanfia-lab/fft3d",
|
| 44 |
|
| 45 |
u = torch.randn(96, 96, 144, device="cuda") # [nz, ny, nx], fp32 or fp64
|
| 46 |
spectrum = fft3d.r2c(u) # [96, 96, 73, 2] == torch.view_as_real(torch.fft.rfftn(u))
|
|
@@ -60,24 +72,25 @@ spectrum = launch() # ...then transform its current contents
|
|
| 60 |
`launch()`, which transforms the buffer's current contents into one output
|
| 61 |
tensor that it reuses on every call. Calls of one `launch` must be ordered
|
| 62 |
(one stream). Keep `launch` alive as long as a CUDA graph captured from it.
|
| 63 |
-
fp64 r2c has no two-phase form; its `launch()` calls `r2c(x)`.
|
| 64 |
- Calls on different CUDA streams may run concurrently, including calls of the
|
| 65 |
-
same shape.
|
|
|
|
| 66 |
- The first call for a new shape compiles its kernels (CuTe DSL JIT), typically
|
| 67 |
-
in 1 to 30 s, up to about 4 minutes for a very long z axis over
|
| 68 |
-
plane (such as 4096x8x16). Make that first call outside CUDA graph capture;
|
| 69 |
later calls can be captured.
|
| 70 |
- `c2r` expects a Hermitian-consistent half-spectrum, such as the output of
|
| 71 |
`r2c` or Fourier-space data that keeps its symmetry. On other input the result
|
| 72 |
depends on the order of the 1D passes, as with cuFFT; these kernels then
|
| 73 |
match `torch.fft.irfftn`.
|
| 74 |
-
-
|
| 75 |
-
including odd and prime extents and up to 1024x1024x512. fp64 r2c raises
|
| 76 |
`NotImplementedError` when a y or z axis needs a direct DFT longer than 6336
|
| 77 |
points (prime factors above 13, or smooth lengths above 1536); fp64 c2r raises
|
| 78 |
it for nz from 1280 to 4096, ny from 3072 to 4096 or nx / 2 from 3072 to 4096.
|
| 79 |
|
| 80 |
-
##
|
| 81 |
|
| 82 |
fp32 kernels compute in fp32 from twiddle tables computed in float64 on the
|
| 83 |
host: no reduced precision, no tensor cores, no approximate math. Against the
|
|
@@ -98,7 +111,7 @@ magnitude is unaffected.
|
|
| 98 |
|
| 99 |
Repeated calls give bitwise identical results.
|
| 100 |
|
| 101 |
-
##
|
| 102 |
|
| 103 |
Speedup over cuFFT's 3D plan:
|
| 104 |
|
|
@@ -141,7 +154,96 @@ timing and executed out of place; its C2R overwrites its input and is given a
|
|
| 141 |
copy. `torch.fft` is `rfftn` / `irfftn` on the same library. Speeds differ by a
|
| 142 |
few percent between RTX 5090 cards.
|
| 143 |
|
| 144 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
Written by Claude Opus 5.5 in four 12-hour runs of KDA (kernel design agents),
|
| 147 |
one per KDA task (`fft3d_r2c`, `fft3d_c2r`, `fft3d_r2c_f64`, `fft3d_c2r_f64`),
|
|
@@ -151,6 +253,7 @@ and accepted by the KDA judge. Published from commit `e539498` of
|
|
| 151 |
its tests and the fixes made after the judge's checks. MIT-licensed.
|
| 152 |
|
| 153 |
## Available functions
|
|
|
|
| 154 |
- `c2r`
|
| 155 |
- `prepare_c2r`
|
| 156 |
- `prepare_r2c`
|
|
|
|
| 10 |
- fp64
|
| 11 |
- blackwell
|
| 12 |
- rtx-5090
|
| 13 |
+
- b200
|
| 14 |
---
|
| 15 |
|
| 16 |
# KDA 3D FFTs on Packed Hermitian Half-Spectra
|
| 17 |
|
| 18 |
+
NVIDIA B200 and RTX 5090 share the `r2c`, `c2r`, `prepare_r2c`, and
|
| 19 |
+
`prepare_c2r` interface in `build/torch-cuda`. The package selects the
|
| 20 |
+
implementation from the CUDA device and tensor dtype.
|
| 21 |
+
See [NVIDIA B200](#nvidia-b200) for B200 requirements and measurements; the
|
| 22 |
+
accuracy and performance sections before it describe the RTX 5090.
|
| 23 |
+
|
| 24 |
CuTe-DSL forward (real-to-complex) and inverse (complex-to-real) unnormalized 3D
|
| 25 |
FFTs of one real grid, in fp32 and fp64, for the NVIDIA RTX 5090. The spectrum
|
| 26 |
is the packed half-spectrum that structured-grid spectral solvers use: x
|
|
|
|
| 35 |
|
| 36 |
## Requirements
|
| 37 |
|
| 38 |
+
For RTX 5090:
|
| 39 |
+
|
| 40 |
- NVIDIA GPU with compute capability 12.0; tuned and tested on the RTX 5090
|
| 41 |
only
|
| 42 |
- CUDA 13 compatible driver
|
|
|
|
| 45 |
|
| 46 |
## Usage
|
| 47 |
|
| 48 |
+
The pending unified package is available from the pull-request revision below.
|
| 49 |
+
The existing `version=1` release remains the RTX 5090 package.
|
| 50 |
+
|
| 51 |
```python
|
| 52 |
import torch
|
| 53 |
from kernels import get_kernel
|
| 54 |
|
| 55 |
+
fft3d = get_kernel("humanfia-lab/fft3d", revision="refs/pr/1", trust_remote_code=True)
|
| 56 |
|
| 57 |
u = torch.randn(96, 96, 144, device="cuda") # [nz, ny, nx], fp32 or fp64
|
| 58 |
spectrum = fft3d.r2c(u) # [96, 96, 73, 2] == torch.view_as_real(torch.fft.rfftn(u))
|
|
|
|
| 72 |
`launch()`, which transforms the buffer's current contents into one output
|
| 73 |
tensor that it reuses on every call. Calls of one `launch` must be ordered
|
| 74 |
(one stream). Keep `launch` alive as long as a CUDA graph captured from it.
|
| 75 |
+
On RTX 5090, fp64 r2c has no two-phase form; its `launch()` calls `r2c(x)`.
|
| 76 |
- Calls on different CUDA streams may run concurrently, including calls of the
|
| 77 |
+
same shape. On B200, prepare and warm each shape on its execution stream
|
| 78 |
+
before capturing a graph; a prepared launch stays bound to that stream.
|
| 79 |
- The first call for a new shape compiles its kernels (CuTe DSL JIT), typically
|
| 80 |
+
on RTX 5090 in 1 to 30 s, up to about 4 minutes for a very long z axis over
|
| 81 |
+
a tiny y-x plane (such as 4096x8x16). Make that first call outside CUDA graph capture;
|
| 82 |
later calls can be captured.
|
| 83 |
- `c2r` expects a Hermitian-consistent half-spectrum, such as the output of
|
| 84 |
`r2c` or Fourier-space data that keeps its symmetry. On other input the result
|
| 85 |
depends on the order of the 1D passes, as with cuFFT; these kernels then
|
| 86 |
match `torch.fft.irfftn`.
|
| 87 |
+
- RTX 5090: tuned on the eight grids below and checked on dozens of others
|
| 88 |
+
per kernel, including odd and prime extents and up to 1024x1024x512. fp64 r2c raises
|
| 89 |
`NotImplementedError` when a y or z axis needs a direct DFT longer than 6336
|
| 90 |
points (prime factors above 13, or smooth lengths above 1536); fp64 c2r raises
|
| 91 |
it for nz from 1280 to 4096, ny from 3072 to 4096 or nx / 2 from 3072 to 4096.
|
| 92 |
|
| 93 |
+
## RTX 5090 accuracy
|
| 94 |
|
| 95 |
fp32 kernels compute in fp32 from twiddle tables computed in float64 on the
|
| 96 |
host: no reduced precision, no tensor cores, no approximate math. Against the
|
|
|
|
| 111 |
|
| 112 |
Repeated calls give bitwise identical results.
|
| 113 |
|
| 114 |
+
## RTX 5090 performance
|
| 115 |
|
| 116 |
Speedup over cuFFT's 3D plan:
|
| 117 |
|
|
|
|
| 154 |
copy. `torch.fft` is `rfftn` / `irfftn` on the same library. Speeds differ by a
|
| 155 |
few percent between RTX 5090 cards.
|
| 156 |
|
| 157 |
+
## NVIDIA B200
|
| 158 |
+
|
| 159 |
+
The B200 implementation is in `build/torch-cuda/_b200` and uses the same
|
| 160 |
+
public interface shown above. It uses Triton, CUDA C++, and cuFFT, and supports
|
| 161 |
+
exactly the eight grids below, with no leading batch dimension. Other grids
|
| 162 |
+
raise `ValueError`. Both directions are unnormalized, return fresh contiguous
|
| 163 |
+
outputs, and preserve inputs. Both preparation functions return a callable
|
| 164 |
+
bound to the input buffer, with one reused output.
|
| 165 |
+
|
| 166 |
+
Requirements: B200 (compute capability 10.0), a CUDA 13 development environment
|
| 167 |
+
with `nvcc` and a C++ compiler, and the PyTorch CUDA build with its matching
|
| 168 |
+
Triton. The performance measurements used PyTorch 2.13.0+cu130 and Triton 3.7.1.
|
| 169 |
+
Keep cuFFT, NVRTC, and nvJitLink from a compatible CUDA toolkit on the library
|
| 170 |
+
search path; for CUDA 13.0, for example:
|
| 171 |
+
|
| 172 |
+
```bash
|
| 173 |
+
export CUDA_HOME=/usr/local/cuda-13.0
|
| 174 |
+
export PATH="$CUDA_HOME/bin:$PATH"
|
| 175 |
+
export LD_LIBRARY_PATH="$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}"
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
FP32 means 32-bit floating point and FP64 means 64-bit floating point; R2C is
|
| 179 |
+
real-to-complex and C2R is complex-to-real. FP32 requests retain FP64 intermediate
|
| 180 |
+
arithmetic. Against an FP64 reference, the elementwise limits are
|
| 181 |
+
`0.01 + 0.001*abs(reference)` for FP32 and `1e-6 + 1e-9*abs(reference)` for FP64.
|
| 182 |
+
|
| 183 |
+
**Across all 32 B200 workloads, the geometric mean is 1.1300x over direct cuFFT
|
| 184 |
+
3D plans and 1.3644x over default `torch.fft` in the same run.** A separate
|
| 185 |
+
complete run measured 1.1277x over direct 3D plans. Ratios below 1 mean the
|
| 186 |
+
published kernel is slower. In particular, FP32 forward averages 0.90x: the
|
| 187 |
+
B200 kernels are not faster on every workload.
|
| 188 |
+
|
| 189 |
+
Speedup over a single three-dimensional cuFFT plan, including the inverse
|
| 190 |
+
input-preservation copy:
|
| 191 |
+
|
| 192 |
+
| Grid (nz x ny x nx) | R2C FP32 | C2R FP32 | R2C FP64 | C2R FP64 |
|
| 193 |
+
|---|---:|---:|---:|---:|
|
| 194 |
+
| 32x32x32 | 2.00x | 2.03x | 2.20x | 2.26x |
|
| 195 |
+
| 64x64x64 | 0.88x | 1.07x | 1.11x | 1.26x |
|
| 196 |
+
| 80x80x112 | 0.92x | 1.20x | 1.15x | 1.35x |
|
| 197 |
+
| 96x96x96 | 1.04x | 1.25x | 1.40x | 1.56x |
|
| 198 |
+
| 96x96x144 | 0.84x | 0.97x | 1.06x | 1.21x |
|
| 199 |
+
| 128x128x128 | 0.74x | 0.88x | 1.13x | 1.31x |
|
| 200 |
+
| 192x192x192 | 0.69x | 0.80x | 1.00x | 1.15x |
|
| 201 |
+
| 256x256x256 | 0.58x | 0.76x | 1.00x | 1.34x |
|
| 202 |
+
| Geometric mean | 0.90x | 1.07x | 1.21x | 1.40x |
|
| 203 |
+
|
| 204 |
+
Latency in microseconds, **default torch.fft / direct cuFFT 3D / B200 kernel**:
|
| 205 |
+
|
| 206 |
+
| Grid (nz x ny x nx) | R2C FP32 | C2R FP32 | R2C FP64 | C2R FP64 |
|
| 207 |
+
|---|---:|---:|---:|---:|
|
| 208 |
+
| 32x32x32 | 13.76 / 11.25 / 5.63 | 9.28 / 11.58 / 5.70 | 14.40 / 12.03 / 5.47 | 10.16 / 11.84 / 5.23 |
|
| 209 |
+
| 64x64x64 | 12.22 / 9.33 / 10.61 | 11.58 / 11.87 / 11.06 | 14.46 / 11.71 / 10.56 | 12.37 / 14.64 / 11.58 |
|
| 210 |
+
| 80x80x112 | 17.50 / 12.58 / 13.60 | 14.59 / 16.61 / 13.86 | 21.63 / 16.53 / 14.40 | 19.36 / 19.42 / 14.40 |
|
| 211 |
+
| 96x96x96 | 21.41 / 15.49 / 14.85 | 17.26 / 18.75 / 15.01 | 29.18 / 21.79 / 15.52 | 22.66 / 25.50 / 16.32 |
|
| 212 |
+
| 96x96x144 | 22.78 / 14.46 / 17.31 | 19.58 / 17.68 / 18.14 | 29.42 / 19.42 / 18.27 | 28.30 / 24.19 / 19.92 |
|
| 213 |
+
| 128x128x128 | 27.68 / 17.63 / 23.71 | 31.01 / 21.49 / 24.38 | 38.30 / 28.80 / 25.47 | 35.62 / 34.69 / 26.43 |
|
| 214 |
+
| 192x192x192 | 75.89 / 49.07 / 71.26 | 74.74 / 60.61 / 76.18 | 114.82 / 78.06 / 78.14 | 120.77 / 98.59 / 85.38 |
|
| 215 |
+
| 256x256x256 | 167.39 / 85.92 / 148.10 | 139.47 / 106.83 / 140.30 | 259.99 / 160.02 / 160.51 | 195.57 / 206.66 / 154.58 |
|
| 216 |
+
|
| 217 |
+
Measured October 14, 2026, on an exclusive NVIDIA B200, driver 610.57.04,
|
| 218 |
+
PyTorch 2.13.0+cu130, Triton 3.7.1, and the same cuFFT 12.0 library shipped with
|
| 219 |
+
PyTorch. Each plan is created before timing with `cufftMakePlan3d`. Timing uses
|
| 220 |
+
NVIDIA's CUDA Profiling Tools Interface (CUPTI) on CUDA graph replay with a cold
|
| 221 |
+
L2 cache, 3 warmups and 30 samples per trial, and the median of 3 trial medians.
|
| 222 |
+
The order of implementations changes between trials. Allocation and planning
|
| 223 |
+
are excluded from GPU execution time; inverse input copies are included.
|
| 224 |
+
|
| 225 |
+
The previous B200 **1.410691x** figure compared against default `torch.fft`
|
| 226 |
+
using a CUDA-event timing protocol. It was not a direct-3D-cuFFT score and is
|
| 227 |
+
superseded as the native-baseline headline. It is not numerically interchangeable
|
| 228 |
+
with these graph measurements. In
|
| 229 |
+
[PyTorch 2.13's CUDA implementation](https://github.com/pytorch/pytorch/blob/v2.13.0/aten/src/ATen/native/cuda/SpectralOps.cpp#L305-L314),
|
| 230 |
+
transform axes starting with `(0, 1)` take two separate cuFFT plans. Adding a
|
| 231 |
+
batch dimension of size one and transforming axes `(1, 2, 3)` reaches the single
|
| 232 |
+
3D-plan path through PyTorch. That path and direct cuFFT had matching GPU-kernel
|
| 233 |
+
sequences in all eight profiled cases. A single 3D plan can still launch several
|
| 234 |
+
GPU kernels, and the split path is faster for some inverse sizes.
|
| 235 |
+
|
| 236 |
+
Inverse timings above use Hermitian-consistent spectra, as
|
| 237 |
+
[required by cuFFT](https://docs.nvidia.com/cuda/cufft/index.html#type-of-transform).
|
| 238 |
+
For arbitrary packed spectra accepted by the B200 implementation, directly
|
| 239 |
+
substituting cuFFT is not equivalent. Including a boundary-plane projection
|
| 240 |
+
with the input copy makes the direct baseline match that wider contract; the
|
| 241 |
+
32-workload geometric mean is then **1.1125x**. All 144 candidate numerical and
|
| 242 |
+
input-contract checks passed. Native FP32 cuFFT exceeds the historical strict
|
| 243 |
+
per-element tolerance on several constant-input stress cases; the timed random
|
| 244 |
+
inputs pass. The implementation retains its FP64 intermediate arithmetic.
|
| 245 |
+
|
| 246 |
+
## RTX 5090 source and licensing
|
| 247 |
|
| 248 |
Written by Claude Opus 5.5 in four 12-hour runs of KDA (kernel design agents),
|
| 249 |
one per KDA task (`fft3d_r2c`, `fft3d_c2r`, `fft3d_r2c_f64`, `fft3d_c2r_f64`),
|
|
|
|
| 253 |
its tests and the fixes made after the judge's checks. MIT-licensed.
|
| 254 |
|
| 255 |
## Available functions
|
| 256 |
+
|
| 257 |
- `c2r`
|
| 258 |
- `prepare_c2r`
|
| 259 |
- `prepare_r2c`
|
build/torch-cuda/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 the KDA authors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
build/torch-cuda/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""3D FFTs on packed Hermitian half-spectra for NVIDIA B200 and RTX 5090.
|
| 2 |
+
|
| 3 |
+
r2c(x): x is a contiguous float32 or float64 [nz, ny, nx] CUDA tensor (x fastest); returns a
|
| 4 |
+
new [nz, ny, nx // 2 + 1, 2] tensor of the same dtype, torch.view_as_real(torch.fft.rfftn(x)):
|
| 5 |
+
the unnormalized forward transform, packed with an x-row stride of exactly nx // 2 + 1.
|
| 6 |
+
|
| 7 |
+
c2r(X): X is such a half-spectrum, a contiguous float32 or float64 [nz, ny, nf, 2]; returns a
|
| 8 |
+
new real [nz, ny, 2 * (nf - 1)], torch.fft.irfftn(torch.view_as_complex(X), norm="forward"):
|
| 9 |
+
the unnormalized inverse, so c2r(r2c(x)) is nz * ny * nx * x. X is not modified.
|
| 10 |
+
|
| 11 |
+
prepare_r2c(x) / prepare_c2r(X) plan for exactly this input buffer and return launch(), which
|
| 12 |
+
transforms the buffer's current contents and returns an output tensor it reuses on every
|
| 13 |
+
call; keep launch alive as long as a CUDA graph captured from it. On RTX 5090, fp64 r2c has
|
| 14 |
+
no two-phase form, so its launch() is r2c(x) and returns a new tensor each call.
|
| 15 |
+
|
| 16 |
+
The implementation is selected by device and dtype, and imported on first use.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import functools
|
| 20 |
+
import importlib
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
|
| 24 |
+
__all__ = ["c2r", "prepare_c2r", "prepare_r2c", "r2c"]
|
| 25 |
+
|
| 26 |
+
_PACKAGES = {
|
| 27 |
+
("r2c", torch.float32): "_r2c_f32",
|
| 28 |
+
("r2c", torch.float64): "_r2c_f64",
|
| 29 |
+
("c2r", torch.float32): "_c2r_f32",
|
| 30 |
+
("c2r", torch.float64): "_c2r_f64",
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _kernel(direction, tensor):
|
| 35 |
+
package = _PACKAGES.get((direction, tensor.dtype))
|
| 36 |
+
if package is None:
|
| 37 |
+
raise TypeError(f"{direction} takes float32 or float64, not {tensor.dtype}")
|
| 38 |
+
if tensor.device.type != "cuda":
|
| 39 |
+
raise ValueError(f"{direction} requires a CUDA tensor")
|
| 40 |
+
if torch.cuda.get_device_capability(tensor.device) == (10, 0):
|
| 41 |
+
from ._b200 import kernel
|
| 42 |
+
return kernel(direction, tensor.dtype)
|
| 43 |
+
return importlib.import_module(f".{package}", __name__)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def r2c(x):
|
| 47 |
+
return _kernel("r2c", x).run(x)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def c2r(X):
|
| 51 |
+
return _kernel("c2r", X).run(X)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def prepare_r2c(x):
|
| 55 |
+
kernel = _kernel("r2c", x)
|
| 56 |
+
return kernel.prepare(x) if hasattr(kernel, "prepare") else functools.partial(kernel.run, x)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def prepare_c2r(X):
|
| 60 |
+
return _kernel("c2r", X).prepare(X)
|
build/torch-cuda/_b200/__init__.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Selected B200 FFT plans behind the shared public FFT3D interface."""
|
| 2 |
+
import importlib
|
| 3 |
+
import threading
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch._C import _cuda_getCurrentRawStream
|
| 7 |
+
|
| 8 |
+
from .prepared_call import Plan, load_module
|
| 9 |
+
|
| 10 |
+
_SHAPES = frozenset((
|
| 11 |
+
(32, 32, 32), (64, 64, 64), (80, 80, 112), (96, 96, 96),
|
| 12 |
+
(96, 96, 144), (128, 128, 128), (192, 192, 192), (256, 256, 256),
|
| 13 |
+
))
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _plan(module, shape, dtype, device, stream, **options):
|
| 17 |
+
cls = importlib.import_module('.' + module, __name__).Plan
|
| 18 |
+
return cls(shape, dtype, device=device, stream=stream, **options)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _owner(direction, shape, dtype, device, stream):
|
| 22 |
+
double = dtype == torch.float64
|
| 23 |
+
options = dict(device=device, stream=stream)
|
| 24 |
+
if direction == 'r2c':
|
| 25 |
+
if shape == (32, 32, 32):
|
| 26 |
+
return _plan('forward_small', shape, dtype, **options, direction='r2c',
|
| 27 |
+
rows_per_block=4, columns_per_block=4, num_warps=4,
|
| 28 |
+
real_strategy='fused_xy', dispatch='native')
|
| 29 |
+
if shape == (64, 64, 64):
|
| 30 |
+
return _plan('forward_cuda_f32', shape, dtype, **options, columns=8, warps=8)
|
| 31 |
+
if shape in ((128, 128, 128), (96, 96, 144)) or (not double and shape == (192, 192, 192)):
|
| 32 |
+
return _plan('forward_paired_y', shape, dtype, **options,
|
| 33 |
+
pair_order='yx', patient=shape == (96, 96, 144))
|
| 34 |
+
if double and shape in ((192, 192, 192), (256, 256, 256)):
|
| 35 |
+
aligned = _plan('forward_native', shape, dtype, **options, direction='r2c', variant='3d')
|
| 36 |
+
from .forward_scalar import Plan as ScalarPlan
|
| 37 |
+
return ScalarPlan(aligned)
|
| 38 |
+
return _plan('forward_half', shape, dtype, **options, direction='r2c',
|
| 39 |
+
axis='z', patient=shape in ((80, 80, 112), (96, 96, 96)), xt=False)
|
| 40 |
+
if shape == (32, 32, 32):
|
| 41 |
+
return _plan('inverse_small_f64' if double else 'inverse_small_f32', shape, dtype,
|
| 42 |
+
**options, direction='c2r', columns_per_block=4, num_warps=4)
|
| 43 |
+
if shape == (64, 64, 64):
|
| 44 |
+
return _plan('inverse_cuda_f64' if double else 'inverse_cuda_f32', shape, dtype,
|
| 45 |
+
**options, columns=8, warps=8)
|
| 46 |
+
if shape == (80, 80, 112):
|
| 47 |
+
if not double:
|
| 48 |
+
return _plan('inverse_pitched', shape, dtype, **options, variant='xy_z_patient')
|
| 49 |
+
aligned = _plan('inverse_narrow', shape, dtype, **options, direction='c2r',
|
| 50 |
+
strategy='xy_z', patient=True)
|
| 51 |
+
from .inverse_scalar import ScalarInputPlan
|
| 52 |
+
return ScalarInputPlan(aligned)
|
| 53 |
+
if shape == (96, 96, 96):
|
| 54 |
+
return _plan('inverse_partial_f32', shape, dtype, **options, variant='xy_z_patient')
|
| 55 |
+
if shape == (96, 96, 144):
|
| 56 |
+
return _plan('inverse_vector' if double else 'inverse_paired_y', shape, dtype,
|
| 57 |
+
**options, variant='3d_patient' if double else 'xy_z_patient')
|
| 58 |
+
if shape == (128, 128, 128):
|
| 59 |
+
return _plan('inverse_dense_f64' if double else 'inverse_dense_f32', shape, dtype,
|
| 60 |
+
**options, variant='xy_z')
|
| 61 |
+
if shape == (192, 192, 192):
|
| 62 |
+
return _plan('inverse_paired_y_f64' if double else 'inverse_paired_y_f32', shape, dtype,
|
| 63 |
+
**options, variant='xy_z_patient' if double else 'xy_z')
|
| 64 |
+
return _plan('inverse_paired_x_f32', shape, dtype, **options,
|
| 65 |
+
columns=8, warps=2, frequency=False, patient=False)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _shape(tensor, direction, dtype):
|
| 69 |
+
if tensor.dtype != dtype:
|
| 70 |
+
raise TypeError('Expected the requested floating-point dtype')
|
| 71 |
+
if tensor.device.type != 'cuda' or not tensor.is_contiguous():
|
| 72 |
+
raise ValueError('Expected a contiguous CUDA tensor')
|
| 73 |
+
if direction == 'r2c':
|
| 74 |
+
if tensor.ndim != 3:
|
| 75 |
+
raise ValueError('r2c expects [nz, ny, nx]')
|
| 76 |
+
shape = tuple(tensor.shape)
|
| 77 |
+
else:
|
| 78 |
+
if tensor.ndim != 4 or tensor.shape[-1] != 2:
|
| 79 |
+
raise ValueError('c2r expects [nz, ny, nx//2+1, 2]')
|
| 80 |
+
shape = (*tensor.shape[:2], 2 * (tensor.shape[2] - 1))
|
| 81 |
+
if shape not in _SHAPES:
|
| 82 |
+
raise ValueError(f'Unsupported B200 grid {shape}; expected one of {sorted(_SHAPES)}')
|
| 83 |
+
return shape
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class _Launch:
|
| 87 |
+
def __init__(self, tensor, plan):
|
| 88 |
+
self.tensor, self.plan = tensor, plan
|
| 89 |
+
self.output = torch.empty(plan.output_shape, dtype=plan.dtype, device=plan.device)
|
| 90 |
+
|
| 91 |
+
def __call__(self):
|
| 92 |
+
return self.plan.execute(self.tensor, self.output, stream=torch.cuda.current_stream(self.tensor.device))
|
| 93 |
+
|
| 94 |
+
def close(self):
|
| 95 |
+
"""Release resources after all work, including captured graphs, completes."""
|
| 96 |
+
self.plan.close()
|
| 97 |
+
|
| 98 |
+
def __del__(self):
|
| 99 |
+
if not self.plan.closed:
|
| 100 |
+
try:
|
| 101 |
+
self.plan.stream.synchronize()
|
| 102 |
+
self.plan.close()
|
| 103 |
+
except (RuntimeError, AttributeError):
|
| 104 |
+
pass
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class _Kernel:
|
| 108 |
+
def __init__(self, direction, dtype):
|
| 109 |
+
self.direction, self.dtype = direction, dtype
|
| 110 |
+
self._plans = {}
|
| 111 |
+
self._lock = threading.RLock()
|
| 112 |
+
self._runner = None
|
| 113 |
+
|
| 114 |
+
def _prepare(self, tensor):
|
| 115 |
+
shape = _shape(tensor, self.direction, self.dtype)
|
| 116 |
+
stream = torch.cuda.current_stream(tensor.device)
|
| 117 |
+
owner = _owner(self.direction, shape, self.dtype, tensor.device, stream)
|
| 118 |
+
try:
|
| 119 |
+
return Plan(owner)
|
| 120 |
+
except BaseException:
|
| 121 |
+
owner.close()
|
| 122 |
+
raise
|
| 123 |
+
|
| 124 |
+
def _cached(self, tensor):
|
| 125 |
+
key = (tuple(tensor.shape), tensor.device.index, _cuda_getCurrentRawStream(tensor.device.index))
|
| 126 |
+
with self._lock:
|
| 127 |
+
plan = self._plans.get(key)
|
| 128 |
+
if plan is None:
|
| 129 |
+
plan = self._prepare(tensor)
|
| 130 |
+
self._plans[key] = plan
|
| 131 |
+
return plan.run
|
| 132 |
+
|
| 133 |
+
def run(self, tensor):
|
| 134 |
+
if self._runner is None:
|
| 135 |
+
with self._lock:
|
| 136 |
+
if self._runner is None:
|
| 137 |
+
self._runner = load_module().bind_cache(self._cached,
|
| 138 |
+
8 if self.dtype == torch.float64 else 4, 3 if self.direction == 'r2c' else 4)
|
| 139 |
+
return self._runner(tensor)
|
| 140 |
+
|
| 141 |
+
def prepare(self, tensor):
|
| 142 |
+
"""Bind the current input buffer and reuse one output, matching the public API."""
|
| 143 |
+
return _Launch(tensor, self._prepare(tensor))
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
_KERNELS = {(direction, dtype): _Kernel(direction, dtype)
|
| 147 |
+
for direction in ('r2c', 'c2r') for dtype in (torch.float32, torch.float64)}
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def kernel(direction, dtype):
|
| 151 |
+
return _KERNELS[direction, dtype]
|
build/torch-cuda/_b200/c2r_cuda_z_inverse_cuda_kernel.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compile and load fixed CUDA FFT kernels only during plan preparation."""
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
import functools
|
| 4 |
+
import hashlib
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from types import SimpleNamespace
|
| 7 |
+
|
| 8 |
+
from .cuda_z_inverse_lto import library, version, _check
|
| 9 |
+
from .cuda_z_inverse_native_dispatch import load_driver
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@functools.lru_cache(maxsize=128)
|
| 13 |
+
def compile_kernel(shape, columns, warps, arch, device_ordinal):
|
| 14 |
+
# The ordinal keeps loaded CUDA modules within their owning device context.
|
| 15 |
+
nz, ny, nx = shape
|
| 16 |
+
base, odd = 1, nz
|
| 17 |
+
while odd % 2 == 0:
|
| 18 |
+
odd //= 2
|
| 19 |
+
base *= 2
|
| 20 |
+
if odd not in (1, 3, 5, 7) or base < warps*32//columns:
|
| 21 |
+
raise ValueError('Unsupported radix decomposition')
|
| 22 |
+
source = Path(__file__).with_name('c2r_cuda_z_inverse_fft_axis.cu').read_bytes()
|
| 23 |
+
options = ['--std=c++17', f'--gpu-architecture=sm_{arch}',
|
| 24 |
+
f'-DLENGTH={nz}', f'-DINNER={ny*(nx//2)}', f'-DBASE={base}',
|
| 25 |
+
f'-DLOG_BASE={base.bit_length()-1}', f'-DODD={odd}',
|
| 26 |
+
f'-DCOLUMNS={columns}', f'-DWARPS={warps}']
|
| 27 |
+
lib = library()
|
| 28 |
+
lib.nvrtcGetCUBINSize.argtypes = [ct.c_void_p, ct.POINTER(ct.c_size_t)]
|
| 29 |
+
lib.nvrtcGetCUBIN.argtypes = [ct.c_void_p, ct.c_void_p]
|
| 30 |
+
program = ct.c_void_p()
|
| 31 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'fft_axis.cu', 0, None, None))
|
| 32 |
+
try:
|
| 33 |
+
opts = (ct.c_char_p * len(options))(*(s.encode() for s in options))
|
| 34 |
+
code = lib.nvrtcCompileProgram(program, len(options), opts)
|
| 35 |
+
size = ct.c_size_t()
|
| 36 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(size)))
|
| 37 |
+
log = ct.create_string_buffer(size.value)
|
| 38 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 39 |
+
if code:
|
| 40 |
+
raise RuntimeError(log.value.decode())
|
| 41 |
+
_check(lib.nvrtcGetCUBINSize(program, ct.byref(size)))
|
| 42 |
+
blob = ct.create_string_buffer(size.value)
|
| 43 |
+
_check(lib.nvrtcGetCUBIN(program, blob))
|
| 44 |
+
finally:
|
| 45 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 46 |
+
driver = load_driver()
|
| 47 |
+
driver.cuModuleLoadData.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p]
|
| 48 |
+
driver.cuModuleGetFunction.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p, ct.c_char_p]
|
| 49 |
+
driver.cuFuncGetAttribute.argtypes = [ct.POINTER(ct.c_int), ct.c_int, ct.c_void_p]
|
| 50 |
+
module, function = ct.c_void_p(), ct.c_void_p()
|
| 51 |
+
_check(driver.cuModuleLoadData(ct.byref(module), blob))
|
| 52 |
+
_check(driver.cuModuleGetFunction(ct.byref(function), module, b'fft_axis'))
|
| 53 |
+
registers, local = ct.c_int(), ct.c_int()
|
| 54 |
+
_check(driver.cuFuncGetAttribute(ct.byref(registers), 4, function))
|
| 55 |
+
_check(driver.cuFuncGetAttribute(ct.byref(local), 3, function))
|
| 56 |
+
# local is CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, not an instruction count.
|
| 57 |
+
metadata = dict(options=options, nvrtc=version(), local_memory_bytes=local.value, source_sha256=hashlib.sha256(source).hexdigest(),
|
| 58 |
+
cubin_sha256=hashlib.sha256(blob.raw).hexdigest(), compiler_log=log.value.decode())
|
| 59 |
+
return SimpleNamespace(module=module, function=function.value, name='fft_axis',
|
| 60 |
+
hash=metadata['cubin_sha256'], threads=32*warps, shared=0,
|
| 61 |
+
n_regs=registers.value, n_spills=local.value,
|
| 62 |
+
grid=((ny*(nx//2)+columns-1)//columns, 1, 1),
|
| 63 |
+
compiler_metadata=metadata)
|
build/torch-cuda/_b200/c2r_cuda_z_inverse_fft_axis.cu
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Exact mathematical extents; each warp owns independent adjacent columns.
|
| 3 |
+
__device__ __forceinline__ double2 add(double2 a, double2 b) {
|
| 4 |
+
return make_double2(a.x + b.x, a.y + b.y);
|
| 5 |
+
}
|
| 6 |
+
__device__ __forceinline__ double2 sub(double2 a, double2 b) {
|
| 7 |
+
return make_double2(a.x - b.x, a.y - b.y);
|
| 8 |
+
}
|
| 9 |
+
__device__ __forceinline__ double2 mul(double2 a, double2 b) {
|
| 10 |
+
return make_double2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x);
|
| 11 |
+
}
|
| 12 |
+
template<int H, int N, int G, int C, int Bound>
|
| 13 |
+
__device__ __forceinline__ void stage(double2 (&a)[N/G], const double2 *twiddle,
|
| 14 |
+
int lane_z, double2 *exchange) {
|
| 15 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 16 |
+
#pragma unroll
|
| 17 |
+
for (int r = 0; r < N/G; ++r) {
|
| 18 |
+
if ((r % (4*H/G)) < H/G) {
|
| 19 |
+
double2 a0 = a[r], a1 = a[r+H/G], a2 = a[r+2*H/G], a3 = a[r+3*H/G];
|
| 20 |
+
int k = (r*G+lane_z) % H;
|
| 21 |
+
double2 w1=twiddle[k*(N/(4*H))]; w1.y=-w1.y;
|
| 22 |
+
double2 w2=twiddle[2*k*(N/(4*H))]; w2.y=-w2.y;
|
| 23 |
+
double2 w3=twiddle[3*k*(N/(4*H))]; w3.y=-w3.y;
|
| 24 |
+
// Input is binary bit reversed, so the middle branches swap.
|
| 25 |
+
double2 b1=mul(a2,w1), b2=mul(a1,w2), b3=mul(a3,w3);
|
| 26 |
+
double2 even=add(a0,b2), even_diff=sub(a0,b2);
|
| 27 |
+
double2 odd=add(b1,b3), odd_diff=sub(b1,b3);
|
| 28 |
+
double2 turn=make_double2(-odd_diff.y,odd_diff.x);
|
| 29 |
+
a[r]=add(even,odd); a[r+H/G]=add(even_diff,turn);
|
| 30 |
+
a[r+2*H/G]=sub(even,odd); a[r+3*H/G]=sub(even_diff,turn);
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
} else if constexpr (H < G) {
|
| 34 |
+
if constexpr (H*C >= 32) {
|
| 35 |
+
#pragma unroll
|
| 36 |
+
for (int r = 0; r < N/G; ++r) exchange[r*blockDim.x+threadIdx.x]=a[r];
|
| 37 |
+
__syncthreads();
|
| 38 |
+
}
|
| 39 |
+
#pragma unroll
|
| 40 |
+
for (int r = 0; r < N/G; ++r) {
|
| 41 |
+
double2 v;
|
| 42 |
+
if constexpr (H*C < 32) {
|
| 43 |
+
v = make_double2(__shfl_xor_sync(0xffffffff, a[r].x, H*C),
|
| 44 |
+
__shfl_xor_sync(0xffffffff, a[r].y, H*C));
|
| 45 |
+
} else {
|
| 46 |
+
v = exchange[r*blockDim.x+(threadIdx.x ^ (H*C))];
|
| 47 |
+
}
|
| 48 |
+
double2 lo = (lane_z & H) ? v : a[r];
|
| 49 |
+
double2 hi = (lane_z & H) ? a[r] : v;
|
| 50 |
+
if constexpr (H > 1) {
|
| 51 |
+
double2 w = twiddle[(lane_z & (H-1)) * (N/(2*H))];
|
| 52 |
+
w.y = -w.y;
|
| 53 |
+
hi = mul(hi, w);
|
| 54 |
+
}
|
| 55 |
+
a[r] = (lane_z & H) ? sub(lo, hi) : add(lo, hi);
|
| 56 |
+
}
|
| 57 |
+
if constexpr (H*C >= 32) __syncthreads();
|
| 58 |
+
} else {
|
| 59 |
+
#pragma unroll
|
| 60 |
+
for (int r = 0; r < N/G; ++r) {
|
| 61 |
+
if ((r & (H/G)) == 0) {
|
| 62 |
+
double2 lo = a[r], hi = a[r + H/G];
|
| 63 |
+
double2 w = twiddle[((r*G + lane_z) & (H-1)) * (N/(2*H))];
|
| 64 |
+
w.y = -w.y;
|
| 65 |
+
hi = mul(hi, w);
|
| 66 |
+
a[r] = add(lo, hi);
|
| 67 |
+
a[r + H/G] = sub(lo, hi);
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 72 |
+
if constexpr (4*H < Bound) stage<4*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 73 |
+
} else if constexpr (2*H < Bound) stage<2*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
extern "C" __global__ __launch_bounds__(WARPS*32)
|
| 77 |
+
void fft_axis(const double2 *__restrict__ input, float *__restrict__ output,
|
| 78 |
+
const double2 *__restrict__ twiddle, void *, void *) {
|
| 79 |
+
constexpr int G = WARPS*32/COLUMNS, R = LENGTH/G;
|
| 80 |
+
__shared__ double2 exchange[LENGTH*COLUMNS];
|
| 81 |
+
int lane_z = threadIdx.x / COLUMNS;
|
| 82 |
+
int column = blockIdx.x * COLUMNS + threadIdx.x % COLUMNS;
|
| 83 |
+
double2 a[R];
|
| 84 |
+
#pragma unroll
|
| 85 |
+
for (int r = 0; r < R; ++r) {
|
| 86 |
+
int index = r*G + lane_z;
|
| 87 |
+
int source = (__brev((unsigned)(index % BASE)) >> (32-LOG_BASE))*ODD
|
| 88 |
+
+ index/BASE;
|
| 89 |
+
a[r] = column < INNER ? input[source*INNER + column] : make_double2(0., 0.);
|
| 90 |
+
}
|
| 91 |
+
stage<1, LENGTH, G, COLUMNS, BASE>(a, twiddle, lane_z, exchange);
|
| 92 |
+
if constexpr (ODD != 1) {
|
| 93 |
+
#pragma unroll
|
| 94 |
+
for (int r = 0; r < BASE/G; ++r) {
|
| 95 |
+
double2 b[ODD];
|
| 96 |
+
b[0] = a[r];
|
| 97 |
+
#pragma unroll
|
| 98 |
+
for (int t = 1; t < ODD; ++t) {
|
| 99 |
+
double2 w = twiddle[t*(r*G+lane_z)];
|
| 100 |
+
w.y = -w.y;
|
| 101 |
+
b[t] = mul(a[r+t*(BASE/G)], w);
|
| 102 |
+
}
|
| 103 |
+
#pragma unroll
|
| 104 |
+
for (int k = 0; k < ODD; ++k) {
|
| 105 |
+
double2 v = b[0];
|
| 106 |
+
#pragma unroll
|
| 107 |
+
for (int t = 1; t < ODD; ++t) {
|
| 108 |
+
double2 w = twiddle[((k*t)%ODD)*BASE];
|
| 109 |
+
w.y = -w.y;
|
| 110 |
+
v = add(v, mul(b[t], w));
|
| 111 |
+
}
|
| 112 |
+
a[r+k*(BASE/G)] = v;
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
#pragma unroll
|
| 117 |
+
for (int r = 0; r < R; ++r) {
|
| 118 |
+
if (column < INNER) {
|
| 119 |
+
int offset = 2*((r*G+lane_z)*INNER+column);
|
| 120 |
+
if ((reinterpret_cast<unsigned long long>(output) & 7ULL) == 0) {
|
| 121 |
+
reinterpret_cast<float2 *>(output)[offset/2] = make_float2((float)a[r].x, (float)a[r].y);
|
| 122 |
+
} else {
|
| 123 |
+
output[offset] = (float)a[r].x;
|
| 124 |
+
output[offset+1] = (float)a[r].y;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
}
|
build/torch-cuda/_b200/c2r_dense_half_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Select index width from the complete physical packed extent at compilation.
|
| 3 |
+
// The callback ABI remains 64-bit. Every valid caller index fits the selected
|
| 4 |
+
// unsigned type; pointer arithmetic still computes full-width device addresses.
|
| 5 |
+
#if (1ULL * FFT_NZ * FFT_NY * (FFT_N / 2 + 1)) <= 0x100000000ULL
|
| 6 |
+
using fft_index_t = unsigned int;
|
| 7 |
+
static_assert(sizeof(fft_index_t) == 4, "A 32-bit unsigned integer is required");
|
| 8 |
+
#else
|
| 9 |
+
using fft_index_t = unsigned long long;
|
| 10 |
+
#endif
|
| 11 |
+
#if FFT_DOUBLE
|
| 12 |
+
using caller_scalar_t = double;
|
| 13 |
+
#else
|
| 14 |
+
using caller_scalar_t = float;
|
| 15 |
+
#endif
|
| 16 |
+
__device__ double2 fft_scalar_pair(const caller_scalar_t *input, fft_index_t index) {
|
| 17 |
+
double2 value;
|
| 18 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 19 |
+
value = reinterpret_cast<const double2 *>(input)[index];
|
| 20 |
+
#elif !FFT_DOUBLE && (FFT_INPUT_SHIFT % 2 == 0)
|
| 21 |
+
// The pair address is aligned for scalar residues zero and two.
|
| 22 |
+
const float2 packed = reinterpret_cast<const float2 *>(input)[index];
|
| 23 |
+
value.x = static_cast<double>(packed.x);
|
| 24 |
+
value.y = static_cast<double>(packed.y);
|
| 25 |
+
#else
|
| 26 |
+
value.x = static_cast<double>(input[2ULL * index]);
|
| 27 |
+
value.y = static_cast<double>(input[2ULL * index + 1]);
|
| 28 |
+
#endif
|
| 29 |
+
return value;
|
| 30 |
+
}
|
| 31 |
+
// The cuFFT logical input is dense. Callback arithmetic reads exact nf-stride
|
| 32 |
+
// caller storage directly; no input repacking is performed.
|
| 33 |
+
// Reconstruct twice the half-length complex spectrum from immutable packed
|
| 34 |
+
// caller input. Endpoint projection preserves irfftn for arbitrary spectra.
|
| 35 |
+
#if FFT_LOAD
|
| 36 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 37 |
+
void *caller_info, void *shared_memory) {
|
| 38 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 39 |
+
constexpr fft_index_t nf = m + 1;
|
| 40 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 41 |
+
const fft_index_t k = index % m;
|
| 42 |
+
const fft_index_t row = index / m;
|
| 43 |
+
const fft_index_t y = row % FFT_NY;
|
| 44 |
+
const fft_index_t z = row / FFT_NY;
|
| 45 |
+
const fft_index_t mirror_y = y == 0 ? 0 : FFT_NY - y;
|
| 46 |
+
const fft_index_t mirror_z = z == 0 ? 0 : FFT_NZ - z;
|
| 47 |
+
const fft_index_t mirror_row = mirror_z * FFT_NY + mirror_y;
|
| 48 |
+
const caller_scalar_t *input = static_cast<const caller_scalar_t *>(data) + FFT_INPUT_SHIFT;
|
| 49 |
+
double2 a = fft_scalar_pair(input, row * nf + k);
|
| 50 |
+
double2 b = fft_scalar_pair(input, mirror_row * nf + m - k);
|
| 51 |
+
if (k == 0) {
|
| 52 |
+
// Project each boundary plane against its Z/Y conjugate partner.
|
| 53 |
+
// b represents the conjugate of the projected Nyquist value.
|
| 54 |
+
const double2 a_mirror = fft_scalar_pair(input, mirror_row * nf);
|
| 55 |
+
const double2 b_mirror = fft_scalar_pair(input, row * nf + m);
|
| 56 |
+
a.x = 0.5 * (a.x + a_mirror.x);
|
| 57 |
+
a.y = 0.5 * (a.y - a_mirror.y);
|
| 58 |
+
b.x = 0.5 * (b.x + b_mirror.x);
|
| 59 |
+
b.y = 0.5 * (b.y - b_mirror.y);
|
| 60 |
+
}
|
| 61 |
+
const double2 w = static_cast<const double2 *>(caller_info)[k];
|
| 62 |
+
double2 result;
|
| 63 |
+
result.x = a.x + b.x - w.x * (a.y + b.y) + w.y * (a.x - b.x);
|
| 64 |
+
result.y = a.y - b.y + w.x * (a.x - b.x) + w.y * (a.y + b.y);
|
| 65 |
+
return result;
|
| 66 |
+
}
|
| 67 |
+
#else
|
| 68 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 69 |
+
double2 value, void *caller_info, void *shared_memory) {
|
| 70 |
+
caller_scalar_t *output = static_cast<caller_scalar_t *>(physical_output) + FFT_INPUT_SHIFT;
|
| 71 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 72 |
+
// A nominal cuFFT output address is complex aligned.
|
| 73 |
+
reinterpret_cast<double2 *>(output)[offset] = value;
|
| 74 |
+
#elif !FFT_DOUBLE && (FFT_INPUT_SHIFT % 2 == 0)
|
| 75 |
+
// Both components fit one aligned 64-bit store at these scalar residues.
|
| 76 |
+
float2 packed;
|
| 77 |
+
packed.x = static_cast<float>(value.x);
|
| 78 |
+
packed.y = static_cast<float>(value.y);
|
| 79 |
+
reinterpret_cast<float2 *>(output)[offset] = packed;
|
| 80 |
+
#else
|
| 81 |
+
output[2 * offset] = static_cast<caller_scalar_t>(value.x);
|
| 82 |
+
output[2 * offset + 1] = static_cast<caller_scalar_t>(value.y);
|
| 83 |
+
#endif
|
| 84 |
+
}
|
| 85 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_cuda_kernel.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compile and load fixed CUDA FFT kernels only during plan preparation."""
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
import functools
|
| 4 |
+
import hashlib
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from types import SimpleNamespace
|
| 7 |
+
|
| 8 |
+
from .c2r_f64_cuda_z_inverse_lto import library, version, _check
|
| 9 |
+
from .c2r_f64_cuda_z_inverse_native_dispatch import load_driver
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@functools.lru_cache(maxsize=128)
|
| 13 |
+
def compile_kernel(shape, columns, warps, arch, device_ordinal):
|
| 14 |
+
# The ordinal keeps loaded CUDA modules within their owning device context.
|
| 15 |
+
nz, ny, nx = shape
|
| 16 |
+
base, odd = 1, nz
|
| 17 |
+
while odd % 2 == 0:
|
| 18 |
+
odd //= 2
|
| 19 |
+
base *= 2
|
| 20 |
+
if odd not in (1, 3, 5, 7) or base < warps*32//columns:
|
| 21 |
+
raise ValueError('Unsupported radix decomposition')
|
| 22 |
+
source = Path(__file__).with_name('c2r_f64_cuda_z_inverse_fft_axis.cu').read_bytes()
|
| 23 |
+
options = ['--std=c++17', f'--gpu-architecture=sm_{arch}',
|
| 24 |
+
f'-DLENGTH={nz}', f'-DINNER={ny*(nx//2)}', f'-DBASE={base}',
|
| 25 |
+
f'-DLOG_BASE={base.bit_length()-1}', f'-DODD={odd}',
|
| 26 |
+
f'-DCOLUMNS={columns}', f'-DWARPS={warps}']
|
| 27 |
+
lib = library()
|
| 28 |
+
lib.nvrtcGetCUBINSize.argtypes = [ct.c_void_p, ct.POINTER(ct.c_size_t)]
|
| 29 |
+
lib.nvrtcGetCUBIN.argtypes = [ct.c_void_p, ct.c_void_p]
|
| 30 |
+
program = ct.c_void_p()
|
| 31 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'fft_axis.cu', 0, None, None))
|
| 32 |
+
try:
|
| 33 |
+
opts = (ct.c_char_p * len(options))(*(s.encode() for s in options))
|
| 34 |
+
code = lib.nvrtcCompileProgram(program, len(options), opts)
|
| 35 |
+
size = ct.c_size_t()
|
| 36 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(size)))
|
| 37 |
+
log = ct.create_string_buffer(size.value)
|
| 38 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 39 |
+
if code:
|
| 40 |
+
raise RuntimeError(log.value.decode())
|
| 41 |
+
_check(lib.nvrtcGetCUBINSize(program, ct.byref(size)))
|
| 42 |
+
blob = ct.create_string_buffer(size.value)
|
| 43 |
+
_check(lib.nvrtcGetCUBIN(program, blob))
|
| 44 |
+
finally:
|
| 45 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 46 |
+
driver = load_driver()
|
| 47 |
+
driver.cuModuleLoadData.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p]
|
| 48 |
+
driver.cuModuleGetFunction.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p, ct.c_char_p]
|
| 49 |
+
driver.cuFuncGetAttribute.argtypes = [ct.POINTER(ct.c_int), ct.c_int, ct.c_void_p]
|
| 50 |
+
module, function = ct.c_void_p(), ct.c_void_p()
|
| 51 |
+
_check(driver.cuModuleLoadData(ct.byref(module), blob))
|
| 52 |
+
_check(driver.cuModuleGetFunction(ct.byref(function), module, b'fft_axis'))
|
| 53 |
+
registers, local = ct.c_int(), ct.c_int()
|
| 54 |
+
_check(driver.cuFuncGetAttribute(ct.byref(registers), 4, function))
|
| 55 |
+
_check(driver.cuFuncGetAttribute(ct.byref(local), 3, function))
|
| 56 |
+
# local is CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, not an instruction count.
|
| 57 |
+
metadata = dict(options=options, nvrtc=version(), local_memory_bytes=local.value, source_sha256=hashlib.sha256(source).hexdigest(),
|
| 58 |
+
cubin_sha256=hashlib.sha256(blob.raw).hexdigest(), compiler_log=log.value.decode())
|
| 59 |
+
return SimpleNamespace(module=module, function=function.value, name='fft_axis',
|
| 60 |
+
hash=metadata['cubin_sha256'], threads=32*warps, shared=0,
|
| 61 |
+
n_regs=registers.value, n_spills=local.value,
|
| 62 |
+
grid=((ny*(nx//2)+columns-1)//columns, 1, 1),
|
| 63 |
+
compiler_metadata=metadata)
|
build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_fft_axis.cu
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Exact mathematical extents; each warp owns independent adjacent columns.
|
| 3 |
+
__device__ __forceinline__ double2 add(double2 a, double2 b) {
|
| 4 |
+
return make_double2(a.x + b.x, a.y + b.y);
|
| 5 |
+
}
|
| 6 |
+
__device__ __forceinline__ double2 sub(double2 a, double2 b) {
|
| 7 |
+
return make_double2(a.x - b.x, a.y - b.y);
|
| 8 |
+
}
|
| 9 |
+
__device__ __forceinline__ double2 mul(double2 a, double2 b) {
|
| 10 |
+
return make_double2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x);
|
| 11 |
+
}
|
| 12 |
+
template<int H, int N, int G, int C, int Bound>
|
| 13 |
+
__device__ __forceinline__ void stage(double2 (&a)[N/G], const double2 *twiddle,
|
| 14 |
+
int lane_z, double2 *exchange) {
|
| 15 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 16 |
+
#pragma unroll
|
| 17 |
+
for (int r = 0; r < N/G; ++r) {
|
| 18 |
+
if ((r % (4*H/G)) < H/G) {
|
| 19 |
+
double2 a0 = a[r], a1 = a[r+H/G], a2 = a[r+2*H/G], a3 = a[r+3*H/G];
|
| 20 |
+
int k = (r*G+lane_z) % H;
|
| 21 |
+
double2 w1=twiddle[k*(N/(4*H))]; w1.y=-w1.y;
|
| 22 |
+
double2 w2=twiddle[2*k*(N/(4*H))]; w2.y=-w2.y;
|
| 23 |
+
double2 w3=twiddle[3*k*(N/(4*H))]; w3.y=-w3.y;
|
| 24 |
+
// Input is binary bit reversed, so the middle branches swap.
|
| 25 |
+
double2 b1=mul(a2,w1), b2=mul(a1,w2), b3=mul(a3,w3);
|
| 26 |
+
double2 even=add(a0,b2), even_diff=sub(a0,b2);
|
| 27 |
+
double2 odd=add(b1,b3), odd_diff=sub(b1,b3);
|
| 28 |
+
double2 turn=make_double2(-odd_diff.y,odd_diff.x);
|
| 29 |
+
a[r]=add(even,odd); a[r+H/G]=add(even_diff,turn);
|
| 30 |
+
a[r+2*H/G]=sub(even,odd); a[r+3*H/G]=sub(even_diff,turn);
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
} else if constexpr (H < G) {
|
| 34 |
+
if constexpr (H*C >= 32) {
|
| 35 |
+
#pragma unroll
|
| 36 |
+
for (int r = 0; r < N/G; ++r) exchange[r*blockDim.x+threadIdx.x]=a[r];
|
| 37 |
+
__syncthreads();
|
| 38 |
+
}
|
| 39 |
+
#pragma unroll
|
| 40 |
+
for (int r = 0; r < N/G; ++r) {
|
| 41 |
+
double2 v;
|
| 42 |
+
if constexpr (H*C < 32) {
|
| 43 |
+
v = make_double2(__shfl_xor_sync(0xffffffff, a[r].x, H*C),
|
| 44 |
+
__shfl_xor_sync(0xffffffff, a[r].y, H*C));
|
| 45 |
+
} else {
|
| 46 |
+
v = exchange[r*blockDim.x+(threadIdx.x ^ (H*C))];
|
| 47 |
+
}
|
| 48 |
+
double2 lo = (lane_z & H) ? v : a[r];
|
| 49 |
+
double2 hi = (lane_z & H) ? a[r] : v;
|
| 50 |
+
if constexpr (H > 1) {
|
| 51 |
+
double2 w = twiddle[(lane_z & (H-1)) * (N/(2*H))];
|
| 52 |
+
w.y = -w.y;
|
| 53 |
+
hi = mul(hi, w);
|
| 54 |
+
}
|
| 55 |
+
a[r] = (lane_z & H) ? sub(lo, hi) : add(lo, hi);
|
| 56 |
+
}
|
| 57 |
+
if constexpr (H*C >= 32) __syncthreads();
|
| 58 |
+
} else {
|
| 59 |
+
#pragma unroll
|
| 60 |
+
for (int r = 0; r < N/G; ++r) {
|
| 61 |
+
if ((r & (H/G)) == 0) {
|
| 62 |
+
double2 lo = a[r], hi = a[r + H/G];
|
| 63 |
+
double2 w = twiddle[((r*G + lane_z) & (H-1)) * (N/(2*H))];
|
| 64 |
+
w.y = -w.y;
|
| 65 |
+
hi = mul(hi, w);
|
| 66 |
+
a[r] = add(lo, hi);
|
| 67 |
+
a[r + H/G] = sub(lo, hi);
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 72 |
+
if constexpr (4*H < Bound) stage<4*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 73 |
+
} else if constexpr (2*H < Bound) stage<2*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
extern "C" __global__ __launch_bounds__(WARPS*32)
|
| 77 |
+
void fft_axis(const double2 *__restrict__ input, double *__restrict__ output,
|
| 78 |
+
const double2 *__restrict__ twiddle, void *, void *) {
|
| 79 |
+
constexpr int G = WARPS*32/COLUMNS, R = LENGTH/G;
|
| 80 |
+
__shared__ double2 exchange[LENGTH*COLUMNS];
|
| 81 |
+
int lane_z = threadIdx.x / COLUMNS;
|
| 82 |
+
int column = blockIdx.x * COLUMNS + threadIdx.x % COLUMNS;
|
| 83 |
+
double2 a[R];
|
| 84 |
+
#pragma unroll
|
| 85 |
+
for (int r = 0; r < R; ++r) {
|
| 86 |
+
int index = r*G + lane_z;
|
| 87 |
+
int source = (__brev((unsigned)(index % BASE)) >> (32-LOG_BASE))*ODD
|
| 88 |
+
+ index/BASE;
|
| 89 |
+
a[r] = column < INNER ? input[source*INNER + column] : make_double2(0., 0.);
|
| 90 |
+
}
|
| 91 |
+
stage<1, LENGTH, G, COLUMNS, BASE>(a, twiddle, lane_z, exchange);
|
| 92 |
+
if constexpr (ODD != 1) {
|
| 93 |
+
#pragma unroll
|
| 94 |
+
for (int r = 0; r < BASE/G; ++r) {
|
| 95 |
+
double2 b[ODD];
|
| 96 |
+
b[0] = a[r];
|
| 97 |
+
#pragma unroll
|
| 98 |
+
for (int t = 1; t < ODD; ++t) {
|
| 99 |
+
double2 w = twiddle[t*(r*G+lane_z)];
|
| 100 |
+
w.y = -w.y;
|
| 101 |
+
b[t] = mul(a[r+t*(BASE/G)], w);
|
| 102 |
+
}
|
| 103 |
+
#pragma unroll
|
| 104 |
+
for (int k = 0; k < ODD; ++k) {
|
| 105 |
+
double2 v = b[0];
|
| 106 |
+
#pragma unroll
|
| 107 |
+
for (int t = 1; t < ODD; ++t) {
|
| 108 |
+
double2 w = twiddle[((k*t)%ODD)*BASE];
|
| 109 |
+
w.y = -w.y;
|
| 110 |
+
v = add(v, mul(b[t], w));
|
| 111 |
+
}
|
| 112 |
+
a[r+k*(BASE/G)] = v;
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
#pragma unroll
|
| 117 |
+
for (int r = 0; r < R; ++r) {
|
| 118 |
+
if (column < INNER) {
|
| 119 |
+
int offset = 2*((r*G+lane_z)*INNER+column);
|
| 120 |
+
if ((reinterpret_cast<unsigned long long>(output) & 15ULL) == 0) {
|
| 121 |
+
reinterpret_cast<double2 *>(output)[offset/2] = a[r];
|
| 122 |
+
} else {
|
| 123 |
+
output[offset] = a[r].x;
|
| 124 |
+
output[offset+1] = a[r].y;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
}
|
build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_lto.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile cuFFT LTO callbacks with the installed NVRTC, before execution.
|
| 3 |
+
|
| 4 |
+
This module can compile offline without initializing CUDA or loading code on
|
| 5 |
+
a GPU. Compiled blobs are cached by source, public shape, precision, NVRTC
|
| 6 |
+
version and target. No input tensor values or pointers are cached.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import ctypes as ct
|
| 11 |
+
import ctypes.util
|
| 12 |
+
import functools
|
| 13 |
+
import hashlib
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_LOCK = threading.Lock()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@functools.lru_cache(maxsize=1)
|
| 22 |
+
def library():
|
| 23 |
+
candidates = [ctypes.util.find_library('nvrtc')]
|
| 24 |
+
for root in (Path('/usr/local/cuda/lib64'),
|
| 25 |
+
Path('/usr/local/cuda/targets/x86_64-linux/lib')):
|
| 26 |
+
candidates.extend(str(p) for p in sorted(root.glob('libnvrtc.so*')))
|
| 27 |
+
candidates.extend(('libnvrtc.so.13', 'libnvrtc.so.12', 'libnvrtc.so'))
|
| 28 |
+
lib = None
|
| 29 |
+
for name in candidates:
|
| 30 |
+
if name:
|
| 31 |
+
try:
|
| 32 |
+
lib = ct.CDLL(name)
|
| 33 |
+
break
|
| 34 |
+
except OSError:
|
| 35 |
+
pass
|
| 36 |
+
if lib is None:
|
| 37 |
+
raise RuntimeError('The installed NVRTC library is unavailable')
|
| 38 |
+
signatures = {
|
| 39 |
+
'nvrtcVersion': [ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)],
|
| 40 |
+
'nvrtcCreateProgram': [ct.POINTER(ct.c_void_p), ct.c_char_p, ct.c_char_p,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)],
|
| 42 |
+
'nvrtcCompileProgram': [ct.c_void_p, ct.c_int, ct.POINTER(ct.c_char_p)],
|
| 43 |
+
'nvrtcGetProgramLogSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 44 |
+
'nvrtcGetProgramLog': [ct.c_void_p, ct.c_void_p],
|
| 45 |
+
'nvrtcGetLTOIRSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 46 |
+
'nvrtcGetLTOIR': [ct.c_void_p, ct.c_void_p],
|
| 47 |
+
'nvrtcDestroyProgram': [ct.POINTER(ct.c_void_p)],
|
| 48 |
+
}
|
| 49 |
+
for name, signature in signatures.items():
|
| 50 |
+
function = getattr(lib, name)
|
| 51 |
+
function.argtypes = signature
|
| 52 |
+
function.restype = ct.c_int
|
| 53 |
+
return lib
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _check(result):
|
| 57 |
+
if result:
|
| 58 |
+
raise RuntimeError(f'NVRTC returned error {result}')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def version():
|
| 62 |
+
major, minor = ct.c_int(), ct.c_int()
|
| 63 |
+
_check(library().nvrtcVersion(ct.byref(major), ct.byref(minor)))
|
| 64 |
+
return major.value, minor.value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@functools.lru_cache(maxsize=128)
|
| 68 |
+
def _compile(source, shape, double, arch, nvrtc_version, input_shift, kind):
|
| 69 |
+
nz, ny, nx = shape
|
| 70 |
+
lib = library()
|
| 71 |
+
options = ('--std=c++17', '--relocatable-device-code=true', '-dlto',
|
| 72 |
+
f'--gpu-architecture=compute_{arch}', f'-DFFT_N={nx}',
|
| 73 |
+
f'-DFFT_NZ={nz}', f'-DFFT_NY={ny}',
|
| 74 |
+
f'-DFFT_DOUBLE={int(double)}', f'-DFFT_INPUT_SHIFT={input_shift}',
|
| 75 |
+
f'-DFFT_LOAD={int(kind == "load")}')
|
| 76 |
+
program = ct.c_void_p()
|
| 77 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'endpoint.cu', 0, None, None))
|
| 78 |
+
try:
|
| 79 |
+
option_array = (ct.c_char_p * len(options))(*(v.encode() for v in options))
|
| 80 |
+
result = lib.nvrtcCompileProgram(program, len(options), option_array)
|
| 81 |
+
log_size = ct.c_size_t()
|
| 82 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(log_size)))
|
| 83 |
+
log = ct.create_string_buffer(log_size.value)
|
| 84 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 85 |
+
if result:
|
| 86 |
+
raise RuntimeError(f'NVRTC returned error {result}: {log.value.decode()}')
|
| 87 |
+
size = ct.c_size_t()
|
| 88 |
+
_check(lib.nvrtcGetLTOIRSize(program, ct.byref(size)))
|
| 89 |
+
data = ct.create_string_buffer(size.value)
|
| 90 |
+
_check(lib.nvrtcGetLTOIR(program, data))
|
| 91 |
+
blob = data.raw
|
| 92 |
+
metadata = dict(shape=shape, double=bool(double), target=f'compute_{arch}',
|
| 93 |
+
nvrtc=list(nvrtc_version), options=list(options),
|
| 94 |
+
source_sha256=hashlib.sha256(source).hexdigest(),
|
| 95 |
+
lto_ir_sha256=hashlib.sha256(blob).hexdigest(),
|
| 96 |
+
lto_ir_bytes=len(blob), compiler_log=log.value.decode())
|
| 97 |
+
return blob, metadata
|
| 98 |
+
finally:
|
| 99 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def compile_callback(shape, input_shift, kind, arch=100):
|
| 103 |
+
"""Reconstruct a half-length spectrum in FP64 or store its inverse as FP64."""
|
| 104 |
+
if kind not in ('load', 'store'):
|
| 105 |
+
raise ValueError('Expected a load or store callback')
|
| 106 |
+
source = Path(__file__).with_name('c2r_f64_cuda_z_inverse_wide_redirect.cu').read_bytes()
|
| 107 |
+
with _LOCK:
|
| 108 |
+
blob, metadata = _compile(source, tuple(shape), True, int(arch), version(), input_shift, kind)
|
| 109 |
+
return (('fft_wide_' + kind).encode(), 1 if kind == 'load' else 5,
|
| 110 |
+
ct.create_string_buffer(blob, len(blob)), dict(metadata, kind=kind))
|
build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_native_dispatch.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Prepare a combined cuFFT and CUDA dispatcher before execution."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ctypes as ct
|
| 6 |
+
import fcntl
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import shlex
|
| 11 |
+
import subprocess
|
| 12 |
+
import tempfile
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
class Descriptor(ct.Structure):
|
| 16 |
+
_fields_ = [('complex_exec', ct.c_void_p), ('launch', ct.c_void_p),
|
| 17 |
+
('handle', ct.c_int), ('intermediate', ct.c_uint64),
|
| 18 |
+
('twiddle', ct.c_uint64), ('stream', ct.c_void_p),
|
| 19 |
+
('function', ct.c_void_p), ('grid', ct.c_uint * 3),
|
| 20 |
+
('threads', ct.c_uint), ('shared', ct.c_uint)]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
_LIBRARY = None
|
| 24 |
+
_DRIVER = None
|
| 25 |
+
_LOCK = threading.Lock()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def load_library():
|
| 29 |
+
"""Build with the installed host compiler and validate structure size."""
|
| 30 |
+
global _LIBRARY
|
| 31 |
+
with _LOCK:
|
| 32 |
+
if _LIBRARY is not None:
|
| 33 |
+
return _LIBRARY
|
| 34 |
+
source = Path(__file__).with_name('cuda_z_inverse_native_dispatch.c')
|
| 35 |
+
compiler = shlex.split(os.environ.get('CC', 'cc'))
|
| 36 |
+
flags = ['-std=c11', '-O3', '-fPIC', '-shared', '-Wall', '-Wextra', '-Werror']
|
| 37 |
+
key = hashlib.sha256(source.read_bytes() + repr((compiler, flags)).encode()).hexdigest()
|
| 38 |
+
cache = Path(tempfile.gettempdir()) / ('fft-half-triton-z-native-' + str(os.getuid())) / key
|
| 39 |
+
cache.mkdir(parents=True, exist_ok=True)
|
| 40 |
+
target = cache / 'dispatch.so'
|
| 41 |
+
with (cache / 'build.lock').open('w') as lock:
|
| 42 |
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
| 43 |
+
if not target.is_file():
|
| 44 |
+
pending = cache / ('dispatch.' + str(os.getpid()) + '.so')
|
| 45 |
+
try:
|
| 46 |
+
subprocess.run(compiler + flags + [str(source), '-o', str(pending)],
|
| 47 |
+
check=True, capture_output=True, text=True)
|
| 48 |
+
pending.replace(target)
|
| 49 |
+
finally:
|
| 50 |
+
pending.unlink(missing_ok=True)
|
| 51 |
+
lib = ct.CDLL(str(target))
|
| 52 |
+
lib.fft_execute.argtypes = [ct.POINTER(Descriptor), ct.c_uint64, ct.c_uint64]
|
| 53 |
+
lib.fft_execute.restype = ct.c_int
|
| 54 |
+
lib.fft_dispatch_size.argtypes = []
|
| 55 |
+
lib.fft_dispatch_size.restype = ct.c_size_t
|
| 56 |
+
if lib.fft_dispatch_size() != ct.sizeof(Descriptor):
|
| 57 |
+
raise RuntimeError('Native dispatcher structure size mismatch')
|
| 58 |
+
_LIBRARY = lib
|
| 59 |
+
return lib
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def load_driver():
|
| 63 |
+
"""Resolve the driver function during preparation only."""
|
| 64 |
+
global _DRIVER
|
| 65 |
+
with _LOCK:
|
| 66 |
+
if _DRIVER is None:
|
| 67 |
+
_DRIVER = ct.CDLL('libcuda.so.1')
|
| 68 |
+
_DRIVER.cuLaunchKernel.restype = ct.c_int
|
| 69 |
+
return _DRIVER
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class PreparedDispatch:
|
| 73 |
+
"""Immutable setup descriptor, with fresh caller pointers at each call.
|
| 74 |
+
|
| 75 |
+
The Plan retains the cuFFT handles and compiled CUDA kernel. Concurrent
|
| 76 |
+
execution requires independent plans because cuFFT workspace is reused.
|
| 77 |
+
"""
|
| 78 |
+
def __init__(self, complex_exec, handle, compiled, grid, intermediate, twiddle, stream):
|
| 79 |
+
abi = dict(threads=compiled.threads, shared_bytes=compiled.shared)
|
| 80 |
+
if not compiled.function or compiled.module is None:
|
| 81 |
+
raise ValueError('The CUDA kernel must be loaded during setup')
|
| 82 |
+
if not 0 < handle < 2**31:
|
| 83 |
+
raise ValueError('A prepared cuFFT handle is required')
|
| 84 |
+
if len(grid) != 3 or any(not 0 < n < 2**32 for n in grid):
|
| 85 |
+
raise ValueError('Invalid launch grid')
|
| 86 |
+
if not intermediate or not twiddle:
|
| 87 |
+
raise ValueError('Prepared intermediate and twiddle storage is required')
|
| 88 |
+
self.library = load_library()
|
| 89 |
+
self.driver = load_driver()
|
| 90 |
+
self.descriptor = Descriptor(
|
| 91 |
+
complex_exec=ct.cast(complex_exec, ct.c_void_p).value,
|
| 92 |
+
launch=ct.cast(self.driver.cuLaunchKernel, ct.c_void_p).value,
|
| 93 |
+
handle=handle, intermediate=intermediate,
|
| 94 |
+
twiddle=twiddle, stream=stream, function=compiled.function,
|
| 95 |
+
grid=(ct.c_uint * 3)(*grid), threads=abi['threads'], shared=abi['shared_bytes'])
|
| 96 |
+
self.pointer = ct.pointer(self.descriptor)
|
| 97 |
+
self.function = self.library.fft_execute
|
| 98 |
+
|
| 99 |
+
def execute(self, input_ptr, output_ptr):
|
| 100 |
+
return self.function(self.pointer, input_ptr, output_ptr)
|
build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Select index width from the complete physical packed extent at compilation.
|
| 3 |
+
// The callback ABI remains 64-bit. Every valid caller index fits the selected
|
| 4 |
+
// unsigned type; pointer arithmetic still computes full-width device addresses.
|
| 5 |
+
#if (1ULL * FFT_NZ * FFT_NY * (FFT_N / 2 + 1)) <= 0x100000000ULL
|
| 6 |
+
using fft_index_t = unsigned int;
|
| 7 |
+
static_assert(sizeof(fft_index_t) == 4, "A 32-bit unsigned integer is required");
|
| 8 |
+
#else
|
| 9 |
+
using fft_index_t = unsigned long long;
|
| 10 |
+
#endif
|
| 11 |
+
__device__ double2 fft_scalar_pair(const double *input, fft_index_t index) {
|
| 12 |
+
double2 value;
|
| 13 |
+
#if FFT_INPUT_SHIFT == 0
|
| 14 |
+
value = reinterpret_cast<const double2 *>(input)[index];
|
| 15 |
+
#else
|
| 16 |
+
value.x = input[2ULL * index];
|
| 17 |
+
value.y = input[2ULL * index + 1];
|
| 18 |
+
#endif
|
| 19 |
+
return value;
|
| 20 |
+
}
|
| 21 |
+
// Reconstruct twice the half-length complex spectrum from immutable packed
|
| 22 |
+
// caller input. Endpoint projection preserves irfftn for arbitrary spectra.
|
| 23 |
+
#if FFT_LOAD
|
| 24 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 25 |
+
void *caller_info, void *shared_memory) {
|
| 26 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 27 |
+
constexpr fft_index_t nf = m + 1;
|
| 28 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 29 |
+
const fft_index_t k = index % m;
|
| 30 |
+
const fft_index_t row = index / m;
|
| 31 |
+
const fft_index_t y = row % FFT_NY;
|
| 32 |
+
const fft_index_t z = row / FFT_NY;
|
| 33 |
+
const fft_index_t mirror_y = y == 0 ? 0 : FFT_NY - y;
|
| 34 |
+
const fft_index_t mirror_z = z == 0 ? 0 : FFT_NZ - z;
|
| 35 |
+
const fft_index_t mirror_row = mirror_z * FFT_NY + mirror_y;
|
| 36 |
+
const double *input = static_cast<const double *>(data) + FFT_INPUT_SHIFT;
|
| 37 |
+
double2 a = fft_scalar_pair(input, row*nf+k);
|
| 38 |
+
double2 b = fft_scalar_pair(input, mirror_row * nf + m - k);
|
| 39 |
+
if (k == 0) {
|
| 40 |
+
// Project each boundary plane against its Z/Y conjugate partner.
|
| 41 |
+
// b represents the conjugate of the projected Nyquist value.
|
| 42 |
+
const double2 a_mirror = fft_scalar_pair(input, mirror_row * nf);
|
| 43 |
+
const double2 b_mirror = fft_scalar_pair(input, row * nf + m);
|
| 44 |
+
a.x = 0.5 * (a.x + a_mirror.x);
|
| 45 |
+
a.y = 0.5 * (a.y - a_mirror.y);
|
| 46 |
+
b.x = 0.5 * (b.x + b_mirror.x);
|
| 47 |
+
b.y = 0.5 * (b.y - b_mirror.y);
|
| 48 |
+
}
|
| 49 |
+
const unsigned long long table = static_cast<const unsigned long long *>(caller_info)[1];
|
| 50 |
+
const double2 w = reinterpret_cast<const double2 *>(table)[k];
|
| 51 |
+
double2 result;
|
| 52 |
+
result.x = a.x + b.x - w.x * (a.y + b.y) + w.y * (a.x - b.x);
|
| 53 |
+
result.y = a.y - b.y + w.x * (a.x - b.x) + w.y * (a.y + b.y);
|
| 54 |
+
return result;
|
| 55 |
+
}
|
| 56 |
+
#else
|
| 57 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 58 |
+
double2 value, void *caller_info, void *shared_memory) {
|
| 59 |
+
const unsigned long long address =
|
| 60 |
+
*static_cast<const unsigned long long *>(caller_info);
|
| 61 |
+
double *output = reinterpret_cast<double *>(address);
|
| 62 |
+
output[2 * offset] = static_cast<double>(value.x);
|
| 63 |
+
output[2 * offset + 1] = static_cast<double>(value.y);
|
| 64 |
+
}
|
| 65 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_dense_half_inverse_lto.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile cuFFT LTO callbacks with the installed NVRTC, before execution.
|
| 3 |
+
|
| 4 |
+
This module can compile offline without initializing CUDA or loading code on
|
| 5 |
+
a GPU. Compiled blobs are cached by source, public shape, precision, NVRTC
|
| 6 |
+
version and target. No input tensor values or pointers are cached.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import ctypes as ct
|
| 11 |
+
import ctypes.util
|
| 12 |
+
import functools
|
| 13 |
+
import hashlib
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_LOCK = threading.Lock()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@functools.lru_cache(maxsize=1)
|
| 22 |
+
def library():
|
| 23 |
+
candidates = [ctypes.util.find_library('nvrtc')]
|
| 24 |
+
for root in (Path('/usr/local/cuda/lib64'),
|
| 25 |
+
Path('/usr/local/cuda/targets/x86_64-linux/lib')):
|
| 26 |
+
candidates.extend(str(p) for p in sorted(root.glob('libnvrtc.so*')))
|
| 27 |
+
candidates.extend(('libnvrtc.so.13', 'libnvrtc.so.12', 'libnvrtc.so'))
|
| 28 |
+
lib = None
|
| 29 |
+
for name in candidates:
|
| 30 |
+
if name:
|
| 31 |
+
try:
|
| 32 |
+
lib = ct.CDLL(name)
|
| 33 |
+
break
|
| 34 |
+
except OSError:
|
| 35 |
+
pass
|
| 36 |
+
if lib is None:
|
| 37 |
+
raise RuntimeError('The installed NVRTC library is unavailable')
|
| 38 |
+
signatures = {
|
| 39 |
+
'nvrtcVersion': [ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)],
|
| 40 |
+
'nvrtcCreateProgram': [ct.POINTER(ct.c_void_p), ct.c_char_p, ct.c_char_p,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)],
|
| 42 |
+
'nvrtcCompileProgram': [ct.c_void_p, ct.c_int, ct.POINTER(ct.c_char_p)],
|
| 43 |
+
'nvrtcGetProgramLogSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 44 |
+
'nvrtcGetProgramLog': [ct.c_void_p, ct.c_void_p],
|
| 45 |
+
'nvrtcGetLTOIRSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 46 |
+
'nvrtcGetLTOIR': [ct.c_void_p, ct.c_void_p],
|
| 47 |
+
'nvrtcDestroyProgram': [ct.POINTER(ct.c_void_p)],
|
| 48 |
+
}
|
| 49 |
+
for name, signature in signatures.items():
|
| 50 |
+
function = getattr(lib, name)
|
| 51 |
+
function.argtypes = signature
|
| 52 |
+
function.restype = ct.c_int
|
| 53 |
+
return lib
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _check(result):
|
| 57 |
+
if result:
|
| 58 |
+
raise RuntimeError(f'NVRTC returned error {result}')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def version():
|
| 62 |
+
major, minor = ct.c_int(), ct.c_int()
|
| 63 |
+
_check(library().nvrtcVersion(ct.byref(major), ct.byref(minor)))
|
| 64 |
+
return major.value, minor.value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@functools.lru_cache(maxsize=128)
|
| 68 |
+
def _compile(source, shape, double, arch, nvrtc_version, input_shift, kind):
|
| 69 |
+
nz, ny, nx = shape
|
| 70 |
+
lib = library()
|
| 71 |
+
options = ('--std=c++17', '--relocatable-device-code=true', '-dlto',
|
| 72 |
+
f'--gpu-architecture=compute_{arch}', f'-DFFT_N={nx}',
|
| 73 |
+
f'-DFFT_NZ={nz}', f'-DFFT_NY={ny}',
|
| 74 |
+
f'-DFFT_DOUBLE={int(double)}', f'-DFFT_INPUT_SHIFT={input_shift}',
|
| 75 |
+
f'-DFFT_LOAD={int(kind == "load")}')
|
| 76 |
+
program = ct.c_void_p()
|
| 77 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'endpoint.cu', 0, None, None))
|
| 78 |
+
try:
|
| 79 |
+
option_array = (ct.c_char_p * len(options))(*(v.encode() for v in options))
|
| 80 |
+
result = lib.nvrtcCompileProgram(program, len(options), option_array)
|
| 81 |
+
log_size = ct.c_size_t()
|
| 82 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(log_size)))
|
| 83 |
+
log = ct.create_string_buffer(log_size.value)
|
| 84 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 85 |
+
if result:
|
| 86 |
+
raise RuntimeError(f'NVRTC returned error {result}: {log.value.decode()}')
|
| 87 |
+
size = ct.c_size_t()
|
| 88 |
+
_check(lib.nvrtcGetLTOIRSize(program, ct.byref(size)))
|
| 89 |
+
data = ct.create_string_buffer(size.value)
|
| 90 |
+
_check(lib.nvrtcGetLTOIR(program, data))
|
| 91 |
+
blob = data.raw
|
| 92 |
+
metadata = dict(shape=shape, double=bool(double), target=f'compute_{arch}',
|
| 93 |
+
nvrtc=list(nvrtc_version), options=list(options),
|
| 94 |
+
source_sha256=hashlib.sha256(source).hexdigest(),
|
| 95 |
+
lto_ir_sha256=hashlib.sha256(blob).hexdigest(),
|
| 96 |
+
lto_ir_bytes=len(blob), compiler_log=log.value.decode())
|
| 97 |
+
return blob, metadata
|
| 98 |
+
finally:
|
| 99 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def compile_callback(shape, input_shift, kind, arch=100):
|
| 103 |
+
"""Reconstruct a half-length spectrum in FP64 or store its inverse as FP64."""
|
| 104 |
+
if kind not in ('load', 'store'):
|
| 105 |
+
raise ValueError('Expected a load or store callback')
|
| 106 |
+
source = Path(__file__).with_name('c2r_f64_dense_half_inverse_wide_redirect.cu').read_bytes()
|
| 107 |
+
with _LOCK:
|
| 108 |
+
blob, metadata = _compile(source, tuple(shape), True, int(arch), version(), input_shift, kind)
|
| 109 |
+
return (('fft_wide_' + kind).encode(), 1 if kind == 'load' else 5,
|
| 110 |
+
ct.create_string_buffer(blob, len(blob)), dict(metadata, kind=kind))
|
build/torch-cuda/_b200/c2r_f64_dense_half_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Select index width from the complete physical packed extent at compilation.
|
| 3 |
+
// The callback ABI remains 64-bit. Every valid caller index fits the selected
|
| 4 |
+
// unsigned type; pointer arithmetic still computes full-width device addresses.
|
| 5 |
+
#if (1ULL * FFT_NZ * FFT_NY * (FFT_N / 2 + 1)) <= 0x100000000ULL
|
| 6 |
+
using fft_index_t = unsigned int;
|
| 7 |
+
static_assert(sizeof(fft_index_t) == 4, "A 32-bit unsigned integer is required");
|
| 8 |
+
#else
|
| 9 |
+
using fft_index_t = unsigned long long;
|
| 10 |
+
#endif
|
| 11 |
+
__device__ double2 fft_scalar_pair(const double *input, fft_index_t index) {
|
| 12 |
+
double2 value;
|
| 13 |
+
#if FFT_INPUT_SHIFT == 0
|
| 14 |
+
value = reinterpret_cast<const double2 *>(input)[index];
|
| 15 |
+
#else
|
| 16 |
+
value.x = input[2ULL * index];
|
| 17 |
+
value.y = input[2ULL * index + 1];
|
| 18 |
+
#endif
|
| 19 |
+
return value;
|
| 20 |
+
}
|
| 21 |
+
// The cuFFT logical input is dense. Callback arithmetic reads exact nf-stride
|
| 22 |
+
// caller storage directly; no input repacking is performed.
|
| 23 |
+
// Reconstruct twice the half-length complex spectrum from immutable packed
|
| 24 |
+
// caller input. Endpoint projection preserves irfftn for arbitrary spectra.
|
| 25 |
+
#if FFT_LOAD
|
| 26 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 27 |
+
void *caller_info, void *shared_memory) {
|
| 28 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 29 |
+
constexpr fft_index_t nf = m + 1;
|
| 30 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 31 |
+
const fft_index_t k = index % m;
|
| 32 |
+
const fft_index_t row = index / m;
|
| 33 |
+
const fft_index_t y = row % FFT_NY;
|
| 34 |
+
const fft_index_t z = row / FFT_NY;
|
| 35 |
+
const fft_index_t mirror_y = y == 0 ? 0 : FFT_NY - y;
|
| 36 |
+
const fft_index_t mirror_z = z == 0 ? 0 : FFT_NZ - z;
|
| 37 |
+
const fft_index_t mirror_row = mirror_z * FFT_NY + mirror_y;
|
| 38 |
+
const double *input = static_cast<const double *>(data) + FFT_INPUT_SHIFT;
|
| 39 |
+
double2 a = fft_scalar_pair(input, row * nf + k);
|
| 40 |
+
double2 b = fft_scalar_pair(input, mirror_row * nf + m - k);
|
| 41 |
+
if (k == 0) {
|
| 42 |
+
// Project each boundary plane against its Z/Y conjugate partner.
|
| 43 |
+
// b represents the conjugate of the projected Nyquist value.
|
| 44 |
+
const double2 a_mirror = fft_scalar_pair(input, mirror_row * nf);
|
| 45 |
+
const double2 b_mirror = fft_scalar_pair(input, row * nf + m);
|
| 46 |
+
a.x = 0.5 * (a.x + a_mirror.x);
|
| 47 |
+
a.y = 0.5 * (a.y - a_mirror.y);
|
| 48 |
+
b.x = 0.5 * (b.x + b_mirror.x);
|
| 49 |
+
b.y = 0.5 * (b.y - b_mirror.y);
|
| 50 |
+
}
|
| 51 |
+
const unsigned long long table = static_cast<const unsigned long long *>(caller_info)[1];
|
| 52 |
+
const double2 w = reinterpret_cast<const double2 *>(table)[k];
|
| 53 |
+
double2 result;
|
| 54 |
+
result.x = a.x + b.x - w.x * (a.y + b.y) + w.y * (a.x - b.x);
|
| 55 |
+
result.y = a.y - b.y + w.x * (a.x - b.x) + w.y * (a.y + b.y);
|
| 56 |
+
return result;
|
| 57 |
+
}
|
| 58 |
+
#else
|
| 59 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 60 |
+
double2 value, void *caller_info, void *shared_memory) {
|
| 61 |
+
const unsigned long long address =
|
| 62 |
+
*static_cast<const unsigned long long *>(caller_info);
|
| 63 |
+
double *output = reinterpret_cast<double *>(address);
|
| 64 |
+
output[2 * offset] = static_cast<double>(value.x);
|
| 65 |
+
output[2 * offset + 1] = static_cast<double>(value.y);
|
| 66 |
+
}
|
| 67 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_half_vector_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Select index width from the complete physical packed extent at compilation.
|
| 3 |
+
// The callback ABI remains 64-bit. Every valid caller index fits the selected
|
| 4 |
+
// unsigned type; pointer arithmetic still computes full-width device addresses.
|
| 5 |
+
#if (1ULL * FFT_NZ * FFT_NY * (FFT_N / 2 + 1)) <= 0x100000000ULL
|
| 6 |
+
using fft_index_t = unsigned int;
|
| 7 |
+
static_assert(sizeof(fft_index_t) == 4, "A 32-bit unsigned integer is required");
|
| 8 |
+
#else
|
| 9 |
+
using fft_index_t = unsigned long long;
|
| 10 |
+
#endif
|
| 11 |
+
__device__ double2 fft_scalar_pair(const double *input, fft_index_t index) {
|
| 12 |
+
double2 value;
|
| 13 |
+
#if FFT_INPUT_SHIFT == 0
|
| 14 |
+
value = reinterpret_cast<const double2 *>(input)[index];
|
| 15 |
+
#else
|
| 16 |
+
value.x = input[2ULL * index];
|
| 17 |
+
value.y = input[2ULL * index + 1];
|
| 18 |
+
#endif
|
| 19 |
+
return value;
|
| 20 |
+
}
|
| 21 |
+
// Reconstruct twice the half-length complex spectrum from immutable packed
|
| 22 |
+
// caller input. Endpoint projection preserves irfftn for arbitrary spectra.
|
| 23 |
+
#if FFT_LOAD
|
| 24 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 25 |
+
void *caller_info, void *shared_memory) {
|
| 26 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 27 |
+
constexpr fft_index_t nf = m + 1;
|
| 28 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 29 |
+
const fft_index_t k = index % nf;
|
| 30 |
+
const fft_index_t row = index / nf;
|
| 31 |
+
const fft_index_t y = row % FFT_NY;
|
| 32 |
+
const fft_index_t z = row / FFT_NY;
|
| 33 |
+
const fft_index_t mirror_y = y == 0 ? 0 : FFT_NY - y;
|
| 34 |
+
const fft_index_t mirror_z = z == 0 ? 0 : FFT_NZ - z;
|
| 35 |
+
const fft_index_t mirror_row = mirror_z * FFT_NY + mirror_y;
|
| 36 |
+
const double *input = static_cast<const double *>(data) + FFT_INPUT_SHIFT;
|
| 37 |
+
double2 a = fft_scalar_pair(input, index);
|
| 38 |
+
double2 b = fft_scalar_pair(input, mirror_row * nf + m - k);
|
| 39 |
+
if (k == 0) {
|
| 40 |
+
// Project each boundary plane against its Z/Y conjugate partner.
|
| 41 |
+
// b represents the conjugate of the projected Nyquist value.
|
| 42 |
+
const double2 a_mirror = fft_scalar_pair(input, mirror_row * nf);
|
| 43 |
+
const double2 b_mirror = fft_scalar_pair(input, row * nf + m);
|
| 44 |
+
a.x = 0.5 * (a.x + a_mirror.x);
|
| 45 |
+
a.y = 0.5 * (a.y - a_mirror.y);
|
| 46 |
+
b.x = 0.5 * (b.x + b_mirror.x);
|
| 47 |
+
b.y = 0.5 * (b.y - b_mirror.y);
|
| 48 |
+
}
|
| 49 |
+
const unsigned long long table = static_cast<const unsigned long long *>(caller_info)[1];
|
| 50 |
+
const double2 w = reinterpret_cast<const double2 *>(table)[k];
|
| 51 |
+
double2 result;
|
| 52 |
+
result.x = a.x + b.x - w.x * (a.y + b.y) + w.y * (a.x - b.x);
|
| 53 |
+
result.y = a.y - b.y + w.x * (a.x - b.x) + w.y * (a.y + b.y);
|
| 54 |
+
return result;
|
| 55 |
+
}
|
| 56 |
+
#else
|
| 57 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 58 |
+
double2 value, void *caller_info, void *shared_memory) {
|
| 59 |
+
const unsigned long long address =
|
| 60 |
+
*static_cast<const unsigned long long *>(caller_info);
|
| 61 |
+
double *output = reinterpret_cast<double *>(address);
|
| 62 |
+
output[2 * offset] = static_cast<double>(value.x);
|
| 63 |
+
output[2 * offset + 1] = static_cast<double>(value.y);
|
| 64 |
+
}
|
| 65 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_narrow_index_inverse_scalar_output_reconstruct.cu
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Only completed Z/Y coefficients are read. All constants and callback plans
|
| 3 |
+
// are prepared before execution; no caller address or data is cached.
|
| 4 |
+
|
| 5 |
+
#if FFT_CALLBACK_KIND != 2
|
| 6 |
+
__device__ double2 fft_half_load(void *data, unsigned long long offset,
|
| 7 |
+
void *caller_info, void *shared_memory) {
|
| 8 |
+
constexpr unsigned long long m = FFT_N / 2;
|
| 9 |
+
#if FFT_ROWMAJOR
|
| 10 |
+
const unsigned long long row = offset / (m + 1);
|
| 11 |
+
const unsigned long long k = offset % (m + 1);
|
| 12 |
+
const unsigned long long mirror = row * (m + 1) + m - k;
|
| 13 |
+
#else
|
| 14 |
+
const unsigned long long k = offset / FFT_ROWS;
|
| 15 |
+
const unsigned long long row = offset % FFT_ROWS;
|
| 16 |
+
const unsigned long long mirror = (m - k) * FFT_ROWS + row;
|
| 17 |
+
#endif
|
| 18 |
+
const double2 a = static_cast<const double2 *>(data)[offset];
|
| 19 |
+
const double2 b = static_cast<const double2 *>(data)[mirror];
|
| 20 |
+
const double2 w = static_cast<const double2 *>(caller_info)[k];
|
| 21 |
+
// Endpoint imaginary parts are ignored after the Z/Y inverse, matching
|
| 22 |
+
// irfftn for arbitrary input (including inconsistent endpoint planes).
|
| 23 |
+
const double ai = k == 0 ? 0.0 : a.y;
|
| 24 |
+
const double bi = k == 0 ? 0.0 : b.y;
|
| 25 |
+
double2 result;
|
| 26 |
+
result.x = a.x + b.x - w.x * (ai + bi) + w.y * (a.x - b.x);
|
| 27 |
+
result.y = ai - bi + w.x * (a.x - b.x) + w.y * (ai + bi);
|
| 28 |
+
return result;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
#endif
|
| 32 |
+
|
| 33 |
+
#if FFT_CALLBACK_KIND != 1
|
| 34 |
+
__device__ void fft_half_store(void *data, unsigned long long offset,
|
| 35 |
+
double2 value, void *caller_info,
|
| 36 |
+
void *shared_memory) {
|
| 37 |
+
// The API receives an aligned base. A setup-specialized scalar offset
|
| 38 |
+
// writes the exact caller address; no preceding scalar is accessed.
|
| 39 |
+
// Re/Im of the M-point result are the even/odd N-point real outputs.
|
| 40 |
+
volatile double *output = static_cast<volatile double *>(data);
|
| 41 |
+
output[2 * offset + FFT_STORE_SHIFT] = value.x;
|
| 42 |
+
output[2 * offset + 1 + FFT_STORE_SHIFT] = value.y;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_paired_x_inverse_cuda_kernel.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compile and load fixed CUDA FFT kernels only during plan preparation."""
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
import functools
|
| 4 |
+
import hashlib
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from types import SimpleNamespace
|
| 7 |
+
|
| 8 |
+
from .paired_x_inverse_lto import library, version, _check
|
| 9 |
+
from .paired_x_inverse_native_dispatch import load_driver
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@functools.lru_cache(maxsize=128)
|
| 13 |
+
def compile_kernel(shape, columns, warps, arch, device_ordinal, frequency, double):
|
| 14 |
+
# The ordinal keeps loaded CUDA modules within their owning device context.
|
| 15 |
+
nz, ny, nx = shape
|
| 16 |
+
base, odd = 1, nx // 2
|
| 17 |
+
while odd % 2 == 0:
|
| 18 |
+
odd //= 2
|
| 19 |
+
base *= 2
|
| 20 |
+
if odd != 1 or base < 2*warps*32//columns:
|
| 21 |
+
raise ValueError('Unsupported radix decomposition')
|
| 22 |
+
source = Path(__file__).with_name('c2r_f64_paired_x_inverse_fft_axis.cu').read_bytes()
|
| 23 |
+
options = ['--std=c++17', f'--gpu-architecture=sm_{arch}',
|
| 24 |
+
f'-DLENGTH={nx//2}', f'-DINNER={nz*ny}', f'-DBASE={base}',
|
| 25 |
+
f'-DNZ={nz}', f'-DNY={ny}', f'-DFREQUENCY={int(frequency)}', f'-DDOUBLE={int(double)}',
|
| 26 |
+
f'-DLOG_BASE={base.bit_length()-1}', f'-DODD={odd}',
|
| 27 |
+
f'-DCOLUMNS={columns}', f'-DWARPS={warps}']
|
| 28 |
+
lib = library()
|
| 29 |
+
lib.nvrtcGetCUBINSize.argtypes = [ct.c_void_p, ct.POINTER(ct.c_size_t)]
|
| 30 |
+
lib.nvrtcGetCUBIN.argtypes = [ct.c_void_p, ct.c_void_p]
|
| 31 |
+
program = ct.c_void_p()
|
| 32 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'fft_axis.cu', 0, None, None))
|
| 33 |
+
try:
|
| 34 |
+
opts = (ct.c_char_p * len(options))(*(s.encode() for s in options))
|
| 35 |
+
code = lib.nvrtcCompileProgram(program, len(options), opts)
|
| 36 |
+
size = ct.c_size_t()
|
| 37 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(size)))
|
| 38 |
+
log = ct.create_string_buffer(size.value)
|
| 39 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 40 |
+
if code:
|
| 41 |
+
raise RuntimeError(log.value.decode())
|
| 42 |
+
_check(lib.nvrtcGetCUBINSize(program, ct.byref(size)))
|
| 43 |
+
blob = ct.create_string_buffer(size.value)
|
| 44 |
+
_check(lib.nvrtcGetCUBIN(program, blob))
|
| 45 |
+
finally:
|
| 46 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 47 |
+
driver = load_driver()
|
| 48 |
+
driver.cuModuleLoadData.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p]
|
| 49 |
+
driver.cuModuleGetFunction.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p, ct.c_char_p]
|
| 50 |
+
driver.cuFuncGetAttribute.argtypes = [ct.POINTER(ct.c_int), ct.c_int, ct.c_void_p]
|
| 51 |
+
module, function = ct.c_void_p(), ct.c_void_p()
|
| 52 |
+
_check(driver.cuModuleLoadData(ct.byref(module), blob))
|
| 53 |
+
_check(driver.cuModuleGetFunction(ct.byref(function), module, b'fft_axis'))
|
| 54 |
+
registers, local = ct.c_int(), ct.c_int()
|
| 55 |
+
_check(driver.cuFuncGetAttribute(ct.byref(registers), 4, function))
|
| 56 |
+
_check(driver.cuFuncGetAttribute(ct.byref(local), 3, function))
|
| 57 |
+
# local is CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, not an instruction count.
|
| 58 |
+
metadata = dict(options=options, nvrtc=version(), local_memory_bytes=local.value, source_sha256=hashlib.sha256(source).hexdigest(),
|
| 59 |
+
cubin_sha256=hashlib.sha256(blob.raw).hexdigest(), compiler_log=log.value.decode())
|
| 60 |
+
return SimpleNamespace(module=module, function=function.value, name='fft_axis',
|
| 61 |
+
hash=metadata['cubin_sha256'], threads=32*warps, shared=0,
|
| 62 |
+
n_regs=registers.value, n_spills=local.value,
|
| 63 |
+
grid=((nz*ny//2+2+columns//2-1)//(columns//2), 1, 1),
|
| 64 |
+
compiler_metadata=metadata)
|
build/torch-cuda/_b200/c2r_f64_paired_x_inverse_fft_axis.cu
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#define FIRST_RADIX 4
|
| 2 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
// Exact mathematical extents; each warp owns independent adjacent columns.
|
| 4 |
+
__device__ __forceinline__ double2 add(double2 a, double2 b) {
|
| 5 |
+
return make_double2(a.x + b.x, a.y + b.y);
|
| 6 |
+
}
|
| 7 |
+
__device__ __forceinline__ double2 sub(double2 a, double2 b) {
|
| 8 |
+
return make_double2(a.x - b.x, a.y - b.y);
|
| 9 |
+
}
|
| 10 |
+
__device__ __forceinline__ double2 mul(double2 a, double2 b) {
|
| 11 |
+
return make_double2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x);
|
| 12 |
+
}
|
| 13 |
+
template<int H, int N, int G, int C, int Bound>
|
| 14 |
+
__device__ __forceinline__ void stage(double2 (&a)[N/G], const double2 *twiddle,
|
| 15 |
+
int lane_z, double2 *exchange) {
|
| 16 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 17 |
+
#pragma unroll
|
| 18 |
+
for (int r = 0; r < N/G; ++r) {
|
| 19 |
+
if ((r % (4*H/G)) < H/G) {
|
| 20 |
+
double2 a0 = a[r], a1 = a[r+H/G], a2 = a[r+2*H/G], a3 = a[r+3*H/G];
|
| 21 |
+
int k = (r*G+lane_z) % H;
|
| 22 |
+
double2 w1=twiddle[k*(N/(4*H))]; w1.y=-w1.y;
|
| 23 |
+
double2 w2=twiddle[2*k*(N/(4*H))]; w2.y=-w2.y;
|
| 24 |
+
double2 w3=twiddle[3*k*(N/(4*H))]; w3.y=-w3.y;
|
| 25 |
+
// Input is binary bit reversed, so the middle branches swap.
|
| 26 |
+
double2 b1=mul(a2,w1), b2=mul(a1,w2), b3=mul(a3,w3);
|
| 27 |
+
double2 even=add(a0,b2), even_diff=sub(a0,b2);
|
| 28 |
+
double2 odd=add(b1,b3), odd_diff=sub(b1,b3);
|
| 29 |
+
double2 turn=make_double2(-odd_diff.y,odd_diff.x);
|
| 30 |
+
a[r]=add(even,odd); a[r+H/G]=add(even_diff,turn);
|
| 31 |
+
a[r+2*H/G]=sub(even,odd); a[r+3*H/G]=sub(even_diff,turn);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
} else if constexpr (H < G) {
|
| 35 |
+
if constexpr (H*C >= 32) {
|
| 36 |
+
#pragma unroll
|
| 37 |
+
for (int r = 0; r < N/G; ++r) exchange[r*blockDim.x+threadIdx.x]=a[r];
|
| 38 |
+
__syncthreads();
|
| 39 |
+
}
|
| 40 |
+
#pragma unroll
|
| 41 |
+
for (int r = 0; r < N/G; ++r) {
|
| 42 |
+
double2 v;
|
| 43 |
+
if constexpr (H*C < 32) {
|
| 44 |
+
v = make_double2(__shfl_xor_sync(0xffffffff, a[r].x, H*C),
|
| 45 |
+
__shfl_xor_sync(0xffffffff, a[r].y, H*C));
|
| 46 |
+
} else {
|
| 47 |
+
v = exchange[r*blockDim.x+(threadIdx.x ^ (H*C))];
|
| 48 |
+
}
|
| 49 |
+
double2 lo = (lane_z & H) ? v : a[r];
|
| 50 |
+
double2 hi = (lane_z & H) ? a[r] : v;
|
| 51 |
+
if constexpr (H > 1) {
|
| 52 |
+
double2 w = twiddle[(lane_z & (H-1)) * (N/(2*H))];
|
| 53 |
+
w.y = -w.y;
|
| 54 |
+
hi = mul(hi, w);
|
| 55 |
+
}
|
| 56 |
+
a[r] = (lane_z & H) ? sub(lo, hi) : add(lo, hi);
|
| 57 |
+
}
|
| 58 |
+
if constexpr (H*C >= 32) __syncthreads();
|
| 59 |
+
} else {
|
| 60 |
+
#pragma unroll
|
| 61 |
+
for (int r = 0; r < N/G; ++r) {
|
| 62 |
+
if ((r & (H/G)) == 0) {
|
| 63 |
+
double2 lo = a[r], hi = a[r + H/G];
|
| 64 |
+
double2 w = twiddle[((r*G + lane_z) & (H-1)) * (N/(2*H))];
|
| 65 |
+
w.y = -w.y;
|
| 66 |
+
hi = mul(hi, w);
|
| 67 |
+
a[r] = add(lo, hi);
|
| 68 |
+
a[r + H/G] = sub(lo, hi);
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 73 |
+
if constexpr (4*H < Bound) stage<4*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 74 |
+
} else if constexpr (2*H < Bound) stage<2*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
#if DOUBLE
|
| 78 |
+
using scalar_t = double;
|
| 79 |
+
#else
|
| 80 |
+
using scalar_t = float;
|
| 81 |
+
#endif
|
| 82 |
+
__device__ __forceinline__ double2 pair_load(const scalar_t *input, int offset) {
|
| 83 |
+
#if DOUBLE
|
| 84 |
+
if ((reinterpret_cast<unsigned long long>(input) & 15ULL) == 0)
|
| 85 |
+
return reinterpret_cast<const double2 *>(input)[offset];
|
| 86 |
+
#else
|
| 87 |
+
if ((reinterpret_cast<unsigned long long>(input) & 7ULL) == 0) {
|
| 88 |
+
float2 v = reinterpret_cast<const float2 *>(input)[offset];
|
| 89 |
+
return make_double2((double)v.x, (double)v.y);
|
| 90 |
+
}
|
| 91 |
+
#endif
|
| 92 |
+
return make_double2((double)input[2*offset], (double)input[2*offset+1]);
|
| 93 |
+
}
|
| 94 |
+
__device__ __forceinline__ double2 reconstruct(const scalar_t *input, int row, int k,
|
| 95 |
+
const double2 *twiddle) {
|
| 96 |
+
constexpr int nf = LENGTH+1;
|
| 97 |
+
int y = row%NY, z = row/NY;
|
| 98 |
+
int mirror = (z == 0 ? 0 : NZ-z)*NY + (y == 0 ? 0 : NY-y);
|
| 99 |
+
double2 a = pair_load(input, row*nf+k);
|
| 100 |
+
double2 b = pair_load(input, mirror*nf+LENGTH-k);
|
| 101 |
+
if (k == 0) {
|
| 102 |
+
double2 am = pair_load(input, mirror*nf);
|
| 103 |
+
double2 bm = pair_load(input, row*nf+LENGTH);
|
| 104 |
+
a = make_double2(0.5*(a.x+am.x), 0.5*(a.y-am.y));
|
| 105 |
+
b = make_double2(0.5*(b.x+bm.x), 0.5*(b.y-bm.y));
|
| 106 |
+
}
|
| 107 |
+
double2 w = twiddle[k];
|
| 108 |
+
return make_double2(a.x+b.x-w.x*(a.y+b.y)+w.y*(a.x-b.x),
|
| 109 |
+
a.y-b.y+w.x*(a.x-b.x)+w.y*(a.y+b.y));
|
| 110 |
+
}
|
| 111 |
+
// The table contains shape-only row representatives after the FFT roots.
|
| 112 |
+
__device__ __forceinline__ int paired_row(const double2 *twiddle, int pair, int side) {
|
| 113 |
+
if (pair >= INNER/2 + 2) return INNER;
|
| 114 |
+
int row = pair <= NY/2 ? pair : pair + NY/2 - 1;
|
| 115 |
+
int z = row/NY, y = row%NY;
|
| 116 |
+
int mirror = (z == 0 ? 0 : NZ-z)*NY + (y == 0 ? 0 : NY-y);
|
| 117 |
+
return side ? mirror : row;
|
| 118 |
+
}
|
| 119 |
+
// This bijection is applied only within a reconstructed mathematical row.
|
| 120 |
+
__device__ __forceinline__ int shared_index(int k) { return k ^ (k >> 4); }
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
__device__ __forceinline__ void exact_four(double2 (&a)[4]) {
|
| 124 |
+
double2 even = add(a[0],a[2]), even_diff = sub(a[0],a[2]);
|
| 125 |
+
double2 odd = add(a[1],a[3]), odd_diff = sub(a[1],a[3]);
|
| 126 |
+
double2 turn = make_double2(-odd_diff.y,odd_diff.x);
|
| 127 |
+
a[0] = add(even,odd); a[1] = add(even_diff,turn);
|
| 128 |
+
a[2] = sub(even,odd); a[3] = sub(even_diff,turn);
|
| 129 |
+
}
|
| 130 |
+
template<int F>
|
| 131 |
+
__device__ __forceinline__ void first_transform(double2 (&a)[F], const double2 *) {
|
| 132 |
+
if constexpr (F == 4) {
|
| 133 |
+
exact_four(a);
|
| 134 |
+
} else {
|
| 135 |
+
static_assert(F == 8);
|
| 136 |
+
double2 even[4] = {a[0],a[2],a[4],a[6]};
|
| 137 |
+
double2 odd[4] = {a[1],a[3],a[5],a[7]};
|
| 138 |
+
exact_four(even); exact_four(odd);
|
| 139 |
+
constexpr double c = 0x1.6a09e667f3bcdp-1;
|
| 140 |
+
#pragma unroll
|
| 141 |
+
for (int j = 0; j < 4; ++j) {
|
| 142 |
+
double2 v;
|
| 143 |
+
if (j == 0) v = odd[j];
|
| 144 |
+
if (j == 1) v = make_double2(c*(odd[j].x-odd[j].y),c*(odd[j].x+odd[j].y));
|
| 145 |
+
if (j == 2) v = make_double2(-odd[j].y,odd[j].x);
|
| 146 |
+
if (j == 3) v = make_double2(-c*(odd[j].x+odd[j].y),c*(odd[j].x-odd[j].y));
|
| 147 |
+
a[j] = add(even[j],v); a[j+4] = sub(even[j],v);
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
extern "C" __global__ __launch_bounds__(WARPS*32)
|
| 153 |
+
void fft_axis(const scalar_t *__restrict__ input, double2 *__restrict__ output,
|
| 154 |
+
const double2 *__restrict__ twiddle, unsigned long long *slot, scalar_t *caller_output) {
|
| 155 |
+
constexpr int G = WARPS*32/COLUMNS, R = LENGTH/G;
|
| 156 |
+
__shared__ double2 exchange[LENGTH*COLUMNS];
|
| 157 |
+
int lane_z = threadIdx.x % G;
|
| 158 |
+
int local_row = threadIdx.x / G;
|
| 159 |
+
int pair = blockIdx.x*(COLUMNS/2) + local_row/2;
|
| 160 |
+
int column = paired_row(twiddle, pair, local_row%2);
|
| 161 |
+
bool active = column < INNER && (local_row%2 == 0 ||
|
| 162 |
+
column != paired_row(twiddle, pair, 0));
|
| 163 |
+
|
| 164 |
+
// Each group reconstructs F evenly spaced frequencies for both rows,
|
| 165 |
+
// completes their first log2(F) butterflies, and exchanges that result.
|
| 166 |
+
constexpr int F = FIRST_RADIX, D = LENGTH/F;
|
| 167 |
+
int pair_lane = threadIdx.x % (2*G);
|
| 168 |
+
int pair_local = threadIdx.x / (2*G);
|
| 169 |
+
int row_a = paired_row(twiddle, pair, 0);
|
| 170 |
+
int row_b = paired_row(twiddle, pair, 1);
|
| 171 |
+
#pragma unroll
|
| 172 |
+
for (int r = 0; r < (D+2*G-1)/(2*G); ++r) {
|
| 173 |
+
int k = r*(2*G) + pair_lane;
|
| 174 |
+
if (k < D) {
|
| 175 |
+
double2 aa[F], bb[F];
|
| 176 |
+
#pragma unroll
|
| 177 |
+
for (int j = 0; j < F; ++j) {
|
| 178 |
+
int frequency = k+j*D;
|
| 179 |
+
double2 ca = make_double2(0., 0.), cb = ca;
|
| 180 |
+
if (row_a < INNER) {
|
| 181 |
+
double2 av = pair_load(input, row_a*(LENGTH+1)+frequency);
|
| 182 |
+
double2 bv = pair_load(input, row_b*(LENGTH+1)+LENGTH-frequency);
|
| 183 |
+
if (frequency == 0) {
|
| 184 |
+
double2 am = pair_load(input, row_b*(LENGTH+1));
|
| 185 |
+
double2 bm = pair_load(input, row_a*(LENGTH+1)+LENGTH);
|
| 186 |
+
av = make_double2(0.5*(av.x+am.x), 0.5*(av.y-am.y));
|
| 187 |
+
bv = make_double2(0.5*(bv.x+bm.x), 0.5*(bv.y-bm.y));
|
| 188 |
+
double sx = av.x+bv.x, sy = av.y+bv.y;
|
| 189 |
+
double dx = av.x-bv.x, dy = av.y-bv.y;
|
| 190 |
+
ca = make_double2(sx-sy, dy+dx);
|
| 191 |
+
cb = make_double2(sx+sy, dx-dy);
|
| 192 |
+
} else {
|
| 193 |
+
double2 w = twiddle[LENGTH+frequency];
|
| 194 |
+
double sx = av.x+bv.x, sy = av.y+bv.y;
|
| 195 |
+
double dx = av.x-bv.x, dy = av.y-bv.y;
|
| 196 |
+
double tr = -w.x*sy+w.y*dx;
|
| 197 |
+
double ti = w.x*dx+w.y*sy;
|
| 198 |
+
ca = make_double2(sx+tr, dy+ti);
|
| 199 |
+
cb = make_double2(sx-tr, ti-dy);
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
aa[j] = ca;
|
| 203 |
+
if (k == 0) bb[(F-j)%F] = cb;
|
| 204 |
+
else bb[F-1-j] = cb;
|
| 205 |
+
}
|
| 206 |
+
first_transform<F>(aa, twiddle);
|
| 207 |
+
first_transform<F>(bb, twiddle);
|
| 208 |
+
int base_a = __brev((unsigned)k) >> (32-LOG_BASE);
|
| 209 |
+
int base_b = __brev((unsigned)(k == 0 ? 0 : D-k)) >> (32-LOG_BASE);
|
| 210 |
+
#pragma unroll
|
| 211 |
+
for (int j = 0; j < F; ++j) {
|
| 212 |
+
exchange[(2*pair_local)*LENGTH+shared_index(base_a+j)] = aa[j];
|
| 213 |
+
exchange[(2*pair_local+1)*LENGTH+shared_index(base_b+j)] = bb[j];
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
__syncthreads();
|
| 218 |
+
double2 a[R];
|
| 219 |
+
#pragma unroll
|
| 220 |
+
for (int r = 0; r < R; ++r) {
|
| 221 |
+
int index = r*G + lane_z;
|
| 222 |
+
a[r] = exchange[local_row*LENGTH+shared_index(index)];
|
| 223 |
+
}
|
| 224 |
+
__syncthreads();
|
| 225 |
+
stage<FIRST_RADIX, LENGTH, G, 1, BASE>(a, twiddle, lane_z, exchange);
|
| 226 |
+
if constexpr (ODD != 1) {
|
| 227 |
+
#pragma unroll
|
| 228 |
+
for (int r = 0; r < BASE/G; ++r) {
|
| 229 |
+
double2 b[ODD];
|
| 230 |
+
b[0] = a[r];
|
| 231 |
+
#pragma unroll
|
| 232 |
+
for (int t = 1; t < ODD; ++t) {
|
| 233 |
+
double2 w = twiddle[t*(r*G+lane_z)];
|
| 234 |
+
w.y = -w.y;
|
| 235 |
+
b[t] = mul(a[r+t*(BASE/G)], w);
|
| 236 |
+
}
|
| 237 |
+
#pragma unroll
|
| 238 |
+
for (int k = 0; k < ODD; ++k) {
|
| 239 |
+
double2 v = b[0];
|
| 240 |
+
#pragma unroll
|
| 241 |
+
for (int t = 1; t < ODD; ++t) {
|
| 242 |
+
double2 w = twiddle[((k*t)%ODD)*BASE];
|
| 243 |
+
w.y = -w.y;
|
| 244 |
+
v = add(v, mul(b[t], w));
|
| 245 |
+
}
|
| 246 |
+
a[r+k*(BASE/G)] = v;
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
#if FREQUENCY
|
| 251 |
+
#pragma unroll
|
| 252 |
+
for (int r = 0; r < R; ++r) {
|
| 253 |
+
exchange[(threadIdx.x/G)*LENGTH + r*G + lane_z] = a[r];
|
| 254 |
+
}
|
| 255 |
+
__syncthreads();
|
| 256 |
+
#pragma unroll
|
| 257 |
+
for (int r = 0; r < R; ++r) {
|
| 258 |
+
int index = r*(WARPS*32)+threadIdx.x;
|
| 259 |
+
int row_local = index%COLUMNS;
|
| 260 |
+
int k = index/COLUMNS;
|
| 261 |
+
int row = blockIdx.x*COLUMNS+row_local;
|
| 262 |
+
if (row < INNER) output[k*INNER+row] = exchange[row_local*LENGTH+k];
|
| 263 |
+
}
|
| 264 |
+
#else
|
| 265 |
+
#pragma unroll
|
| 266 |
+
for (int r = 0; r < R; ++r) {
|
| 267 |
+
if (active) output[((column%NY)*NZ+column/NY)*LENGTH+r*G+lane_z] = a[r];
|
| 268 |
+
}
|
| 269 |
+
#endif
|
| 270 |
+
}
|
build/torch-cuda/_b200/c2r_f64_paired_x_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// The nominal FP64 output remains full-sized for cuFFT internal stages.
|
| 3 |
+
#if FFT_DOUBLE
|
| 4 |
+
using scalar_t = double;
|
| 5 |
+
#else
|
| 6 |
+
using scalar_t = float;
|
| 7 |
+
#endif
|
| 8 |
+
#if FFT_MIDDLE
|
| 9 |
+
__device__ void fft_wide_middle(void *, unsigned long long offset, double2 value,
|
| 10 |
+
void *caller_info, void *) {
|
| 11 |
+
constexpr unsigned int m = FFT_N/2;
|
| 12 |
+
unsigned int index = static_cast<unsigned int>(offset);
|
| 13 |
+
unsigned int y = index%FFT_NY, k=(index/FFT_NY)%m, z=index/(FFT_NY*m);
|
| 14 |
+
static_cast<double2 *>(caller_info)[(z*FFT_NY+y)*m+k] = value;
|
| 15 |
+
}
|
| 16 |
+
#else
|
| 17 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 18 |
+
double2 value, void *, void *) {
|
| 19 |
+
scalar_t *output = static_cast<scalar_t *>(physical_output) + FFT_INPUT_SHIFT;
|
| 20 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 21 |
+
reinterpret_cast<double2 *>(output)[offset] = value;
|
| 22 |
+
#elif !FFT_DOUBLE && FFT_INPUT_SHIFT % 2 == 0
|
| 23 |
+
reinterpret_cast<float2 *>(output)[offset] = make_float2((float)value.x,(float)value.y);
|
| 24 |
+
#else
|
| 25 |
+
output[2*offset] = static_cast<scalar_t>(value.x);
|
| 26 |
+
output[2*offset+1] = static_cast<scalar_t>(value.y);
|
| 27 |
+
#endif
|
| 28 |
+
}
|
| 29 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_paired_y_direct_inverse_lto.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile cuFFT LTO callbacks with the installed NVRTC, before execution.
|
| 3 |
+
|
| 4 |
+
This module can compile offline without initializing CUDA or loading code on
|
| 5 |
+
a GPU. Compiled blobs are cached by source, public shape, precision, NVRTC
|
| 6 |
+
version and target. No input tensor values or pointers are cached.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import ctypes as ct
|
| 11 |
+
import ctypes.util
|
| 12 |
+
import functools
|
| 13 |
+
import hashlib
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_LOCK = threading.Lock()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@functools.lru_cache(maxsize=1)
|
| 22 |
+
def library():
|
| 23 |
+
candidates = [ctypes.util.find_library('nvrtc')]
|
| 24 |
+
for root in (Path('/usr/local/cuda/lib64'),
|
| 25 |
+
Path('/usr/local/cuda/targets/x86_64-linux/lib')):
|
| 26 |
+
candidates.extend(str(p) for p in sorted(root.glob('libnvrtc.so*')))
|
| 27 |
+
candidates.extend(('libnvrtc.so.13', 'libnvrtc.so.12', 'libnvrtc.so'))
|
| 28 |
+
lib = None
|
| 29 |
+
for name in candidates:
|
| 30 |
+
if name:
|
| 31 |
+
try:
|
| 32 |
+
lib = ct.CDLL(name)
|
| 33 |
+
break
|
| 34 |
+
except OSError:
|
| 35 |
+
pass
|
| 36 |
+
if lib is None:
|
| 37 |
+
raise RuntimeError('The installed NVRTC library is unavailable')
|
| 38 |
+
signatures = {
|
| 39 |
+
'nvrtcVersion': [ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)],
|
| 40 |
+
'nvrtcCreateProgram': [ct.POINTER(ct.c_void_p), ct.c_char_p, ct.c_char_p,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)],
|
| 42 |
+
'nvrtcCompileProgram': [ct.c_void_p, ct.c_int, ct.POINTER(ct.c_char_p)],
|
| 43 |
+
'nvrtcGetProgramLogSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 44 |
+
'nvrtcGetProgramLog': [ct.c_void_p, ct.c_void_p],
|
| 45 |
+
'nvrtcGetLTOIRSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 46 |
+
'nvrtcGetLTOIR': [ct.c_void_p, ct.c_void_p],
|
| 47 |
+
'nvrtcDestroyProgram': [ct.POINTER(ct.c_void_p)],
|
| 48 |
+
}
|
| 49 |
+
for name, signature in signatures.items():
|
| 50 |
+
function = getattr(lib, name)
|
| 51 |
+
function.argtypes = signature
|
| 52 |
+
function.restype = ct.c_int
|
| 53 |
+
return lib
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _check(result):
|
| 57 |
+
if result:
|
| 58 |
+
raise RuntimeError(f'NVRTC returned error {result}')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def version():
|
| 62 |
+
major, minor = ct.c_int(), ct.c_int()
|
| 63 |
+
_check(library().nvrtcVersion(ct.byref(major), ct.byref(minor)))
|
| 64 |
+
return major.value, minor.value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@functools.lru_cache(maxsize=128)
|
| 68 |
+
def _compile(source, shape, double, arch, nvrtc_version, input_shift, kind):
|
| 69 |
+
nz, ny, nx = shape
|
| 70 |
+
from .dense_half_inverse_schedule import twiddles
|
| 71 |
+
roots = twiddles(ny)
|
| 72 |
+
initializer = ','.join(value.hex() for value in roots)
|
| 73 |
+
declaration = ('__device__ __constant__ __align__(16) double fft_twiddles[] = {'
|
| 74 |
+
+ initializer + '};\n')
|
| 75 |
+
source = declaration.encode() + source
|
| 76 |
+
lib = library()
|
| 77 |
+
options = ('--std=c++17', '--relocatable-device-code=true', '-dlto',
|
| 78 |
+
f'--gpu-architecture=compute_{arch}', f'-DFFT_N={nx}',
|
| 79 |
+
f'-DFFT_NZ={nz}', f'-DFFT_NY={ny}',
|
| 80 |
+
f'-DFFT_DOUBLE={int(double)}', f'-DFFT_INPUT_SHIFT={input_shift}',
|
| 81 |
+
f'-DFFT_LOAD={int(kind == "load")}')
|
| 82 |
+
program = ct.c_void_p()
|
| 83 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'endpoint.cu', 0, None, None))
|
| 84 |
+
try:
|
| 85 |
+
option_array = (ct.c_char_p * len(options))(*(v.encode() for v in options))
|
| 86 |
+
result = lib.nvrtcCompileProgram(program, len(options), option_array)
|
| 87 |
+
log_size = ct.c_size_t()
|
| 88 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(log_size)))
|
| 89 |
+
log = ct.create_string_buffer(log_size.value)
|
| 90 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 91 |
+
if result:
|
| 92 |
+
raise RuntimeError(f'NVRTC returned error {result}: {log.value.decode()}')
|
| 93 |
+
size = ct.c_size_t()
|
| 94 |
+
_check(lib.nvrtcGetLTOIRSize(program, ct.byref(size)))
|
| 95 |
+
data = ct.create_string_buffer(size.value)
|
| 96 |
+
_check(lib.nvrtcGetLTOIR(program, data))
|
| 97 |
+
blob = data.raw
|
| 98 |
+
metadata = dict(shape=shape, double=bool(double), target=f'compute_{arch}',
|
| 99 |
+
nvrtc=list(nvrtc_version), options=list(options),
|
| 100 |
+
source_sha256=hashlib.sha256(source).hexdigest(),
|
| 101 |
+
lto_ir_sha256=hashlib.sha256(blob).hexdigest(),
|
| 102 |
+
lto_ir_bytes=len(blob), compiler_log=log.value.decode())
|
| 103 |
+
return blob, metadata
|
| 104 |
+
finally:
|
| 105 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def compile_callback(shape, input_shift, kind, arch=100, double=False):
|
| 109 |
+
"""Reconstruct a half-length spectrum in FP64 or store its inverse as FP32."""
|
| 110 |
+
if kind not in ('load', 'store'):
|
| 111 |
+
raise ValueError('Expected a load or store callback')
|
| 112 |
+
source = Path(__file__).with_name('c2r_f64_paired_y_direct_inverse_wide_redirect.cu').read_bytes()
|
| 113 |
+
with _LOCK:
|
| 114 |
+
blob, metadata = _compile(source, tuple(shape), bool(double), int(arch), version(), input_shift, kind)
|
| 115 |
+
return (('fft_wide_' + kind).encode(), 1 if kind == 'load' else 5,
|
| 116 |
+
ct.create_string_buffer(blob, len(blob)), dict(metadata, kind=kind))
|
build/torch-cuda/_b200/c2r_f64_paired_y_direct_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Reconstruct a real-pair transform from arbitrary tightly packed X spectra.
|
| 3 |
+
#define PACK_Y 1
|
| 4 |
+
#if (1ULL * FFT_NZ * FFT_NY * FFT_N) <= 0x100000000ULL
|
| 5 |
+
using index_t = unsigned int;
|
| 6 |
+
#else
|
| 7 |
+
using index_t = unsigned long long;
|
| 8 |
+
#endif
|
| 9 |
+
#if FFT_DOUBLE
|
| 10 |
+
using scalar_t = double;
|
| 11 |
+
#else
|
| 12 |
+
using scalar_t = float;
|
| 13 |
+
#endif
|
| 14 |
+
static __device__ __forceinline__ index_t negative(index_t k, index_t n) {
|
| 15 |
+
return k == 0 ? 0 : n - k;
|
| 16 |
+
}
|
| 17 |
+
static __device__ __forceinline__ double2 pair_load(const scalar_t *input, index_t k) {
|
| 18 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 19 |
+
return __ldcs(reinterpret_cast<const double2 *>(input)+k);
|
| 20 |
+
#elif !FFT_DOUBLE && FFT_INPUT_SHIFT % 2 == 0
|
| 21 |
+
const float2 v = __ldcs(reinterpret_cast<const float2 *>(input)+k);
|
| 22 |
+
return make_double2(static_cast<double>(v.x), static_cast<double>(v.y));
|
| 23 |
+
#else
|
| 24 |
+
return make_double2(static_cast<double>(__ldcs(input+2ULL*k)),
|
| 25 |
+
static_cast<double>(__ldcs(input+2ULL*k+1)));
|
| 26 |
+
#endif
|
| 27 |
+
}
|
| 28 |
+
static __device__ __forceinline__ double2 full_spectrum(
|
| 29 |
+
const scalar_t *input, index_t z, index_t y, index_t x) {
|
| 30 |
+
constexpr index_t nf = FFT_N/2 + 1;
|
| 31 |
+
const bool conjugate = x > FFT_N/2;
|
| 32 |
+
if (conjugate) {
|
| 33 |
+
z = negative(z, FFT_NZ);
|
| 34 |
+
y = negative(y, FFT_NY);
|
| 35 |
+
x = FFT_N-x;
|
| 36 |
+
}
|
| 37 |
+
double2 a = pair_load(input, (z*FFT_NY+y)*nf+x);
|
| 38 |
+
if (x == 0 || x == FFT_N/2) {
|
| 39 |
+
const double2 mirror = pair_load(input,
|
| 40 |
+
(negative(z,FFT_NZ)*FFT_NY+negative(y,FFT_NY))*nf+x);
|
| 41 |
+
a.x = 0.5*a.x+0.5*mirror.x;
|
| 42 |
+
a.y = 0.5*a.y-0.5*mirror.y;
|
| 43 |
+
}
|
| 44 |
+
if (conjugate) a.y = -a.y;
|
| 45 |
+
return a;
|
| 46 |
+
}
|
| 47 |
+
#if FFT_LOAD
|
| 48 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 49 |
+
void *caller_info, void *) {
|
| 50 |
+
constexpr index_t hy = PACK_Y ? FFT_NY/2 : FFT_NY;
|
| 51 |
+
const index_t index = static_cast<index_t>(offset);
|
| 52 |
+
const index_t x = index % FFT_N;
|
| 53 |
+
const index_t y = (index / FFT_N) % hy;
|
| 54 |
+
const index_t z = index / (hy*FFT_N);
|
| 55 |
+
const scalar_t *input = static_cast<const scalar_t *>(data)+FFT_INPUT_SHIFT;
|
| 56 |
+
// Both packed frequencies use one common X reflection. Endpoint
|
| 57 |
+
// projection is needed only for the two original X boundary planes.
|
| 58 |
+
const bool conjugate = x > FFT_N/2;
|
| 59 |
+
const index_t px = conjugate ? FFT_N-x : x;
|
| 60 |
+
const index_t az = conjugate ? negative(z,FFT_NZ) : z;
|
| 61 |
+
const index_t ay = conjugate ? negative(y,FFT_NY) : y;
|
| 62 |
+
#if PACK_Y
|
| 63 |
+
const index_t bz = az;
|
| 64 |
+
const index_t by = conjugate ? negative(y+FFT_NY/2,FFT_NY) : y+FFT_NY/2;
|
| 65 |
+
const index_t k = y;
|
| 66 |
+
#else
|
| 67 |
+
const index_t bz = conjugate ? negative(z+FFT_NZ/2,FFT_NZ) : z+FFT_NZ/2;
|
| 68 |
+
const index_t by = ay;
|
| 69 |
+
const index_t k = z;
|
| 70 |
+
#endif
|
| 71 |
+
constexpr index_t nf = FFT_N/2+1;
|
| 72 |
+
double2 a = pair_load(input,(az*FFT_NY+ay)*nf+px);
|
| 73 |
+
double2 b = pair_load(input,(bz*FFT_NY+by)*nf+px);
|
| 74 |
+
if (px == 0 || px == FFT_N/2) {
|
| 75 |
+
const double2 am = pair_load(input,
|
| 76 |
+
(negative(az,FFT_NZ)*FFT_NY+negative(ay,FFT_NY))*nf+px);
|
| 77 |
+
const double2 bm = pair_load(input,
|
| 78 |
+
(negative(bz,FFT_NZ)*FFT_NY+negative(by,FFT_NY))*nf+px);
|
| 79 |
+
a.x = 0.5*a.x+0.5*am.x;
|
| 80 |
+
a.y = 0.5*a.y-0.5*am.y;
|
| 81 |
+
b.x = 0.5*b.x+0.5*bm.x;
|
| 82 |
+
b.y = 0.5*b.y-0.5*bm.y;
|
| 83 |
+
}
|
| 84 |
+
if (conjugate) a.y = -a.y;
|
| 85 |
+
else b.y = -b.y;
|
| 86 |
+
const double2 w = reinterpret_cast<const double2 *>(fft_twiddles)[k];
|
| 87 |
+
// Preserve FP64 arithmetic while selecting the rounding sequence.
|
| 88 |
+
const double sr = a.x + b.x, dr = a.x - b.x;
|
| 89 |
+
const double si = a.y + b.y, di = a.y - b.y;
|
| 90 |
+
double2 result;
|
| 91 |
+
result.x = __fma_rn(w.y, dr, __dadd_rn(sr, -__dmul_rn(w.x, si)));
|
| 92 |
+
result.y = __fma_rn(w.y, si, __dadd_rn(di, __dmul_rn(w.x, dr)));
|
| 93 |
+
return result;
|
| 94 |
+
}
|
| 95 |
+
#else
|
| 96 |
+
__device__ void fft_wide_store(void *data, unsigned long long offset,
|
| 97 |
+
double2 value, void *, void *) {
|
| 98 |
+
constexpr index_t hy = PACK_Y ? FFT_NY/2 : FFT_NY;
|
| 99 |
+
const index_t index = static_cast<index_t>(offset);
|
| 100 |
+
const index_t x = index % FFT_N;
|
| 101 |
+
const index_t y = (index / FFT_N) % hy;
|
| 102 |
+
const index_t z = index / (hy*FFT_N);
|
| 103 |
+
scalar_t *output = static_cast<scalar_t *>(data)+FFT_INPUT_SHIFT;
|
| 104 |
+
#if PACK_Y
|
| 105 |
+
const index_t even = (z*FFT_NY+2*y)*FFT_N+x;
|
| 106 |
+
constexpr index_t distance = FFT_N;
|
| 107 |
+
#else
|
| 108 |
+
const index_t even = (2*z*FFT_NY+y)*FFT_N+x;
|
| 109 |
+
constexpr index_t distance = FFT_NY*FFT_N;
|
| 110 |
+
#endif
|
| 111 |
+
__stcs(output+even, static_cast<scalar_t>(value.x));
|
| 112 |
+
__stcs(output+even+distance, static_cast<scalar_t>(value.y));
|
| 113 |
+
}
|
| 114 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_partial_y_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Select index width from the complete physical packed extent at compilation.
|
| 3 |
+
// The callback ABI remains 64-bit. Every valid caller index fits the selected
|
| 4 |
+
// unsigned type; pointer arithmetic still computes full-width device addresses.
|
| 5 |
+
#if (1ULL * FFT_NZ * FFT_NY * (FFT_N / 2 + 1)) <= 0x100000000ULL
|
| 6 |
+
using fft_index_t = unsigned int;
|
| 7 |
+
static_assert(sizeof(fft_index_t) == 4, "A 32-bit unsigned integer is required");
|
| 8 |
+
#else
|
| 9 |
+
using fft_index_t = unsigned long long;
|
| 10 |
+
#endif
|
| 11 |
+
#if FFT_DOUBLE
|
| 12 |
+
using caller_scalar_t = double;
|
| 13 |
+
#else
|
| 14 |
+
using caller_scalar_t = float;
|
| 15 |
+
#endif
|
| 16 |
+
static __device__ __forceinline__ double2 fft_scalar_pair(const caller_scalar_t *input, fft_index_t index) {
|
| 17 |
+
double2 value;
|
| 18 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 19 |
+
value = reinterpret_cast<const double2 *>(input)[index];
|
| 20 |
+
#elif !FFT_DOUBLE && (FFT_INPUT_SHIFT % 2 == 0)
|
| 21 |
+
// The pair address is aligned for scalar residues zero and two.
|
| 22 |
+
const float2 packed = reinterpret_cast<const float2 *>(input)[index];
|
| 23 |
+
value.x = static_cast<double>(packed.x);
|
| 24 |
+
value.y = static_cast<double>(packed.y);
|
| 25 |
+
#else
|
| 26 |
+
value.x = static_cast<double>(input[2ULL * index]);
|
| 27 |
+
value.y = static_cast<double>(input[2ULL * index + 1]);
|
| 28 |
+
#endif
|
| 29 |
+
return value;
|
| 30 |
+
}
|
| 31 |
+
// The cuFFT logical input is dense. Callback arithmetic reads exact nf-stride
|
| 32 |
+
// caller storage directly; no input repacking is performed.
|
| 33 |
+
// Reconstruct twice the half-length complex spectrum from immutable packed
|
| 34 |
+
// caller input. Endpoint projection preserves irfftn for arbitrary spectra.
|
| 35 |
+
#if FFT_LOAD
|
| 36 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 37 |
+
void *caller_info, void *shared_memory) {
|
| 38 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 39 |
+
constexpr fft_index_t nf = m + 1;
|
| 40 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 41 |
+
const fft_index_t k = index % m;
|
| 42 |
+
constexpr fft_index_t short_y = FFT_NY / FFT_RADIX;
|
| 43 |
+
const fft_index_t logical_row = index / m;
|
| 44 |
+
const fft_index_t j = logical_row % short_y;
|
| 45 |
+
const fft_index_t r = (logical_row / short_y) % FFT_RADIX;
|
| 46 |
+
const fft_index_t z = logical_row / FFT_NY;
|
| 47 |
+
const fft_index_t y = FFT_RADIX * j + r;
|
| 48 |
+
const fft_index_t row = z * FFT_NY + y;
|
| 49 |
+
const fft_index_t mirror_y = y == 0 ? 0 : FFT_NY - y;
|
| 50 |
+
const fft_index_t mirror_z = z == 0 ? 0 : FFT_NZ - z;
|
| 51 |
+
const fft_index_t mirror_row = mirror_z * FFT_NY + mirror_y;
|
| 52 |
+
const caller_scalar_t *input = static_cast<const caller_scalar_t *>(data) + FFT_INPUT_SHIFT;
|
| 53 |
+
double2 a = fft_scalar_pair(input, row * nf + k);
|
| 54 |
+
double2 b = fft_scalar_pair(input, mirror_row * nf + m - k);
|
| 55 |
+
if (k == 0) {
|
| 56 |
+
// Project each boundary plane against its Z/Y conjugate partner.
|
| 57 |
+
// b represents the conjugate of the projected Nyquist value.
|
| 58 |
+
const double2 a_mirror = fft_scalar_pair(input, mirror_row * nf);
|
| 59 |
+
const double2 b_mirror = fft_scalar_pair(input, row * nf + m);
|
| 60 |
+
a.x = 0.5 * (a.x + a_mirror.x);
|
| 61 |
+
a.y = 0.5 * (a.y - a_mirror.y);
|
| 62 |
+
b.x = 0.5 * (b.x + b_mirror.x);
|
| 63 |
+
b.y = 0.5 * (b.y - b_mirror.y);
|
| 64 |
+
}
|
| 65 |
+
const double2 w = static_cast<const double2 *>(caller_info)[k];
|
| 66 |
+
double2 result;
|
| 67 |
+
result.x = a.x + b.x - w.x * (a.y + b.y) + w.y * (a.x - b.x);
|
| 68 |
+
result.y = a.y - b.y + w.x * (a.x - b.x) + w.y * (a.y + b.y);
|
| 69 |
+
return result;
|
| 70 |
+
}
|
| 71 |
+
#elif FFT_FINISH
|
| 72 |
+
// Complete the factored inverse Y transform while reading mathematical XY
|
| 73 |
+
// results for the Z FFT. The intermediate has no padding or repack pass.
|
| 74 |
+
__device__ double2 fft_wide_finish(void *data, unsigned long long offset,
|
| 75 |
+
void *caller_info, void *shared_memory) {
|
| 76 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 77 |
+
constexpr fft_index_t short_y = FFT_NY / FFT_RADIX;
|
| 78 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 79 |
+
const fft_index_t x = index % m;
|
| 80 |
+
const fft_index_t row = index / m;
|
| 81 |
+
const fft_index_t y = row % FFT_NY;
|
| 82 |
+
const fft_index_t z = row / FFT_NY;
|
| 83 |
+
const fft_index_t q = y % short_y;
|
| 84 |
+
const double2 *input = static_cast<const double2 *>(data);
|
| 85 |
+
const double2 *table = static_cast<const double2 *>(caller_info) + FFT_N;
|
| 86 |
+
double2 result = input[(z * FFT_NY + q) * m + x];
|
| 87 |
+
#pragma unroll
|
| 88 |
+
for (fft_index_t r = 1; r < FFT_RADIX; ++r) {
|
| 89 |
+
const double2 v = input[(z * FFT_NY + r * short_y + q) * m + x];
|
| 90 |
+
const double2 w = table[(r * y) % FFT_NY];
|
| 91 |
+
result.x += v.x * w.x + v.y * w.y;
|
| 92 |
+
result.y += v.y * w.x - v.x * w.y;
|
| 93 |
+
}
|
| 94 |
+
return result;
|
| 95 |
+
}
|
| 96 |
+
#else
|
| 97 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 98 |
+
double2 value, void *caller_info, void *shared_memory) {
|
| 99 |
+
caller_scalar_t *output = static_cast<caller_scalar_t *>(physical_output) + FFT_INPUT_SHIFT;
|
| 100 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 101 |
+
// A nominal cuFFT output address is complex aligned.
|
| 102 |
+
reinterpret_cast<double2 *>(output)[offset] = value;
|
| 103 |
+
#elif !FFT_DOUBLE && (FFT_INPUT_SHIFT % 2 == 0)
|
| 104 |
+
// Both components fit one aligned 64-bit store at these scalar residues.
|
| 105 |
+
float2 packed;
|
| 106 |
+
packed.x = static_cast<float>(value.x);
|
| 107 |
+
packed.y = static_cast<float>(value.y);
|
| 108 |
+
reinterpret_cast<float2 *>(output)[offset] = packed;
|
| 109 |
+
#else
|
| 110 |
+
output[2 * offset] = static_cast<caller_scalar_t>(value.x);
|
| 111 |
+
output[2 * offset + 1] = static_cast<caller_scalar_t>(value.y);
|
| 112 |
+
#endif
|
| 113 |
+
}
|
| 114 |
+
#endif
|
build/torch-cuda/_b200/c2r_f64_scalar_input_inverse_scalar_io.cu
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// API addresses are rounded down to 16 bytes. Compile-time scalar residues
|
| 3 |
+
// recover the exact caller address; no scalar preceding the caller is used.
|
| 4 |
+
#if FFT_DOUBLE
|
| 5 |
+
using scalar = double;
|
| 6 |
+
#else
|
| 7 |
+
using scalar = float;
|
| 8 |
+
#endif
|
| 9 |
+
|
| 10 |
+
#if IS_LOAD
|
| 11 |
+
__device__ double2 fft_scalar_load(void *data, unsigned long long offset,
|
| 12 |
+
void *caller_info, void *shared_memory) {
|
| 13 |
+
const volatile scalar *input = static_cast<const volatile scalar *>(data);
|
| 14 |
+
double2 value;
|
| 15 |
+
value.x = static_cast<double>(input[2 * offset + SCALAR_SHIFT]);
|
| 16 |
+
value.y = static_cast<double>(input[2 * offset + SCALAR_SHIFT + 1]);
|
| 17 |
+
return value;
|
| 18 |
+
}
|
| 19 |
+
#else
|
| 20 |
+
__device__ void fft_scalar_store(void *data, unsigned long long offset,
|
| 21 |
+
double value, void *caller_info,
|
| 22 |
+
void *shared_memory) {
|
| 23 |
+
static_cast<volatile scalar *>(data)[offset + SCALAR_SHIFT] =
|
| 24 |
+
static_cast<scalar>(value);
|
| 25 |
+
}
|
| 26 |
+
#endif
|
build/torch-cuda/_b200/c2r_paired_y_direct_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Reconstruct a real-pair transform from arbitrary tightly packed X spectra.
|
| 3 |
+
#define PACK_Y 1
|
| 4 |
+
#if (1ULL * FFT_NZ * FFT_NY * FFT_N) <= 0x100000000ULL
|
| 5 |
+
using index_t = unsigned int;
|
| 6 |
+
#else
|
| 7 |
+
using index_t = unsigned long long;
|
| 8 |
+
#endif
|
| 9 |
+
#if FFT_DOUBLE
|
| 10 |
+
using scalar_t = double;
|
| 11 |
+
#else
|
| 12 |
+
using scalar_t = float;
|
| 13 |
+
#endif
|
| 14 |
+
static __device__ __forceinline__ index_t negative(index_t k, index_t n) {
|
| 15 |
+
return k == 0 ? 0 : n - k;
|
| 16 |
+
}
|
| 17 |
+
static __device__ __forceinline__ double2 pair_load(const scalar_t *input, index_t k) {
|
| 18 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 19 |
+
return reinterpret_cast<const double2 *>(input)[k];
|
| 20 |
+
#elif !FFT_DOUBLE && FFT_INPUT_SHIFT % 2 == 0
|
| 21 |
+
const float2 v = reinterpret_cast<const float2 *>(input)[k];
|
| 22 |
+
return make_double2(static_cast<double>(v.x), static_cast<double>(v.y));
|
| 23 |
+
#else
|
| 24 |
+
return make_double2(static_cast<double>(input[2ULL*k]),
|
| 25 |
+
static_cast<double>(input[2ULL*k+1]));
|
| 26 |
+
#endif
|
| 27 |
+
}
|
| 28 |
+
static __device__ __forceinline__ double2 full_spectrum(
|
| 29 |
+
const scalar_t *input, index_t z, index_t y, index_t x) {
|
| 30 |
+
constexpr index_t nf = FFT_N/2 + 1;
|
| 31 |
+
const bool conjugate = x > FFT_N/2;
|
| 32 |
+
if (conjugate) {
|
| 33 |
+
z = negative(z, FFT_NZ);
|
| 34 |
+
y = negative(y, FFT_NY);
|
| 35 |
+
x = FFT_N-x;
|
| 36 |
+
}
|
| 37 |
+
double2 a = pair_load(input, (z*FFT_NY+y)*nf+x);
|
| 38 |
+
if (x == 0 || x == FFT_N/2) {
|
| 39 |
+
const double2 mirror = pair_load(input,
|
| 40 |
+
(negative(z,FFT_NZ)*FFT_NY+negative(y,FFT_NY))*nf+x);
|
| 41 |
+
a.x = 0.5*a.x+0.5*mirror.x;
|
| 42 |
+
a.y = 0.5*a.y-0.5*mirror.y;
|
| 43 |
+
}
|
| 44 |
+
if (conjugate) a.y = -a.y;
|
| 45 |
+
return a;
|
| 46 |
+
}
|
| 47 |
+
#if FFT_LOAD
|
| 48 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 49 |
+
void *caller_info, void *) {
|
| 50 |
+
constexpr index_t hy = PACK_Y ? FFT_NY/2 : FFT_NY;
|
| 51 |
+
const index_t index = static_cast<index_t>(offset);
|
| 52 |
+
const index_t x = index % FFT_N;
|
| 53 |
+
const index_t y = (index / FFT_N) % hy;
|
| 54 |
+
const index_t z = index / (hy*FFT_N);
|
| 55 |
+
const scalar_t *input = static_cast<const scalar_t *>(data)+FFT_INPUT_SHIFT;
|
| 56 |
+
// Both packed frequencies use one common X reflection. Endpoint
|
| 57 |
+
// projection is needed only for the two original X boundary planes.
|
| 58 |
+
const bool conjugate = x > FFT_N/2;
|
| 59 |
+
const index_t px = conjugate ? FFT_N-x : x;
|
| 60 |
+
const index_t az = conjugate ? negative(z,FFT_NZ) : z;
|
| 61 |
+
const index_t ay = conjugate ? negative(y,FFT_NY) : y;
|
| 62 |
+
#if PACK_Y
|
| 63 |
+
const index_t bz = az;
|
| 64 |
+
const index_t by = conjugate ? negative(y+FFT_NY/2,FFT_NY) : y+FFT_NY/2;
|
| 65 |
+
const index_t k = y;
|
| 66 |
+
#else
|
| 67 |
+
const index_t bz = conjugate ? negative(z+FFT_NZ/2,FFT_NZ) : z+FFT_NZ/2;
|
| 68 |
+
const index_t by = ay;
|
| 69 |
+
const index_t k = z;
|
| 70 |
+
#endif
|
| 71 |
+
constexpr index_t nf = FFT_N/2+1;
|
| 72 |
+
double2 a = pair_load(input,(az*FFT_NY+ay)*nf+px);
|
| 73 |
+
double2 b = pair_load(input,(bz*FFT_NY+by)*nf+px);
|
| 74 |
+
if (px == 0 || px == FFT_N/2) {
|
| 75 |
+
const double2 am = pair_load(input,
|
| 76 |
+
(negative(az,FFT_NZ)*FFT_NY+negative(ay,FFT_NY))*nf+px);
|
| 77 |
+
const double2 bm = pair_load(input,
|
| 78 |
+
(negative(bz,FFT_NZ)*FFT_NY+negative(by,FFT_NY))*nf+px);
|
| 79 |
+
a.x = 0.5*a.x+0.5*am.x;
|
| 80 |
+
a.y = 0.5*a.y-0.5*am.y;
|
| 81 |
+
b.x = 0.5*b.x+0.5*bm.x;
|
| 82 |
+
b.y = 0.5*b.y-0.5*bm.y;
|
| 83 |
+
}
|
| 84 |
+
if (conjugate) a.y = -a.y;
|
| 85 |
+
else b.y = -b.y;
|
| 86 |
+
const double2 w = static_cast<const double2 *>(caller_info)[k];
|
| 87 |
+
return make_double2(a.x+b.x-w.x*(a.y+b.y)+w.y*(a.x-b.x),
|
| 88 |
+
a.y-b.y+w.x*(a.x-b.x)+w.y*(a.y+b.y));
|
| 89 |
+
}
|
| 90 |
+
#else
|
| 91 |
+
__device__ void fft_wide_store(void *data, unsigned long long offset,
|
| 92 |
+
double2 value, void *, void *) {
|
| 93 |
+
constexpr index_t hy = PACK_Y ? FFT_NY/2 : FFT_NY;
|
| 94 |
+
const index_t index = static_cast<index_t>(offset);
|
| 95 |
+
const index_t x = index % FFT_N;
|
| 96 |
+
const index_t y = (index / FFT_N) % hy;
|
| 97 |
+
const index_t z = index / (hy*FFT_N);
|
| 98 |
+
scalar_t *output = static_cast<scalar_t *>(data)+FFT_INPUT_SHIFT;
|
| 99 |
+
#if PACK_Y
|
| 100 |
+
const index_t even = (z*FFT_NY+2*y)*FFT_N+x;
|
| 101 |
+
constexpr index_t distance = FFT_N;
|
| 102 |
+
#else
|
| 103 |
+
const index_t even = (2*z*FFT_NY+y)*FFT_N+x;
|
| 104 |
+
constexpr index_t distance = FFT_NY*FFT_N;
|
| 105 |
+
#endif
|
| 106 |
+
output[even] = static_cast<scalar_t>(value.x);
|
| 107 |
+
output[even+distance] = static_cast<scalar_t>(value.y);
|
| 108 |
+
}
|
| 109 |
+
#endif
|
build/torch-cuda/_b200/c2r_paired_y_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Reconstruct a real-pair transform from arbitrary tightly packed X spectra.
|
| 3 |
+
#define PACK_Y 1
|
| 4 |
+
#if (1ULL * FFT_NZ * FFT_NY * FFT_N) <= 0x100000000ULL
|
| 5 |
+
using index_t = unsigned int;
|
| 6 |
+
#else
|
| 7 |
+
using index_t = unsigned long long;
|
| 8 |
+
#endif
|
| 9 |
+
#if FFT_DOUBLE
|
| 10 |
+
using scalar_t = double;
|
| 11 |
+
#else
|
| 12 |
+
using scalar_t = float;
|
| 13 |
+
#endif
|
| 14 |
+
static __device__ __forceinline__ index_t negative(index_t k, index_t n) {
|
| 15 |
+
return k == 0 ? 0 : n - k;
|
| 16 |
+
}
|
| 17 |
+
static __device__ __forceinline__ double2 pair_load(const scalar_t *input, index_t k) {
|
| 18 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 19 |
+
return reinterpret_cast<const double2 *>(input)[k];
|
| 20 |
+
#elif !FFT_DOUBLE && FFT_INPUT_SHIFT % 2 == 0
|
| 21 |
+
const float2 v = reinterpret_cast<const float2 *>(input)[k];
|
| 22 |
+
return make_double2(static_cast<double>(v.x), static_cast<double>(v.y));
|
| 23 |
+
#else
|
| 24 |
+
return make_double2(static_cast<double>(input[2ULL*k]),
|
| 25 |
+
static_cast<double>(input[2ULL*k+1]));
|
| 26 |
+
#endif
|
| 27 |
+
}
|
| 28 |
+
static __device__ __forceinline__ double2 full_spectrum(
|
| 29 |
+
const scalar_t *input, index_t z, index_t y, index_t x) {
|
| 30 |
+
constexpr index_t nf = FFT_N/2 + 1;
|
| 31 |
+
const bool conjugate = x > FFT_N/2;
|
| 32 |
+
if (conjugate) {
|
| 33 |
+
z = negative(z, FFT_NZ);
|
| 34 |
+
y = negative(y, FFT_NY);
|
| 35 |
+
x = FFT_N-x;
|
| 36 |
+
}
|
| 37 |
+
double2 a = pair_load(input, (z*FFT_NY+y)*nf+x);
|
| 38 |
+
if (x == 0 || x == FFT_N/2) {
|
| 39 |
+
const double2 mirror = pair_load(input,
|
| 40 |
+
(negative(z,FFT_NZ)*FFT_NY+negative(y,FFT_NY))*nf+x);
|
| 41 |
+
a.x = 0.5*a.x+0.5*mirror.x;
|
| 42 |
+
a.y = 0.5*a.y-0.5*mirror.y;
|
| 43 |
+
}
|
| 44 |
+
if (conjugate) a.y = -a.y;
|
| 45 |
+
return a;
|
| 46 |
+
}
|
| 47 |
+
#if FFT_LOAD
|
| 48 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 49 |
+
void *caller_info, void *) {
|
| 50 |
+
constexpr index_t hy = PACK_Y ? FFT_NY/2 : FFT_NY;
|
| 51 |
+
const index_t index = static_cast<index_t>(offset);
|
| 52 |
+
const index_t x = index % FFT_N;
|
| 53 |
+
const index_t y = (index / FFT_N) % hy;
|
| 54 |
+
const index_t z = index / (hy*FFT_N);
|
| 55 |
+
const scalar_t *input = static_cast<const scalar_t *>(data)+FFT_INPUT_SHIFT;
|
| 56 |
+
const double2 a = full_spectrum(input,z,y,x);
|
| 57 |
+
#if PACK_Y
|
| 58 |
+
const double2 b = full_spectrum(input,negative(z,FFT_NZ),FFT_NY/2-y,negative(x,FFT_N));
|
| 59 |
+
const index_t k = y;
|
| 60 |
+
#else
|
| 61 |
+
const double2 b = full_spectrum(input,FFT_NZ/2-z,negative(y,FFT_NY),negative(x,FFT_N));
|
| 62 |
+
const index_t k = z;
|
| 63 |
+
#endif
|
| 64 |
+
const double2 w = static_cast<const double2 *>(caller_info)[k];
|
| 65 |
+
return make_double2(a.x+b.x-w.x*(a.y+b.y)+w.y*(a.x-b.x),
|
| 66 |
+
a.y-b.y+w.x*(a.x-b.x)+w.y*(a.y+b.y));
|
| 67 |
+
}
|
| 68 |
+
#else
|
| 69 |
+
__device__ void fft_wide_store(void *data, unsigned long long offset,
|
| 70 |
+
double2 value, void *, void *) {
|
| 71 |
+
constexpr index_t hy = PACK_Y ? FFT_NY/2 : FFT_NY;
|
| 72 |
+
const index_t index = static_cast<index_t>(offset);
|
| 73 |
+
const index_t x = index % FFT_N;
|
| 74 |
+
const index_t y = (index / FFT_N) % hy;
|
| 75 |
+
const index_t z = index / (hy*FFT_N);
|
| 76 |
+
scalar_t *output = static_cast<scalar_t *>(data)+FFT_INPUT_SHIFT;
|
| 77 |
+
#if PACK_Y
|
| 78 |
+
const index_t even = (z*FFT_NY+2*y)*FFT_N+x;
|
| 79 |
+
constexpr index_t distance = FFT_N;
|
| 80 |
+
#else
|
| 81 |
+
const index_t even = (2*z*FFT_NY+y)*FFT_N+x;
|
| 82 |
+
constexpr index_t distance = FFT_NY*FFT_N;
|
| 83 |
+
#endif
|
| 84 |
+
output[even] = static_cast<scalar_t>(value.x);
|
| 85 |
+
output[even+distance] = static_cast<scalar_t>(value.y);
|
| 86 |
+
}
|
| 87 |
+
#endif
|
build/torch-cuda/_b200/c2r_pitched_half_inverse_wide_redirect.cu
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Select index width from the complete physical packed extent at compilation.
|
| 3 |
+
// The callback ABI remains 64-bit. Every valid caller index fits the selected
|
| 4 |
+
// unsigned type; pointer arithmetic still computes full-width device addresses.
|
| 5 |
+
#if (1ULL * FFT_NZ * FFT_NY * (FFT_N / 2 + 128)) <= 0x100000000ULL
|
| 6 |
+
using fft_index_t = unsigned int;
|
| 7 |
+
static_assert(sizeof(fft_index_t) == 4, "A 32-bit unsigned integer is required");
|
| 8 |
+
#else
|
| 9 |
+
using fft_index_t = unsigned long long;
|
| 10 |
+
#endif
|
| 11 |
+
#if FFT_DOUBLE
|
| 12 |
+
using caller_scalar_t = double;
|
| 13 |
+
#else
|
| 14 |
+
using caller_scalar_t = float;
|
| 15 |
+
#endif
|
| 16 |
+
__device__ double2 fft_scalar_pair(const caller_scalar_t *input, fft_index_t index) {
|
| 17 |
+
double2 value;
|
| 18 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 19 |
+
value = reinterpret_cast<const double2 *>(input)[index];
|
| 20 |
+
#elif !FFT_DOUBLE && (FFT_INPUT_SHIFT % 2 == 0)
|
| 21 |
+
// The pair address is aligned for scalar residues zero and two.
|
| 22 |
+
const float2 packed = reinterpret_cast<const float2 *>(input)[index];
|
| 23 |
+
value.x = static_cast<double>(packed.x);
|
| 24 |
+
value.y = static_cast<double>(packed.y);
|
| 25 |
+
#else
|
| 26 |
+
value.x = static_cast<double>(input[2ULL * index]);
|
| 27 |
+
value.y = static_cast<double>(input[2ULL * index + 1]);
|
| 28 |
+
#endif
|
| 29 |
+
return value;
|
| 30 |
+
}
|
| 31 |
+
// The cuFFT logical input uses a planned virtual row stride. Callback arithmetic reads exact nf-stride
|
| 32 |
+
// caller storage directly; no input repacking is performed.
|
| 33 |
+
// Reconstruct twice the half-length complex spectrum from immutable packed
|
| 34 |
+
// caller input. Endpoint projection preserves irfftn for arbitrary spectra.
|
| 35 |
+
#if FFT_LOAD
|
| 36 |
+
__device__ double2 fft_wide_load(void *data, unsigned long long offset,
|
| 37 |
+
void *caller_info, void *shared_memory) {
|
| 38 |
+
constexpr fft_index_t m = FFT_N / 2;
|
| 39 |
+
constexpr fft_index_t nf = m + 1;
|
| 40 |
+
constexpr fft_index_t pitch = m + 128;
|
| 41 |
+
const fft_index_t index = static_cast<fft_index_t>(offset);
|
| 42 |
+
const fft_index_t k = index % pitch;
|
| 43 |
+
const fft_index_t row = index / pitch;
|
| 44 |
+
const fft_index_t y = row % FFT_NY;
|
| 45 |
+
const fft_index_t z = row / FFT_NY;
|
| 46 |
+
const fft_index_t mirror_y = y == 0 ? 0 : FFT_NY - y;
|
| 47 |
+
const fft_index_t mirror_z = z == 0 ? 0 : FFT_NZ - z;
|
| 48 |
+
const fft_index_t mirror_row = mirror_z * FFT_NY + mirror_y;
|
| 49 |
+
const caller_scalar_t *input = static_cast<const caller_scalar_t *>(data) + FFT_INPUT_SHIFT;
|
| 50 |
+
double2 a = fft_scalar_pair(input, row * nf + k);
|
| 51 |
+
double2 b = fft_scalar_pair(input, mirror_row * nf + m - k);
|
| 52 |
+
if (k == 0) {
|
| 53 |
+
// Project each boundary plane against its Z/Y conjugate partner.
|
| 54 |
+
// b represents the conjugate of the projected Nyquist value.
|
| 55 |
+
const double2 a_mirror = fft_scalar_pair(input, mirror_row * nf);
|
| 56 |
+
const double2 b_mirror = fft_scalar_pair(input, row * nf + m);
|
| 57 |
+
a.x = 0.5 * (a.x + a_mirror.x);
|
| 58 |
+
a.y = 0.5 * (a.y - a_mirror.y);
|
| 59 |
+
b.x = 0.5 * (b.x + b_mirror.x);
|
| 60 |
+
b.y = 0.5 * (b.y - b_mirror.y);
|
| 61 |
+
}
|
| 62 |
+
const double2 w = static_cast<const double2 *>(caller_info)[k];
|
| 63 |
+
double2 result;
|
| 64 |
+
result.x = a.x + b.x - w.x * (a.y + b.y) + w.y * (a.x - b.x);
|
| 65 |
+
result.y = a.y - b.y + w.x * (a.x - b.x) + w.y * (a.y + b.y);
|
| 66 |
+
return result;
|
| 67 |
+
}
|
| 68 |
+
#else
|
| 69 |
+
__device__ void fft_wide_store(void *physical_output, unsigned long long offset,
|
| 70 |
+
double2 value, void *caller_info, void *shared_memory) {
|
| 71 |
+
caller_scalar_t *output = static_cast<caller_scalar_t *>(physical_output) + FFT_INPUT_SHIFT;
|
| 72 |
+
#if FFT_DOUBLE && FFT_INPUT_SHIFT == 0
|
| 73 |
+
// A nominal cuFFT output address is complex aligned.
|
| 74 |
+
reinterpret_cast<double2 *>(output)[offset] = value;
|
| 75 |
+
#elif !FFT_DOUBLE && (FFT_INPUT_SHIFT % 2 == 0)
|
| 76 |
+
// Both components fit one aligned 64-bit store at these scalar residues.
|
| 77 |
+
float2 packed;
|
| 78 |
+
packed.x = static_cast<float>(value.x);
|
| 79 |
+
packed.y = static_cast<float>(value.y);
|
| 80 |
+
reinterpret_cast<float2 *>(output)[offset] = packed;
|
| 81 |
+
#else
|
| 82 |
+
output[2 * offset] = static_cast<caller_scalar_t>(value.x);
|
| 83 |
+
output[2 * offset + 1] = static_cast<caller_scalar_t>(value.y);
|
| 84 |
+
#endif
|
| 85 |
+
}
|
| 86 |
+
#endif
|
build/torch-cuda/_b200/cuda_kernel.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compile and load fixed CUDA FFT kernels only during plan preparation."""
|
| 2 |
+
import ctypes as ct
|
| 3 |
+
import functools
|
| 4 |
+
import hashlib
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from types import SimpleNamespace
|
| 7 |
+
|
| 8 |
+
from .cuda_twiddle_forward_lto import library, version, _check
|
| 9 |
+
from .cuda_twiddle_forward_native_dispatch import load_driver
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@functools.lru_cache(maxsize=128)
|
| 13 |
+
def compile_kernel(shape, columns, warps, arch, device_ordinal, double=False):
|
| 14 |
+
# The ordinal keeps loaded CUDA modules within their owning device context.
|
| 15 |
+
nz, ny, nx = shape
|
| 16 |
+
base, odd = 1, nz
|
| 17 |
+
while odd % 2 == 0:
|
| 18 |
+
odd //= 2
|
| 19 |
+
base *= 2
|
| 20 |
+
if odd not in (1, 3, 5, 7) or base < warps*32//columns:
|
| 21 |
+
raise ValueError('Unsupported radix decomposition')
|
| 22 |
+
source = Path(__file__).with_name('fft_axis.cu').read_bytes()
|
| 23 |
+
options = ['--std=c++17', f'--gpu-architecture=sm_{arch}',
|
| 24 |
+
f'-DLENGTH={nz}', f'-DINNER={ny*(nx//2+1)}', f'-DBASE={base}',
|
| 25 |
+
f'-DLOG_BASE={base.bit_length()-1}', f'-DODD={odd}',
|
| 26 |
+
f'-DCOLUMNS={columns}', f'-DWARPS={warps}', f'-DNX={nx}',
|
| 27 |
+
f'-DNY={ny}', f'-DFFT_DOUBLE={int(double)}']
|
| 28 |
+
lib = library()
|
| 29 |
+
lib.nvrtcGetCUBINSize.argtypes = [ct.c_void_p, ct.POINTER(ct.c_size_t)]
|
| 30 |
+
lib.nvrtcGetCUBIN.argtypes = [ct.c_void_p, ct.c_void_p]
|
| 31 |
+
program = ct.c_void_p()
|
| 32 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'fft_axis.cu', 0, None, None))
|
| 33 |
+
try:
|
| 34 |
+
opts = (ct.c_char_p * len(options))(*(s.encode() for s in options))
|
| 35 |
+
code = lib.nvrtcCompileProgram(program, len(options), opts)
|
| 36 |
+
size = ct.c_size_t()
|
| 37 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(size)))
|
| 38 |
+
log = ct.create_string_buffer(size.value)
|
| 39 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 40 |
+
if code:
|
| 41 |
+
raise RuntimeError(log.value.decode())
|
| 42 |
+
_check(lib.nvrtcGetCUBINSize(program, ct.byref(size)))
|
| 43 |
+
blob = ct.create_string_buffer(size.value)
|
| 44 |
+
_check(lib.nvrtcGetCUBIN(program, blob))
|
| 45 |
+
finally:
|
| 46 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 47 |
+
driver = load_driver()
|
| 48 |
+
driver.cuModuleLoadData.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p]
|
| 49 |
+
driver.cuModuleGetFunction.argtypes = [ct.POINTER(ct.c_void_p), ct.c_void_p, ct.c_char_p]
|
| 50 |
+
driver.cuFuncGetAttribute.argtypes = [ct.POINTER(ct.c_int), ct.c_int, ct.c_void_p]
|
| 51 |
+
module, function = ct.c_void_p(), ct.c_void_p()
|
| 52 |
+
_check(driver.cuModuleLoadData(ct.byref(module), blob))
|
| 53 |
+
_check(driver.cuModuleGetFunction(ct.byref(function), module, b'fft_axis'))
|
| 54 |
+
registers, local = ct.c_int(), ct.c_int()
|
| 55 |
+
_check(driver.cuFuncGetAttribute(ct.byref(registers), 4, function))
|
| 56 |
+
_check(driver.cuFuncGetAttribute(ct.byref(local), 3, function))
|
| 57 |
+
# local is CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, not an instruction count.
|
| 58 |
+
metadata = dict(options=options, nvrtc=version(), local_memory_bytes=local.value, source_sha256=hashlib.sha256(source).hexdigest(),
|
| 59 |
+
cubin_sha256=hashlib.sha256(blob.raw).hexdigest(), compiler_log=log.value.decode())
|
| 60 |
+
return SimpleNamespace(module=module, function=function.value, name='fft_axis',
|
| 61 |
+
hash=metadata['cubin_sha256'], threads=32*warps, shared=0,
|
| 62 |
+
n_regs=registers.value, n_spills=local.value,
|
| 63 |
+
grid=((ny*(nx//2+1)+columns-1)//columns, 1, 1),
|
| 64 |
+
compiler_metadata=metadata)
|
build/torch-cuda/_b200/cuda_twiddle_forward_lto.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile cuFFT LTO callbacks with the installed NVRTC, before execution.
|
| 3 |
+
|
| 4 |
+
This module can compile offline without initializing CUDA or loading code on
|
| 5 |
+
a GPU. Compiled blobs are cached by source, public shape, precision, NVRTC
|
| 6 |
+
version and target. No input tensor values or pointers are cached.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import ctypes as ct
|
| 11 |
+
import ctypes.util
|
| 12 |
+
import functools
|
| 13 |
+
import hashlib
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_LOCK = threading.Lock()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@functools.lru_cache(maxsize=1)
|
| 22 |
+
def library():
|
| 23 |
+
candidates = [ctypes.util.find_library('nvrtc')]
|
| 24 |
+
for root in (Path('/usr/local/cuda/lib64'),
|
| 25 |
+
Path('/usr/local/cuda/targets/x86_64-linux/lib')):
|
| 26 |
+
candidates.extend(str(p) for p in sorted(root.glob('libnvrtc.so*')))
|
| 27 |
+
candidates.extend(('libnvrtc.so.13', 'libnvrtc.so.12', 'libnvrtc.so'))
|
| 28 |
+
lib = None
|
| 29 |
+
for name in candidates:
|
| 30 |
+
if name:
|
| 31 |
+
try:
|
| 32 |
+
lib = ct.CDLL(name)
|
| 33 |
+
break
|
| 34 |
+
except OSError:
|
| 35 |
+
pass
|
| 36 |
+
if lib is None:
|
| 37 |
+
raise RuntimeError('The installed NVRTC library is unavailable')
|
| 38 |
+
signatures = {
|
| 39 |
+
'nvrtcVersion': [ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)],
|
| 40 |
+
'nvrtcCreateProgram': [ct.POINTER(ct.c_void_p), ct.c_char_p, ct.c_char_p,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)],
|
| 42 |
+
'nvrtcCompileProgram': [ct.c_void_p, ct.c_int, ct.POINTER(ct.c_char_p)],
|
| 43 |
+
'nvrtcGetProgramLogSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 44 |
+
'nvrtcGetProgramLog': [ct.c_void_p, ct.c_void_p],
|
| 45 |
+
'nvrtcGetLTOIRSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 46 |
+
'nvrtcGetLTOIR': [ct.c_void_p, ct.c_void_p],
|
| 47 |
+
'nvrtcDestroyProgram': [ct.POINTER(ct.c_void_p)],
|
| 48 |
+
}
|
| 49 |
+
for name, signature in signatures.items():
|
| 50 |
+
function = getattr(lib, name)
|
| 51 |
+
function.argtypes = signature
|
| 52 |
+
function.restype = ct.c_int
|
| 53 |
+
return lib
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _check(result):
|
| 57 |
+
if result:
|
| 58 |
+
raise RuntimeError(f'NVRTC returned error {result}')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def version():
|
| 62 |
+
major, minor = ct.c_int(), ct.c_int()
|
| 63 |
+
_check(library().nvrtcVersion(ct.byref(major), ct.byref(minor)))
|
| 64 |
+
return major.value, minor.value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@functools.lru_cache(maxsize=128)
|
| 68 |
+
def _compile(source, double, arch, nvrtc_version):
|
| 69 |
+
lib = library()
|
| 70 |
+
options = ('--std=c++17', '--relocatable-device-code=true', '-dlto',
|
| 71 |
+
f'--gpu-architecture=compute_{arch}', f'-DFFT_DOUBLE={int(double)}')
|
| 72 |
+
program = ct.c_void_p()
|
| 73 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'scalar_store.cu', 0, None, None))
|
| 74 |
+
try:
|
| 75 |
+
option_array = (ct.c_char_p * len(options))(*(v.encode() for v in options))
|
| 76 |
+
result = lib.nvrtcCompileProgram(program, len(options), option_array)
|
| 77 |
+
log_size = ct.c_size_t()
|
| 78 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(log_size)))
|
| 79 |
+
log = ct.create_string_buffer(log_size.value)
|
| 80 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 81 |
+
if result:
|
| 82 |
+
raise RuntimeError(f'NVRTC returned error {result}: {log.value.decode()}')
|
| 83 |
+
size = ct.c_size_t()
|
| 84 |
+
_check(lib.nvrtcGetLTOIRSize(program, ct.byref(size)))
|
| 85 |
+
data = ct.create_string_buffer(size.value)
|
| 86 |
+
_check(lib.nvrtcGetLTOIR(program, data))
|
| 87 |
+
blob = data.raw
|
| 88 |
+
metadata = dict(double=bool(double), target=f'compute_{arch}',
|
| 89 |
+
nvrtc=list(nvrtc_version), options=list(options),
|
| 90 |
+
source_sha256=hashlib.sha256(source).hexdigest(),
|
| 91 |
+
lto_ir_sha256=hashlib.sha256(blob).hexdigest(),
|
| 92 |
+
lto_ir_bytes=len(blob), compiler_log=log.value.decode())
|
| 93 |
+
return blob, metadata
|
| 94 |
+
finally:
|
| 95 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def compile_callback(double, role, shift, arch=100, shape=(256,256,256), axis='z'):
|
| 99 |
+
"""Compile every scalar residue at preparation, never during execution."""
|
| 100 |
+
if role not in ('pair', 'reconstruct', 'store') or not 0 <= shift < (2 if double else 4):
|
| 101 |
+
raise ValueError('Invalid callback role or scalar residue')
|
| 102 |
+
prefix = f'#define SCALAR_SHIFT {shift}\n#define IS_LOAD {int(role == "pair")}\n'
|
| 103 |
+
prefix += f'#define RECONSTRUCT {int(role == "reconstruct")}\n'
|
| 104 |
+
nz, ny, nx = shape
|
| 105 |
+
prefix += f'#define NZ {nz}\n#define NY {ny}\n#define NX {nx}\n'
|
| 106 |
+
prefix += f'#define NF {nx//2+1}\n#define FIRST_Y {int(axis == "y")}\n'
|
| 107 |
+
source = prefix.encode() + Path(__file__).with_name('scalar_io.cu').read_bytes()
|
| 108 |
+
with _LOCK:
|
| 109 |
+
blob, metadata = _compile(source, bool(double), int(arch), version())
|
| 110 |
+
metadata = dict(metadata, role=role, scalar_shift=shift,
|
| 111 |
+
arithmetic='float64', caller_dtype='float64' if double else 'float32')
|
| 112 |
+
return [(b'fft_pair_load' if role == 'pair' else (b'fft_reconstruct_load' if role == 'reconstruct' else b'fft_scalar_store'),
|
| 113 |
+
5 if role == 'store' else 1, ct.create_string_buffer(blob, len(blob)), metadata)]
|
build/torch-cuda/_b200/cuda_twiddle_forward_native_dispatch.c
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* SPDX-License-Identifier: Apache-2.0 */
|
| 2 |
+
/* Enqueue the forward Y/X cuFFT and Z-axis CUDA FFT in one host call.
|
| 3 |
+
* All handles, functions, grids, workspace and twiddles are prepared first.
|
| 4 |
+
* No data is copied, padded or reordered by this dispatcher.
|
| 5 |
+
*/
|
| 6 |
+
#include <stddef.h>
|
| 7 |
+
#include <stdint.h>
|
| 8 |
+
|
| 9 |
+
typedef int (*complex_fn)(int, void *, void *, int);
|
| 10 |
+
typedef int (*launch_fn)(void *, unsigned int, unsigned int, unsigned int,
|
| 11 |
+
unsigned int, unsigned int, unsigned int,
|
| 12 |
+
unsigned int, void *, void **, void **);
|
| 13 |
+
|
| 14 |
+
struct fft_dispatch {
|
| 15 |
+
complex_fn complex_exec;
|
| 16 |
+
launch_fn launch;
|
| 17 |
+
int handle;
|
| 18 |
+
uint64_t intermediate;
|
| 19 |
+
uint64_t twiddle;
|
| 20 |
+
void *stream;
|
| 21 |
+
void *function;
|
| 22 |
+
unsigned int grid[3];
|
| 23 |
+
unsigned int threads;
|
| 24 |
+
unsigned int shared;
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
int fft_execute(const struct fft_dispatch *plan, uint64_t input, uint64_t output) {
|
| 28 |
+
int result = plan->complex_exec(plan->handle, (void *)(uintptr_t)input,
|
| 29 |
+
(void *)(uintptr_t)plan->intermediate, -1);
|
| 30 |
+
if (result != 0) {
|
| 31 |
+
return result;
|
| 32 |
+
}
|
| 33 |
+
uint64_t values[5] = {plan->intermediate, output, plan->twiddle, 0, 0};
|
| 34 |
+
void *parameters[5] = {&values[0], &values[1], &values[2], &values[3], &values[4]};
|
| 35 |
+
result = plan->launch(plan->function, plan->grid[0], plan->grid[1], plan->grid[2],
|
| 36 |
+
plan->threads, 1, 1, plan->shared, plan->stream,
|
| 37 |
+
parameters, NULL);
|
| 38 |
+
/* Positive codes originate in cuFFT; negative codes in the CUDA driver. */
|
| 39 |
+
return -result;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
size_t fft_dispatch_size(void) { return sizeof(struct fft_dispatch); }
|
build/torch-cuda/_b200/cuda_twiddle_forward_native_dispatch.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Prepare a combined cuFFT and CUDA dispatcher before execution."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ctypes as ct
|
| 6 |
+
import fcntl
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import shlex
|
| 11 |
+
import subprocess
|
| 12 |
+
import tempfile
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Descriptor(ct.Structure):
|
| 17 |
+
_fields_ = [('complex_exec', ct.c_void_p), ('launch', ct.c_void_p),
|
| 18 |
+
('handle', ct.c_int), ('intermediate', ct.c_uint64),
|
| 19 |
+
('twiddle', ct.c_uint64), ('stream', ct.c_void_p),
|
| 20 |
+
('function', ct.c_void_p), ('grid', ct.c_uint * 3),
|
| 21 |
+
('threads', ct.c_uint), ('shared', ct.c_uint)]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
_LIBRARY = None
|
| 25 |
+
_DRIVER = None
|
| 26 |
+
_LOCK = threading.Lock()
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def load_library():
|
| 30 |
+
"""Build with the installed host compiler and validate structure size."""
|
| 31 |
+
global _LIBRARY
|
| 32 |
+
with _LOCK:
|
| 33 |
+
if _LIBRARY is not None:
|
| 34 |
+
return _LIBRARY
|
| 35 |
+
source = Path(__file__).with_name('cuda_twiddle_forward_native_dispatch.c')
|
| 36 |
+
compiler = shlex.split(os.environ.get('CC', 'cc'))
|
| 37 |
+
flags = ['-std=c11', '-O3', '-fPIC', '-shared', '-Wall', '-Wextra', '-Werror']
|
| 38 |
+
key = hashlib.sha256(source.read_bytes() + repr((compiler, flags)).encode()).hexdigest()
|
| 39 |
+
cache = Path(tempfile.gettempdir()) / ('fft-half-triton-z-native-' + str(os.getuid())) / key
|
| 40 |
+
cache.mkdir(parents=True, exist_ok=True)
|
| 41 |
+
target = cache / 'dispatch.so'
|
| 42 |
+
with (cache / 'build.lock').open('w') as lock:
|
| 43 |
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
| 44 |
+
if not target.is_file():
|
| 45 |
+
pending = cache / ('dispatch.' + str(os.getpid()) + '.so')
|
| 46 |
+
try:
|
| 47 |
+
subprocess.run(compiler + flags + [str(source), '-o', str(pending)],
|
| 48 |
+
check=True, capture_output=True, text=True)
|
| 49 |
+
pending.replace(target)
|
| 50 |
+
finally:
|
| 51 |
+
pending.unlink(missing_ok=True)
|
| 52 |
+
lib = ct.CDLL(str(target))
|
| 53 |
+
lib.fft_execute.argtypes = [ct.POINTER(Descriptor), ct.c_uint64, ct.c_uint64]
|
| 54 |
+
lib.fft_execute.restype = ct.c_int
|
| 55 |
+
lib.fft_dispatch_size.argtypes = []
|
| 56 |
+
lib.fft_dispatch_size.restype = ct.c_size_t
|
| 57 |
+
if lib.fft_dispatch_size() != ct.sizeof(Descriptor):
|
| 58 |
+
raise RuntimeError('Native dispatcher structure size mismatch')
|
| 59 |
+
_LIBRARY = lib
|
| 60 |
+
return lib
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def load_driver():
|
| 64 |
+
"""Resolve the driver function during preparation only."""
|
| 65 |
+
global _DRIVER
|
| 66 |
+
with _LOCK:
|
| 67 |
+
if _DRIVER is None:
|
| 68 |
+
_DRIVER = ct.CDLL('libcuda.so.1')
|
| 69 |
+
_DRIVER.cuLaunchKernel.restype = ct.c_int
|
| 70 |
+
return _DRIVER
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class PreparedDispatch:
|
| 74 |
+
"""Immutable setup descriptor, with fresh caller pointers at each call.
|
| 75 |
+
|
| 76 |
+
The Plan retains the cuFFT handles and compiled CUDA kernel. Concurrent
|
| 77 |
+
execution requires independent plans because cuFFT workspace is reused.
|
| 78 |
+
"""
|
| 79 |
+
def __init__(self, complex_exec, handle, compiled, grid, intermediate, twiddle, stream):
|
| 80 |
+
abi = dict(threads=compiled.threads, shared_bytes=compiled.shared)
|
| 81 |
+
if not compiled.function or compiled.module is None:
|
| 82 |
+
raise ValueError('The CUDA kernel must be loaded during setup')
|
| 83 |
+
if not 0 < handle < 2**31:
|
| 84 |
+
raise ValueError('A prepared cuFFT handle is required')
|
| 85 |
+
if len(grid) != 3 or any(not 0 < n < 2**32 for n in grid):
|
| 86 |
+
raise ValueError('Invalid launch grid')
|
| 87 |
+
if not intermediate or not twiddle:
|
| 88 |
+
raise ValueError('Prepared intermediate and twiddle storage is required')
|
| 89 |
+
self.library = load_library()
|
| 90 |
+
self.driver = load_driver()
|
| 91 |
+
self.descriptor = Descriptor(
|
| 92 |
+
complex_exec=ct.cast(complex_exec, ct.c_void_p).value,
|
| 93 |
+
launch=ct.cast(self.driver.cuLaunchKernel, ct.c_void_p).value,
|
| 94 |
+
handle=handle, intermediate=intermediate,
|
| 95 |
+
twiddle=twiddle, stream=stream, function=compiled.function,
|
| 96 |
+
grid=(ct.c_uint * 3)(*grid), threads=abi['threads'], shared=abi['shared_bytes'])
|
| 97 |
+
self.pointer = ct.pointer(self.descriptor)
|
| 98 |
+
self.function = self.library.fft_execute
|
| 99 |
+
|
| 100 |
+
def execute(self, input_ptr, output_ptr):
|
| 101 |
+
return self.function(self.pointer, input_ptr, output_ptr)
|
build/torch-cuda/_b200/cuda_z_inverse_cufft_support.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""cuFFT ABI and preallocated handles with optional setup-time LTO callbacks."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ctypes as ct
|
| 6 |
+
import ctypes.util
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _load_cufft():
|
| 13 |
+
candidates = [ctypes.util.find_library("cufft")]
|
| 14 |
+
for root in (Path(torch.__file__).resolve().parent.parent / "nvidia/cufft/lib",
|
| 15 |
+
Path("/usr/local/cuda/lib64")):
|
| 16 |
+
candidates.extend(str(p) for p in sorted(root.glob("libcufft.so*")))
|
| 17 |
+
candidates.extend(("libcufft.so.12", "libcufft.so.11", "libcufft.so"))
|
| 18 |
+
for name in candidates:
|
| 19 |
+
if name:
|
| 20 |
+
try:
|
| 21 |
+
return ct.CDLL(name)
|
| 22 |
+
except OSError:
|
| 23 |
+
pass
|
| 24 |
+
raise RuntimeError("The installed cuFFT shared library could not be loaded")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
_lib = _load_cufft()
|
| 28 |
+
_int_p = ct.POINTER(ct.c_int)
|
| 29 |
+
_size_p = ct.POINTER(ct.c_size_t)
|
| 30 |
+
_SIGNATURES = {
|
| 31 |
+
"cufftCreate": [_int_p],
|
| 32 |
+
"cufftDestroy": [ct.c_int],
|
| 33 |
+
"cufftSetAutoAllocation": [ct.c_int, ct.c_int],
|
| 34 |
+
"cufftSetWorkArea": [ct.c_int, ct.c_void_p],
|
| 35 |
+
"cufftSetStream": [ct.c_int, ct.c_void_p],
|
| 36 |
+
"cufftMakePlanMany": [ct.c_int, ct.c_int, _int_p, _int_p, ct.c_int,
|
| 37 |
+
ct.c_int, _int_p, ct.c_int, ct.c_int, ct.c_int,
|
| 38 |
+
ct.c_int, _size_p],
|
| 39 |
+
"cufftGetVersion": [_int_p],
|
| 40 |
+
"cufftXtSetJITCallback": [ct.c_int, ct.c_char_p, ct.c_void_p, ct.c_size_t,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_void_p)],
|
| 42 |
+
"cufftSetPlanPropertyInt64": [ct.c_int, ct.c_int, ct.c_longlong],
|
| 43 |
+
}
|
| 44 |
+
for _name in ("R2C", "D2Z", "C2R", "Z2D"):
|
| 45 |
+
_SIGNATURES["cufftExec" + _name] = [ct.c_int, ct.c_void_p, ct.c_void_p]
|
| 46 |
+
for _name in ("C2C", "Z2Z"):
|
| 47 |
+
_SIGNATURES["cufftExec" + _name] = [ct.c_int, ct.c_void_p, ct.c_void_p, ct.c_int]
|
| 48 |
+
for _name, _signature in _SIGNATURES.items():
|
| 49 |
+
_fn = getattr(_lib, _name)
|
| 50 |
+
_fn.argtypes = _signature
|
| 51 |
+
_fn.restype = ct.c_int
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _check(result):
|
| 55 |
+
if result:
|
| 56 |
+
raise RuntimeError(f"cuFFT returned error {result}")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def version():
|
| 60 |
+
value = ct.c_int()
|
| 61 |
+
_check(_lib.cufftGetVersion(ct.byref(value)))
|
| 62 |
+
return value.value
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class _Handle:
|
| 66 |
+
def __init__(self, sizes, kind, inembed, istride, idist, onembed, ostride,
|
| 67 |
+
odist, batch, device, stream, callback=None, patient=False,
|
| 68 |
+
caller_info=None):
|
| 69 |
+
handle = ct.c_int()
|
| 70 |
+
_check(_lib.cufftCreate(ct.byref(handle)))
|
| 71 |
+
self.value = handle.value
|
| 72 |
+
self.workspace = None
|
| 73 |
+
self.callback = callback
|
| 74 |
+
try:
|
| 75 |
+
_check(_lib.cufftSetAutoAllocation(self.value, 0))
|
| 76 |
+
if patient:
|
| 77 |
+
# NVFFT_PLAN_PROPERTY_INT64_PATIENT_JIT in cufft.h is enum 1.
|
| 78 |
+
_check(_lib.cufftSetPlanPropertyInt64(self.value, 1, 1))
|
| 79 |
+
if callback is not None:
|
| 80 |
+
for symbol, callback_type, blob, _metadata in callback:
|
| 81 |
+
info = ct.c_void_p(caller_info) if caller_info is not None else None
|
| 82 |
+
_check(_lib.cufftXtSetJITCallback(self.value, symbol, blob, len(blob),
|
| 83 |
+
callback_type,
|
| 84 |
+
ct.byref(info) if info is not None else None))
|
| 85 |
+
size = ct.c_size_t()
|
| 86 |
+
array = ct.c_int * len(sizes)
|
| 87 |
+
_check(_lib.cufftMakePlanMany(
|
| 88 |
+
self.value, len(sizes), array(*sizes), array(*inembed),
|
| 89 |
+
istride, idist, array(*onembed), ostride, odist, kind,
|
| 90 |
+
batch, ct.byref(size)))
|
| 91 |
+
self.workspace_bytes = size.value
|
| 92 |
+
self.workspace = torch.empty(size.value, dtype=torch.uint8, device=device)
|
| 93 |
+
_check(_lib.cufftSetWorkArea(self.value, self.workspace.data_ptr()))
|
| 94 |
+
_check(_lib.cufftSetStream(self.value, stream.cuda_stream))
|
| 95 |
+
except BaseException:
|
| 96 |
+
_lib.cufftDestroy(self.value)
|
| 97 |
+
self.value = None
|
| 98 |
+
raise
|
| 99 |
+
|
| 100 |
+
def close(self):
|
| 101 |
+
if self.value is not None:
|
| 102 |
+
_check(_lib.cufftDestroy(self.value))
|
| 103 |
+
self.value = None
|
| 104 |
+
self.workspace = None
|
| 105 |
+
self.callback = None
|
build/torch-cuda/_b200/cuda_z_inverse_lto.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile cuFFT LTO callbacks with the installed NVRTC, before execution.
|
| 3 |
+
|
| 4 |
+
This module can compile offline without initializing CUDA or loading code on
|
| 5 |
+
a GPU. Compiled blobs are cached by source, public shape, precision, NVRTC
|
| 6 |
+
version and target. No input tensor values or pointers are cached.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import ctypes as ct
|
| 11 |
+
import ctypes.util
|
| 12 |
+
import functools
|
| 13 |
+
import hashlib
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_LOCK = threading.Lock()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@functools.lru_cache(maxsize=1)
|
| 22 |
+
def library():
|
| 23 |
+
candidates = [ctypes.util.find_library('nvrtc')]
|
| 24 |
+
for root in (Path('/usr/local/cuda/lib64'),
|
| 25 |
+
Path('/usr/local/cuda/targets/x86_64-linux/lib')):
|
| 26 |
+
candidates.extend(str(p) for p in sorted(root.glob('libnvrtc.so*')))
|
| 27 |
+
candidates.extend(('libnvrtc.so.13', 'libnvrtc.so.12', 'libnvrtc.so'))
|
| 28 |
+
lib = None
|
| 29 |
+
for name in candidates:
|
| 30 |
+
if name:
|
| 31 |
+
try:
|
| 32 |
+
lib = ct.CDLL(name)
|
| 33 |
+
break
|
| 34 |
+
except OSError:
|
| 35 |
+
pass
|
| 36 |
+
if lib is None:
|
| 37 |
+
raise RuntimeError('The installed NVRTC library is unavailable')
|
| 38 |
+
signatures = {
|
| 39 |
+
'nvrtcVersion': [ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)],
|
| 40 |
+
'nvrtcCreateProgram': [ct.POINTER(ct.c_void_p), ct.c_char_p, ct.c_char_p,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)],
|
| 42 |
+
'nvrtcCompileProgram': [ct.c_void_p, ct.c_int, ct.POINTER(ct.c_char_p)],
|
| 43 |
+
'nvrtcGetProgramLogSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 44 |
+
'nvrtcGetProgramLog': [ct.c_void_p, ct.c_void_p],
|
| 45 |
+
'nvrtcGetLTOIRSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 46 |
+
'nvrtcGetLTOIR': [ct.c_void_p, ct.c_void_p],
|
| 47 |
+
'nvrtcDestroyProgram': [ct.POINTER(ct.c_void_p)],
|
| 48 |
+
}
|
| 49 |
+
for name, signature in signatures.items():
|
| 50 |
+
function = getattr(lib, name)
|
| 51 |
+
function.argtypes = signature
|
| 52 |
+
function.restype = ct.c_int
|
| 53 |
+
return lib
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _check(result):
|
| 57 |
+
if result:
|
| 58 |
+
raise RuntimeError(f'NVRTC returned error {result}')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def version():
|
| 62 |
+
major, minor = ct.c_int(), ct.c_int()
|
| 63 |
+
_check(library().nvrtcVersion(ct.byref(major), ct.byref(minor)))
|
| 64 |
+
return major.value, minor.value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@functools.lru_cache(maxsize=128)
|
| 68 |
+
def _compile(source, shape, double, arch, nvrtc_version, input_shift, kind):
|
| 69 |
+
nz, ny, nx = shape
|
| 70 |
+
lib = library()
|
| 71 |
+
options = ('--std=c++17', '--relocatable-device-code=true', '-dlto',
|
| 72 |
+
f'--gpu-architecture=compute_{arch}', f'-DFFT_N={nx}',
|
| 73 |
+
f'-DFFT_NZ={nz}', f'-DFFT_NY={ny}',
|
| 74 |
+
f'-DFFT_DOUBLE={int(double)}', f'-DFFT_INPUT_SHIFT={input_shift}',
|
| 75 |
+
f'-DFFT_LOAD={int(kind == "load")}')
|
| 76 |
+
program = ct.c_void_p()
|
| 77 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'endpoint.cu', 0, None, None))
|
| 78 |
+
try:
|
| 79 |
+
option_array = (ct.c_char_p * len(options))(*(v.encode() for v in options))
|
| 80 |
+
result = lib.nvrtcCompileProgram(program, len(options), option_array)
|
| 81 |
+
log_size = ct.c_size_t()
|
| 82 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(log_size)))
|
| 83 |
+
log = ct.create_string_buffer(log_size.value)
|
| 84 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 85 |
+
if result:
|
| 86 |
+
raise RuntimeError(f'NVRTC returned error {result}: {log.value.decode()}')
|
| 87 |
+
size = ct.c_size_t()
|
| 88 |
+
_check(lib.nvrtcGetLTOIRSize(program, ct.byref(size)))
|
| 89 |
+
data = ct.create_string_buffer(size.value)
|
| 90 |
+
_check(lib.nvrtcGetLTOIR(program, data))
|
| 91 |
+
blob = data.raw
|
| 92 |
+
metadata = dict(shape=shape, double=bool(double), target=f'compute_{arch}',
|
| 93 |
+
nvrtc=list(nvrtc_version), options=list(options),
|
| 94 |
+
source_sha256=hashlib.sha256(source).hexdigest(),
|
| 95 |
+
lto_ir_sha256=hashlib.sha256(blob).hexdigest(),
|
| 96 |
+
lto_ir_bytes=len(blob), compiler_log=log.value.decode())
|
| 97 |
+
return blob, metadata
|
| 98 |
+
finally:
|
| 99 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def compile_callback(shape, input_shift, kind, arch=100):
|
| 103 |
+
"""Reconstruct a half-length spectrum in FP64 or store its inverse as FP32."""
|
| 104 |
+
if kind not in ('load', 'store'):
|
| 105 |
+
raise ValueError('Expected a load or store callback')
|
| 106 |
+
source = Path(__file__).with_name('wide_redirect.cu').read_bytes()
|
| 107 |
+
with _LOCK:
|
| 108 |
+
blob, metadata = _compile(source, tuple(shape), True, int(arch), version(), input_shift, kind)
|
| 109 |
+
return (('fft_wide_' + kind).encode(), 1 if kind == 'load' else 5,
|
| 110 |
+
ct.create_string_buffer(blob, len(blob)), dict(metadata, kind=kind))
|
build/torch-cuda/_b200/cuda_z_inverse_native_dispatch.c
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* SPDX-License-Identifier: Apache-2.0 */
|
| 2 |
+
/* Enqueue the inverse Y/X cuFFT and Z-axis CUDA FFT in one host call.
|
| 3 |
+
* All handles, functions, grids, workspace and twiddles are prepared first.
|
| 4 |
+
* No data is copied, padded or reordered by this dispatcher.
|
| 5 |
+
*/
|
| 6 |
+
#include <stddef.h>
|
| 7 |
+
#include <stdint.h>
|
| 8 |
+
|
| 9 |
+
typedef int (*complex_fn)(int, void *, void *, int);
|
| 10 |
+
typedef int (*launch_fn)(void *, unsigned int, unsigned int, unsigned int,
|
| 11 |
+
unsigned int, unsigned int, unsigned int,
|
| 12 |
+
unsigned int, void *, void **, void **);
|
| 13 |
+
|
| 14 |
+
struct fft_dispatch {
|
| 15 |
+
complex_fn complex_exec;
|
| 16 |
+
launch_fn launch;
|
| 17 |
+
int handle;
|
| 18 |
+
uint64_t intermediate;
|
| 19 |
+
uint64_t twiddle;
|
| 20 |
+
void *stream;
|
| 21 |
+
void *function;
|
| 22 |
+
unsigned int grid[3];
|
| 23 |
+
unsigned int threads;
|
| 24 |
+
unsigned int shared;
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
int fft_execute(const struct fft_dispatch *plan, uint64_t input, uint64_t output) {
|
| 28 |
+
int result = plan->complex_exec(plan->handle, (void *)(uintptr_t)input,
|
| 29 |
+
(void *)(uintptr_t)plan->intermediate, 1);
|
| 30 |
+
if (result != 0) {
|
| 31 |
+
return result;
|
| 32 |
+
}
|
| 33 |
+
uint64_t values[5] = {plan->intermediate, output, plan->twiddle, 0, 0};
|
| 34 |
+
void *parameters[5] = {&values[0], &values[1], &values[2], &values[3], &values[4]};
|
| 35 |
+
result = plan->launch(plan->function, plan->grid[0], plan->grid[1], plan->grid[2],
|
| 36 |
+
plan->threads, 1, 1, plan->shared, plan->stream,
|
| 37 |
+
parameters, NULL);
|
| 38 |
+
/* Positive codes originate in cuFFT; negative codes in the CUDA driver. */
|
| 39 |
+
return -result;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
size_t fft_dispatch_size(void) { return sizeof(struct fft_dispatch); }
|
build/torch-cuda/_b200/cuda_z_inverse_native_dispatch.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Prepare a combined cuFFT and CUDA dispatcher before execution."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ctypes as ct
|
| 6 |
+
import fcntl
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import shlex
|
| 11 |
+
import subprocess
|
| 12 |
+
import tempfile
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Descriptor(ct.Structure):
|
| 17 |
+
_fields_ = [('complex_exec', ct.c_void_p), ('launch', ct.c_void_p),
|
| 18 |
+
('handle', ct.c_int), ('intermediate', ct.c_uint64),
|
| 19 |
+
('twiddle', ct.c_uint64), ('stream', ct.c_void_p),
|
| 20 |
+
('function', ct.c_void_p), ('grid', ct.c_uint * 3),
|
| 21 |
+
('threads', ct.c_uint), ('shared', ct.c_uint)]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
_LIBRARY = None
|
| 25 |
+
_DRIVER = None
|
| 26 |
+
_LOCK = threading.Lock()
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def load_library():
|
| 30 |
+
"""Build with the installed host compiler and validate structure size."""
|
| 31 |
+
global _LIBRARY
|
| 32 |
+
with _LOCK:
|
| 33 |
+
if _LIBRARY is not None:
|
| 34 |
+
return _LIBRARY
|
| 35 |
+
source = Path(__file__).with_name('cuda_z_inverse_native_dispatch.c')
|
| 36 |
+
compiler = shlex.split(os.environ.get('CC', 'cc'))
|
| 37 |
+
flags = ['-std=c11', '-O3', '-fPIC', '-shared', '-Wall', '-Wextra', '-Werror']
|
| 38 |
+
key = hashlib.sha256(source.read_bytes() + repr((compiler, flags)).encode()).hexdigest()
|
| 39 |
+
cache = Path(tempfile.gettempdir()) / ('fft-half-triton-z-native-' + str(os.getuid())) / key
|
| 40 |
+
cache.mkdir(parents=True, exist_ok=True)
|
| 41 |
+
target = cache / 'dispatch.so'
|
| 42 |
+
with (cache / 'build.lock').open('w') as lock:
|
| 43 |
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
| 44 |
+
if not target.is_file():
|
| 45 |
+
pending = cache / ('dispatch.' + str(os.getpid()) + '.so')
|
| 46 |
+
try:
|
| 47 |
+
subprocess.run(compiler + flags + [str(source), '-o', str(pending)],
|
| 48 |
+
check=True, capture_output=True, text=True)
|
| 49 |
+
pending.replace(target)
|
| 50 |
+
finally:
|
| 51 |
+
pending.unlink(missing_ok=True)
|
| 52 |
+
lib = ct.CDLL(str(target))
|
| 53 |
+
lib.fft_execute.argtypes = [ct.POINTER(Descriptor), ct.c_uint64, ct.c_uint64]
|
| 54 |
+
lib.fft_execute.restype = ct.c_int
|
| 55 |
+
lib.fft_dispatch_size.argtypes = []
|
| 56 |
+
lib.fft_dispatch_size.restype = ct.c_size_t
|
| 57 |
+
if lib.fft_dispatch_size() != ct.sizeof(Descriptor):
|
| 58 |
+
raise RuntimeError('Native dispatcher structure size mismatch')
|
| 59 |
+
_LIBRARY = lib
|
| 60 |
+
return lib
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def load_driver():
|
| 64 |
+
"""Resolve the driver function during preparation only."""
|
| 65 |
+
global _DRIVER
|
| 66 |
+
with _LOCK:
|
| 67 |
+
if _DRIVER is None:
|
| 68 |
+
_DRIVER = ct.CDLL('libcuda.so.1')
|
| 69 |
+
_DRIVER.cuLaunchKernel.restype = ct.c_int
|
| 70 |
+
return _DRIVER
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class PreparedDispatch:
|
| 74 |
+
"""Immutable setup descriptor, with fresh caller pointers at each call.
|
| 75 |
+
|
| 76 |
+
The Plan retains the cuFFT handles and compiled CUDA kernel. Concurrent
|
| 77 |
+
execution requires independent plans because cuFFT workspace is reused.
|
| 78 |
+
"""
|
| 79 |
+
def __init__(self, complex_exec, handle, compiled, grid, intermediate, twiddle, stream):
|
| 80 |
+
abi = dict(threads=compiled.threads, shared_bytes=compiled.shared)
|
| 81 |
+
if not compiled.function or compiled.module is None:
|
| 82 |
+
raise ValueError('The CUDA kernel must be loaded during setup')
|
| 83 |
+
if not 0 < handle < 2**31:
|
| 84 |
+
raise ValueError('A prepared cuFFT handle is required')
|
| 85 |
+
if len(grid) != 3 or any(not 0 < n < 2**32 for n in grid):
|
| 86 |
+
raise ValueError('Invalid launch grid')
|
| 87 |
+
if not intermediate or not twiddle:
|
| 88 |
+
raise ValueError('Prepared intermediate and twiddle storage is required')
|
| 89 |
+
self.library = load_library()
|
| 90 |
+
self.driver = load_driver()
|
| 91 |
+
self.descriptor = Descriptor(
|
| 92 |
+
complex_exec=ct.cast(complex_exec, ct.c_void_p).value,
|
| 93 |
+
launch=ct.cast(self.driver.cuLaunchKernel, ct.c_void_p).value,
|
| 94 |
+
handle=handle, intermediate=intermediate,
|
| 95 |
+
twiddle=twiddle, stream=stream, function=compiled.function,
|
| 96 |
+
grid=(ct.c_uint * 3)(*grid), threads=abi['threads'], shared=abi['shared_bytes'])
|
| 97 |
+
self.pointer = ct.pointer(self.descriptor)
|
| 98 |
+
self.function = self.library.fft_execute
|
| 99 |
+
|
| 100 |
+
def execute(self, input_ptr, output_ptr):
|
| 101 |
+
return self.function(self.pointer, input_ptr, output_ptr)
|
build/torch-cuda/_b200/cuda_z_inverse_schedule.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Host-only mixed-radix schedules and accurately rounded twiddle tables."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def factors(length):
|
| 9 |
+
"""Return a deterministic DIT factorization; no measurements occur here."""
|
| 10 |
+
remaining = int(length)
|
| 11 |
+
if remaining < 1:
|
| 12 |
+
raise ValueError("FFT lengths must be positive")
|
| 13 |
+
result = []
|
| 14 |
+
for radix in (4, 2):
|
| 15 |
+
while remaining % radix == 0:
|
| 16 |
+
result.append(radix)
|
| 17 |
+
remaining //= radix
|
| 18 |
+
# Collapse the final pair of radix-3 stages for lengths 9 * 2**k.
|
| 19 |
+
# This gives the radix-9 kernel power-of-two branch lengths; other odd
|
| 20 |
+
# factorizations retain the established paired or generic network.
|
| 21 |
+
if remaining == 9:
|
| 22 |
+
result.append(9)
|
| 23 |
+
remaining = 1
|
| 24 |
+
for radix in (3, 5, 7):
|
| 25 |
+
while remaining % radix == 0:
|
| 26 |
+
result.append(radix)
|
| 27 |
+
remaining //= radix
|
| 28 |
+
if remaining != 1:
|
| 29 |
+
raise ValueError("This prototype supports lengths with factors 2, 3, 5, 7")
|
| 30 |
+
return tuple(result)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def stage_sizes(radices):
|
| 34 |
+
sizes = []
|
| 35 |
+
length = 1
|
| 36 |
+
for radix in radices:
|
| 37 |
+
length *= radix
|
| 38 |
+
sizes.append(length)
|
| 39 |
+
return tuple(sizes)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def permutation(length):
|
| 43 |
+
"""Map DIT input positions to natural input indices, for CPU validation."""
|
| 44 |
+
radices = factors(length)
|
| 45 |
+
result = []
|
| 46 |
+
for index in range(length):
|
| 47 |
+
divisor, reverse = 1, 0
|
| 48 |
+
for radix in radices:
|
| 49 |
+
reverse += ((index // divisor) % radix) * (length // (divisor * radix))
|
| 50 |
+
divisor *= radix
|
| 51 |
+
result.append(reverse)
|
| 52 |
+
return result
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def twiddles(length):
|
| 56 |
+
"""Interleaved exp(-2*pi*i*k/N), computed in host double precision."""
|
| 57 |
+
result = []
|
| 58 |
+
for index in range(length):
|
| 59 |
+
if index == 0:
|
| 60 |
+
value = (1.0, 0.0)
|
| 61 |
+
elif 2 * index == length:
|
| 62 |
+
value = (-1.0, 0.0)
|
| 63 |
+
elif 4 * index == length:
|
| 64 |
+
value = (0.0, -1.0)
|
| 65 |
+
elif 4 * index == 3 * length:
|
| 66 |
+
value = (0.0, 1.0)
|
| 67 |
+
else:
|
| 68 |
+
angle = -math.tau * index / length
|
| 69 |
+
value = (math.cos(angle), math.sin(angle))
|
| 70 |
+
result.extend(value)
|
| 71 |
+
return result
|
build/torch-cuda/_b200/dense_half_inverse_abi.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Validate the pointer-publication kernel before creating a native launcher."""
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def validate_kernel_abi(compiled):
|
| 7 |
+
metadata = compiled.metadata
|
| 8 |
+
if metadata.num_ctas != 1 or getattr(metadata, 'warp_size', 32) != 32:
|
| 9 |
+
raise ValueError('Only one-CTA kernels with warp size 32 are supported')
|
| 10 |
+
for name in ('global_scratch_size', 'profile_scratch_size',
|
| 11 |
+
'launch_cooperative_grid', 'launch_pdl', 'tmem_size'):
|
| 12 |
+
if getattr(metadata, name, 0):
|
| 13 |
+
raise ValueError('Unsupported kernel requirement: ' + name)
|
| 14 |
+
if getattr(metadata, 'tensordesc_meta', None):
|
| 15 |
+
raise ValueError('Tensor descriptors are unsupported')
|
| 16 |
+
if metadata.num_warps != 1:
|
| 17 |
+
raise ValueError('Pointer publication uses exactly one warp')
|
| 18 |
+
if list(compiled.src.signature.values()) != ['*i64', 'u64']:
|
| 19 |
+
raise ValueError('Expected an int64 slot pointer and a uint64 output address')
|
| 20 |
+
match = re.search(r'\.entry\s+' + re.escape(metadata.name) + r'\s*\((.*?)\)',
|
| 21 |
+
compiled.asm['ptx'], flags=re.DOTALL)
|
| 22 |
+
if match is None:
|
| 23 |
+
raise ValueError('Kernel entry signature was not found in PTX')
|
| 24 |
+
parameters = [part.strip() for part in match.group(1).split(',')]
|
| 25 |
+
if len(parameters) != 4 or any(not re.fullmatch(
|
| 26 |
+
r'\.param\s+\.u64(?:\s+\.ptr\s+\.global\s+\.align\s+\d+)?\s+\w+',
|
| 27 |
+
part) for part in parameters):
|
| 28 |
+
raise ValueError('Expected two 64-bit arguments and two null scratch pointers')
|
| 29 |
+
return dict(parameter_count=4, null_scratch_arguments=2,
|
| 30 |
+
threads=32, shared_bytes=metadata.shared)
|
build/torch-cuda/_b200/dense_half_inverse_kernels.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Stream-ordered pointer publication, with no tensor-data copy."""
|
| 3 |
+
import triton
|
| 4 |
+
import triton.language as tl
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@triton.jit(do_not_specialize=['output_address'],
|
| 8 |
+
do_not_specialize_on_alignment=['slot', 'output_address'])
|
| 9 |
+
def publish_output(slot, output_address):
|
| 10 |
+
tl.store(slot, output_address.to(tl.uint64))
|
build/torch-cuda/_b200/dense_half_inverse_lto.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile cuFFT LTO callbacks with the installed NVRTC, before execution.
|
| 3 |
+
|
| 4 |
+
This module can compile offline without initializing CUDA or loading code on
|
| 5 |
+
a GPU. Compiled blobs are cached by source, public shape, precision, NVRTC
|
| 6 |
+
version and target. No input tensor values or pointers are cached.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import ctypes as ct
|
| 11 |
+
import ctypes.util
|
| 12 |
+
import functools
|
| 13 |
+
import hashlib
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_LOCK = threading.Lock()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@functools.lru_cache(maxsize=1)
|
| 22 |
+
def library():
|
| 23 |
+
candidates = [ctypes.util.find_library('nvrtc')]
|
| 24 |
+
for root in (Path('/usr/local/cuda/lib64'),
|
| 25 |
+
Path('/usr/local/cuda/targets/x86_64-linux/lib')):
|
| 26 |
+
candidates.extend(str(p) for p in sorted(root.glob('libnvrtc.so*')))
|
| 27 |
+
candidates.extend(('libnvrtc.so.13', 'libnvrtc.so.12', 'libnvrtc.so'))
|
| 28 |
+
lib = None
|
| 29 |
+
for name in candidates:
|
| 30 |
+
if name:
|
| 31 |
+
try:
|
| 32 |
+
lib = ct.CDLL(name)
|
| 33 |
+
break
|
| 34 |
+
except OSError:
|
| 35 |
+
pass
|
| 36 |
+
if lib is None:
|
| 37 |
+
raise RuntimeError('The installed NVRTC library is unavailable')
|
| 38 |
+
signatures = {
|
| 39 |
+
'nvrtcVersion': [ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)],
|
| 40 |
+
'nvrtcCreateProgram': [ct.POINTER(ct.c_void_p), ct.c_char_p, ct.c_char_p,
|
| 41 |
+
ct.c_int, ct.POINTER(ct.c_char_p), ct.POINTER(ct.c_char_p)],
|
| 42 |
+
'nvrtcCompileProgram': [ct.c_void_p, ct.c_int, ct.POINTER(ct.c_char_p)],
|
| 43 |
+
'nvrtcGetProgramLogSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 44 |
+
'nvrtcGetProgramLog': [ct.c_void_p, ct.c_void_p],
|
| 45 |
+
'nvrtcGetLTOIRSize': [ct.c_void_p, ct.POINTER(ct.c_size_t)],
|
| 46 |
+
'nvrtcGetLTOIR': [ct.c_void_p, ct.c_void_p],
|
| 47 |
+
'nvrtcDestroyProgram': [ct.POINTER(ct.c_void_p)],
|
| 48 |
+
}
|
| 49 |
+
for name, signature in signatures.items():
|
| 50 |
+
function = getattr(lib, name)
|
| 51 |
+
function.argtypes = signature
|
| 52 |
+
function.restype = ct.c_int
|
| 53 |
+
return lib
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _check(result):
|
| 57 |
+
if result:
|
| 58 |
+
raise RuntimeError(f'NVRTC returned error {result}')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def version():
|
| 62 |
+
major, minor = ct.c_int(), ct.c_int()
|
| 63 |
+
_check(library().nvrtcVersion(ct.byref(major), ct.byref(minor)))
|
| 64 |
+
return major.value, minor.value
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@functools.lru_cache(maxsize=128)
|
| 68 |
+
def _compile(source, shape, double, arch, nvrtc_version, input_shift, kind):
|
| 69 |
+
nz, ny, nx = shape
|
| 70 |
+
lib = library()
|
| 71 |
+
options = ('--std=c++17', '--relocatable-device-code=true', '-dlto',
|
| 72 |
+
f'--gpu-architecture=compute_{arch}', f'-DFFT_N={nx}',
|
| 73 |
+
f'-DFFT_NZ={nz}', f'-DFFT_NY={ny}',
|
| 74 |
+
f'-DFFT_DOUBLE={int(double)}', f'-DFFT_INPUT_SHIFT={input_shift}',
|
| 75 |
+
f'-DFFT_LOAD={int(kind == "load")}')
|
| 76 |
+
program = ct.c_void_p()
|
| 77 |
+
_check(lib.nvrtcCreateProgram(ct.byref(program), source, b'endpoint.cu', 0, None, None))
|
| 78 |
+
try:
|
| 79 |
+
option_array = (ct.c_char_p * len(options))(*(v.encode() for v in options))
|
| 80 |
+
result = lib.nvrtcCompileProgram(program, len(options), option_array)
|
| 81 |
+
log_size = ct.c_size_t()
|
| 82 |
+
_check(lib.nvrtcGetProgramLogSize(program, ct.byref(log_size)))
|
| 83 |
+
log = ct.create_string_buffer(log_size.value)
|
| 84 |
+
_check(lib.nvrtcGetProgramLog(program, log))
|
| 85 |
+
if result:
|
| 86 |
+
raise RuntimeError(f'NVRTC returned error {result}: {log.value.decode()}')
|
| 87 |
+
size = ct.c_size_t()
|
| 88 |
+
_check(lib.nvrtcGetLTOIRSize(program, ct.byref(size)))
|
| 89 |
+
data = ct.create_string_buffer(size.value)
|
| 90 |
+
_check(lib.nvrtcGetLTOIR(program, data))
|
| 91 |
+
blob = data.raw
|
| 92 |
+
metadata = dict(shape=shape, double=bool(double), target=f'compute_{arch}',
|
| 93 |
+
nvrtc=list(nvrtc_version), options=list(options),
|
| 94 |
+
source_sha256=hashlib.sha256(source).hexdigest(),
|
| 95 |
+
lto_ir_sha256=hashlib.sha256(blob).hexdigest(),
|
| 96 |
+
lto_ir_bytes=len(blob), compiler_log=log.value.decode())
|
| 97 |
+
return blob, metadata
|
| 98 |
+
finally:
|
| 99 |
+
_check(lib.nvrtcDestroyProgram(ct.byref(program)))
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def compile_callback(shape, input_shift, kind, arch=100, double=False):
|
| 103 |
+
"""Reconstruct a half-length spectrum in FP64 or store its inverse as FP32."""
|
| 104 |
+
if kind not in ('load', 'store'):
|
| 105 |
+
raise ValueError('Expected a load or store callback')
|
| 106 |
+
source = Path(__file__).with_name('c2r_dense_half_inverse_wide_redirect.cu').read_bytes()
|
| 107 |
+
with _LOCK:
|
| 108 |
+
blob, metadata = _compile(source, tuple(shape), bool(double), int(arch), version(), input_shift, kind)
|
| 109 |
+
return (('fft_wide_' + kind).encode(), 1 if kind == 'load' else 5,
|
| 110 |
+
ct.create_string_buffer(blob, len(blob)), dict(metadata, kind=kind))
|
build/torch-cuda/_b200/dense_half_inverse_native_dispatch.c
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* SPDX-License-Identifier: Apache-2.0 */
|
| 2 |
+
/* Publish the caller output address and enqueue the prepared FP64 FFT chain.
|
| 3 |
+
* Only a scalar pointer is published. Caller tensor data is never repacked.
|
| 4 |
+
* Each stream owns an independent descriptor, slot, workspace and handles.
|
| 5 |
+
*/
|
| 6 |
+
#include <stddef.h>
|
| 7 |
+
#include <stdint.h>
|
| 8 |
+
|
| 9 |
+
typedef int (*complex_fn)(int, void *, void *, int);
|
| 10 |
+
typedef int (*launch_fn)(void *, unsigned int, unsigned int, unsigned int,
|
| 11 |
+
unsigned int, unsigned int, unsigned int,
|
| 12 |
+
unsigned int, void *, void **, void **);
|
| 13 |
+
|
| 14 |
+
struct fft_dispatch {
|
| 15 |
+
complex_fn first_exec;
|
| 16 |
+
complex_fn final_exec;
|
| 17 |
+
launch_fn launch;
|
| 18 |
+
int first_handle;
|
| 19 |
+
int final_handle;
|
| 20 |
+
uint64_t slot;
|
| 21 |
+
uint64_t scratch;
|
| 22 |
+
void *stream;
|
| 23 |
+
void *function;
|
| 24 |
+
unsigned int threads;
|
| 25 |
+
unsigned int shared;
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
int fft_execute(const struct fft_dispatch *plan, uint64_t input, uint64_t output) {
|
| 29 |
+
uint64_t values[4] = {plan->slot, output, 0, 0};
|
| 30 |
+
void *parameters[4] = {&values[0], &values[1], &values[2], &values[3]};
|
| 31 |
+
int result = plan->launch(plan->function, 1, 1, 1,
|
| 32 |
+
plan->threads, 1, 1, plan->shared, plan->stream,
|
| 33 |
+
parameters, NULL);
|
| 34 |
+
/* Positive codes originate in cuFFT; negative codes in the CUDA driver. */
|
| 35 |
+
if (result != 0) return -result;
|
| 36 |
+
result = plan->first_exec(plan->first_handle, (void *)(uintptr_t)input,
|
| 37 |
+
(void *)(uintptr_t)plan->scratch, 1);
|
| 38 |
+
if (result != 0 || plan->final_handle == 0) return result;
|
| 39 |
+
return plan->final_exec(plan->final_handle, (void *)(uintptr_t)plan->scratch,
|
| 40 |
+
(void *)(uintptr_t)plan->scratch, 1);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
size_t fft_dispatch_size(void) { return sizeof(struct fft_dispatch); }
|
build/torch-cuda/_b200/dense_half_inverse_native_dispatch.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Prepare a combined cuFFT and Triton dispatcher before execution."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ctypes as ct
|
| 6 |
+
import fcntl
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import shlex
|
| 11 |
+
import subprocess
|
| 12 |
+
import tempfile
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
from .dense_half_inverse_abi import validate_kernel_abi
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class Descriptor(ct.Structure):
|
| 19 |
+
_fields_ = [('first_exec', ct.c_void_p), ('final_exec', ct.c_void_p),
|
| 20 |
+
('launch', ct.c_void_p), ('first_handle', ct.c_int),
|
| 21 |
+
('final_handle', ct.c_int), ('slot', ct.c_uint64),
|
| 22 |
+
('scratch', ct.c_uint64), ('stream', ct.c_void_p),
|
| 23 |
+
('function', ct.c_void_p), ('threads', ct.c_uint),
|
| 24 |
+
('shared', ct.c_uint)]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
_LIBRARY = None
|
| 28 |
+
_DRIVER = None
|
| 29 |
+
_LOCK = threading.Lock()
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def load_library():
|
| 33 |
+
"""Build with the installed host compiler and validate structure size."""
|
| 34 |
+
global _LIBRARY
|
| 35 |
+
with _LOCK:
|
| 36 |
+
if _LIBRARY is not None:
|
| 37 |
+
return _LIBRARY
|
| 38 |
+
source = Path(__file__).with_name('dense_half_inverse_native_dispatch.c')
|
| 39 |
+
compiler = shlex.split(os.environ.get('CC', 'cc'))
|
| 40 |
+
flags = ['-std=c11', '-O3', '-fPIC', '-shared', '-Wall', '-Wextra', '-Werror']
|
| 41 |
+
key = hashlib.sha256(source.read_bytes() + repr((compiler, flags)).encode()).hexdigest()
|
| 42 |
+
cache = Path(tempfile.gettempdir()) / ('fft-half-inverse-native-' + str(os.getuid())) / key
|
| 43 |
+
cache.mkdir(parents=True, exist_ok=True)
|
| 44 |
+
target = cache / 'dispatch.so'
|
| 45 |
+
with (cache / 'build.lock').open('w') as lock:
|
| 46 |
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
| 47 |
+
if not target.is_file():
|
| 48 |
+
pending = cache / ('dispatch.' + str(os.getpid()) + '.so')
|
| 49 |
+
try:
|
| 50 |
+
subprocess.run(compiler + flags + [str(source), '-o', str(pending)],
|
| 51 |
+
check=True, capture_output=True, text=True)
|
| 52 |
+
pending.replace(target)
|
| 53 |
+
finally:
|
| 54 |
+
pending.unlink(missing_ok=True)
|
| 55 |
+
lib = ct.CDLL(str(target))
|
| 56 |
+
lib.fft_execute.argtypes = [ct.POINTER(Descriptor), ct.c_uint64, ct.c_uint64]
|
| 57 |
+
lib.fft_execute.restype = ct.c_int
|
| 58 |
+
lib.fft_dispatch_size.argtypes = []
|
| 59 |
+
lib.fft_dispatch_size.restype = ct.c_size_t
|
| 60 |
+
if lib.fft_dispatch_size() != ct.sizeof(Descriptor):
|
| 61 |
+
raise RuntimeError('Native dispatcher structure size mismatch')
|
| 62 |
+
_LIBRARY = lib
|
| 63 |
+
return lib
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def load_driver():
|
| 67 |
+
"""Resolve the driver function during preparation only."""
|
| 68 |
+
global _DRIVER
|
| 69 |
+
with _LOCK:
|
| 70 |
+
if _DRIVER is None:
|
| 71 |
+
_DRIVER = ct.CDLL('libcuda.so.1')
|
| 72 |
+
_DRIVER.cuLaunchKernel.restype = ct.c_int
|
| 73 |
+
return _DRIVER
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class PreparedDispatch:
|
| 77 |
+
"""Immutable setup descriptor, with fresh caller pointers at each call.
|
| 78 |
+
|
| 79 |
+
The Plan retains the cuFFT handles and compiled Triton kernel. Concurrent
|
| 80 |
+
execution requires independent plans because cuFFT workspace is reused.
|
| 81 |
+
"""
|
| 82 |
+
def __init__(self, first_exec, first_handle, final_exec, final_handle,
|
| 83 |
+
compiled, slot, scratch, stream):
|
| 84 |
+
abi = validate_kernel_abi(compiled)
|
| 85 |
+
if not compiled.function or compiled.module is None:
|
| 86 |
+
raise ValueError('The Triton kernel must be loaded during setup')
|
| 87 |
+
if not 0 < first_handle < 2**31 or not 0 <= final_handle < 2**31:
|
| 88 |
+
raise ValueError('Valid prepared cuFFT handles are required')
|
| 89 |
+
if not slot or not scratch:
|
| 90 |
+
raise ValueError('Prepared pointer slot and FFT scratch are required')
|
| 91 |
+
self.library = load_library()
|
| 92 |
+
self.driver = load_driver()
|
| 93 |
+
self.descriptor = Descriptor(
|
| 94 |
+
first_exec=ct.cast(first_exec, ct.c_void_p).value,
|
| 95 |
+
final_exec=ct.cast(final_exec, ct.c_void_p).value,
|
| 96 |
+
launch=ct.cast(self.driver.cuLaunchKernel, ct.c_void_p).value,
|
| 97 |
+
first_handle=first_handle, final_handle=final_handle,
|
| 98 |
+
slot=slot, scratch=scratch, stream=stream, function=compiled.function,
|
| 99 |
+
threads=abi['threads'], shared=abi['shared_bytes'])
|
| 100 |
+
self.pointer = ct.pointer(self.descriptor)
|
| 101 |
+
self.function = self.library.fft_execute
|
| 102 |
+
|
| 103 |
+
def execute(self, input_ptr, output_ptr):
|
| 104 |
+
return self.function(self.pointer, input_ptr, output_ptr)
|
build/torch-cuda/_b200/dense_half_inverse_schedule.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Host-only mixed-radix schedules and accurately rounded twiddle tables."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def factors(length, strategy='radix8'):
|
| 9 |
+
"""Return a deterministic DIT factorization; no measurements occur here."""
|
| 10 |
+
remaining = int(length)
|
| 11 |
+
if remaining < 1:
|
| 12 |
+
raise ValueError("FFT lengths must be positive")
|
| 13 |
+
result = []
|
| 14 |
+
if strategy not in ('radix4', 'radix8'):
|
| 15 |
+
raise ValueError('Unknown factorization strategy')
|
| 16 |
+
for radix in ((8, 4, 2) if strategy == 'radix8' else (4, 2)):
|
| 17 |
+
while remaining % radix == 0:
|
| 18 |
+
result.append(radix)
|
| 19 |
+
remaining //= radix
|
| 20 |
+
# Collapse the final pair of radix-3 stages for lengths 9 * 2**k.
|
| 21 |
+
# This gives the radix-9 kernel power-of-two branch lengths; other odd
|
| 22 |
+
# factorizations retain the established paired or generic network.
|
| 23 |
+
if remaining == 9:
|
| 24 |
+
result.append(9)
|
| 25 |
+
remaining = 1
|
| 26 |
+
for radix in (3, 5, 7):
|
| 27 |
+
while remaining % radix == 0:
|
| 28 |
+
result.append(radix)
|
| 29 |
+
remaining //= radix
|
| 30 |
+
if remaining != 1:
|
| 31 |
+
raise ValueError("This prototype supports lengths with factors 2, 3, 5, 7")
|
| 32 |
+
return tuple(result)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def stage_sizes(radices):
|
| 36 |
+
sizes = []
|
| 37 |
+
length = 1
|
| 38 |
+
for radix in radices:
|
| 39 |
+
length *= radix
|
| 40 |
+
sizes.append(length)
|
| 41 |
+
return tuple(sizes)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def permutation(length):
|
| 45 |
+
"""Map DIT input positions to natural input indices, for CPU validation."""
|
| 46 |
+
radices = factors(length)
|
| 47 |
+
result = []
|
| 48 |
+
for index in range(length):
|
| 49 |
+
divisor, reverse = 1, 0
|
| 50 |
+
for radix in radices:
|
| 51 |
+
reverse += ((index // divisor) % radix) * (length // (divisor * radix))
|
| 52 |
+
divisor *= radix
|
| 53 |
+
result.append(reverse)
|
| 54 |
+
return result
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def twiddles(length):
|
| 58 |
+
"""Interleaved exp(-2*pi*i*k/N), computed in host double precision."""
|
| 59 |
+
result = []
|
| 60 |
+
for index in range(length):
|
| 61 |
+
if index == 0:
|
| 62 |
+
value = (1.0, 0.0)
|
| 63 |
+
elif 2 * index == length:
|
| 64 |
+
value = (-1.0, 0.0)
|
| 65 |
+
elif 4 * index == length:
|
| 66 |
+
value = (0.0, -1.0)
|
| 67 |
+
elif 4 * index == 3 * length:
|
| 68 |
+
value = (0.0, 1.0)
|
| 69 |
+
else:
|
| 70 |
+
angle = -math.tau * index / length
|
| 71 |
+
value = (math.cos(angle), math.sin(angle))
|
| 72 |
+
result.extend(value)
|
| 73 |
+
return result
|
build/torch-cuda/_b200/direct_dispatch.c
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* SPDX-License-Identifier: Apache-2.0 */
|
| 2 |
+
/* Execute a prepared cuFFT chain without per-stage Python crossings.
|
| 3 |
+
*
|
| 4 |
+
* The caller owns all handles and buffers. Function addresses are resolved
|
| 5 |
+
* during setup by the installed cuFFT loader. This translation unit has no
|
| 6 |
+
* CUDA dependency of its own and performs no allocation or synchronization.
|
| 7 |
+
*/
|
| 8 |
+
#include <stddef.h>
|
| 9 |
+
|
| 10 |
+
enum { FFT_INPUT = 0, FFT_OUTPUT = 1, FFT_SCRATCH = 2 };
|
| 11 |
+
enum { FFT_REAL = 0, FFT_COMPLEX = 1 };
|
| 12 |
+
|
| 13 |
+
typedef int (*fft_real_fn)(int, void *, void *);
|
| 14 |
+
typedef int (*fft_complex_fn)(int, void *, void *, int);
|
| 15 |
+
|
| 16 |
+
struct fft_stage {
|
| 17 |
+
void *function;
|
| 18 |
+
int handle;
|
| 19 |
+
int kind;
|
| 20 |
+
int direction;
|
| 21 |
+
int input_slot;
|
| 22 |
+
int output_slot;
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
struct fft_chain {
|
| 26 |
+
int count;
|
| 27 |
+
void *scratch;
|
| 28 |
+
struct fft_stage stages[3];
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
int fft_execute(const struct fft_chain *chain, void *input, void *output) {
|
| 32 |
+
void *buffers[3] = {input, output, chain->scratch};
|
| 33 |
+
for (int i = 0; i < chain->count; ++i) {
|
| 34 |
+
const struct fft_stage *stage = &chain->stages[i];
|
| 35 |
+
int result;
|
| 36 |
+
if (stage->kind == FFT_REAL) {
|
| 37 |
+
result = ((fft_real_fn)stage->function)(
|
| 38 |
+
stage->handle, buffers[stage->input_slot], buffers[stage->output_slot]);
|
| 39 |
+
} else {
|
| 40 |
+
result = ((fft_complex_fn)stage->function)(
|
| 41 |
+
stage->handle, buffers[stage->input_slot], buffers[stage->output_slot],
|
| 42 |
+
stage->direction);
|
| 43 |
+
}
|
| 44 |
+
if (result != 0) {
|
| 45 |
+
return result;
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
return 0;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/* Used only by the CPU ABI validation, never by FFT execution. */
|
| 52 |
+
size_t fft_chain_size(void) { return sizeof(struct fft_chain); }
|
| 53 |
+
size_t fft_stage_size(void) { return sizeof(struct fft_stage); }
|
build/torch-cuda/_b200/direct_dispatch.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Compile and prepare a small host dispatcher during FFT plan creation."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ctypes as ct
|
| 6 |
+
import fcntl
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import shlex
|
| 11 |
+
import subprocess
|
| 12 |
+
import tempfile
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Stage(ct.Structure):
|
| 17 |
+
_fields_ = [("function", ct.c_void_p), ("handle", ct.c_int),
|
| 18 |
+
("kind", ct.c_int), ("direction", ct.c_int),
|
| 19 |
+
("input_slot", ct.c_int), ("output_slot", ct.c_int)]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Chain(ct.Structure):
|
| 23 |
+
_fields_ = [("count", ct.c_int), ("scratch", ct.c_void_p),
|
| 24 |
+
("stages", Stage * 3)]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
_LIBRARY = None
|
| 28 |
+
_LOCK = threading.Lock()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load_library():
|
| 32 |
+
"""Build/load before execution; use only the installed host C compiler."""
|
| 33 |
+
global _LIBRARY
|
| 34 |
+
if _LIBRARY is not None:
|
| 35 |
+
return _LIBRARY
|
| 36 |
+
with _LOCK:
|
| 37 |
+
if _LIBRARY is not None:
|
| 38 |
+
return _LIBRARY
|
| 39 |
+
source = Path(__file__).with_name('direct_dispatch.c')
|
| 40 |
+
compiler = shlex.split(os.environ.get('CC', 'cc'))
|
| 41 |
+
flags = ['-std=c11', '-O3', '-fPIC', '-shared', '-Wall', '-Wextra', '-Werror']
|
| 42 |
+
key = hashlib.sha256(source.read_bytes() + repr((compiler, flags)).encode()).hexdigest()
|
| 43 |
+
cache = Path(tempfile.gettempdir()) / ('fft-native-' + str(os.getuid())) / key
|
| 44 |
+
cache.mkdir(parents=True, exist_ok=True)
|
| 45 |
+
target = cache / 'dispatch.so'
|
| 46 |
+
# A process lock prevents concurrent preparations from loading a partial DSO.
|
| 47 |
+
with (cache / 'build.lock').open('w') as lock:
|
| 48 |
+
fcntl.flock(lock, fcntl.LOCK_EX)
|
| 49 |
+
if not target.is_file():
|
| 50 |
+
pending = cache / ('dispatch.' + str(os.getpid()) + '.so')
|
| 51 |
+
try:
|
| 52 |
+
subprocess.run(compiler + flags + [str(source), '-o', str(pending)],
|
| 53 |
+
check=True, capture_output=True, text=True)
|
| 54 |
+
pending.replace(target)
|
| 55 |
+
finally:
|
| 56 |
+
pending.unlink(missing_ok=True)
|
| 57 |
+
lib = ct.CDLL(str(target))
|
| 58 |
+
lib.fft_execute.argtypes = [ct.POINTER(Chain), ct.c_void_p, ct.c_void_p]
|
| 59 |
+
lib.fft_execute.restype = ct.c_int
|
| 60 |
+
for name in ('fft_chain_size', 'fft_stage_size'):
|
| 61 |
+
getattr(lib, name).argtypes = []
|
| 62 |
+
getattr(lib, name).restype = ct.c_size_t
|
| 63 |
+
if lib.fft_chain_size() != ct.sizeof(Chain) or lib.fft_stage_size() != ct.sizeof(Stage):
|
| 64 |
+
raise RuntimeError('Native FFT dispatcher ABI mismatch')
|
| 65 |
+
_LIBRARY = lib
|
| 66 |
+
return lib
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class PreparedChain:
|
| 70 |
+
"""Immutable stage descriptors plus a pre-resolved execution function.
|
| 71 |
+
|
| 72 |
+
Each stage is (function, handle, kind, direction, input_slot, output_slot).
|
| 73 |
+
kind=0 denotes a real transform, kind=1 a complex transform. Slots are
|
| 74 |
+
0=input, 1=output, 2=mathematical intermediate. Tensor values are never
|
| 75 |
+
cached; caller input/output pointers are supplied anew on each call.
|
| 76 |
+
"""
|
| 77 |
+
def __init__(self, stages, scratch_ptr=0):
|
| 78 |
+
if not 1 <= len(stages) <= 3:
|
| 79 |
+
raise ValueError('A chain must contain one to three FFT stages')
|
| 80 |
+
self.library = load_library()
|
| 81 |
+
self.functions = tuple(stage[0] for stage in stages)
|
| 82 |
+
self.chain = Chain(count=len(stages), scratch=scratch_ptr)
|
| 83 |
+
for index, (function, handle, kind, direction, source, target) in enumerate(stages):
|
| 84 |
+
if kind not in (0, 1) or source not in (0, 1, 2) or target not in (0, 1, 2):
|
| 85 |
+
raise ValueError('Invalid FFT stage metadata')
|
| 86 |
+
if (source == 2 or target == 2) and not scratch_ptr:
|
| 87 |
+
raise ValueError('Scratch pointer required for this FFT chain')
|
| 88 |
+
if kind == 1 and direction not in (-1, 1):
|
| 89 |
+
raise ValueError('Complex FFT direction must be -1 or +1')
|
| 90 |
+
address = ct.cast(function, ct.c_void_p).value
|
| 91 |
+
if not address:
|
| 92 |
+
raise ValueError('A resolved cuFFT function address is required')
|
| 93 |
+
self.chain.stages[index] = Stage(address, handle, kind, direction, source, target)
|
| 94 |
+
self.pointer = ct.pointer(self.chain)
|
| 95 |
+
self.function = self.library.fft_execute
|
| 96 |
+
|
| 97 |
+
def execute(self, input_ptr, output_ptr):
|
| 98 |
+
return self.function(self.pointer, input_ptr, output_ptr)
|
build/torch-cuda/_b200/fft_axis.cu
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
// Exact mathematical extents; each warp owns independent adjacent columns.
|
| 3 |
+
__device__ __forceinline__ double2 add(double2 a, double2 b) {
|
| 4 |
+
return make_double2(a.x + b.x, a.y + b.y);
|
| 5 |
+
}
|
| 6 |
+
__device__ __forceinline__ double2 sub(double2 a, double2 b) {
|
| 7 |
+
return make_double2(a.x - b.x, a.y - b.y);
|
| 8 |
+
}
|
| 9 |
+
__device__ __forceinline__ double2 mul(double2 a, double2 b) {
|
| 10 |
+
return make_double2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
template<int H, int N>
|
| 14 |
+
__device__ __forceinline__ double2 rotate_forward(double2 value, int k,
|
| 15 |
+
const double2 *twiddle) {
|
| 16 |
+
if constexpr (H == 1) {
|
| 17 |
+
return value;
|
| 18 |
+
} else if constexpr (H == 2) {
|
| 19 |
+
return k == 0 ? value : make_double2(value.y, -value.x);
|
| 20 |
+
} else {
|
| 21 |
+
return mul(value, twiddle[k*(N/(2*H))]);
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
template<int H, int N, int G, int C, int Bound>
|
| 26 |
+
__device__ __forceinline__ void stage(double2 (&a)[N/G], const double2 *twiddle,
|
| 27 |
+
int lane_z, double2 *exchange) {
|
| 28 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 29 |
+
#pragma unroll
|
| 30 |
+
for (int r = 0; r < N/G; ++r) {
|
| 31 |
+
if ((r % (4*H/G)) < H/G) {
|
| 32 |
+
double2 a0 = a[r], a1 = a[r+H/G], a2 = a[r+2*H/G], a3 = a[r+3*H/G];
|
| 33 |
+
int k = (r*G+lane_z) % H;
|
| 34 |
+
double2 w1=twiddle[k*(N/(4*H))];
|
| 35 |
+
double2 w2=twiddle[2*k*(N/(4*H))];
|
| 36 |
+
double2 w3=twiddle[3*k*(N/(4*H))];
|
| 37 |
+
// Input is binary bit reversed, so the middle branches swap.
|
| 38 |
+
double2 b1=mul(a2,w1), b2=mul(a1,w2), b3=mul(a3,w3);
|
| 39 |
+
double2 even=add(a0,b2), even_diff=sub(a0,b2);
|
| 40 |
+
double2 odd=add(b1,b3), odd_diff=sub(b1,b3);
|
| 41 |
+
double2 turn=make_double2(odd_diff.y,-odd_diff.x);
|
| 42 |
+
a[r]=add(even,odd); a[r+H/G]=add(even_diff,turn);
|
| 43 |
+
a[r+2*H/G]=sub(even,odd); a[r+3*H/G]=sub(even_diff,turn);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
} else if constexpr (H < G) {
|
| 47 |
+
// Consecutive shared stages alternate disjoint mathematical buffers.
|
| 48 |
+
constexpr int exchange_base=(H & 0xAAAAAAAAU) ? N*C : 0;
|
| 49 |
+
if constexpr (H*C >= 32) {
|
| 50 |
+
#pragma unroll
|
| 51 |
+
for (int r = 0; r < N/G; ++r) exchange[exchange_base+r*blockDim.x+threadIdx.x]=a[r];
|
| 52 |
+
__syncthreads();
|
| 53 |
+
}
|
| 54 |
+
#pragma unroll
|
| 55 |
+
for (int r = 0; r < N/G; ++r) {
|
| 56 |
+
double2 v;
|
| 57 |
+
if constexpr (H*C < 32) {
|
| 58 |
+
v = make_double2(__shfl_xor_sync(0xffffffff, a[r].x, H*C),
|
| 59 |
+
__shfl_xor_sync(0xffffffff, a[r].y, H*C));
|
| 60 |
+
} else {
|
| 61 |
+
v = exchange[exchange_base+r*blockDim.x+(threadIdx.x ^ (H*C))];
|
| 62 |
+
}
|
| 63 |
+
double2 lo = (lane_z & H) ? v : a[r];
|
| 64 |
+
double2 hi = (lane_z & H) ? a[r] : v;
|
| 65 |
+
if constexpr (H > 1) {
|
| 66 |
+
hi = rotate_forward<H, N>(hi, (lane_z & (H-1)), twiddle);
|
| 67 |
+
}
|
| 68 |
+
a[r] = (lane_z & H) ? sub(lo, hi) : add(lo, hi);
|
| 69 |
+
}
|
| 70 |
+
// The next producer barrier protects the opposite bank before reuse.
|
| 71 |
+
} else {
|
| 72 |
+
#pragma unroll
|
| 73 |
+
for (int r = 0; r < N/G; ++r) {
|
| 74 |
+
if ((r & (H/G)) == 0) {
|
| 75 |
+
double2 lo = a[r], hi = a[r + H/G];
|
| 76 |
+
hi = rotate_forward<H, N>(hi, ((r*G + lane_z) & (H-1)), twiddle);
|
| 77 |
+
a[r] = add(lo, hi);
|
| 78 |
+
a[r + H/G] = sub(lo, hi);
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
if constexpr (H >= G && 4*H <= Bound) {
|
| 83 |
+
if constexpr (4*H < Bound) stage<4*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 84 |
+
} else if constexpr (2*H < Bound) stage<2*H, N, G, C, Bound>(a, twiddle, lane_z, exchange);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
#if FFT_DOUBLE
|
| 88 |
+
using output_scalar = double;
|
| 89 |
+
#else
|
| 90 |
+
using output_scalar = float;
|
| 91 |
+
#endif
|
| 92 |
+
|
| 93 |
+
// Reconstruct packed XY spectra from the transformed real-pair operand.
|
| 94 |
+
static __device__ __forceinline__ double2 reconstruct(
|
| 95 |
+
const double2 *input, const double2 *twiddle, int z, int column) {
|
| 96 |
+
constexpr int m = NX/2, nf = m+1;
|
| 97 |
+
const int k = column % nf, y = column / nf;
|
| 98 |
+
const int mirror_y = y == 0 ? 0 : NY-y;
|
| 99 |
+
const double2 a = input[(z*NY+y)*m+k%m];
|
| 100 |
+
const double2 b = input[(z*NY+mirror_y)*m+(m-k)%m];
|
| 101 |
+
const double2 w = twiddle[LENGTH+k];
|
| 102 |
+
const double er = 0.5*(a.x+b.x), ei = 0.5*(a.y-b.y);
|
| 103 |
+
const double odd_r = 0.5*(a.y+b.y), odd_i = -0.5*(a.x-b.x);
|
| 104 |
+
return make_double2(er+w.x*odd_r-w.y*odd_i,
|
| 105 |
+
ei+w.x*odd_i+w.y*odd_r);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
extern "C" __global__ __launch_bounds__(WARPS*32)
|
| 109 |
+
void fft_axis(const double2 *__restrict__ input, output_scalar *__restrict__ output,
|
| 110 |
+
const double2 *__restrict__ twiddle, void *, void *) {
|
| 111 |
+
constexpr int G = WARPS*32/COLUMNS, R = LENGTH/G;
|
| 112 |
+
__shared__ double2 exchange[2*LENGTH*COLUMNS];
|
| 113 |
+
int lane_z = threadIdx.x / COLUMNS;
|
| 114 |
+
int column = blockIdx.x * COLUMNS + threadIdx.x % COLUMNS;
|
| 115 |
+
double2 a[R];
|
| 116 |
+
#pragma unroll
|
| 117 |
+
for (int r = 0; r < R; ++r) {
|
| 118 |
+
int index = r*G + lane_z;
|
| 119 |
+
int source = (__brev((unsigned)(index % BASE)) >> (32-LOG_BASE))*ODD
|
| 120 |
+
+ index/BASE;
|
| 121 |
+
a[r] = column < INNER ? reconstruct(input, twiddle, source, column) : make_double2(0., 0.);
|
| 122 |
+
}
|
| 123 |
+
stage<1, LENGTH, G, COLUMNS, BASE>(a, twiddle, lane_z, exchange);
|
| 124 |
+
if constexpr (ODD != 1) {
|
| 125 |
+
#pragma unroll
|
| 126 |
+
for (int r = 0; r < BASE/G; ++r) {
|
| 127 |
+
double2 b[ODD];
|
| 128 |
+
b[0] = a[r];
|
| 129 |
+
#pragma unroll
|
| 130 |
+
for (int t = 1; t < ODD; ++t) {
|
| 131 |
+
double2 w = twiddle[t*(r*G+lane_z)];
|
| 132 |
+
|
| 133 |
+
b[t] = mul(a[r+t*(BASE/G)], w);
|
| 134 |
+
}
|
| 135 |
+
#pragma unroll
|
| 136 |
+
for (int k = 0; k < ODD; ++k) {
|
| 137 |
+
double2 v = b[0];
|
| 138 |
+
#pragma unroll
|
| 139 |
+
for (int t = 1; t < ODD; ++t) {
|
| 140 |
+
double2 w = twiddle[((k*t)%ODD)*BASE];
|
| 141 |
+
|
| 142 |
+
v = add(v, mul(b[t], w));
|
| 143 |
+
}
|
| 144 |
+
a[r+k*(BASE/G)] = v;
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
#pragma unroll
|
| 149 |
+
for (int r = 0; r < R; ++r) {
|
| 150 |
+
if (column < INNER) {
|
| 151 |
+
int offset = 2*((r*G+lane_z)*INNER+column);
|
| 152 |
+
#if FFT_DOUBLE
|
| 153 |
+
if ((reinterpret_cast<unsigned long long>(output) & 15ULL) == 0) {
|
| 154 |
+
reinterpret_cast<double2 *>(output)[offset/2] = a[r];
|
| 155 |
+
} else {
|
| 156 |
+
output[offset] = a[r].x;
|
| 157 |
+
output[offset+1] = a[r].y;
|
| 158 |
+
}
|
| 159 |
+
#else
|
| 160 |
+
if ((reinterpret_cast<unsigned long long>(output) & 7ULL) == 0) {
|
| 161 |
+
reinterpret_cast<float2 *>(output)[offset/2] = make_float2((float)a[r].x, (float)a[r].y);
|
| 162 |
+
} else {
|
| 163 |
+
output[offset] = (float)a[r].x;
|
| 164 |
+
output[offset+1] = (float)a[r].y;
|
| 165 |
+
}
|
| 166 |
+
#endif
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
}
|
build/torch-cuda/_b200/forward_cuda_f32.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""FP64 half-length XY forward followed by a CUDA register Z forward."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
import threading
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from .cuda_z_inverse_cufft_support import _Handle, _check, _lib
|
| 11 |
+
from .cuda_kernel import compile_kernel
|
| 12 |
+
from .cuda_twiddle_forward_lto import compile_callback
|
| 13 |
+
from .cuda_twiddle_forward_native_dispatch import PreparedDispatch
|
| 14 |
+
from .cuda_z_inverse_schedule import twiddles
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class Plan:
|
| 18 |
+
def __init__(self, shape, dtype, direction='r2c', *, device=None, stream=None,
|
| 19 |
+
variant='xy_z', dispatch='native', columns=8, warps=4):
|
| 20 |
+
self.shape = tuple(int(n) for n in shape)
|
| 21 |
+
if len(self.shape) != 3 or min(self.shape[:2]) < 8 or self.shape[2] < 16 or self.shape[2] % 2:
|
| 22 |
+
raise ValueError('Expected (nz >= 8, ny >= 8, nx >= 16)')
|
| 23 |
+
if dtype not in (torch.float32, torch.float64) or direction != 'r2c':
|
| 24 |
+
raise ValueError('Expected a real forward transform')
|
| 25 |
+
if variant not in ('xy_z', 'xy_z_patient'):
|
| 26 |
+
raise ValueError('Unknown variant')
|
| 27 |
+
if dispatch != 'native':
|
| 28 |
+
raise ValueError('Unknown dispatch mode')
|
| 29 |
+
self.dtype, self.direction, self.variant = dtype, direction, variant
|
| 30 |
+
self.scalar_bytes = 8 if dtype == torch.float64 else 4
|
| 31 |
+
self.residues = 16 // self.scalar_bytes
|
| 32 |
+
self.dispatch = dispatch
|
| 33 |
+
if columns not in (2, 4, 8, 16, 32) or warps not in (2, 4, 8):
|
| 34 |
+
raise ValueError("Unsupported column tile or warp count")
|
| 35 |
+
self.columns, self.warps = columns, warps
|
| 36 |
+
self.native = []
|
| 37 |
+
self.device = torch.device(device if device is not None else torch.cuda.current_device())
|
| 38 |
+
if self.device.type != 'cuda':
|
| 39 |
+
raise ValueError('A CUDA device is required')
|
| 40 |
+
if self.device.index is None:
|
| 41 |
+
self.device = torch.device('cuda', torch.cuda.current_device())
|
| 42 |
+
self.stream = stream if stream is not None else torch.cuda.current_stream(self.device)
|
| 43 |
+
if self.stream.device != self.device:
|
| 44 |
+
raise ValueError('The supplied stream must belong to the plan device')
|
| 45 |
+
self.stream_ptr = self.stream.cuda_stream
|
| 46 |
+
self.closed = False
|
| 47 |
+
self.handles = []
|
| 48 |
+
self.compiled_metadata = []
|
| 49 |
+
nz, ny, nx = self.shape
|
| 50 |
+
nf = nx // 2 + 1
|
| 51 |
+
self.input_shape, self.output_shape = self.shape, (nz, ny, nf, 2)
|
| 52 |
+
m = nx // 2
|
| 53 |
+
self.fft_function = _lib.cufftExecZ2Z
|
| 54 |
+
try:
|
| 55 |
+
with torch.cuda.device(self.device), torch.cuda.stream(self.stream):
|
| 56 |
+
capability = torch.cuda.get_device_capability(self.device)
|
| 57 |
+
arch = capability[0] * 10 + capability[1]
|
| 58 |
+
# This intermediate contains the transformed half-length XY data.
|
| 59 |
+
# The last Z stage writes directly to scalar caller storage.
|
| 60 |
+
self.scratch = torch.empty((nz, ny, m, 2), dtype=torch.float64,
|
| 61 |
+
device=self.device)
|
| 62 |
+
self.scratch_ptr = self.scratch.data_ptr()
|
| 63 |
+
patient = variant.endswith('_patient')
|
| 64 |
+
for residue in range(self.residues):
|
| 65 |
+
load_callback = compile_callback(dtype == torch.float64, 'pair', residue, arch, self.shape)[0]
|
| 66 |
+
self.compiled_metadata.append(dict(load_callback[3], input_residue=residue))
|
| 67 |
+
handle = _Handle(
|
| 68 |
+
(ny, m), 0x69, (ny, m), 1, ny * m,
|
| 69 |
+
(ny, m), 1, ny * m, nz, self.device, self.stream,
|
| 70 |
+
callback=[load_callback], patient=patient)
|
| 71 |
+
self.handles.append(handle)
|
| 72 |
+
self.handle_values = tuple(h.value for h in self.handles)
|
| 73 |
+
self.z_table = torch.tensor(twiddles(nz) + twiddles(nx), dtype=torch.float64,
|
| 74 |
+
device=self.device)
|
| 75 |
+
compiled = compile_kernel(self.shape, columns, warps, arch, self.device.index, dtype == torch.float64)
|
| 76 |
+
self.grid = compiled.grid
|
| 77 |
+
self.compiled_kernel = compiled
|
| 78 |
+
self.runner = None
|
| 79 |
+
if dispatch == 'native':
|
| 80 |
+
for handle in self.handle_values:
|
| 81 |
+
self.native.append(PreparedDispatch(
|
| 82 |
+
self.fft_function, handle, compiled, self.grid,
|
| 83 |
+
self.scratch_ptr, self.z_table.data_ptr(), self.stream_ptr))
|
| 84 |
+
self.native_function = self.native[0].function
|
| 85 |
+
self.native_pointers = tuple(n.pointer for n in self.native)
|
| 86 |
+
self.compiled_metadata.append(dict(name=compiled.name, hash=compiled.hash,
|
| 87 |
+
shared=compiled.shared, compiler=compiled.compiler_metadata,
|
| 88 |
+
registers=compiled.n_regs,
|
| 89 |
+
spills=compiled.n_spills,
|
| 90 |
+
grid=self.grid, warps=warps))
|
| 91 |
+
dummy = torch.zeros(math.prod(self.input_shape) + 4, dtype=dtype,
|
| 92 |
+
device=self.device)
|
| 93 |
+
output = torch.empty(self.output_shape, dtype=dtype, device=self.device)
|
| 94 |
+
for residue in range(self.residues):
|
| 95 |
+
self.execute_ptr(dummy.data_ptr() + residue * self.scalar_bytes, output.data_ptr(), self.stream_ptr)
|
| 96 |
+
self.stream.synchronize()
|
| 97 |
+
except BaseException:
|
| 98 |
+
for handle in self.handles:
|
| 99 |
+
handle.close()
|
| 100 |
+
raise
|
| 101 |
+
|
| 102 |
+
@property
|
| 103 |
+
def workspace_bytes(self):
|
| 104 |
+
return (sum(h.workspace_bytes for h in self.handles)
|
| 105 |
+
+ self.scratch.numel() * 8
|
| 106 |
+
+ self.z_table.numel() * 8)
|
| 107 |
+
|
| 108 |
+
def execute_ptr(self, input_ptr, output_ptr, stream_ptr):
|
| 109 |
+
"""Enqueue the prepared cuFFT XY and CUDA Z transforms."""
|
| 110 |
+
if self.closed:
|
| 111 |
+
raise RuntimeError('The plan is closed')
|
| 112 |
+
if int(stream_ptr) != self.stream_ptr:
|
| 113 |
+
raise ValueError('Prepare an independent plan for a different stream')
|
| 114 |
+
a, b = int(input_ptr), int(output_ptr)
|
| 115 |
+
if not a or not b or a == b or a % self.scalar_bytes or b % self.scalar_bytes:
|
| 116 |
+
raise ValueError('Distinct non-null scalar-aligned pointers are required')
|
| 117 |
+
residue = (a & 15) // self.scalar_bytes
|
| 118 |
+
result = self.native_function(self.native_pointers[residue],
|
| 119 |
+
a - self.scalar_bytes * residue, b)
|
| 120 |
+
if result > 0:
|
| 121 |
+
_check(result)
|
| 122 |
+
if result < 0:
|
| 123 |
+
raise RuntimeError(f'cuLaunchKernel failed with CUDA driver error {-result}')
|
| 124 |
+
|
| 125 |
+
def execute(self, input, output, stream=None):
|
| 126 |
+
for tensor, shape in ((input, self.input_shape), (output, self.output_shape)):
|
| 127 |
+
if (tuple(tensor.shape) != shape or tensor.dtype != self.dtype
|
| 128 |
+
or tensor.device != self.device or not tensor.is_contiguous()):
|
| 129 |
+
raise ValueError('Buffer metadata does not match the plan')
|
| 130 |
+
a, b = input.data_ptr(), output.data_ptr()
|
| 131 |
+
na, nb = input.numel() * input.element_size(), output.numel() * output.element_size()
|
| 132 |
+
if a < b + nb and b < a + na:
|
| 133 |
+
raise ValueError('Input and output must not overlap')
|
| 134 |
+
selected = self.stream if stream is None else stream
|
| 135 |
+
self.execute_ptr(a, b, selected.cuda_stream)
|
| 136 |
+
return output
|
| 137 |
+
|
| 138 |
+
def close(self):
|
| 139 |
+
"""The caller must wait for every outstanding use before closing."""
|
| 140 |
+
if not self.closed:
|
| 141 |
+
for handle in self.handles:
|
| 142 |
+
handle.close()
|
| 143 |
+
self.scratch = self.z_table = self.runner = self.compiled_kernel = None
|
| 144 |
+
self.native = self.native_pointers = None
|
| 145 |
+
self.closed = True
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
_CACHE = {}
|
| 151 |
+
_LOCK = threading.Lock()
|
build/torch-cuda/_b200/forward_half.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Half-length FP64 XY FFT with reconstruction fused into the final Z load.
|
| 3 |
+
|
| 4 |
+
Both cuFFT stages consume the caller's packed layout or a mathematical FFT
|
| 5 |
+
intermediate. The final single-kernel plan writes scalar caller storage.
|
| 6 |
+
"""
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import threading
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from .scalar_input_inverse_cufft_support import _Handle, _lib, _check
|
| 13 |
+
from .half_forward_cufft_z_lto import compile_callback
|
| 14 |
+
from .direct_dispatch import PreparedChain
|
| 15 |
+
from .half_forward_cufft_z_plan_safety import require_single_kernel
|
| 16 |
+
from .cuda_z_inverse_schedule import twiddles
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Plan:
|
| 20 |
+
def __init__(self, shape, dtype, direction='r2c', *, device=None, stream=None,
|
| 21 |
+
axis='z', patient=False, xt=False):
|
| 22 |
+
self.shape = tuple(int(v) for v in shape)
|
| 23 |
+
if (len(self.shape) != 3 or min(self.shape[:2]) < 8
|
| 24 |
+
or self.shape[2] < 16 or self.shape[2] % 2):
|
| 25 |
+
raise ValueError('Expected (nz >= 8, ny >= 8, even nx >= 16)')
|
| 26 |
+
if dtype not in (torch.float32, torch.float64) or direction != 'r2c':
|
| 27 |
+
raise ValueError('Expected a float32 or float64 forward transform')
|
| 28 |
+
if axis != 'z':
|
| 29 |
+
raise ValueError('This decomposition requires Z as the last axis')
|
| 30 |
+
self.dtype, self.direction = dtype, direction
|
| 31 |
+
self.itemsize = 8 if dtype == torch.float64 else 4
|
| 32 |
+
self.residues = 16 // self.itemsize
|
| 33 |
+
self.device = torch.device(device if device is not None else torch.cuda.current_device())
|
| 34 |
+
if self.device.type != 'cuda':
|
| 35 |
+
raise ValueError('A CUDA device is required')
|
| 36 |
+
if self.device.index is None:
|
| 37 |
+
self.device = torch.device('cuda', torch.cuda.current_device())
|
| 38 |
+
self.stream = stream if stream is not None else torch.cuda.current_stream(self.device)
|
| 39 |
+
if self.stream.device != self.device:
|
| 40 |
+
raise ValueError('Stream and plan device must match')
|
| 41 |
+
self.stream_ptr = self.stream.cuda_stream
|
| 42 |
+
self.variant = f'half_xy_reconstruct_z_p{int(patient)}_xt{int(xt)}'
|
| 43 |
+
self.dispatch = 'prepared_native'
|
| 44 |
+
self.closed, self.handles, self.chains = False, [], []
|
| 45 |
+
nz, ny, nx = self.shape
|
| 46 |
+
rows, nf = nz * ny, nx // 2 + 1
|
| 47 |
+
first, other = (nz, ny) if axis == 'z' else (ny, nz)
|
| 48 |
+
self.input_shape, self.output_shape = self.shape, (nz, ny, nf, 2)
|
| 49 |
+
self.compiled_metadata = []
|
| 50 |
+
try:
|
| 51 |
+
with torch.cuda.device(self.device), torch.cuda.stream(self.stream):
|
| 52 |
+
self.intermediate = torch.empty((nz, ny, nx // 2, 2), dtype=torch.float64, device=self.device)
|
| 53 |
+
self.table = torch.tensor(twiddles(nx), dtype=torch.float64, device=self.device)
|
| 54 |
+
cap = torch.cuda.get_device_capability(self.device)
|
| 55 |
+
arch = 10 * cap[0] + cap[1]
|
| 56 |
+
load_handles, store_handles = [], []
|
| 57 |
+
for shift in range(self.residues):
|
| 58 |
+
cb = compile_callback(dtype == torch.float64, 'pair', shift, arch, self.shape, axis)
|
| 59 |
+
self.compiled_metadata.extend(item[3] for item in cb)
|
| 60 |
+
handle = _Handle((ny, nx // 2), 0x69, (ny, nx // 2), 1, ny * (nx // 2),
|
| 61 |
+
(ny, nx // 2), 1, ny * (nx // 2), nz, self.device, self.stream,
|
| 62 |
+
callback=cb, patient=patient)
|
| 63 |
+
self.handles.append(handle)
|
| 64 |
+
load_handles.append(handle)
|
| 65 |
+
cb = (compile_callback(dtype == torch.float64, 'reconstruct', 0, arch, self.shape, axis)
|
| 66 |
+
+ compile_callback(dtype == torch.float64, 'store', shift, arch, self.shape, axis))
|
| 67 |
+
self.compiled_metadata.extend(item[3] for item in cb)
|
| 68 |
+
handle = _Handle((first,), 0x69, (first,), other * nf, 1,
|
| 69 |
+
(first,), other * nf, 1, other * nf, self.device, self.stream,
|
| 70 |
+
callback=cb, patient=patient, xt=xt, caller_info=self.table.data_ptr())
|
| 71 |
+
self.handles.append(handle)
|
| 72 |
+
store_handles.append(handle)
|
| 73 |
+
for input_handle in load_handles:
|
| 74 |
+
for output_handle in store_handles:
|
| 75 |
+
self.chains.append(PreparedChain([
|
| 76 |
+
(_lib.cufftExecZ2Z, input_handle.value, 1, -1, 0, 2),
|
| 77 |
+
(_lib.cufftExecZ2Z, output_handle.value, 1, -1, 2, 1)
|
| 78 |
+
], self.intermediate.data_ptr()))
|
| 79 |
+
self.native_function = self.chains[0].function
|
| 80 |
+
self.native_pointers = tuple(c.pointer for c in self.chains)
|
| 81 |
+
# Every prepared callback is loaded and warmed, including all
|
| 82 |
+
# address combinations. Dummy buffers are setup-only storage.
|
| 83 |
+
dummy_in = torch.zeros(rows * nx + self.residues,
|
| 84 |
+
dtype=dtype, device=self.device)
|
| 85 |
+
# Oversized nominal double-complex storage is setup-only. Every final
|
| 86 |
+
# handle must prove it has a single kernel before caller-sized use.
|
| 87 |
+
dummy_out = torch.empty(nz * ny * nf * 16 + 16,
|
| 88 |
+
dtype=torch.uint8, device=self.device)
|
| 89 |
+
self.final_plan_checks = []
|
| 90 |
+
for handle in store_handles:
|
| 91 |
+
result = require_single_kernel(handle, self.intermediate, dummy_out,
|
| 92 |
+
self.device, self.stream)
|
| 93 |
+
self.final_plan_checks.append(result)
|
| 94 |
+
for i in range(self.residues):
|
| 95 |
+
for o in range(self.residues):
|
| 96 |
+
self.execute_ptr(dummy_in.data_ptr() + i * self.itemsize,
|
| 97 |
+
dummy_out.data_ptr() + o * self.itemsize,
|
| 98 |
+
self.stream_ptr)
|
| 99 |
+
self.stream.synchronize()
|
| 100 |
+
except BaseException:
|
| 101 |
+
for handle in reversed(self.handles):
|
| 102 |
+
handle.close()
|
| 103 |
+
self.closed = True
|
| 104 |
+
raise
|
| 105 |
+
|
| 106 |
+
@property
|
| 107 |
+
def workspace_bytes(self):
|
| 108 |
+
return sum(h.workspace_bytes for h in self.handles) + (self.intermediate.numel() + self.table.numel()) * 8
|
| 109 |
+
|
| 110 |
+
def execute_ptr(self, input_ptr, output_ptr, stream_ptr):
|
| 111 |
+
"""Enqueue using current scalar-aligned addresses on the bound stream."""
|
| 112 |
+
if self.closed:
|
| 113 |
+
raise RuntimeError('The plan is closed')
|
| 114 |
+
if int(stream_ptr) != self.stream_ptr:
|
| 115 |
+
raise ValueError('Create a separate plan for the supplied stream')
|
| 116 |
+
a, b = int(input_ptr), int(output_ptr)
|
| 117 |
+
if not a or not b or a == b:
|
| 118 |
+
raise ValueError('Distinct non-null caller pointers are required')
|
| 119 |
+
if a % self.itemsize or b % self.itemsize:
|
| 120 |
+
raise ValueError('Caller buffers must be scalar aligned')
|
| 121 |
+
i, o = (a & 15) // self.itemsize, (b & 15) // self.itemsize
|
| 122 |
+
_check(self.native_function(self.native_pointers[i * self.residues + o],
|
| 123 |
+
a - (a & 15), b - (b & 15)))
|
| 124 |
+
|
| 125 |
+
def execute(self, input, output, stream=None):
|
| 126 |
+
for tensor, shape in ((input, self.input_shape), (output, self.output_shape)):
|
| 127 |
+
if (tuple(tensor.shape) != shape or tensor.dtype != self.dtype
|
| 128 |
+
or tensor.device != self.device or not tensor.is_contiguous()):
|
| 129 |
+
raise ValueError('Buffer metadata does not match the plan')
|
| 130 |
+
a, b = input.data_ptr(), output.data_ptr()
|
| 131 |
+
na, nb = input.numel() * self.itemsize, output.numel() * self.itemsize
|
| 132 |
+
if a < b + nb and b < a + na:
|
| 133 |
+
raise ValueError('Input and output must not overlap')
|
| 134 |
+
self.execute_ptr(a, b, (self.stream if stream is None else stream).cuda_stream)
|
| 135 |
+
return output
|
| 136 |
+
|
| 137 |
+
def close(self):
|
| 138 |
+
"""The caller must complete outstanding work before closing the plan."""
|
| 139 |
+
if not self.closed:
|
| 140 |
+
for handle in reversed(self.handles):
|
| 141 |
+
handle.close()
|
| 142 |
+
self.intermediate = self.table = self.chains = self.native_pointers = None
|
| 143 |
+
self.closed = True
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
_CACHE = {}
|
| 149 |
+
_LOCK = threading.Lock()
|
build/torch-cuda/_b200/forward_native.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Preplanned Z-then-XY cuFFT inverse with Hermitian boundary projection.
|
| 3 |
+
|
| 4 |
+
The load callback projects the original Z/Y endpoint planes as the complex
|
| 5 |
+
Z transform reads them. The separate-kernel alternative projects Y pairs
|
| 6 |
+
after the Z transform. Both preserve arbitrary packed-input semantics.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import threading
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import triton
|
| 14 |
+
|
| 15 |
+
from .hermitian_projection_cufft_support import _Handle, _check, _lib
|
| 16 |
+
from .hermitian_projection_kernels import project_y_endpoints
|
| 17 |
+
from .hermitian_projection_lto import compile_callback
|
| 18 |
+
from .direct_dispatch import PreparedChain
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class Plan:
|
| 22 |
+
"""All preparation occurs in the constructor, on the supplied stream.
|
| 23 |
+
|
| 24 |
+
Execute calls only enqueue work. Callers own nonoverlapping input/output
|
| 25 |
+
buffers and must keep them and the plan alive until stream completion.
|
| 26 |
+
Prepare independent plans for concurrent streams or host threads.
|
| 27 |
+
"""
|
| 28 |
+
def __init__(self, shape, dtype, direction, *, device=None, stream=None,
|
| 29 |
+
variant=None):
|
| 30 |
+
self.shape = tuple(int(n) for n in shape)
|
| 31 |
+
if len(self.shape) != 3 or min(self.shape[:2]) < 8 or self.shape[2] < 16:
|
| 32 |
+
raise ValueError('Expected (nz >= 8, ny >= 8, nx >= 16)')
|
| 33 |
+
if dtype not in (torch.float32, torch.float64):
|
| 34 |
+
raise TypeError('Only float32 and float64 are supported')
|
| 35 |
+
if direction not in ('r2c', 'c2r'):
|
| 36 |
+
raise ValueError('direction must be r2c or c2r')
|
| 37 |
+
if direction == 'c2r' and self.shape[2] % 2:
|
| 38 |
+
raise ValueError('The inverse request infers an even nx from nf')
|
| 39 |
+
self.variant = variant or ('3d' if direction == 'r2c' else 'z_xy_load')
|
| 40 |
+
permitted = (('3d', 'xy_z', 'x_zy') if direction == 'r2c' else
|
| 41 |
+
('z_xy_load', 'z_xy_kernel'))
|
| 42 |
+
if self.variant not in permitted:
|
| 43 |
+
raise ValueError(f'Unknown {direction} variant: {self.variant}')
|
| 44 |
+
self.dtype, self.direction = dtype, direction
|
| 45 |
+
self.device = torch.device(device if device is not None else torch.cuda.current_device())
|
| 46 |
+
if self.device.type != 'cuda':
|
| 47 |
+
raise ValueError('A CUDA device is required')
|
| 48 |
+
if self.device.index is None:
|
| 49 |
+
self.device = torch.device('cuda', torch.cuda.current_device())
|
| 50 |
+
self.stream = stream if stream is not None else torch.cuda.current_stream(self.device)
|
| 51 |
+
if self.stream.device != self.device:
|
| 52 |
+
raise ValueError('The supplied stream is on a different device')
|
| 53 |
+
self.stream_ptr = self.stream.cuda_stream
|
| 54 |
+
self.closed = False
|
| 55 |
+
self.handles = []
|
| 56 |
+
self.intermediate = None
|
| 57 |
+
self._chain = None
|
| 58 |
+
self.runner = None
|
| 59 |
+
self.compiled_metadata = []
|
| 60 |
+
self.dispatch = 'native_cufft' if self.variant != 'z_xy_kernel' else 'cufft_projection_kernel'
|
| 61 |
+
nz, ny, nx = self.shape
|
| 62 |
+
nf = nx // 2 + 1
|
| 63 |
+
packed = (nz, ny, nf, 2)
|
| 64 |
+
inverse = direction == 'c2r'
|
| 65 |
+
self.input_shape, self.output_shape = (packed, self.shape) if inverse else (self.shape, packed)
|
| 66 |
+
double = dtype == torch.float64
|
| 67 |
+
rkind, ckind, ikind = (0x6A, 0x69, 0x6C) if double else (0x2A, 0x29, 0x2C)
|
| 68 |
+
self._real_exec = getattr(_lib, 'cufftExec' + (
|
| 69 |
+
('Z2D' if double else 'C2R') if inverse else ('D2Z' if double else 'R2C')))
|
| 70 |
+
self._complex_exec = _lib.cufftExecZ2Z if double else _lib.cufftExecC2C
|
| 71 |
+
try:
|
| 72 |
+
with torch.cuda.device(self.device), torch.cuda.stream(self.stream):
|
| 73 |
+
def plan(sizes, kind, ie, istr, idist, oe, ostr, odist, batch,
|
| 74 |
+
callback=None, patient=False):
|
| 75 |
+
handle = _Handle(sizes, kind, ie, istr, idist, oe, ostr, odist,
|
| 76 |
+
batch, self.device, self.stream, callback, patient)
|
| 77 |
+
self.handles.append(handle)
|
| 78 |
+
return handle.value
|
| 79 |
+
|
| 80 |
+
if not inverse:
|
| 81 |
+
if self.variant == '3d':
|
| 82 |
+
self.h0 = plan(self.shape, rkind, self.shape, 1, nz * ny * nx,
|
| 83 |
+
(nz, ny, nf), 1, nz * ny * nf, 1)
|
| 84 |
+
elif self.variant == 'xy_z':
|
| 85 |
+
self.h0 = plan((ny, nx), rkind, (ny, nx), 1, ny * nx,
|
| 86 |
+
(ny, nf), 1, ny * nf, nz)
|
| 87 |
+
self.h1 = plan((nz,), ckind, (nz,), ny * nf, 1,
|
| 88 |
+
(nz,), ny * nf, 1, ny * nf)
|
| 89 |
+
else:
|
| 90 |
+
self.h0 = plan((nx,), rkind, (nx,), 1, nx, (nf,), 1, nf, nz * ny)
|
| 91 |
+
self.h1 = plan((nz, ny), ckind, (nz, ny), nf, 1,
|
| 92 |
+
(nz, ny), nf, 1, nf)
|
| 93 |
+
stages = [(self._real_exec, self.h0, 0, 0, 0, 1)]
|
| 94 |
+
if self.variant in ('xy_z', 'x_zy'):
|
| 95 |
+
stages.append((self._complex_exec, self.h1, 1, -1, 1, 1))
|
| 96 |
+
self._chain = PreparedChain(stages)
|
| 97 |
+
else:
|
| 98 |
+
callback = None
|
| 99 |
+
if self.variant == 'z_xy_load':
|
| 100 |
+
capability = torch.cuda.get_device_capability(self.device)
|
| 101 |
+
callback = compile_callback(self.shape, double,
|
| 102 |
+
10 * capability[0] + capability[1])
|
| 103 |
+
self.compiled_metadata.append(callback[3])
|
| 104 |
+
self.intermediate = torch.empty(packed, dtype=dtype, device=self.device)
|
| 105 |
+
self.tmp_ptr = self.intermediate.data_ptr()
|
| 106 |
+
self.h0 = plan((nz,), ckind, (nz,), ny * nf, 1,
|
| 107 |
+
(nz,), ny * nf, 1, ny * nf, callback=callback)
|
| 108 |
+
self.h1 = plan((ny, nx), ikind, (ny, nf), 1, ny * nf,
|
| 109 |
+
(ny, nx), 1, ny * nx, nz)
|
| 110 |
+
if self.variant == 'z_xy_kernel':
|
| 111 |
+
self.constants = (nz, ny, nf, 256)
|
| 112 |
+
grid = (triton.cdiv(2 * nz * (ny // 2 + 1), 256), 1, 1)
|
| 113 |
+
compiled = project_y_endpoints[grid](self.intermediate, *self.constants,
|
| 114 |
+
num_warps=4)
|
| 115 |
+
self.runner = compiled[grid]
|
| 116 |
+
self.compiled_metadata.append(dict(name=compiled.name, hash=compiled.hash,
|
| 117 |
+
shared=compiled.metadata.shared,
|
| 118 |
+
registers=compiled.n_regs,
|
| 119 |
+
spills=compiled.n_spills, grid=grid))
|
| 120 |
+
else:
|
| 121 |
+
self._chain = PreparedChain([
|
| 122 |
+
(self._complex_exec, self.h0, 1, 1, 0, 2),
|
| 123 |
+
(self._real_exec, self.h1, 0, 0, 2, 1)], self.tmp_ptr)
|
| 124 |
+
if self._chain is not None:
|
| 125 |
+
self._native_function, self._native_pointer = self._chain.function, self._chain.pointer
|
| 126 |
+
dummy_in = torch.zeros(self.input_shape, dtype=dtype, device=self.device)
|
| 127 |
+
dummy_out = torch.empty(self.output_shape, dtype=dtype, device=self.device)
|
| 128 |
+
self.execute_ptr(dummy_in.data_ptr(), dummy_out.data_ptr(), self.stream_ptr)
|
| 129 |
+
self.stream.synchronize()
|
| 130 |
+
except BaseException:
|
| 131 |
+
for handle in self.handles:
|
| 132 |
+
handle.close()
|
| 133 |
+
raise
|
| 134 |
+
|
| 135 |
+
@property
|
| 136 |
+
def workspace_bytes(self):
|
| 137 |
+
return sum(h.workspace_bytes for h in self.handles) + (
|
| 138 |
+
self.intermediate.numel() * self.intermediate.element_size()
|
| 139 |
+
if self.intermediate is not None else 0)
|
| 140 |
+
|
| 141 |
+
def execute_ptr(self, input_ptr, output_ptr, stream_ptr):
|
| 142 |
+
"""Enqueue prepared stages with exact packed pointers on the bound stream."""
|
| 143 |
+
if self.closed:
|
| 144 |
+
raise RuntimeError('The plan is closed')
|
| 145 |
+
if int(stream_ptr) != self.stream_ptr:
|
| 146 |
+
raise ValueError('Create a separate plan for the supplied stream')
|
| 147 |
+
if not input_ptr or not output_ptr or int(input_ptr) == int(output_ptr):
|
| 148 |
+
raise ValueError('Distinct non-null input/output pointers are required')
|
| 149 |
+
if self._chain is not None:
|
| 150 |
+
_check(self._native_function(self._native_pointer, input_ptr, output_ptr))
|
| 151 |
+
else:
|
| 152 |
+
_check(self._complex_exec(self.h0, input_ptr, self.tmp_ptr, 1))
|
| 153 |
+
self.runner(self.tmp_ptr, *self.constants, stream=self.stream_ptr)
|
| 154 |
+
_check(self._real_exec(self.h1, self.tmp_ptr, output_ptr))
|
| 155 |
+
|
| 156 |
+
def execute(self, input, output, stream=None):
|
| 157 |
+
"""Check caller buffers, enqueue the transform, and return output."""
|
| 158 |
+
for tensor, shape in ((input, self.input_shape), (output, self.output_shape)):
|
| 159 |
+
if (tuple(tensor.shape) != shape or tensor.dtype != self.dtype
|
| 160 |
+
or tensor.device != self.device or not tensor.is_contiguous()):
|
| 161 |
+
raise ValueError('Buffer metadata does not match the plan')
|
| 162 |
+
a, b = input.data_ptr(), output.data_ptr()
|
| 163 |
+
na, nb = input.numel() * input.element_size(), output.numel() * output.element_size()
|
| 164 |
+
if a < b + nb and b < a + na:
|
| 165 |
+
raise ValueError('Input and output buffers must not overlap')
|
| 166 |
+
selected = self.stream if stream is None else stream
|
| 167 |
+
self.execute_ptr(a, b, selected.cuda_stream)
|
| 168 |
+
return output
|
| 169 |
+
|
| 170 |
+
def close(self):
|
| 171 |
+
"""Release setup resources after the caller has waited for all uses."""
|
| 172 |
+
if not self.closed:
|
| 173 |
+
for handle in self.handles:
|
| 174 |
+
handle.close()
|
| 175 |
+
self.intermediate = self.runner = self._chain = None
|
| 176 |
+
self.closed = True
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
_CACHE = {}
|
| 182 |
+
_LOCK = threading.Lock()
|
build/torch-cuda/_b200/forward_paired_y.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Y-paired half-length FP64 XY FFT with reconstruction fused into the final Z load.
|
| 3 |
+
|
| 4 |
+
Both cuFFT stages consume the caller's packed layout or a mathematical FFT
|
| 5 |
+
intermediate. The final single-kernel plan writes scalar caller storage.
|
| 6 |
+
"""
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import threading
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from .scalar_input_inverse_cufft_support import _Handle, _lib, _check
|
| 13 |
+
from .paired_y_forward_lto import compile_callback
|
| 14 |
+
from .direct_dispatch import PreparedChain
|
| 15 |
+
from .half_forward_cufft_z_plan_safety import require_single_kernel
|
| 16 |
+
from .cuda_z_inverse_schedule import twiddles
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Plan:
|
| 20 |
+
def __init__(self, shape, dtype, direction='r2c', *, device=None, stream=None,
|
| 21 |
+
axis='z', patient=False, xt=False, pair_order='yx'):
|
| 22 |
+
self.shape = tuple(int(v) for v in shape)
|
| 23 |
+
if (len(self.shape) != 3 or min(self.shape[:2]) < 8
|
| 24 |
+
or self.shape[2] < 16 or self.shape[2] % 2 or self.shape[1] % 2):
|
| 25 |
+
raise ValueError('Expected (nz >= 8, ny >= 8, even nx >= 16)')
|
| 26 |
+
if dtype not in (torch.float32, torch.float64) or direction != 'r2c':
|
| 27 |
+
raise ValueError('Expected a float32 or float64 forward transform')
|
| 28 |
+
if axis != 'z':
|
| 29 |
+
raise ValueError('This decomposition requires Z as the last axis')
|
| 30 |
+
if pair_order not in ('yx', 'xy'):
|
| 31 |
+
raise ValueError('Expected yx or xy pair order')
|
| 32 |
+
self.pair_order = pair_order
|
| 33 |
+
self.dtype, self.direction = dtype, direction
|
| 34 |
+
self.itemsize = 8 if dtype == torch.float64 else 4
|
| 35 |
+
self.residues = 16 // self.itemsize
|
| 36 |
+
self.device = torch.device(device if device is not None else torch.cuda.current_device())
|
| 37 |
+
if self.device.type != 'cuda':
|
| 38 |
+
raise ValueError('A CUDA device is required')
|
| 39 |
+
if self.device.index is None:
|
| 40 |
+
self.device = torch.device('cuda', torch.cuda.current_device())
|
| 41 |
+
self.stream = stream if stream is not None else torch.cuda.current_stream(self.device)
|
| 42 |
+
if self.stream.device != self.device:
|
| 43 |
+
raise ValueError('Stream and plan device must match')
|
| 44 |
+
self.stream_ptr = self.stream.cuda_stream
|
| 45 |
+
self.variant = f'paired_y_{pair_order}_reconstruct_z_p{int(patient)}_xt{int(xt)}'
|
| 46 |
+
self.dispatch = 'prepared_native'
|
| 47 |
+
self.closed, self.handles, self.chains = False, [], []
|
| 48 |
+
nz, ny, nx = self.shape
|
| 49 |
+
rows, nf = nz * ny, nx // 2 + 1
|
| 50 |
+
first, other = (nz, ny) if axis == 'z' else (ny, nz)
|
| 51 |
+
self.input_shape, self.output_shape = self.shape, (nz, ny, nf, 2)
|
| 52 |
+
self.compiled_metadata = []
|
| 53 |
+
try:
|
| 54 |
+
with torch.cuda.device(self.device), torch.cuda.stream(self.stream):
|
| 55 |
+
first_shape = (ny // 2, nx) if pair_order == 'yx' else (nx, ny // 2)
|
| 56 |
+
self.intermediate = torch.empty((nz, *first_shape, 2), dtype=torch.float64, device=self.device)
|
| 57 |
+
self.table = torch.tensor(twiddles(ny), dtype=torch.float64, device=self.device)
|
| 58 |
+
cap = torch.cuda.get_device_capability(self.device)
|
| 59 |
+
arch = 10 * cap[0] + cap[1]
|
| 60 |
+
load_handles, store_handles = [], []
|
| 61 |
+
for shift in range(self.residues):
|
| 62 |
+
cb = compile_callback(dtype == torch.float64, 'pair', shift, arch, self.shape, axis, pair_order)
|
| 63 |
+
self.compiled_metadata.extend(item[3] for item in cb)
|
| 64 |
+
handle = _Handle(first_shape, 0x69, first_shape, 1, ny * (nx // 2),
|
| 65 |
+
first_shape, 1, ny * (nx // 2), nz, self.device, self.stream,
|
| 66 |
+
callback=cb, patient=patient)
|
| 67 |
+
self.handles.append(handle)
|
| 68 |
+
load_handles.append(handle)
|
| 69 |
+
cb = (compile_callback(dtype == torch.float64, 'reconstruct', 0, arch, self.shape, axis, pair_order)
|
| 70 |
+
+ compile_callback(dtype == torch.float64, 'store', shift, arch, self.shape, axis, pair_order))
|
| 71 |
+
self.compiled_metadata.extend(item[3] for item in cb)
|
| 72 |
+
handle = _Handle((first,), 0x69, (first,), other * nf, 1,
|
| 73 |
+
(first,), other * nf, 1, other * nf, self.device, self.stream,
|
| 74 |
+
callback=cb, patient=patient, xt=xt, caller_info=self.table.data_ptr())
|
| 75 |
+
self.handles.append(handle)
|
| 76 |
+
store_handles.append(handle)
|
| 77 |
+
for input_handle in load_handles:
|
| 78 |
+
for output_handle in store_handles:
|
| 79 |
+
self.chains.append(PreparedChain([
|
| 80 |
+
(_lib.cufftExecZ2Z, input_handle.value, 1, -1, 0, 2),
|
| 81 |
+
(_lib.cufftExecZ2Z, output_handle.value, 1, -1, 2, 1)
|
| 82 |
+
], self.intermediate.data_ptr()))
|
| 83 |
+
self.native_function = self.chains[0].function
|
| 84 |
+
self.native_pointers = tuple(c.pointer for c in self.chains)
|
| 85 |
+
# Every prepared callback is loaded and warmed, including all
|
| 86 |
+
# address combinations. Dummy buffers are setup-only storage.
|
| 87 |
+
dummy_in = torch.zeros(rows * nx + self.residues,
|
| 88 |
+
dtype=dtype, device=self.device)
|
| 89 |
+
# Oversized nominal double-complex storage is setup-only. Every final
|
| 90 |
+
# handle must prove it has a single kernel before caller-sized use.
|
| 91 |
+
dummy_out = torch.empty(nz * ny * nf * 16 + 16,
|
| 92 |
+
dtype=torch.uint8, device=self.device)
|
| 93 |
+
self.final_plan_checks = []
|
| 94 |
+
for handle in store_handles:
|
| 95 |
+
result = require_single_kernel(handle, self.intermediate, dummy_out,
|
| 96 |
+
self.device, self.stream)
|
| 97 |
+
self.final_plan_checks.append(result)
|
| 98 |
+
for i in range(self.residues):
|
| 99 |
+
for o in range(self.residues):
|
| 100 |
+
self.execute_ptr(dummy_in.data_ptr() + i * self.itemsize,
|
| 101 |
+
dummy_out.data_ptr() + o * self.itemsize,
|
| 102 |
+
self.stream_ptr)
|
| 103 |
+
self.stream.synchronize()
|
| 104 |
+
except BaseException:
|
| 105 |
+
for handle in reversed(self.handles):
|
| 106 |
+
handle.close()
|
| 107 |
+
self.closed = True
|
| 108 |
+
raise
|
| 109 |
+
|
| 110 |
+
@property
|
| 111 |
+
def workspace_bytes(self):
|
| 112 |
+
return sum(h.workspace_bytes for h in self.handles) + (self.intermediate.numel() + self.table.numel()) * 8
|
| 113 |
+
|
| 114 |
+
def execute_ptr(self, input_ptr, output_ptr, stream_ptr):
|
| 115 |
+
"""Enqueue using current scalar-aligned addresses on the bound stream."""
|
| 116 |
+
if self.closed:
|
| 117 |
+
raise RuntimeError('The plan is closed')
|
| 118 |
+
if int(stream_ptr) != self.stream_ptr:
|
| 119 |
+
raise ValueError('Create a separate plan for the supplied stream')
|
| 120 |
+
a, b = int(input_ptr), int(output_ptr)
|
| 121 |
+
if not a or not b or a == b:
|
| 122 |
+
raise ValueError('Distinct non-null caller pointers are required')
|
| 123 |
+
if a % self.itemsize or b % self.itemsize:
|
| 124 |
+
raise ValueError('Caller buffers must be scalar aligned')
|
| 125 |
+
i, o = (a & 15) // self.itemsize, (b & 15) // self.itemsize
|
| 126 |
+
_check(self.native_function(self.native_pointers[i * self.residues + o],
|
| 127 |
+
a - (a & 15), b - (b & 15)))
|
| 128 |
+
|
| 129 |
+
def execute(self, input, output, stream=None):
|
| 130 |
+
for tensor, shape in ((input, self.input_shape), (output, self.output_shape)):
|
| 131 |
+
if (tuple(tensor.shape) != shape or tensor.dtype != self.dtype
|
| 132 |
+
or tensor.device != self.device or not tensor.is_contiguous()):
|
| 133 |
+
raise ValueError('Buffer metadata does not match the plan')
|
| 134 |
+
a, b = input.data_ptr(), output.data_ptr()
|
| 135 |
+
na, nb = input.numel() * self.itemsize, output.numel() * self.itemsize
|
| 136 |
+
if a < b + nb and b < a + na:
|
| 137 |
+
raise ValueError('Input and output must not overlap')
|
| 138 |
+
self.execute_ptr(a, b, (self.stream if stream is None else stream).cuda_stream)
|
| 139 |
+
return output
|
| 140 |
+
|
| 141 |
+
def close(self):
|
| 142 |
+
"""The caller must complete outstanding work before closing the plan."""
|
| 143 |
+
if not self.closed:
|
| 144 |
+
for handle in reversed(self.handles):
|
| 145 |
+
handle.close()
|
| 146 |
+
self.intermediate = self.table = self.chains = self.native_pointers = None
|
| 147 |
+
self.closed = True
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
_CACHE = {}
|
| 153 |
+
_LOCK = threading.Lock()
|
build/torch-cuda/_b200/forward_scalar.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Prepare scalar-safe FP64 forward access alongside a measured aligned plan.
|
| 3 |
+
|
| 4 |
+
The scalar route reads even/odd caller samples through prepared cuFFT load
|
| 5 |
+
callbacks. Its exact-size half-length complex FFT intermediate contains
|
| 6 |
+
transformed data. Reconstruction writes the exact caller packed output,
|
| 7 |
+
using alignment-aware scalar/vector stores without a copy or repack.
|
| 8 |
+
"""
|
| 9 |
+
from .paired_vector_store_forward_backend import Plan as ScalarPlan
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Plan:
|
| 13 |
+
def __init__(self, aligned):
|
| 14 |
+
self._aligned, self._scalar = aligned, None
|
| 15 |
+
self.closed = False
|
| 16 |
+
try:
|
| 17 |
+
for name in ('shape', 'dtype', 'direction', 'device', 'stream',
|
| 18 |
+
'stream_ptr', 'input_shape', 'output_shape'):
|
| 19 |
+
setattr(self, name, getattr(aligned, name))
|
| 20 |
+
self.variant = 'scalar_forward_' + aligned.variant
|
| 21 |
+
self._scalar = ScalarPlan(self.shape, self.dtype, self.direction,
|
| 22 |
+
device=self.device, stream=self.stream, block=256, patient=False)
|
| 23 |
+
except BaseException:
|
| 24 |
+
aligned.close()
|
| 25 |
+
self.closed = True
|
| 26 |
+
raise
|
| 27 |
+
|
| 28 |
+
@property
|
| 29 |
+
def workspace_bytes(self):
|
| 30 |
+
return self._aligned.workspace_bytes + self._scalar.workspace_bytes
|
| 31 |
+
|
| 32 |
+
def execute_ptr(self, input_ptr, output_ptr, stream_ptr):
|
| 33 |
+
"""Select an already prepared route by current caller addresses only."""
|
| 34 |
+
if self.closed:
|
| 35 |
+
raise RuntimeError('The plan is closed')
|
| 36 |
+
if int(stream_ptr) != self.stream_ptr:
|
| 37 |
+
raise ValueError('Create a separate plan for the supplied stream')
|
| 38 |
+
if not input_ptr or not output_ptr or int(input_ptr) == int(output_ptr):
|
| 39 |
+
raise ValueError('Distinct non-null caller pointers are required')
|
| 40 |
+
if int(input_ptr) % 8 or int(output_ptr) % 8:
|
| 41 |
+
raise ValueError('Caller FP64 buffers must be scalar aligned')
|
| 42 |
+
selected = self._scalar if (int(input_ptr) | int(output_ptr)) & 8 else self._aligned
|
| 43 |
+
selected.execute_ptr(input_ptr, output_ptr, stream_ptr)
|
| 44 |
+
|
| 45 |
+
def execute(self, input, output, stream=None):
|
| 46 |
+
for tensor, shape in ((input, self.input_shape), (output, self.output_shape)):
|
| 47 |
+
if (tuple(tensor.shape) != shape or tensor.dtype != self.dtype or
|
| 48 |
+
tensor.device != self.device or not tensor.is_contiguous()):
|
| 49 |
+
raise ValueError('Buffer metadata does not match the plan')
|
| 50 |
+
a, b = input.data_ptr(), output.data_ptr()
|
| 51 |
+
if a < b + output.numel() * 8 and b < a + input.numel() * 8:
|
| 52 |
+
raise ValueError('Input and output must not overlap')
|
| 53 |
+
self.execute_ptr(a, b, (self.stream if stream is None else stream).cuda_stream)
|
| 54 |
+
return output
|
| 55 |
+
|
| 56 |
+
def close(self):
|
| 57 |
+
"""Release prepared resources after the caller waits for queued work."""
|
| 58 |
+
if not self.closed:
|
| 59 |
+
try:
|
| 60 |
+
self._scalar.close()
|
| 61 |
+
finally:
|
| 62 |
+
self._aligned.close()
|
| 63 |
+
self.closed = True
|
build/torch-cuda/_b200/forward_small.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Precompiled two-pass XY/Z FFT with FP64 arithmetic and caller-precision boundaries.
|
| 3 |
+
|
| 4 |
+
This is an experimental implementation. CPU interpreter validation is not
|
| 5 |
+
GPU validation; do not promote it without the full frozen and deployment
|
| 6 |
+
checks. Execution can submit all stages through one prepared native chain.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import threading
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import triton
|
| 14 |
+
|
| 15 |
+
from .wide_native_kernels import complex_axis, packed_real_axis, real_axis, real_xy
|
| 16 |
+
from .wide_native_schedule import factors, stage_sizes, twiddles
|
| 17 |
+
from .grouped_dif_inverse_native_dispatch import PreparedChain, kernel_descriptor
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Plan:
|
| 21 |
+
def __init__(self, shape, dtype, direction, *, device=None, stream=None,
|
| 22 |
+
rows_per_block=4, columns_per_block=8, num_warps=4,
|
| 23 |
+
real_strategy='fused_xy', dispatch='native'):
|
| 24 |
+
if dispatch not in ('native', 'python'):
|
| 25 |
+
raise ValueError('dispatch must be native or python')
|
| 26 |
+
self.native = None
|
| 27 |
+
self.descriptors = []
|
| 28 |
+
self.compiled_kernels = []
|
| 29 |
+
self.shape = tuple(int(n) for n in shape)
|
| 30 |
+
if len(self.shape) != 3 or min(self.shape[:2]) < 8 or self.shape[2] < 16:
|
| 31 |
+
raise ValueError('Expected (nz >= 8, ny >= 8, nx >= 16)')
|
| 32 |
+
if dtype not in (torch.float32, torch.float64):
|
| 33 |
+
raise TypeError('Only float32 and float64 are supported')
|
| 34 |
+
if direction not in ('r2c', 'c2r'):
|
| 35 |
+
raise ValueError('direction must be r2c or c2r')
|
| 36 |
+
if real_strategy != 'fused_xy':
|
| 37 |
+
raise ValueError('This wide variant requires the fused_xy strategy')
|
| 38 |
+
if direction == 'c2r' and self.shape[2] % 2:
|
| 39 |
+
raise ValueError('The inverse request infers even nx from nf')
|
| 40 |
+
for length in self.shape:
|
| 41 |
+
factors(length)
|
| 42 |
+
for block in (rows_per_block, columns_per_block, num_warps):
|
| 43 |
+
if block < 1 or block & (block - 1):
|
| 44 |
+
raise ValueError('Block sizes and warp count must be powers of two')
|
| 45 |
+
self.dtype, self.direction = dtype, direction
|
| 46 |
+
self.device = torch.device(device if device is not None else torch.cuda.current_device())
|
| 47 |
+
if self.device.type != 'cuda':
|
| 48 |
+
raise ValueError('A CUDA device is required')
|
| 49 |
+
if self.device.index is None:
|
| 50 |
+
self.device = torch.device('cuda', torch.cuda.current_device())
|
| 51 |
+
self.stream = stream if stream is not None else torch.cuda.current_stream(self.device)
|
| 52 |
+
if self.stream.device != self.device:
|
| 53 |
+
raise ValueError('The supplied stream is on a different device')
|
| 54 |
+
self.stream_ptr = self.stream.cuda_stream
|
| 55 |
+
# Odd forward lengths use the general real-axis network. All requested
|
| 56 |
+
# workloads have even nx and directly use the selected real strategy.
|
| 57 |
+
self.real_strategy = (real_strategy if self.shape[2] % 2 == 0 or real_strategy == 'fused_xy' else 'full')
|
| 58 |
+
if self.real_strategy == 'fused_xy' and (triton.next_power_of_2(self.shape[1]) * triton.next_power_of_2(self.shape[2]) > 16384):
|
| 59 |
+
raise ValueError('The fused experimental plane is limited to 16384 register slots')
|
| 60 |
+
self.variant = f'{self.real_strategy}_r{rows_per_block}_c{columns_per_block}_w{num_warps}'
|
| 61 |
+
self.dispatch = dispatch
|
| 62 |
+
self.closed = False
|
| 63 |
+
nz, ny, nx = self.shape
|
| 64 |
+
nf = nx // 2 + 1
|
| 65 |
+
packed = (nz, ny, nf, 2)
|
| 66 |
+
inverse = direction == 'c2r'
|
| 67 |
+
self.input_shape, self.output_shape = (packed, self.shape) if inverse else (self.shape, packed)
|
| 68 |
+
self.launches = []
|
| 69 |
+
self.compiled_metadata = []
|
| 70 |
+
self.intermediate = None
|
| 71 |
+
self.tables = {}
|
| 72 |
+
with torch.cuda.device(self.device), torch.cuda.stream(self.stream):
|
| 73 |
+
for length in set(self.shape):
|
| 74 |
+
self.tables[length] = torch.tensor(twiddles(length), dtype=torch.float64, device=self.device)
|
| 75 |
+
self.intermediate = torch.empty(packed, dtype=torch.float64, device=self.device)
|
| 76 |
+
dummy_in = torch.zeros(self.input_shape, dtype=dtype, device=self.device)
|
| 77 |
+
dummy_out = torch.empty(self.output_shape, dtype=dtype, device=self.device)
|
| 78 |
+
buffers = (dummy_in, dummy_out, self.intermediate)
|
| 79 |
+
|
| 80 |
+
def add(kernel, grid, input_slot, output_slot, length, constants):
|
| 81 |
+
tables = tuple(self.tables[n] for n in (length if isinstance(length, tuple) else (length,)))
|
| 82 |
+
stage_warps = num_warps if kernel == real_xy else 4
|
| 83 |
+
compiled = kernel[grid](buffers[input_slot], buffers[output_slot], *tables,
|
| 84 |
+
*constants, num_warps=stage_warps)
|
| 85 |
+
if compiled.metadata.global_scratch_size or compiled.metadata.profile_scratch_size:
|
| 86 |
+
raise RuntimeError('Execution scratch allocation is not allowed')
|
| 87 |
+
self.compiled_kernels.append(compiled)
|
| 88 |
+
if dispatch == 'native':
|
| 89 |
+
self.descriptors.append(kernel_descriptor(
|
| 90 |
+
compiled, grid, input_slot, output_slot,
|
| 91 |
+
tuple(t.data_ptr() for t in tables)))
|
| 92 |
+
# Construct and initialize the low-level launcher during setup.
|
| 93 |
+
runner = compiled[grid]
|
| 94 |
+
self.launches.append((runner, input_slot, output_slot, tuple(t.data_ptr() for t in tables), constants))
|
| 95 |
+
self.compiled_metadata.append(dict(name=compiled.name, hash=compiled.hash,
|
| 96 |
+
shared=compiled.metadata.shared,
|
| 97 |
+
registers=compiled.n_regs,
|
| 98 |
+
spills=compiled.n_spills,
|
| 99 |
+
grid=grid, num_warps=stage_warps))
|
| 100 |
+
|
| 101 |
+
def real(input_slot, output_slot):
|
| 102 |
+
length = nx // 2 if self.real_strategy == 'half' else nx
|
| 103 |
+
kernel = packed_real_axis if self.real_strategy == 'half' else real_axis
|
| 104 |
+
radices = factors(length)
|
| 105 |
+
constants = (nz * ny, nx, triton.next_power_of_2(length), rows_per_block,
|
| 106 |
+
inverse, radices, stage_sizes(radices))
|
| 107 |
+
add(kernel, (triton.cdiv(nz * ny, rows_per_block), 1, 1),
|
| 108 |
+
input_slot, output_slot, nx, constants)
|
| 109 |
+
|
| 110 |
+
def complex(input_slot, output_slot, length, inner, outer):
|
| 111 |
+
radices = factors(length)
|
| 112 |
+
constants = (length, inner, triton.next_power_of_2(length), columns_per_block,
|
| 113 |
+
inverse, radices, stage_sizes(radices))
|
| 114 |
+
add(complex_axis, (triton.cdiv(inner, columns_per_block), outer, 1),
|
| 115 |
+
input_slot, output_slot, length, constants)
|
| 116 |
+
|
| 117 |
+
def xy(input_slot, output_slot):
|
| 118 |
+
rx, ry = factors(nx), factors(ny)
|
| 119 |
+
constants = (ny, nx, triton.next_power_of_2(ny), triton.next_power_of_2(nx),
|
| 120 |
+
inverse, rx, stage_sizes(rx), ry, stage_sizes(ry))
|
| 121 |
+
add(real_xy, (nz, 1, 1), input_slot, output_slot, (nx, ny), constants)
|
| 122 |
+
|
| 123 |
+
if self.real_strategy == 'fused_xy':
|
| 124 |
+
if inverse:
|
| 125 |
+
complex(0, 2, nz, ny * nf, 1)
|
| 126 |
+
xy(2, 1)
|
| 127 |
+
else:
|
| 128 |
+
xy(0, 2)
|
| 129 |
+
complex(2, 1, nz, ny * nf, 1)
|
| 130 |
+
elif inverse:
|
| 131 |
+
complex(0, 2, nz, ny * nf, 1)
|
| 132 |
+
complex(2, 2, ny, nf, nz)
|
| 133 |
+
real(2, 1)
|
| 134 |
+
else:
|
| 135 |
+
real(0, 1)
|
| 136 |
+
complex(1, 1, ny, nf, nz)
|
| 137 |
+
complex(1, 1, nz, ny * nf, 1)
|
| 138 |
+
self.tmp_ptr = self.intermediate.data_ptr()
|
| 139 |
+
if dispatch == 'native':
|
| 140 |
+
self.native = PreparedChain(self.descriptors, self.tmp_ptr, self.stream_ptr)
|
| 141 |
+
self.execute_ptr(dummy_in.data_ptr(), dummy_out.data_ptr(), self.stream_ptr)
|
| 142 |
+
self.stream.synchronize()
|
| 143 |
+
|
| 144 |
+
@property
|
| 145 |
+
def workspace_bytes(self):
|
| 146 |
+
return sum(t.numel() * t.element_size() for t in self.tables.values()) + (
|
| 147 |
+
self.intermediate.numel() * self.intermediate.element_size()
|
| 148 |
+
if self.intermediate is not None else 0)
|
| 149 |
+
|
| 150 |
+
def execute_ptr(self, input_ptr, output_ptr, stream_ptr):
|
| 151 |
+
"""Enqueue the prepared stages. Caller manages device and buffer lifetime."""
|
| 152 |
+
if self.closed:
|
| 153 |
+
raise RuntimeError('The plan is closed')
|
| 154 |
+
if int(stream_ptr) != self.stream_ptr:
|
| 155 |
+
raise ValueError('Create a separate plan for the supplied stream')
|
| 156 |
+
if not input_ptr or not output_ptr or int(input_ptr) == int(output_ptr):
|
| 157 |
+
raise ValueError('Distinct non-null input/output pointers are required')
|
| 158 |
+
if self.native is not None:
|
| 159 |
+
result = self.native.execute(input_ptr, output_ptr)
|
| 160 |
+
if result:
|
| 161 |
+
raise RuntimeError(f'cuLaunchKernel failed with CUDA driver error {result}')
|
| 162 |
+
return
|
| 163 |
+
pointers = (input_ptr, output_ptr, self.tmp_ptr)
|
| 164 |
+
for runner, src, dst, table_ptrs, constants in self.launches:
|
| 165 |
+
runner(pointers[src], pointers[dst], *table_ptrs, *constants, stream=self.stream_ptr)
|
| 166 |
+
|
| 167 |
+
def execute(self, input, output, stream=None):
|
| 168 |
+
"""Validate exact packed tensor buffers, enqueue, and return output."""
|
| 169 |
+
for tensor, shape in ((input, self.input_shape), (output, self.output_shape)):
|
| 170 |
+
if (tuple(tensor.shape) != shape or tensor.dtype != self.dtype
|
| 171 |
+
or tensor.device != self.device or not tensor.is_contiguous()):
|
| 172 |
+
raise ValueError('Buffer metadata does not match the plan')
|
| 173 |
+
a, b = input.data_ptr(), output.data_ptr()
|
| 174 |
+
na, nb = input.numel() * input.element_size(), output.numel() * output.element_size()
|
| 175 |
+
if a < b + nb and b < a + na:
|
| 176 |
+
raise ValueError('Input and output buffers must not overlap')
|
| 177 |
+
selected = self.stream if stream is None else stream
|
| 178 |
+
self.execute_ptr(a, b, selected.cuda_stream)
|
| 179 |
+
return output
|
| 180 |
+
|
| 181 |
+
def close(self):
|
| 182 |
+
"""Release resources only after the caller has waited for all uses."""
|
| 183 |
+
self.closed = True
|
| 184 |
+
self.native = None
|
| 185 |
+
self.descriptors.clear()
|
| 186 |
+
self.compiled_kernels.clear()
|
| 187 |
+
self.launches.clear()
|
| 188 |
+
self.tables.clear()
|
| 189 |
+
self.intermediate = None
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
_CACHE = {}
|
| 195 |
+
_LOCK = threading.Lock()
|