DongyunZou commited on
Commit
56c2c8d
·
verified ·
1 Parent(s): 07e58d6

Uploaded using `kernel-builder`.

Browse files
README.md CHANGED
@@ -65,6 +65,10 @@ spectrum = launch() # ...then transform its current contents
65
  fp64 r2c has no two-phase form; its `launch()` calls `r2c(x)`.
66
  - Calls on different CUDA streams may run concurrently, including calls of the
67
  same shape.
 
 
 
 
68
  - The first call for a new shape compiles its kernels (CuTe DSL JIT), typically
69
  in 1 to 15 s, up to about 4 minutes for a very long z axis over a tiny y-x
70
  plane (such as 4096x8x16). Make that first call outside CUDA graph capture;
@@ -168,6 +172,17 @@ Every sampled grid is faster than cuFFT's 3D plan and than `torch.fft`. Large
168
  prime extents gain most: 251x251x254 takes 0.25 ms in fp32 r2c (cuFFT 0.59 ms)
169
  and 2.6 ms in fp64 c2r (cuFFT 4.9 ms).
170
 
 
 
 
 
 
 
 
 
 
 
 
171
  ## Changes
172
 
173
  Updated on 2026-10-24 with fixes found by sweeping every planner signature over
@@ -213,6 +228,11 @@ two-stream checks):
213
  - fp64 r2c on data with a large mean (randn + 1e3) missed atol 1e-6 on 21 of 100
214
  sample grids of 1M points and more, where cuFFT passed; now at most 1.07x
215
  cuFFT's error.
 
 
 
 
 
216
 
217
  The eight grids keep their plans and their outputs are bitwise unchanged.
218
 
@@ -224,7 +244,7 @@ built from the requests in [NVlabs/kda#7](https://github.com/NVlabs/kda/pull/7),
224
  and accepted by the KDA judge. Published from commit `e539498` of
225
  `humanfia/kda-kernels-internal`, directory `rtx5090/fft3d/` (now
226
  `sm120/fft3d/`), which also holds its tests and the fixes made after the
227
- judge's checks; updated from commit `f8b3c93` (see [Changes](#changes)).
228
  MIT-licensed.
229
 
230
  ## Available functions
 
65
  fp64 r2c has no two-phase form; its `launch()` calls `r2c(x)`.
66
  - Calls on different CUDA streams may run concurrently, including calls of the
67
  same shape.
68
+ - A call costs about 13 to 17 us of host time (TVM-FFI launches on the current
69
+ stream). Timed per call as the requests' benchmark does (flashinfer-bench:
70
+ synchronize, event, call, event), every kernel is faster than `torch.fft` on
71
+ every grid tried, the smallest included (see Performance).
72
  - The first call for a new shape compiles its kernels (CuTe DSL JIT), typically
73
  in 1 to 15 s, up to about 4 minutes for a very long z axis over a tiny y-x
74
  plane (such as 4096x8x16). Make that first call outside CUDA graph capture;
 
172
  prime extents gain most: 251x251x254 takes 0.25 ms in fp32 r2c (cuFFT 0.59 ms)
173
  and 2.6 ms in fp64 c2r (cuFFT 4.9 ms).
174
 
175
+ Timed per call the way the requests' benchmark (flashinfer-bench 0.1.2) does it,
176
+ on fresh copies of the input with a cleared L2 and the host's launch work
177
+ included, against the requests' baseline (`torch.fft`):
178
+
179
+ | | eight grids (geomean) | 8x8x16 to 48x40x36 (slowest) |
180
+ |---|---:|---:|
181
+ | r2c fp32 | 1.91x | 1.54x |
182
+ | c2r fp32 | 1.72x | 1.11x |
183
+ | r2c fp64 | 2.20x | 1.29x |
184
+ | c2r fp64 | 2.23x | 1.12x |
185
+
186
  ## Changes
187
 
188
  Updated on 2026-10-24 with fixes found by sweeping every planner signature over
 
228
  - fp64 r2c on data with a large mean (randn + 1e3) missed atol 1e-6 on 21 of 100
229
  sample grids of 1M points and more, where cuFFT passed; now at most 1.07x
230
  cuFFT's error.
231
+ - Per-call host time: the fp64 kernels spent about 40 to 50 us of host time per
232
+ call (0.48x to 0.69x of `torch.fft` on small grids when timed per call, as the
233
+ requests' benchmark does). All kernels now launch through TVM-FFI, at about
234
+ 13 to 17 us; outputs are bitwise unchanged. fp64 c2r now transforms an input
235
+ that is not 16-byte aligned from an aligned copy instead of raising.
236
 
237
  The eight grids keep their plans and their outputs are bitwise unchanged.
238
 
 
244
  and accepted by the KDA judge. Published from commit `e539498` of
245
  `humanfia/kda-kernels-internal`, directory `rtx5090/fft3d/` (now
246
  `sm120/fft3d/`), which also holds its tests and the fixes made after the
247
+ judge's checks; updated from commit `b0811f8` (see [Changes](#changes)).
248
  MIT-licensed.
249
 
250
  ## Available functions
build/torch-cuda/__init__.py CHANGED
@@ -32,16 +32,28 @@ _PACKAGES = {
32
  }
33
 
34
 
 
 
 
35
  def _kernel(direction, tensor):
36
- package = _PACKAGES.get((direction, tensor.dtype))
37
- if package is None:
38
- raise TypeError(f"{direction} takes float32 or float64, not {tensor.dtype}")
39
- return importlib.import_module(f".{package}", __name__)
 
 
 
 
 
 
40
 
41
 
42
  def _on(tensor):
43
- """The input's device, current while a call runs (the kernels launch on the current device)."""
44
- return torch.cuda.device(tensor.device) if tensor.is_cuda else contextlib.nullcontext()
 
 
 
45
 
46
 
47
  def r2c(x):
 
32
  }
