Skip to content

[draft]: integrate fused_fft_conv2d backend and torch.compile lowering - #138

Open
moradza wants to merge 1 commit into
mainfrom
cuda13.2
Open

[draft]: integrate fused_fft_conv2d backend and torch.compile lowering#138
moradza wants to merge 1 commit into
mainfrom
cuda13.2

Conversation

@moradza

@moradza moradza commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Adds subquadratic_ops_torch.fused_fft_conv2d as a first-class 2D FFT-conv path. Unlike every other FFT backend it runs the whole rfft2/multiply/irfft2 pipeline in a single cuFFTDx launch and natively in fp32/fp16/bf16 rather than upcasting to fp32. Measured on H200 (B=8, H=768, fwd+bwd, bf16): 3.6-3.9x over torch_fft and 1.2-2.4x over the existing subq_ops path.

Two entry points:

  • fft_backend="subq_ops_fused" on CKConvND — explicit and predictable. Restricted to data_dim=2, non-causal, zero padding, and spatial extents of at most 64 per axis (the kernel's largest FFT tile is 128 and it requires max(X, Y) <= fft_size // 2). The spatial cap is enforced on the first forward pass, not at construction, since input size is unknown there.

  • nvsubquadratic.ops.fftconv_lowering — an inductor pre-grad pass that rewrites fftconv.py's chain onto the fused kernel, so a model already on fft_backend="torch_fft" picks it up without a config change. This matters because inductor cannot codegen complex operators and otherwise falls back to eager cuFFT for the entire chain. Enable per-callable with torch.compile(model, options=fused_fftconv2d_options()), or for a scope with the fused_fftconv2d_lowering() context manager.

    Pre-grad rather than post-grad so autograd is derived from the custom op's registered backward instead of requiring a consistent forward+backward rewrite. It fires only on an exact match of the reference recipe (padding rule, crop offset, shape limits, CUDA device, and a compute capability that supports the required tile — the 128 tile needs SM90+, as SM80/SM86 lack the shared memory). lowering_stats() reports rewrite and per-reason skip counts, since a silent pass is otherwise indistinguishable from one that never ran.

Crop-offset reconciliation: the upstream kernel crops the 'same' window at fft_size // 2 whereas fftconv.py crops at K // 2. The wrapper pre-pads the filter's top/left by the difference, making results interchangeable with the other backends (~3e-7 normwise in fp32, ~3e-3 in bf16). Without it the output is shifted by fft_size // 2 - K // 2 pixels — which reads as a ~1.41 (sqrt 2, fully decorrelated) error, not as reduced accuracy. A regression test pins this so the pre-pad cannot be optimised away.

Also registers the fused operators eagerly when the pass is constructed: inductor's on-disk FX cache lets a compiled artifact call torch.ops.subquadratic_ops_torch.* on a cache hit without any Python call having triggered the wrappers' lazy import, which failed with an opaque op-namespace AttributeError.

Fixes a pre-existing test-gate bug: tests/conftest.py resolved the kernel version from the subquadratic-ops-torch-cu12 distribution only. On the -cu13 install that pyproject.toml pins it returned (0, 0, 0), silently turning requires_subq_ops_v2 into a blanket xfail and hiding every subq_ops test result. It now checks both distributions.

Includes the in-flight CUDA 13.2 build changes already present in the working tree (Dockerfile base image, cu132 torch/DALI pins, cu13 kernel distribution).

Tests: 138 new (op-level equivalence across dtypes/shapes/layouts/FiLM, forward and backward, CKConvND integration, and lowering fire/decline behaviour).

Summary

Environment setup

Create the conda environment (required to run tests):

bash setup_conda_env.sh
conda activate nvsubquadratic

Test plan

  • pre-commit run --all-files passes (pre-commit install if not yet set up).
  • Existing tests pass (pytest tests/).
  • New tests added, or explain why not needed:

Documentation checklist

For every new or modified public symbol in nvsubquadratic/ or experiments/:

  • Every new module has a module-level docstring explaining what it contains and why.
  • Every new public class has a class docstring covering purpose, math/motivation, and key attributes.
  • Every new public method / function has Args: and Returns: blocks with tensor shapes where applicable.
  • Math notation is consistent with the paper (or a comment explains any deviation).
  • Docstrings containing backslashes use r"""...""" (required by ruff D301).
  • If a new file was added, a row has been added to docs-tracker.md with status [x].

See CONVENTIONS.md for the full style guide.

Adds `subquadratic_ops_torch.fused_fft_conv2d` as a first-class 2D FFT-conv
path. Unlike every other FFT backend it runs the whole rfft2/multiply/irfft2
pipeline in a single cuFFTDx launch and natively in fp32/fp16/bf16 rather than
upcasting to fp32. Measured on H200 (B=8, H=768, fwd+bwd, bf16): 3.6-3.9x over
`torch_fft` and 1.2-2.4x over the existing `subq_ops` path.

Two entry points:

* `fft_backend="subq_ops_fused"` on CKConvND — explicit and predictable.
  Restricted to data_dim=2, non-causal, zero padding, and spatial extents of
  at most 64 per axis (the kernel's largest FFT tile is 128 and it requires
  max(X, Y) <= fft_size // 2). The spatial cap is enforced on the first
  forward pass, not at construction, since input size is unknown there.

* `nvsubquadratic.ops.fftconv_lowering` — an inductor pre-grad pass that
  rewrites fftconv.py's chain onto the fused kernel, so a model already on
  `fft_backend="torch_fft"` picks it up without a config change. This matters
  because inductor cannot codegen complex operators and otherwise falls back
  to eager cuFFT for the entire chain. Enable per-callable with
  `torch.compile(model, options=fused_fftconv2d_options())`, or for a scope
  with the `fused_fftconv2d_lowering()` context manager.

  Pre-grad rather than post-grad so autograd is derived from the custom op's
  registered backward instead of requiring a consistent forward+backward
  rewrite. It fires only on an exact match of the reference recipe (padding
  rule, crop offset, shape limits, CUDA device, and a compute capability that
  supports the required tile — the 128 tile needs SM90+, as SM80/SM86 lack the
  shared memory). `lowering_stats()` reports rewrite and per-reason skip counts,
  since a silent pass is otherwise indistinguishable from one that never ran.

Crop-offset reconciliation: the upstream kernel crops the 'same' window at
fft_size // 2 whereas fftconv.py crops at K // 2. The wrapper pre-pads the
filter's top/left by the difference, making results interchangeable with the
other backends (~3e-7 normwise in fp32, ~3e-3 in bf16). Without it the output
is shifted by fft_size // 2 - K // 2 pixels — which reads as a ~1.41 (sqrt 2,
fully decorrelated) error, not as reduced accuracy. A regression test pins this
so the pre-pad cannot be optimised away.

Also registers the fused operators eagerly when the pass is constructed:
inductor's on-disk FX cache lets a compiled artifact call
torch.ops.subquadratic_ops_torch.* on a cache hit without any Python call
having triggered the wrappers' lazy import, which failed with an opaque
op-namespace AttributeError.

Fixes a pre-existing test-gate bug: tests/conftest.py resolved the kernel
version from the `subquadratic-ops-torch-cu12` distribution only. On the
`-cu13` install that pyproject.toml pins it returned (0, 0, 0), silently
turning `requires_subq_ops_v2` into a blanket xfail and hiding every
`subq_ops` test result. It now checks both distributions.

Includes the in-flight CUDA 13.2 build changes already present in the working
tree (Dockerfile base image, cu132 torch/DALI pins, cu13 kernel distribution).

Tests: 138 new (op-level equivalence across dtypes/shapes/layouts/FiLM, forward
and backward, CKConvND integration, and lowering fire/decline behaviour).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@moradza
moradza requested review from Dafidofff and dwromero July 27, 2026 17:29
@moradza moradza changed the title feat(ops): integrate fused_fft_conv2d backend and torch.compile lowering [draft]: integrate fused_fft_conv2d backend and torch.compile lowering Jul 31, 2026
farhadrgh pushed a commit that referenced this pull request Aug 10, 2026
Brings in fft_backend="subq_ops_fused" and the torch.compile lowering pass so
the 2D forward-time benchmarks can exercise the fused cuFFTDx kernel.

Conflict resolutions:
  * pyproject.toml — take #138's subquadratic-ops-torch-cu13>=0.2.2 pin (needed
    for fused_fft_conv2d), noting that 0.2.2 currently ships only from the
    internal NVIDIA GitLab registry (public PyPI tops out at 0.2.1).
  * Dockerfile — keep this branch's CUDA 13.0 base + torch 2.10.0/cu130 pins
    rather than #138's 13.2/2.12.1: apex and mamba only build when the base nvcc
    CUDA matches torch's exactly, and the benchmark image builds both. Adopted
    #138's ARG parameterisation with cu130 defaults so a cu132 image is a
    build-arg away, and added SUBQ_OPS_INDEX_URL for the 0.2.2 registry.
  * tests/conftest.py — take #138's version (queries both the cu12 and cu13
    distributions instead of only cu13).
  * CHANGELOG.md — keep both entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
farhadrgh pushed a commit that referenced this pull request Aug 10, 2026
PR #138 raised pyproject's floor to torch>=2.12.0,<2.13.0, but the Dockerfile
kept TORCH_VERSION=2.10.0. Those two contradict, and the failure was silent:
apex, mamba-ssm and causal-conv1d compiled against 2.10.0 in the middle of the
build, then the final `.[all]` step resolved nvsubquadratic's own requirement and
upgraded torch to 2.12.1 underneath them — leaving compiled extensions built
against headers that no longer matched the installed torch.

Nothing caught this, because the build's verification printed package METADATA
versions, which report happily regardless of what the extension was built
against. Add a guard that compares torch.__version__ to the pin and fails the
build, and note the coupling on both pins so they are not edited apart.

Also fix the FA4 probe in both files: nvidia_cutlass_dsl exposes no __version__,
so reading the attribute raised AttributeError and made a perfectly working FA4
install report as failed on every build. Read the version from metadata instead.

Verified on the rebuilt image: torch 2.12.1+cu130, apex / causal_conv1d /
flash_attn.cute / Mamba2 / fused_fft_conv2d all import, and causal_conv1d_fn
executes on-GPU.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant