# fft-conv-pytorch swaps direct convolution for an FFT round trip when kernels are large

> fft-conv-pytorch implements 1D, 2D and 3D convolution in the frequency domain for PyTorch, which beats direct convolution for large kernels and loses badly for small ones. The crossover is around 100 kernel elements and depends on the machine.

**fkodom/fft-conv-pytorch** — Implementation of 1D, 2D, and 3D FFT convolutions in PyTorch.  Much faster than direct convolutions for large kernel sizes.

- Repository: https://github.com/fkodom/fft-conv-pytorch
- Stars: 524 · Forks: 61
- Language: Python
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/fkodom-fft-conv-pytorch

## The one fact that decides whether you use it

The README states the tradeoff immediately, in three bullets. FFT convolution is faster than direct convolution for large kernels. It is much slower than direct convolution for small kernels. And in the author's local tests, FFT convolution is faster when the kernel has more than 100 or so elements, a figure the README qualifies as dependent on machine and PyTorch version.

That is the entire pitch and the entire warning. This is not a general-purpose drop-in for torch.nn.Conv1d. It is a specialised path for the case where a kernel is big enough that the direct implementation's cost dominates, which in practice means long 1D filters, large averaging windows, or the separable filters used in some signal and image processing pipelines.

The mechanism behind the crossover is straightforward convolution theory rather than a PyTorch trick. A direct convolution computes every output element as a weighted sum over the kernel, so the cost scales with the kernel size for every output position. An FFT convolution multiplies the signal and the kernel in the frequency domain and transforms back, which turns the convolution into a multiplication and makes the cost driven mostly by the transform and the size of the padded signal rather than by the number of kernel elements. Past a certain kernel size the constant overhead of the transforms is amortised and the FFT path wins.

Below that size the same overhead is pure loss, and the direct path's simplicity and cache behaviour win instead. The project does not attempt to hide that; the second bullet is in bold in the README itself.

The author also links a step-by-step explanation, an article titled Fourier Convolutions in PyTorch, for the derivation. That is the place to go if you need to know what the library is actually doing rather than just how to call it.

## Two call styles, and the module versions need extra work

The package exposes a functional entry point and a set of module classes, and the README is candid that they suit different situations.

The functional form is the easiest for generic use cases. You call fft_conv with a signal, a kernel and optionally a bias, and get the output tensor. The module form, FFTConv1d and its siblings, follows the PyTorch module convention with in_channels, out_channels and kernel_size constructor arguments, plus a bias flag, and the weights are assigned as torch.nn.Parameter objects. The README's own comment explains the catch: the object-oriented execution requires some extra work, since the defined classes were designed for use in neural networks.

The shape conventions are spelled out in the example's comments, and they follow the standard PyTorch layout rather than anything unusual. Data shape is (batch, channels, length). Kernel shape is (out_channels, in_channels, kernel_size). Bias shape is (out_channels,). For ordinary 1D convolution, simply set batch=1.

That last note matters for a specific reason. The batch dimension is not decoration in this library. The FFT route operates on the whole tensor, and the author notes that for ordinary 1D convolution you can simply set batch=1, which is a hint that the implementation treats the batch dimension as a real dimension in the transform rather than looping over it. That is the right approach for efficiency and it is also why a single-sample 1D signal has different characteristics from a batched one.

The classes exist so that the operation can be dropped into an nn.Module stack and trained, since the weight and bias are registered parameters. If you are doing inference on a fixed filter, the functional form is simpler and avoids the parameter plumbing.

## Installing the package and the two dependencies that matter

Installation from PyPI is a single command:

```bash
pip install fft-conv-pytorch
```

The source route is a clone and a local install:

```bash
git clone https://github.com/fkodom/fft-conv-pytorch.git
cd fft-conv-pytorch
pip install .
```

The declared runtime dependencies are numpy and torch>=1.8, so the package needs a reasonably recent PyTorch. The python_requires field says 3.7 or newer, and the classifiers list Python 3 and OS Independent.

There is also a test extra, which tells you what the project's own checks are made of. The extras_require entry in setup.py lists black, flake8, isort, pytest and pytest-cov, so the formatting and linting story is black, flake8 and isort, and the test story is pytest with coverage. The repository layout matches: an fft_conv_pytorch/ package directory, a tests/ directory, a setup.py, a setup.cfg, a doc/ directory holding the benchmark plot, a CITATION.cff, and a .coverage file at the top level.