33
 
34
 
35
+ _MODULES = {}
36
+
37
+
38
  def _kernel(direction, tensor):
39
+ module = _MODULES.get((direction, tensor.dtype))
40
+ if module is None:
41
+ package = _PACKAGES.get((direction, tensor.dtype))
42
+ if package is None:
43
+ raise TypeError(f"{direction} takes float32 or float64, not {tensor.dtype}")
44
+ module = _MODULES[direction, tensor.dtype] = importlib.import_module(f".{package}", __name__)
45
+ return module
46
+
47
+
48
+ _NULL = contextlib.nullcontext()
49
 
50
 
51
  def _on(tensor):
52
+ """The input's device, current while a call runs (the kernels launch on the current device). A no-op
53
+ when it already is, which is the common case; switching devices costs a few microseconds per call."""
54
+ if not tensor.is_cuda or tensor.device.index == torch.cuda.current_device():
55
+ return _NULL
56
+ return torch.cuda.device(tensor.device)
57
 
58
 
59
  def r2c(x):
build/torch-cuda/_c2r_f32/kernel.py CHANGED
@@ -316,4 +316,22 @@ def prepare(input):
316
 
317
  def run(input):
318
  """Return the unnormalized 3D C2R inverse FFT of ``input`` as a new [nz, ny, nx] tensor."""
319
- return prepare(input)()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
  def run(input):
318
  """Return the unnormalized 3D C2R inverse FFT of ``input`` as a new [nz, ny, nx] tensor."""
319
+ dev = input.device
320
+ if (
321
+ dev.type != "cuda"
322
+ or dev.index != torch.cuda.current_device()
323
+ or not input.is_contiguous()
324
+ or input.data_ptr() % 16
325
+ ):
326
+ return prepare(input)()
327
+ # the common case without building a launch closure: per-call overhead counts on small grids
328
+ _check(input)
329
+ nz, ny, nf, _ = input.shape
330
+ nx = 2 * (nf - 1)
331
+ plan = _get_plan(nz, ny, nx, dev)
332
+ out = torch.empty((nz, ny, nx), dtype=torch.float32, device=dev)
333
+ if hasattr(plan, "nyq_numel"):
334
+ plan(input.view(-1), out.view(-1), torch.empty(plan.nyq_numel, dtype=torch.float32, device=dev))
335
+ else:
336
+ plan(input.view(-1), out.view(-1))
337
+ return out
build/torch-cuda/_c2r_f64/kernel.py CHANGED
@@ -42,7 +42,6 @@ import torch
42
  from cutlass import cute
43
  from cutlass._mlir import ir
44
  from cutlass._mlir.dialects import llvm
45
- from cutlass.cute.runtime import from_dlpack
46
  from cutlass.cutlass_dsl import Float64, Int32, T, dsl_user_op
47
 
48
  from . import bigprime as BP
@@ -2841,8 +2840,9 @@ def make_tables_2d(nz, ny, M, yzd):
2841
  _PLANS = {}
2842
 
2843
 
2844
- def _stream():
2845
- return cuda.CUstream(torch.cuda.current_stream().cuda_stream)
 
2846
 
2847
 
2848
  class _Plan:
@@ -2851,8 +2851,6 @@ class _Plan:
2851
  self.shape = (nz, ny, nf)
2852
  self.M = M
2853
  self.device = device
2854
- x0 = torch.empty(nz * ny * nf * 2, dtype=torch.float64, device=device)
2855
- o0 = torch.empty(nz * ny * M * 2, dtype=torch.float64, device=device)
2856
  axes = {"z": (nz, 1, ny * M), "y": (ny, nz, M), "x": (M, nz * ny, 1)} # (n, A, inner)
2857
  planned = {ax: _has_plan(n) for ax, (n, _, _) in axes.items()}
2858
  self.direct = not all(planned[ax] or BP.supported(n) for ax, (n, _, _) in axes.items())
@@ -2877,38 +2875,41 @@ class _Plan:
2877
  tables = make_tables_2d(nz, ny, M, YZ2D(ny, yz["A"], yz["rc"], yz.get("N1", 1)))
2878
  else:
2879
  tables = torch.cat([make_tables(nz, ny, M)] + [cfg.table() for cfg in big.values()])
2880
- self.tables = tables.to(device)
2881
- self.tw_t = from_dlpack(self.tables, assumed_align=16)
2882
- args = [from_dlpack(x0, assumed_align=16), from_dlpack(o0, assumed_align=16), self.tw_t]
2883
  if self.direct:
