Fix STFT complex input frame offsets - #28961
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes STFT frame pointer arithmetic when the input is complex-valued (shape [batch, signal_length, 2]) so frame offsets are not multiplied by the real/imag component dimension when the underlying pointer is std::complex<T>*, preventing out-of-bounds reads and cross-batch frame reads. Also adds input validation for frame_step and a defensive bounds check before creating non-owning tensor views.
Changes:
- Validate
frame_step > 0and reject invalid inputs early. - Fix STFT frame start pointer arithmetic for complex input by avoiding double-counting the trailing real/imag dimension.
- Add tests covering invalid
frame_stepand batched complex input to ensure frames do not bleed across batches.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| onnxruntime/core/providers/cpu/signal/dft.cc | Fixes complex-input STFT frame pointer arithmetic and adds validation/bounds checks. |
| onnxruntime/test/providers/cpu/signal/signal_ops_test.cc | Adds regression tests for invalid frame_step and batched complex STFT correctness. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Verdict: ApproveThis is a real OOB read on complex-input STFT and the fix is correct. Test coverage is well-chosen. A couple of small simplification/cleanup observations below, none blocking. Bug analysis — the fix is correctThe dispatch is paired strictly: short_time_fourier_transform<float, float> // real, U = float
short_time_fourier_transform<float, std::complex<float>> // complex, U = std::complex<float>
short_time_fourier_transform<double, double> // real, U = double
short_time_fourier_transform<double, std::complex<double>> // complex, U = std::complex<double>
Pre-PR arithmetic: signal_data + (batch_idx * signal_size * signal_components) + (i * frame_step * signal_components);
Concretely with the test config (
post-PR with
Bug and fix verified. Test coverage is solid
Observations (non-blocking)1.
|
42f5d9f to
dcdbd19
Compare
|
Addressed the non-blocking review feedback in
Validation:
|
Verdict: ApproveAll five observations from the prior round are addressed. The bug fix is unchanged and still correct; the cleanup makes the code strictly clearer than before; test coverage now includes both Verification of each prior observation1. Always-1 ternary — fixed cleanly (took the strongest option)Pre-PR (round 1): const int64_t signal_frame_step_components = std::is_same<T, U>::value ? signal_components : 1;
auto input_frame_begin = signal_data + (batch_idx * signal_size) + (i * frame_step * signal_frame_step_components);Post-PR (round 2): const auto frame_start = i * frame_step;
// signal_data is U*, so one increment advances one input sample, including both lanes for complex input.
auto input_frame_begin = signal_data + (batch_idx * signal_size) + frame_start;That's option (a) from my prior comment — drop the multiplier and rely on Sanity-checked the arithmetic against the new test:
|
Description
Fix STFT frame pointer arithmetic for complex-valued input so frame starts are computed in input samples, not trailing real/imag components. Since the frame view pointer is
U*, one pointer increment advances one full real or complex sample.Also add validation that
frame_stepis positive and keep a defensive bounds check before creating non-owning tensor views.Review feedback addressed: simplified the frame pointer arithmetic, fixed the swapped STFT input comments, documented the defensive bounds check, and added double-complex regression coverage. The new STFT validation/regression tests exclude
kDmlExecutionProviderbecause these CPU STFT validation/regression paths do not consistently match DirectML behavior in Windows GPU CI.Motivation and Context
For complex input shaped
[batch_size, signal_length, 2], pointer increments already advance by one real/imag pair. Multiplying frame offsets bysignal_components == 2again can advance past the valid frame start, allowing later frames to read across batches or beyond the input allocation.Testing
git diff --check -- onnxruntime/core/providers/cpu/signal/dft.cc onnxruntime/test/providers/cpu/signal/signal_ops_test.cc.\.venv\Scripts\python.exe tools\ci_build\build.py --config RelWithDebInfo --build --parallel --target onnxruntime_provider_test --build_dir build\Windows.\onnxruntime_provider_test.exe --gtest_filter="SignalOpsTest.STFTFloat:SignalOpsTest.STFTFrameStepMustBePositive:SignalOpsTest.STFTFloatComplexInputBatched:SignalOpsTest.STFTDoubleComplexInputBatched"frombuild\Windows\RelWithDebInfo\RelWithDebInfo