One packaging detail is worth knowing if you build from a source checkout rather than from a tagged release. The version is not hardcoded. get_version_tag reads the FFT_CONV_PYTORCH_VERSION environment variable if it is set, and otherwise falls back to git describe --tags --abbrev=0, which means the installed version comes from the most recent git tag in your checkout. A clone without tags therefore has no version to report, which is a real annoyance for internal forks.

The package is named fft-conv-pytorch on PyPI while the import is fft_conv_pytorch, which is a normal Python naming conversion but worth noting if you are copying imports from an example.

## A minimal example, and the two execution paths

The example in the README constructs dummy data and runs both paths, which is a good template to copy because it makes the shape contract explicit:

```python
import torch
from fft_conv_pytorch import fft_conv, FFTConv1d
signal = torch.randn(3, 3, 1024 * 1024)
kernel = torch.randn(2, 3, 128)
bias = torch.randn(2)
out = fft_conv(signal, kernel, bias=bias)
fft_conv = FFTConv1d(3, 2, 128, bias=True)
fft_conv.weight = torch.nn.Parameter(kernel)
fft_conv.bias = torch.nn.Parameter(bias)
out = fft_conv(signal)
```

Read the shape comments that sit above those lines in the original. Data shape is (batch, channels, length), kernel shape is (out_channels, in_channels, kernel_size), bias shape is (out_channels,). The example uses a signal of three channels over 1024*1024 samples with a kernel of 128 taps, which is deliberately in the regime where the FFT path makes sense.

One line in the example is worth pausing on: the second call rebinds the name fft_conv to the module instance, shadowing the imported function. That is harmless in a short example and confusing in real code, so pick different names for the two paths in your own scripts.

The functional call takes the bias as a keyword argument, out = fft_conv(signal, kernel, bias=bias). The module path instead registers the kernel as fft_conv.weight and the bias as fft_conv.bias, both wrapped in torch.nn.Parameter, which is what makes them trainable.

One thing the README does not spell out in the example is the argument set for padding, stride, dilation and mode, even though the benchmark table in the same README lists Padding, Stride and Dilation as benchmarked dimensions. The details of how the library maps those onto the frequency-domain computation are in the linked article rather than in this file, so treat that as the first thing to check if your use case needs non-default stride or dilation.

## The benchmark table exists, the numbers do not

The README includes a benchmark section with a clear framing: it benchmarks FFT convolution against the direct convolution from PyTorch in 1D, 2D and 3D, and the exact times are heavily dependent on your local machine, but relative scaling with kernel size is always the same.

That last clause is the useful part. The claim is not a specific speedup factor, it is a scaling property: as the kernel grows, the two curves cross once, and they cross in the same place. That is a testable statement you can verify with your own numbers in a few minutes, and it is a more useful thing to rely on than a published timing on someone else's laptop.

The benchmark configuration is given as a table. For 1D, input size (4096), four input channels, four output channels, bias enabled, padding 0, stride 1, dilation 1. For 2D, input size (512, 512) with the same channel counts and settings. For 3D, input size (64, 64, 64) with the same settings. So the three dimensions are compared at comparable spatial volumes with the same channel counts and the same padding, stride and dilation, and only the dimensionality changes.

The results are shown as a plot in doc/benchmark.png rather than as a table of times, which is a reasonable choice for showing curves, and it also means there is no number in the README you can quote as a performance claim.

The benchmark dimensions matter for interpretation. Padding is 0 and stride and dilation are 1 throughout, so the comparison is the simplest possible case. If your real workload uses a strided or dilated convolution, the crossover point will not be the 100-element figure, and the author already warns that the threshold is machine and PyTorch version dependent.

## Where fft-conv-pytorch is the wrong tool

The first wrong-tool case is the common one. A 3x3 or 7x7 kernel in a convolutional network is deep in the regime where direct convolution is much faster, and the README does not soften that. If you are replacing nn.Conv2d in a standard vision model, this library will make your training slower.

The second case is a workload that needs a large kernel in a shape the library does not cover, or one that needs the many options torch.nn.Conv1d offers. The library implements convolution; it is not a drop-in replacement for the full nn.Conv modules, and the arguments it exposes for padding, stride and dilation are not documented in the README.