2884
- args.append(from_dlpack(o0, assumed_align=16)) # workspace
2885
- self.fn = cute.compile(launch, *args, _stream())
 
 
 
2886
 
2887
  def bind(self, xin, out):
2888
- xt = from_dlpack(xin.view(-1), assumed_align=16)
2889
- ot = from_dlpack(out.view(-1), assumed_align=16)
2890
- fn, tw = self.fn, self.tw_t
2891
  if self.direct:
2892
- ws = torch.empty_like(out).view(-1)
2893
- wt = from_dlpack(ws, assumed_align=16)
2894
 
2895
  def go():
2896
- fn(xt, ot, tw, wt, _stream())
2897
 
2898
  go.ws = ws
2899
  return go
2900
 
2901
  def go():
2902
- fn(xt, ot, tw, _stream())
2903
 
2904
  return go
2905
 
2906
  def run_into(self, xin, out):
2907
- self.bind(xin, out)()
 
 
 
2908
 
2909
 
2910
  def _get_plan(nz, ny, nf, device):
2911
- key = (nz, ny, nf, str(device))
2912
  p = _PLANS.get(key)
2913
  if p is None:
2914
  p = _Plan(nz, ny, nf, device)
@@ -2931,6 +2932,8 @@ def run(input):
2931
  _check(x)
2932
  nz, ny, nf, _ = x.shape
2933
  plan = _get_plan(nz, ny, nf, x.device)
 
 
2934
  out = torch.empty((nz, ny, 2 * (nf - 1)), dtype=torch.float64, device=x.device)
2935
  plan.run_into(x, out)
2936
  return out
@@ -2941,6 +2944,8 @@ def prepare(input):
2941
  _check(input)
2942
  if not input.is_contiguous():
2943
  raise ValueError("prepare binds the input buffer itself, so it must be contiguous")
 
 
2944
  nz, ny, nf, _ = input.shape
2945
  plan = _get_plan(nz, ny, nf, input.device)
2946
  out = torch.empty((nz, ny, 2 * (nf - 1)), dtype=torch.float64, device=input.device)
 
42
  from cutlass import cute
43
  from cutlass._mlir import ir
44
  from cutlass._mlir.dialects import llvm
 
45
  from cutlass.cutlass_dsl import Float64, Int32, T, dsl_user_op
46
 
47
  from . import bigprime as BP
 
2840
  _PLANS = {}
2841
 
2842
 
2843
+ def _fake(n):
2844
+ """Compile-time stand-in for a flat float64 buffer of n elements, 16-byte aligned (the shape stays static)."""
2845
+ return cute.runtime.make_fake_compact_tensor(cutlass.Float64, (n,), assumed_align=16)
2846
 
2847
 
2848
  class _Plan:
 
2851
  self.shape = (nz, ny, nf)
2852
  self.M = M
2853
  self.device = device
 
 
2854
  axes = {"z": (nz, 1, ny * M), "y": (ny, nz, M), "x": (M, nz * ny, 1)} # (n, A, inner)
2855
  planned = {ax: _has_plan(n) for ax, (n, _, _) in axes.items()}
2856
  self.direct = not all(planned[ax] or BP.supported(n) for ax, (n, _, _) in axes.items())
 
2875
  tables = make_tables_2d(nz, ny, M, YZ2D(ny, yz["A"], yz["rc"], yz.get("N1", 1)))
2876
  else:
2877
  tables = torch.cat([make_tables(nz, ny, M)] + [cfg.table() for cfg in big.values()])
2878
+ self.tables = tables.to(device).contiguous()
2879
+ nx_, no_ = nz * ny * nf * 2, nz * ny * M * 2
2880
+ args = [_fake(nx_), _fake(no_), _fake(self.tables.numel())]
2881
  if self.direct:
2882
+ args.append(_fake(no_)) # workspace
2883
+ # TVM-FFI: called with torch tensors directly, on the caller's current stream, so a call costs a
2884
+ # few microseconds of host time (the shapes and the 16-byte alignment are checked at the call)
2885
+ stream = cute.runtime.make_fake_stream(use_tvm_ffi_env_stream=True)
2886
+ self.fn = cute.compile(launch, *args, stream, options="--enable-tvm-ffi")
2887
 
2888
  def bind(self, xin, out):
2889
+ xf, of, fn, tw = xin.view(-1), out.view(-1), self.fn, self.tables
 
 
2890
  if self.direct:
2891
+ ws = torch.empty_like(of)
 
2892
 
2893
  def go():
2894
+ fn(xf, of, tw, ws)
2895
 
2896
  go.ws = ws
2897
  return go
2898
 
2899
  def go():
2900
+ fn(xf, of, tw)
2901
 
2902
  return go
2903
 
2904
  def run_into(self, xin, out):
2905
+ if self.direct:
2906
+ self.fn(xin.view(-1), out.view(-1), self.tables, torch.empty_like(out).view(-1))
2907
+ else:
2908
+ self.fn(xin.view(-1), out.view(-1), self.tables)
2909
 
2910
 
2911
  def _get_plan(nz, ny, nf, device):
2912
+ key = (nz, ny, nf, device)
2913
  p = _PLANS.get(key)
2914
  if p is None:
2915
  p = _Plan(nz, ny, nf, device)
 
2932
  _check(x)
2933
  nz, ny, nf, _ = x.shape
2934
  plan = _get_plan(nz, ny, nf, x.device)
2935
+ if x.data_ptr() % 16:
2936
+ x = x.clone() # the kernels read 16-byte vectors; an aligned copy leaves the input as it is
2937
  out = torch.empty((nz, ny, 2 * (nf - 1)), dtype=torch.float64, device=x.device)
2938
  plan.run_into(x, out)
2939
  return out
 
2944
  _check(input)
2945
  if not input.is_contiguous():
2946
  raise ValueError("prepare binds the input buffer itself, so it must be contiguous")
2947
+ if input.data_ptr() % 16:
2948
+ raise ValueError("prepare binds the input buffer itself, so it must be 16-byte aligned")
2949
  nz, ny, nf, _ = input.shape
2950
  plan = _get_plan(nz, ny, nf, input.device)
2951
  out = torch.empty((nz, ny, 2 * (nf - 1)), dtype=torch.float64, device=input.device)
build/torch-cuda/_ops.py CHANGED
@@ -28,7 +28,7 @@ def get_backend() -> str:
28
 
29
  def _find_ops_name() -> str:
30
  kernel_name = "fft3d"
31
- unique_id = "f2cede9"
32
  backend = get_backend()
33
  return f"_{kernel_name}_{backend}_{unique_id}"
34
 
 
28
 
29
  def _find_ops_name() -> str:
30
  kernel_name = "fft3d"
31
+ unique_id = "9e7519b"
32
  backend = get_backend()
33
  return f"_{kernel_name}_{backend}_{unique_id}"
34
 
build/torch-cuda/_r2c_f32/kernel.py CHANGED
@@ -408,4 +408,13 @@ def prepare(input):
408
 
409
 
410
  def run(input):
411
- return prepare(input)()
 
 
 
 
 
 
 
 
 
 
408
 
409
 
410
  def run(input):
411
+ if not (input.dim() == 3 and input.dtype == torch.float32 and input.is_cuda and input.is_contiguous()):
412
+ _reject(input)
413
+ nz, ny, nx = input.shape
414
+ pl = _get_plan((nz, ny, nx), input.device)
415
+ if pl.sync_words:
416
+ return prepare(input)()
417
+ # the common case without building a launch closure: per-call overhead counts on small grids
418
+ out = torch.empty((nz, ny, nx // 2 + 1, 2), device=input.device, dtype=torch.float32)
419
+ pl.run(input.view(-1), out.view(-1))
420
+ return out
build/torch-cuda/_r2c_f64/kernel.py CHANGED
@@ -27,7 +27,10 @@ the X pass packs two real rows per complex FFT); lengths it cannot split (none u
27
  the direct-DFT kernels (fft_direct.py).
28
 
29
  Pass configurations come from the tuned table (tuned.py) or a cost model (planner.py); each
30
- shape's plan is built and compiled on first use and cached per (shape, device).
 
 
 
31
 
32
  Shapes chained with per-plane X->Y counters (pdl mode 3) keep one counter buffer per launch
33
  stream: the last Y tile of a launch zeroes the counters again, and the next launch on the
@@ -56,6 +59,31 @@ _capturing = torch._C._cuda_isCurrentStreamCapturing
56
  _current_device = torch._C._cuda_getDevice
57
 
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  def _dev(a, device):
60
  """Host table (1-D CPU tensor) -> device copy."""
61
  return a.to(device)
@@ -121,6 +149,7 @@ class FFT3DPlan:
121
  self.shape = (nz, ny, nx)
122
  nf = nx // 2 + 1
123
  self.nf = nf
 
124
  self.device = device
125
  cfgs = default_cfgs(nz, ny, nx)
126
  xcfg, ycfg, zcfg = cfgs
@@ -192,35 +221,33 @@ class FFT3DPlan:
192
  self.nx_t, self.ny_t = len(xtabs), len(ytabs)
193
  self.tabs = [_dev(t, device) for t in list(xtabs) + list(ytabs) + list(ztabs)]
194
  self.compiled = None
195
- self._args = {} # launch key (stream, or (stream, capture id)) -> launch arguments
 
196
 
197
- def _split(self, t):
 
 
198
  a, b = self.nx_t, self.nx_t + self.ny_t
199
- return t[:a], t[a:b], t[b:]
200
-
201
- def _pass_args(self, sync):
202
- """Table + counter arguments of the X, Y and Z launchers for one counter tensor."""
203
- tx, ty, tz = self._split(self._ctabs)
204
- later = list(ty) + list(tz)
205
- fs = from_dlpack(sync)
206
- xargs = list(tx) + ((later + [later[0]] * (6 - len(later)) + [fs]) if self.x_prefetch else [])
207
- ty = list(ty) + ([fs] if self.cfgs[1] is not None else [])
208
- tz = list(tz) + ([fs] if self.cfgs[2] is not None else [])
209
- return xargs, ty, tz
210
-
211
- def compile(self, inp, out, st):
212
- fi = from_dlpack(inp.view(-1))
213
- fo = from_dlpack(out.view(-1))
214
- self._ctabs = [from_dlpack(x) for x in self.tabs]
215
- xargs, ty, tz = self._pass_args(self.sync)
216
- cx = cute.compile(self.xl, fi, fo, *xargs, st)
217
- cy = cute.compile(self.yl, fo, *ty, st)
218
- cz = cute.compile(self.zl, fo, *tz, st)
219
- self.compiled = (cx, cy, cz)
220
-
221
- def _launch_args(self, key, handle):
222
- """(stream, X args, Y args, Z args, counters) for launches on stream `handle`."""
223
- if self.flagged:
224
  n = self.sync.numel()
225
  sync = (
226
  _zeros_outside_capture(n, self.device)
@@ -228,35 +255,20 @@ class FFT3DPlan:
228
  # zeroed on this stream, ahead of the first launch that uses it
229
  else torch.zeros(n, dtype=torch.int32, device=self.device)
230
  )
231
- else:
232
- sync = self.sync
233
- args = (cuda.CUstream(handle), *self._pass_args(sync), sync)
234
- self._args[key] = args
235
  return args
236
 
237
  def launch(self, inp, out):
238
- handle = _raw_stream(inp.get_device())
239
- key = (handle, _capture_id(handle)) if _capturing() else handle
240
- args = self._args.get(key)
241
- if args is None:
242
- if self.compiled is None:
243
- self.compile(inp, out, cuda.CUstream(handle))
244
- args = self._launch_args(key, handle)
245
- cx, cy, cz = self.compiled
246
- st, xargs, ty, tz, _ = args
247
- fi = from_dlpack(inp.view(-1))
248
- fo = from_dlpack(out.view(-1))
249
- cx(fi, fo, *xargs, st)
250
- cy(fo, *ty, st)
251
- cz(fo, *tz, st)
252
-
253
-
254
- def _get_plan(shape, device):
255
- key = (tuple(shape), str(device))
256
  p = _CACHE.get(key)
257
  if p is None:
258
- p = FFT3DPlan(*shape, device)
259
- _CACHE[key] = p
260
  return p
261
 
262
 
@@ -285,13 +297,27 @@ def _validate(x):
285
  return x.contiguous()
286
 
287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  def run(input):
289
  """rfftn of a float64 [nz, ny, nx] CUDA tensor -> new float64 [nz, ny, nx//2+1, 2] (see module docstring)."""
290
- x = _validate(input)
291
- nz, ny, nx = x.shape
292
- plan = _get_plan(x.shape, x.device)
293
  if x.data_ptr() % plan.in_align:
294
  x = x.clone(memory_format=torch.contiguous_format)
295
- out = torch.empty((nz, ny, nx // 2 + 1, 2), dtype=torch.float64, device=x.device)
296
  plan.launch(x, out)
297
  return out
 
27
  the direct-DFT kernels (fft_direct.py).
28
 
29
  Pass configurations come from the tuned table (tuned.py) or a cost model (planner.py); each
30
+ shape's plan is built and compiled on first use and cached per (shape, device). The three
31
+ launches are compiled as one host function for TVM-FFI calls: a call hands the input, output,
32
+ tables and counters (each once) straight to it, and it launches the X, Y and Z kernels on the
33
+ device's current torch stream.
34
 
35
  Shapes chained with per-plane X->Y counters (pdl mode 3) keep one counter buffer per launch
36
  stream: the last Y tile of a launch zeroes the counters again, and the next launch on the
 
59
  _current_device = torch._C._cuda_getDevice
60
 
61
 
62
+ def _ffi(t):
63
+ """Compile-time stand-in for a tensor argument: its static shape and default alignment."""
64
+ return from_dlpack(t, enable_tvm_ffi=True)
65
+
66
+
67
+ def _compile(fn, *args):
68
+ return cute.compile(fn, *args, cute.runtime.make_fake_stream(use_tvm_ffi_env_stream=True), options="--enable-tvm-ffi")
69
+
70
+
71
+ def _fused_launcher(xl, yl, zl, ntabs, xmap, ymap, zmap):
72
+ """One host function launching the X, Y and Z passes, so that a call crosses into compiled
73
+ code once. Its parameters are gin, gout, the tables t0.. and gsync; *map name each pass's
74
+ table / counter arguments. Straight-line launches only, so the AST preprocessor is off."""
75
+ names = ["gin", "gout"] + [f"t{i}" for i in range(ntabs)] + ["gsync"]
76
+ src = (
77
+ f"def launch({', '.join(n + ': cute.Tensor' for n in names)}, stream: cuda.CUstream):\n"
78
+ f" xl(gin, gout, {''.join(a + ', ' for a in xmap)}stream)\n"
79
+ f" yl(gout, {''.join(a + ', ' for a in ymap)}stream)\n"
80
+ f" zl(gout, {''.join(a + ', ' for a in zmap)}stream)\n"
81
+ )
82
+ ns = {"xl": xl, "yl": yl, "zl": zl, "cute": cute, "cuda": cuda}
83
+ exec(src, ns)
84
+ return cute.jit(preprocess=False)(ns["launch"])
85
+
86
+
87
  def _dev(a, device):
88
  """Host table (1-D CPU tensor) -> device copy."""
89
  return a.to(device)
 
149
  self.shape = (nz, ny, nx)
150
  nf = nx // 2 + 1
151
  self.nf = nf
152
+ self.out_shape = (nz, ny, nf, 2)
153
  self.device = device
154
  cfgs = default_cfgs(nz, ny, nx)
155
  xcfg, ycfg, zcfg = cfgs
 
221
  self.nx_t, self.ny_t = len(xtabs), len(ytabs)
222
  self.tabs = [_dev(t, device) for t in list(xtabs) + list(ytabs) + list(ztabs)]
223
  self.compiled = None
224
+ self._args = None # table + counter arguments, when they do not depend on the stream
225
+ self._stream_args = {} # mode 3: launch key (stream, or (stream, capture id)) -> arguments
226
 
227
+ def _pass_maps(self):
228
+ """Names of the X, Y and Z launchers' table + counter arguments (see _fused_launcher)."""
229
+ t = [f"t{i}" for i in range(len(self.tabs))]
230
  a, b = self.nx_t, self.nx_t + self.ny_t
231
+ tx, ty, tz = t[:a], t[a:b], t[b:]
232
+ later = ty + tz
233
+ xmap = tx + ((later + [later[0]] * (6 - len(later)) + ["gsync"]) if self.x_prefetch else [])
234
+ ymap = ty + (["gsync"] if self.cfgs[1] is not None else [])
235
+ zmap = tz + (["gsync"] if self.cfgs[2] is not None else [])
236
+ return xmap, ymap, zmap
237
+
238
+ def compile(self, inp, out):
239
+ launch = _fused_launcher(self.xl, self.yl, self.zl, len(self.tabs), *self._pass_maps())
240
+ args = [_ffi(inp.view(-1)), _ffi(out.view(-1))] + [_ffi(x) for x in self.tabs] + [_ffi(self.sync)]
241
+ self.compiled = _compile(launch, *args)
242
+ if not self.flagged:
243
+ self._args = (*self.tabs, self.sync)
244
+
245
+ def _launch_args(self, inp):
246
+ """Mode 3: the arguments for launches on the current stream, with that stream's counters."""
247
+ handle = _raw_stream(inp.get_device())
248
+ key = (handle, _capture_id(handle)) if _capturing() else handle
249
+ args = self._stream_args.get(key)
250
+ if args is None:
 
 
 
 
 
251
  n = self.sync.numel()
252
  sync = (
253
  _zeros_outside_capture(n, self.device)
 
255
  # zeroed on this stream, ahead of the first launch that uses it
256
  else torch.zeros(n, dtype=torch.int32, device=self.device)
257
  )
258
+ args = self._stream_args[key] = (*self.tabs, sync)
 
 
 
259
  return args
260
 
261
  def launch(self, inp, out):
262
+ if self.compiled is None:
263
+ self.compile(inp, out)
264
+ self.compiled(inp.view(-1), out.view(-1), *(self._args or self._launch_args(inp)))
265
+
266
+
267
+ def _get_plan(x):
268
+ key = (x.shape, x.get_device())
 
 
 
 
 
 
 
 
 
 
 
269
  p = _CACHE.get(key)
270
  if p is None:
271
+ p = _CACHE[key] = FFT3DPlan(*x.shape, x.device)
 
272
  return p
273
 
274
 
 
297
  return x.contiguous()
298
 
299
 
300
+ def _ready(x):
301
+ """True for the common input that needs no checks beyond these and no copy."""
302
+ return (
303
+ type(x) is torch.Tensor
304
+ and x.dtype is torch.float64
305
+ and x.is_cuda
306
+ and x.dim() == 3
307
+ and x.is_contiguous()
308
+ and not x.requires_grad
309
+ and not x.is_neg()
310
+ and 0 not in x.shape
311
+ and x.get_device() == _current_device()
312
+ )
313
+
314
+
315
  def run(input):
316
  """rfftn of a float64 [nz, ny, nx] CUDA tensor -> new float64 [nz, ny, nx//2+1, 2] (see module docstring)."""
317
+ x = input if _ready(input) else _validate(input)
318
+ plan = _get_plan(x)
 
319
  if x.data_ptr() % plan.in_align:
320
  x = x.clone(memory_format=torch.contiguous_format)
321
+ out = x.new_empty(plan.out_shape)
322
  plan.launch(x, out)
323
  return out
build/torch-cuda/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "fft3d",
3
- "id": "_fft3d_cuda_f2cede9",
4
  "version": 1,
5
  "license": "MIT",
6
  "python-depends": [
@@ -17,22 +17,22 @@
17
  "algorithm": "sha256",
18
  "files": {
19
  "LICENSE.txt": "AIyEysQ0a1okjH0MEq1kTbdKj938zWEHbQJBfSkK23s=",
20
- "__init__.py": "FpeC/pysTUfgktdzoT9ywiuLxGtBIQ2QqqfsEDvegvA=",
21
  "_c2r_f32/__init__.py": "XBy0pSUwgUTmssbhC7XEt/ELeTb4ayilaHy7B/V0pHw=",
22
  "_c2r_f32/anyfft.py": "ehbLwanScRoMLeAvopBDiFfJlVhlpimFcRuoqKgpFy0=",
23
  "_c2r_f32/fftgen.py": "vlccjhLwsmKsoyZ+C8DPeR7fIe7LPMxEVsq1cDF5faE=",
24
  "_c2r_f32/generic.py": "F286vgjce5gqBY+L9ZzqY8kAauvLc0tvxUTAYBJBfrA=",
25
  "_c2r_f32/inplace.py": "J+tnMJWGaiz3Gug+wbJzgqgskLgHxnbqHWNb9h1GSCU=",
26
- "_c2r_f32/kernel.py": "ebURfjnZ7meANsrBG3yY7iO1waL0TSONIbB8RX+TxG8=",
27
  "_c2r_f32/tables.py": "RvVnytUIHgceogs37Z8lcjI5mxWclCA8D7kqC2YKm9w=",
28
  "_c2r_f32/twopass.py": "zI/xziZlOyfjNByjxB9Lag16rgClQVEn9kwlcoKQXT0=",
29
  "_c2r_f64/__init__.py": "6NiqCxU8/XZoiu7IY/nThfL5G3EovojIu+BSdpPGqEk=",
30
  "_c2r_f64/bigprime.py": "02gw/YOCiA32nxWpmrF1spuAExE4M5f3JgYj/zJaI5U=",
31
  "_c2r_f64/fftgen.py": "1Fn09b4uYmDSwItnhAycTXZVDWcmvXmf28BdjtMjsEQ=",
32
- "_c2r_f64/kernel.py": "0uvhv7AdWZHYMoQqpS9A2yqJtEM/mFrwmfRmfIgOQc8=",
33
  "_c2r_f64/softfp.py": "B4zzVG3AA7dtfqYCCORehavfX7Xz8ypzqiML/W5hgBE=",
34
  "_c2r_f64/twiddles.py": "WXaDgblQUWoX/1Qoofd5mD5aUpG1EkjYsdKD3YAUPW4=",
35
- "_ops.py": "Bc1c8UdnMmHRPewDW5TdU2LnIFAJFkFkES2ME9IJPsM=",
36
  "_r2c_f32/__init__.py": "b+FuhAkJolHH1YTeeRAb4NMHe+7YQId1VYOQ+W+vqsI=",
37
  "_r2c_f32/fft3k.py": "ZoLp8dkbyHLI57MrUAhd06t3+lDM+kIavTOk7YPaVPc=",
38
  "_r2c_f32/fft_core.py": "WThy1Zi8S9R5zSQgluOWnrhge+zXreeLR5OjysAgXNc=",
@@ -42,7 +42,7 @@
42
  "_r2c_f32/fft_xy.py": "Z4nZN7GM9AbNVr9JYgckfPifxfybumN5tMcmqgm/ePo=",
43
  "_r2c_f32/fft_ys1w.py": "4Vske6f2ATSSTb//EkpoHS5E3s0m9xGWOLuH3KWJSC8=",
44
  "_r2c_f32/fft_ysplit.py": "ukVz02AMlODEqFb/0qvSwAs5rA4CMFlqpI1YSRY0yqQ=",
45
- "_r2c_f32/kernel.py": "GojR3ze9eI+yfP8Yqw/fjBVLZo+BNvo2oNcdklqYa7k=",
46
  "_r2c_f32/plan.py": "eSTB1bK8zgHLcjpMf04C5F5LVHjqot2JdFXnm8/VRes=",
47
  "_r2c_f32/ptx.py": "3mKM+qkQfpV6NCAXz4V4FLf6Z35LtiONgb5oSGF09fw=",
48
  "_r2c_f32/stockham.py": "5lS6YIDy/WKT4gTUgsQsrJHBEaAWAyjELVhD6uHJysM=",
@@ -55,7 +55,7 @@
55
  "_r2c_f64/fft_plan.py": "OqPrkiahnt/XreodPMi90N7+kHinYgYbLuWAdVd8eqI=",
56
  "_r2c_f64/fft_stages.py": "5ylt/t+bA+9ILsAG5P9CCJkwdJmfvvx/oB29w1LoO9A=",
57
  "_r2c_f64/fftgen.py": "1Fn09b4uYmDSwItnhAycTXZVDWcmvXmf28BdjtMjsEQ=",
58
- "_r2c_f64/kernel.py": "aPX6PcigYrN9GMVKOF8DnMtRj1I+HAx6a2MtjXjI048=",
59
  "_r2c_f64/planner.py": "VgtohOIirLvHoK/AAciicLFmPD5nJSOCYxOomeYQZEY=",
60
  "_r2c_f64/tuned.py": "YtEsBZO9C63haDWFVnMBtIpTXwSKuvxAF6ffpz9wrd0="
61
  }
@@ -67,7 +67,7 @@
67
  "dirty": false
68
  },
69
  "kernel": {
70
- "commit": "f2cede95bff053e1a955986b718572d24161fa35",
71
  "dirty": false
72
  }
73
  }
 
1
  {
2
  "name": "fft3d",
3
+ "id": "_fft3d_cuda_9e7519b",
4
  "version": 1,
5
  "license": "MIT",
6
  "python-depends": [
 
17
  "algorithm": "sha256",
18
  "files": {
19
  "LICENSE.txt": "AIyEysQ0a1okjH0MEq1kTbdKj938zWEHbQJBfSkK23s=",
20
+ "__init__.py": "q3wlQ/Ew0+FfsBtKwNvoOH9sRHaZbLGqgCBortEP+XU=",
21
  "_c2r_f32/__init__.py": "XBy0pSUwgUTmssbhC7XEt/ELeTb4ayilaHy7B/V0pHw=",
22
  "_c2r_f32/anyfft.py": "ehbLwanScRoMLeAvopBDiFfJlVhlpimFcRuoqKgpFy0=",
23
  "_c2r_f32/fftgen.py": "vlccjhLwsmKsoyZ+C8DPeR7fIe7LPMxEVsq1cDF5faE=",
24
  "_c2r_f32/generic.py": "F286vgjce5gqBY+L9ZzqY8kAauvLc0tvxUTAYBJBfrA=",
25
  "_c2r_f32/inplace.py": "J+tnMJWGaiz3Gug+wbJzgqgskLgHxnbqHWNb9h1GSCU=",
26
+ "_c2r_f32/kernel.py": "hX4XP44Cd2BE06j95sIF8RJd8nwq7sik86DIM+6uLc0=",
27
  "_c2r_f32/tables.py": "RvVnytUIHgceogs37Z8lcjI5mxWclCA8D7kqC2YKm9w=",
28
  "_c2r_f32/twopass.py": "zI/xziZlOyfjNByjxB9Lag16rgClQVEn9kwlcoKQXT0=",
29
  "_c2r_f64/__init__.py": "6NiqCxU8/XZoiu7IY/nThfL5G3EovojIu+BSdpPGqEk=",
30
  "_c2r_f64/bigprime.py": "02gw/YOCiA32nxWpmrF1spuAExE4M5f3JgYj/zJaI5U=",
31
  "_c2r_f64/fftgen.py": "1Fn09b4uYmDSwItnhAycTXZVDWcmvXmf28BdjtMjsEQ=",
32
+ "_c2r_f64/kernel.py": "F/KBXDpll4RdODgvHEsdqqU+vMxeNrJumfmiIMDOnY0=",
33
  "_c2r_f64/softfp.py": "B4zzVG3AA7dtfqYCCORehavfX7Xz8ypzqiML/W5hgBE=",
34
  "_c2r_f64/twiddles.py": "WXaDgblQUWoX/1Qoofd5mD5aUpG1EkjYsdKD3YAUPW4=",
35
+ "_ops.py": "wpRc/vzJsXg3CcTlM6GxXCVZFLCb6nJ0ocSnhY/mvK8=",
36
  "_r2c_f32/__init__.py": "b+FuhAkJolHH1YTeeRAb4NMHe+7YQId1VYOQ+W+vqsI=",
37
  "_r2c_f32/fft3k.py": "ZoLp8dkbyHLI57MrUAhd06t3+lDM+kIavTOk7YPaVPc=",
38
  "_r2c_f32/fft_core.py": "WThy1Zi8S9R5zSQgluOWnrhge+zXreeLR5OjysAgXNc=",
 
42
  "_r2c_f32/fft_xy.py": "Z4nZN7GM9AbNVr9JYgckfPifxfybumN5tMcmqgm/ePo=",
43
  "_r2c_f32/fft_ys1w.py": "4Vske6f2ATSSTb//EkpoHS5E3s0m9xGWOLuH3KWJSC8=",
44
  "_r2c_f32/fft_ysplit.py": "ukVz02AMlODEqFb/0qvSwAs5rA4CMFlqpI1YSRY0yqQ=",
45
+ "_r2c_f32/kernel.py": "k/jj+b9XOwBzf/Uug9hwXg0JUqOTjVJ6mhalZs1PNi8=",
46
  "_r2c_f32/plan.py": "eSTB1bK8zgHLcjpMf04C5F5LVHjqot2JdFXnm8/VRes=",
47
  "_r2c_f32/ptx.py": "3mKM+qkQfpV6NCAXz4V4FLf6Z35LtiONgb5oSGF09fw=",
48
  "_r2c_f32/stockham.py": "5lS6YIDy/WKT4gTUgsQsrJHBEaAWAyjELVhD6uHJysM=",
 
55
  "_r2c_f64/fft_plan.py": "OqPrkiahnt/XreodPMi90N7+kHinYgYbLuWAdVd8eqI=",
56
  "_r2c_f64/fft_stages.py": "5ylt/t+bA+9ILsAG5P9CCJkwdJmfvvx/oB29w1LoO9A=",
57
  "_r2c_f64/fftgen.py": "1Fn09b4uYmDSwItnhAycTXZVDWcmvXmf28BdjtMjsEQ=",
58
+ "_r2c_f64/kernel.py": "3DZZyoLqdJnlS9WRS+ujv0M+aMAhAN1/B1zvpSSbdBY=",
59
  "_r2c_f64/planner.py": "VgtohOIirLvHoK/AAciicLFmPD5nJSOCYxOomeYQZEY=",
60
  "_r2c_f64/tuned.py": "YtEsBZO9C63haDWFVnMBtIpTXwSKuvxAF6ffpz9wrd0="
61
  }
 
67
  "dirty": false
68
  },
69
  "kernel": {
70
+ "commit": "9e7519bbed9eaea4ef8f23df5cdc6654543f8f72",
71
  "dirty": false
72
  }
73
  }