Repository navigation
Add Stft op support to the OSS QNN backend (#24031) - #24031
Merged
Merged
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24031
Note: Links to docs will display an error until the docs builds have been completed. ❌ 1 Cancelled Job, 6 PendingAs of commit 241a7de with merge base a4e65bf ( CANCELLED JOB - The following job was cancelled. Please retry:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
Contributor
|
@billmguo has exported this pull request. If you are a Meta employee, you can view the originating Diff in D126511338. |
billmguo
added a commit
to billmguo/executorch
that referenced
this pull request
Oct 20, 2026
Summary: The OSS ExecuTorch QNN backend (`executorch/backends/qualcomm`) had no `torch.stft` support, so any model using it (e.g. the llama4 STITO speech front end) decomposed stft into `unfold` + `_fft_r2c` on CPU. QNN has a native `Stft` op on HTP (FP16/FP32 only, no quantized configuration, per the QNN 2.48/2.50 HtpOpDefSupplement), so this lowers `aten.stft.default` to it. `ConvertComplexStft` (export pass): `torch.stft` returns a complex tensor that QNN cannot represent, so this rewrites the supported complex consumers into the real-valued form: `view_as_real(stft(return_complex=True))` becomes `stft(return_complex=False)`, `abs(stft)` becomes `sqrt(sum(x**2, -1))`, and the common power-spectrum case `abs(stft)**2` becomes `sum(x**2, -1)`. An stft with any other complex consumer is left alone and decomposes as before. `op_stft.py`: lowers the real-valued `aten.stft.default` as Reshape -> Stft -> Transpose [-> Reshape]. QNN takes `[batch, signal_length, 1]` and produces `[batch, frames, freq, 2]` while torch produces `[batch, freq, frames, 2]` (no batch dim for a 1D signal). The window is zero-padded on both sides to `n_fft`, matching torch when `win_length < n_fft`. `partition/utils.py`: `aten.stft.default` is added to `get_skip_decomp_table()`, and `filter_fn` only preserves nodes QNN can run (real output, float input, `normalized=False`, and a window given or `win_length == n_fft`, since QNN's default rectangular window spans `n_fft`). Everything else decomposes to the existing CPU path, so models that don't match are unchanged. fbcode and xplat copies are kept byte-identical. Differential Revision: D126511338
billmguo
force-pushed
the
export-D126511338
branch
from
October 20, 2026 23:38
3397820 to
0998bd0
Compare
billmguo
added a commit
to billmguo/executorch
that referenced
this pull request
Oct 20, 2026
Summary: Pull Request resolved: pytorch#24031 The OSS ExecuTorch QNN backend (`executorch/backends/qualcomm`) had no `torch.stft` support, so any model using it (e.g. the llama4 STITO speech front end) decomposed stft into `unfold` + `_fft_r2c` on CPU. QNN has a native `Stft` op on HTP (FP16/FP32 only, no quantized configuration, per the QNN 2.48/2.50 HtpOpDefSupplement), so this lowers `aten.stft.default` to it. `ConvertComplexStft` (export pass): `torch.stft` returns a complex tensor that QNN cannot represent, so this rewrites the supported complex consumers into the real-valued form: `view_as_real(stft(return_complex=True))` becomes `stft(return_complex=False)`, `abs(stft)` becomes `sqrt(sum(x**2, -1))`, and the common power-spectrum case `abs(stft)**2` becomes `sum(x**2, -1)`. An stft with any other complex consumer is left alone and decomposes as before. `op_stft.py`: lowers the real-valued `aten.stft.default` as Reshape -> Stft -> Transpose [-> Reshape]. QNN takes `[batch, signal_length, 1]` and produces `[batch, frames, freq, 2]` while torch produces `[batch, freq, frames, 2]` (no batch dim for a 1D signal). The window is zero-padded on both sides to `n_fft`, matching torch when `win_length < n_fft`. `partition/utils.py`: `aten.stft.default` is added to `get_skip_decomp_table()`, and `filter_fn` only preserves nodes QNN can run (real output, float input, `normalized=False`, and a window given or `win_length == n_fft`, since QNN's default rectangular window spans `n_fft`). Everything else decomposes to the existing CPU path, so models that don't match are unchanged. fbcode and xplat copies are kept byte-identical. Differential Revision: D126511338
billmguo
force-pushed
the
export-D126511338
branch
from
October 20, 2026 23:41
0998bd0 to
5116320
Compare
Summary: The OSS ExecuTorch QNN backend (`executorch/backends/qualcomm`) had no `torch.stft` support, so any model using it (e.g. the llama4 STITO speech front end) decomposed stft into `unfold` + `_fft_r2c` on CPU. QNN has a native `Stft` op on HTP (FP16/FP32 only, no quantized configuration, per the QNN 2.48/2.50 HtpOpDefSupplement), so this lowers `aten.stft.default` to it. `ConvertComplexStft` (export pass): `torch.stft` returns a complex tensor that QNN cannot represent, so this rewrites the supported complex consumers into the real-valued form: `view_as_real(stft(return_complex=True))` becomes `stft(return_complex=False)`, `abs(stft)` becomes `sqrt(sum(x**2, -1))`, and the common power-spectrum case `abs(stft)**2` becomes `sum(x**2, -1)`. An stft with any other complex consumer is left alone and decomposes as before. `op_stft.py`: lowers the real-valued `aten.stft.default` as Reshape -> Stft -> Transpose [-> Reshape]. QNN takes `[batch, signal_length, 1]` and produces `[batch, frames, freq, 2]` while torch produces `[batch, freq, frames, 2]` (no batch dim for a 1D signal). The window is zero-padded on both sides to `n_fft`, matching torch when `win_length < n_fft`. `partition/utils.py`: `aten.stft.default` is added to `get_skip_decomp_table()`, and `filter_fn` only preserves nodes QNN can run (real output, float input, `normalized=False`, and a window given or `win_length == n_fft`, since QNN's default rectangular window spans `n_fft`). Everything else decomposes to the existing CPU path, so models that don't match are unchanged. fbcode and xplat copies are kept byte-identical. Differential Revision: D126511338
billmguo
force-pushed
the
export-D126511338
branch
from
October 20, 2026 23:43
5116320 to
241a7de
Compare
YIWENX14
approved these changes
Oct 21, 2026
Contributor
Author
|
@pytorchbot label "release notes: none" |
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:
The OSS ExecuTorch QNN backend (
executorch/backends/qualcomm) had notorch.stftsupport, so any model using it (e.g. the llama4 STITO speech front end) decomposed stft intounfold+_fft_r2con CPU. QNN has a nativeStftop on HTP (FP16/FP32 only, no quantized configuration, per the QNN 2.48/2.50 HtpOpDefSupplement), so this lowersaten.stft.defaultto it.ConvertComplexStft(export pass):torch.stftreturns a complex tensor that QNN cannot represent, so this rewrites the supported complex consumers into the real-valued form:view_as_real(stft(return_complex=True))becomesstft(return_complex=False),abs(stft)becomessqrt(sum(x**2, -1)), and the common power-spectrum caseabs(stft)**2becomessum(x**2, -1). An stft with any other complex consumer is left alone and decomposes as before.op_stft.py: lowers the real-valuedaten.stft.defaultas Reshape -> Stft -> Transpose [-> Reshape]. QNN takes[batch, signal_length, 1]and produces[batch, frames, freq, 2]while torch produces[batch, freq, frames, 2](no batch dim for a 1D signal). The window is zero-padded on both sides ton_fft, matching torch whenwin_length < n_fft.partition/utils.py:aten.stft.defaultis added toget_skip_decomp_table(), andfilter_fnonly preserves nodes QNN can run (real output, float input,normalized=False, and a window given orwin_length == n_fft, since QNN's default rectangular window spansn_fft). Everything else decomposes to the existing CPU path, so models that don't match are unchanged.fbcode and xplat copies are kept byte-identical.
Differential Revision: D126511338
cc @cbilgin @psiddh