Conversation
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
subquadratic_ops_torch.fused_fft_conv2das 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 overtorch_fftand 1.2-2.4x over the existingsubq_opspath.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 onfft_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 withtorch.compile(model, options=fused_fftconv2d_options()), or for a scope with thefused_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-cu12distribution only. On the-cu13install that pyproject.toml pins it returned (0, 0, 0), silently turningrequires_subq_ops_v2into a blanket xfail and hiding everysubq_opstest 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):
Test plan
pre-commit run --all-filespasses (pre-commit installif not yet set up).pytest tests/).Documentation checklist
For every new or modified public symbol in
nvsubquadratic/orexperiments/:Args:andReturns:blocks with tensor shapes where applicable.r"""..."""(required by ruff D301).docs-tracker.mdwith status[x].