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.
Files changed (50) hide show
  1. README.md +113 -10
  2. build/torch-cuda/LICENSE.txt +21 -0
  3. build/torch-cuda/__init__.py +60 -0
  4. build/torch-cuda/_b200/__init__.py +151 -0
  5. build/torch-cuda/_b200/c2r_cuda_z_inverse_cuda_kernel.py +63 -0
  6. build/torch-cuda/_b200/c2r_cuda_z_inverse_fft_axis.cu +128 -0
  7. build/torch-cuda/_b200/c2r_dense_half_inverse_wide_redirect.cu +85 -0
  8. build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_cuda_kernel.py +63 -0
  9. build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_fft_axis.cu +128 -0
  10. build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_lto.py +110 -0
  11. build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_native_dispatch.py +100 -0
  12. build/torch-cuda/_b200/c2r_f64_cuda_z_inverse_wide_redirect.cu +65 -0
  13. build/torch-cuda/_b200/c2r_f64_dense_half_inverse_lto.py +110 -0
  14. build/torch-cuda/_b200/c2r_f64_dense_half_inverse_wide_redirect.cu +67 -0
  15. build/torch-cuda/_b200/c2r_f64_half_vector_inverse_wide_redirect.cu +65 -0
  16. build/torch-cuda/_b200/c2r_f64_narrow_index_inverse_scalar_output_reconstruct.cu +45 -0
  17. build/torch-cuda/_b200/c2r_f64_paired_x_inverse_cuda_kernel.py +64 -0
  18. build/torch-cuda/_b200/c2r_f64_paired_x_inverse_fft_axis.cu +270 -0
  19. build/torch-cuda/_b200/c2r_f64_paired_x_inverse_wide_redirect.cu +29 -0
  20. build/torch-cuda/_b200/c2r_f64_paired_y_direct_inverse_lto.py +116 -0
  21. build/torch-cuda/_b200/c2r_f64_paired_y_direct_inverse_wide_redirect.cu +114 -0
  22. build/torch-cuda/_b200/c2r_f64_partial_y_inverse_wide_redirect.cu +114 -0
  23. build/torch-cuda/_b200/c2r_f64_scalar_input_inverse_scalar_io.cu +26 -0
  24. build/torch-cuda/_b200/c2r_paired_y_direct_inverse_wide_redirect.cu +109 -0
  25. build/torch-cuda/_b200/c2r_paired_y_inverse_wide_redirect.cu +87 -0
  26. build/torch-cuda/_b200/c2r_pitched_half_inverse_wide_redirect.cu +86 -0
  27. build/torch-cuda/_b200/cuda_kernel.py +64 -0
  28. build/torch-cuda/_b200/cuda_twiddle_forward_lto.py +113 -0
  29. build/torch-cuda/_b200/cuda_twiddle_forward_native_dispatch.c +42 -0
  30. build/torch-cuda/_b200/cuda_twiddle_forward_native_dispatch.py +101 -0
  31. build/torch-cuda/_b200/cuda_z_inverse_cufft_support.py +105 -0
  32. build/torch-cuda/_b200/cuda_z_inverse_lto.py +110 -0
  33. build/torch-cuda/_b200/cuda_z_inverse_native_dispatch.c +42 -0
  34. build/torch-cuda/_b200/cuda_z_inverse_native_dispatch.py +101 -0
  35. build/torch-cuda/_b200/cuda_z_inverse_schedule.py +71 -0
  36. build/torch-cuda/_b200/dense_half_inverse_abi.py +30 -0
  37. build/torch-cuda/_b200/dense_half_inverse_kernels.py +10 -0
  38. build/torch-cuda/_b200/dense_half_inverse_lto.py +110 -0
  39. build/torch-cuda/_b200/dense_half_inverse_native_dispatch.c +43 -0
  40. build/torch-cuda/_b200/dense_half_inverse_native_dispatch.py +104 -0
  41. build/torch-cuda/_b200/dense_half_inverse_schedule.py +73 -0
  42. build/torch-cuda/_b200/direct_dispatch.c +53 -0
  43. build/torch-cuda/_b200/direct_dispatch.py +98 -0
  44. build/torch-cuda/_b200/fft_axis.cu +169 -0
  45. build/torch-cuda/_b200/forward_cuda_f32.py +151 -0
  46. build/torch-cuda/_b200/forward_half.py +149 -0
  47. build/torch-cuda/_b200/forward_native.py +182 -0
  48. build/torch-cuda/_b200/forward_paired_y.py +153 -0
  49. build/torch-cuda/_b200/forward_scalar.py +63 -0
  50. 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", version=1, trust_remote_code=True)
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 a tiny y-x
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
- - Tuned on the eight grids below and checked on dozens of others per kernel,
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
- ## Accuracy
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
- ## Performance
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
- ## Source and licensing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()