Add a portable _fft_r2c kernel - #22055
Open
msluszniak wants to merge 1 commit into
Open
Conversation
_fft_r2c.out only had an optimized (pocketfft-backed) kernel. A program that leaves it undelegated builds fine and writes a .pte, but a runtime carrying only the portable kernels cannot load it: the failure surfaces as 0x14 OperatorMissing from load_method, or an abort in executor_runner, long after the export reported success. _fft_r2c.out and _fft_c2r.out are two of only three ops in optimized.yaml with no counterpart in the portable functions.yaml (the third is linear.out, which ExecuTorch decomposes anyway). This is a direct O(n^2) evaluation of the transform sum, not a fast Fourier transform, in keeping with the portable library's role as the dependency-free reference: kernels/optimized keeps the asymptotically faster pocketfft path for anyone who links it. Audio front-ends transforming a few hundred points per frame, which is where this op tends to show up, are the intended case. Multi-dimensional transforms run the real transform along the last requested dimension and complex transforms along the rest, matching pocketfft's multi-axis r2c. A complex pass has to read a whole line before overwriting it; lines up to 128 elements use a stack buffer, and longer ones ask the runtime for temporary memory. The single-dimension case, which is what torch.fft.rfft lowers to, needs no line buffer at all. The quarter-turn twiddle factors are returned exactly instead of through cos/sin, so a real input's Nyquist bin comes out with a zero imaginary part rather than rounding noise around 1e-16, which is what pocketfft produces and what the existing tests expect. Checked against numpy: max absolute error 2.5e-15 for a length-5 transform, 1.8e-13 on a 4x512 transform (1.4e-15 relative to the largest output), and 1.8e-14 for a 6x8 rfft2. Registers the shared op_fft_r2c_test for portable as well as aten and optimized; all five cases pass against both kernel libraries.
msluszniak
requested review from
kirklandsign,
larryliu0820 and
manuelcandales
as code owners
August 22, 2026 17:53
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22055
Note: Links to docs will display an error until the docs builds have been completed.
|
This PR needs a
|
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.
Summary
Addresses the first half of #21950.
_fft_r2c.outonly had an optimized (pocketfft-backed) kernel. A program that leaves it undelegated builds fine and writes a.pte, but a runtime carrying only the portable kernels cannot load it. The failure surfaces as0x14 OperatorMissingfromload_method, long after the export reported success._fft_r2c.outand_fft_c2r.outare two of only three ops inoptimized.yamlwith no counterpart in the portablefunctions.yaml:The third,
linear.out, ExecuTorch decomposes anyway, so the FFT pair is the real gap.Approach
This is a direct O(n^2) evaluation of the transform sum, not a fast Fourier transform, in keeping with the portable library's role as the dependency-free reference;
kernels/optimizedkeeps the asymptotically faster pocketfft path for anyone who links it. Audio front-ends transforming a few hundred points per frame, which is where this op tends to show up, are the intended case.Two details worth calling out:
r2c. A complex pass has to read a whole line before overwriting it; lines up to 128 elements (2 KB for double) use a stack buffer and longer ones ask the runtime for temporary memory. The single-dimension case, which is whattorch.fft.rfftlowers to, needs no line buffer at all.cos/sin, so a real input's Nyquist bin comes out with a zero imaginary part instead of rounding noise around 1e-16. That is what pocketfft produces and what the existing tests expect.Test plan
Registers the shared
op_fft_r2c_testforportablealongsideatenandoptimized. All five cases pass against both kernel libraries:End to end on the model from the issue (
torch.fft.rfft(x).abs().pow(2)over[8, 512], exported with no partitioner so the op stays on the CPU), run throughexecutor_runnerbuilt with portable kernels only:[262144., 1.2e-27, 2.4e-27, ...]which is the correct
abs(rfft(ones))**2:512**2in bin 0 and zero elsewhere.Accuracy against numpy, since the tests above use exactly representable values:
rfft, length 5 (odd, no Nyquist bin, no quarter-turn twiddles)rfft, 4x512rfft2, 6x8I wanted to add the length-5 case as a regular test, but
tensors_are_close()falls through to a bitwisememcmpfor complex dtypes, so a complex comparison cannot have a tolerance and only exactly representable expected values can pass. That is why the existing cases all use small integers. Happy to fix that separately if it would be welcome.Not addressed here
The issue also asks for lowering to fail when an op is left undelegated and no kernel exists, rather than emitting a
.ptethat dies at load. That one is a change to the AOT/runtime contract, since the export side does not know which kernel library the runtime will link (portable, optimized, or a selective build), so it needs its own discussion rather than riding along here.