The third case is precision-sensitive work where an FFT-based result is not acceptable. Frequency-domain convolution is a floating point operation with different numerical behaviour from a direct sum, and the README does not discuss accuracy, and there is no mention of complex dtypes or double precision handling.

For the alternative approach, and there are two, the differences are instructive. The first is to use a separable or factorised convolution: if your large kernel decomposes into small ones, applying them in sequence is mathematically close to the same operation and costs far less than either direct large-kernel convolution or an FFT round trip. The second is to use a different primitive altogether: an FFT-based convolution exists in several other libraries, but the deeper point is that PyTorch's own direct convolution is heavily optimised and is the right default for everything except genuinely large kernels.

So the decision is narrow. If your kernel is large and you are on CPU or an unusual device where the optimised path is weak, this library is worth measuring. If your kernel is small, or your hardware is a GPU with well-optimised direct convolution, use the direct path and keep the complexity out of your training loop.

## MIT licensed, with releases that trail the last commit

The licence is MIT, which is the permissive choice and the one you want for a library you intend to build on.

The maintenance picture is a little more mixed. The recent releases are 1.2.0 on 2023-09-28, 1.1.3 on 2022-02-10 and 1.1.2 on 2022-01-29, while the last push to the repository was on 2026-10-16, on the master branch. So the tree is being worked on, and the published version series stopped some time ago.

That gap is not unusual for a research-adjacent utility, and the repository has the markers of a well-kept small project: a tests/ directory, a CITATION.cff for academic citation, a doc/ folder, a .github/ directory for workflows, and a .coverage file showing that coverage is measured locally as well as in CI. The author's own blog post on the technique is the primary documentation for the mathematics.

One detail to plan for if you vendor this into a larger project. The version is derived from git tags at build time rather than from a static version string, so a fork or an internal mirror without the upstream tags will produce a package with no usable version. If you pin dependencies, pin the released version from PyPI, not a branch.

The practical summary is that this is a small, single-author, MIT licensed piece of research code with a working test suite and a clear scope. It does one thing, does it in a way the author has explained in depth, and tells you plainly when not to use it.

## Conclusion

fft-conv-pytorch suits a PyTorch researcher applying a very large separable or dense kernel, where the direct convolution cost grows faster than the transform, and who is comfortable with the numerical behaviour of a frequency-domain implementation. It does not suit ordinary 3x3 or 7x7 kernels, where the same library is much slower than direct convolution, and the author puts the crossover at around 100 kernel elements. Before adopting, benchmark your own kernel size and PyTorch version rather than trusting the threshold, and read the step-by-step article linked from the README to see how the padding and bias arguments are handled.

## FAQ

### When is FFT convolution faster than direct convolution?

The README states that FFT convolution is faster when the kernel has more than 100 or so elements, and that this threshold is dependent on the machine and the PyTorch version. For small kernels it is much slower than direct convolution.

### How do I install fft-conv-pytorch?

Run `pip install fft-conv-pytorch` for the published package, or clone https://github.com/fkodom/fft-conv-pytorch.git and run `pip install .` from the source directory. The runtime dependencies are numpy and torch>=1.8.

### What shapes do fft_conv and FFTConv1d expect?

Data shape is (batch, channels, length), kernel shape is (out_channels, in_channels, kernel_size) and bias shape is (out_channels,). The README notes that for ordinary 1D convolution you can simply set batch=1.

### Does fft-conv-pytorch support 2D and 3D convolution?

Yes. The package implements 1D, 2D and 3D FFT convolutions, and the README benchmarks all three against the direct convolution from PyTorch at input sizes of (4096), (512, 512) and (64, 64, 64).

## Sources

- [fkodom/fft-conv-pytorch on GitHub](https://github.com/fkodom/fft-conv-pytorch)
- [Issues](https://github.com/fkodom/fft-conv-pytorch/issues)
- [License: MIT](https://github.com/fkodom/fft-conv-pytorch/blob/master/LICENSE)
- [README](https://github.com/fkodom/fft-conv-pytorch/blob/master/README.md)
- [Releases](https://github.com/fkodom/fft-conv-pytorch/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fkodom-fft-conv-pytorch
