From 27976e5435625e78d8d62101382078951c90bc9a Mon Sep 17 00:00:00 2001 From: Gopalakrishnan Nallasamy Date: Tue, 9 Jun 2026 12:34:07 -0700 Subject: [PATCH 1/3] Fix STFT complex input frame offsets --- onnxruntime/core/providers/cpu/signal/dft.cc | 8 ++- .../providers/cpu/signal/signal_ops_test.cc | 61 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/cpu/signal/dft.cc b/onnxruntime/core/providers/cpu/signal/dft.cc index f462680e02587..08ef8b6b180ad 100644 --- a/onnxruntime/core/providers/cpu/signal/dft.cc +++ b/onnxruntime/core/providers/cpu/signal/dft.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -532,6 +533,7 @@ static Status short_time_fourier_transform(OpKernelContext* ctx, bool is_oneside // Get signal const auto* signal = ctx->Input(0); const auto frame_step = signal::get_scalar_value_from_tensor(ctx->Input(1)); + ORT_RETURN_IF_NOT(frame_step > 0, "frame_step must be greater than zero."); const auto* window = ctx->Input(2); const auto* frame_length_tensor = ctx->Input(3); @@ -592,12 +594,16 @@ static Status short_time_fourier_transform(OpKernelContext* ctx, bool is_oneside Tensor b_fft, chirp; InlinedVector> V; InlinedVector> temp_output; + const int64_t signal_frame_step_components = std::is_same::value ? signal_components : 1; // Run each dft of each batch as if it was a real-valued batch size 1 dft operation for (int64_t batch_idx = 0; batch_idx < batch_size; batch_idx++) { for (int64_t i = 0; i < n_dfts; i++) { + const auto frame_start = i * frame_step; + ORT_RETURN_IF_NOT(frame_start <= signal_size - window_size, "STFT input frame is out of bounds."); auto input_frame_begin = - signal_data + (batch_idx * signal_size * signal_components) + (i * frame_step * signal_components); + signal_data + (batch_idx * signal_size * signal_frame_step_components) + + (frame_start * signal_frame_step_components); auto output_frame_begin = Y_data + (batch_idx * n_dfts * dft_output_size * output_components) + (i * dft_output_size * output_components); diff --git a/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc b/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc index 966090eed861e..cfc398d3837ea 100644 --- a/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc +++ b/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc @@ -233,6 +233,67 @@ TEST(SignalOpsTest, STFTFloat) { test.Run(); } +static void TestSTFTInvalidFrameStep(int64_t frame_step) { + OpTester test("STFT", kMinOpsetVersion); + + vector signal(64, 1); + test.AddInput("signal", {1, 64, 1}, signal); + test.AddInput("frame_step", {}, {frame_step}); + vector window(16, 1); + test.AddInput("window", {16}, window); + test.AddInput("frame_length", {}, {16}); + + vector output_shape = {1, 7, 9, 2}; + vector expected_output(1 * 7 * 9 * 2, 0.f); + test.AddOutput("output", output_shape, expected_output); + test.Run(OpTester::ExpectResult::kExpectFailure, "frame_step must be greater than zero"); +} + +TEST(SignalOpsTest, STFTFrameStepMustBePositive) { + TestSTFTInvalidFrameStep(0); + TestSTFTInvalidFrameStep(-1); +} + +TEST(SignalOpsTest, STFTFloatComplexInputBatched) { + OpTester test("STFT", kMinOpsetVersion); + test.AddAttribute("onesided", static_cast(false)); + + constexpr int64_t batch_size = 2; + constexpr int64_t signal_size = 128; + constexpr int64_t signal_components = 2; + constexpr int64_t frame_length = 32; + constexpr int64_t frame_step = 16; + constexpr int64_t n_dfts = 7; + constexpr int64_t dft_output_size = frame_length; + constexpr int64_t output_components = 2; + + vector signal(batch_size * signal_size * signal_components, 0.f); + for (int64_t batch_idx = 0; batch_idx < batch_size; ++batch_idx) { + const float signal_value = batch_idx == 0 ? 1.f : 99.f; + for (int64_t sample_idx = 0; sample_idx < signal_size; ++sample_idx) { + signal[(batch_idx * signal_size + sample_idx) * signal_components] = signal_value; + } + } + + test.AddInput("signal", {batch_size, signal_size, signal_components}, signal); + test.AddInput("frame_step", {}, {frame_step}); + test.AddOptionalInputEdge(); + test.AddInput("frame_length", {}, {frame_length}); + + vector output_shape = {batch_size, n_dfts, dft_output_size, output_components}; + vector expected_output(batch_size * n_dfts * dft_output_size * output_components, 0.f); + for (int64_t batch_idx = 0; batch_idx < batch_size; ++batch_idx) { + const float dc_value = (batch_idx == 0 ? 1.f : 99.f) * frame_length; + for (int64_t frame_idx = 0; frame_idx < n_dfts; ++frame_idx) { + expected_output[((batch_idx * n_dfts + frame_idx) * dft_output_size) * output_components] = dc_value; + } + } + + test.AddOutput("output", output_shape, expected_output); + test.SetOutputAbsErr("output", 0.001f); + test.Run(); +} + TEST(SignalOpsTest, HannWindowFloat) { OpTester test("HannWindow", kMinOpsetVersion); From e3b1529a22f61f8f701934324bbb3d8324d21790 Mon Sep 17 00:00:00 2001 From: Gopalakrishnan Nallasamy Date: Tue, 9 Jun 2026 15:00:36 -0700 Subject: [PATCH 2/3] Exclude DML from STFT regression tests --- onnxruntime/test/providers/cpu/signal/signal_ops_test.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc b/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc index cfc398d3837ea..8636cda765bbf 100644 --- a/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc +++ b/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc @@ -246,7 +246,9 @@ static void TestSTFTInvalidFrameStep(int64_t frame_step) { vector output_shape = {1, 7, 9, 2}; vector expected_output(1 * 7 * 9 * 2, 0.f); test.AddOutput("output", output_shape, expected_output); - test.Run(OpTester::ExpectResult::kExpectFailure, "frame_step must be greater than zero"); + test.Config(OpTester::ExpectResult::kExpectFailure, "frame_step must be greater than zero"); + test.ConfigExcludeEps({kDmlExecutionProvider}); + test.RunWithConfig(); } TEST(SignalOpsTest, STFTFrameStepMustBePositive) { @@ -291,7 +293,8 @@ TEST(SignalOpsTest, STFTFloatComplexInputBatched) { test.AddOutput("output", output_shape, expected_output); test.SetOutputAbsErr("output", 0.001f); - test.Run(); + test.ConfigExcludeEps({kDmlExecutionProvider}); + test.RunWithConfig(); } TEST(SignalOpsTest, HannWindowFloat) { From dcdbd19260a5bf7a67c77f2989c4215e5d27f106 Mon Sep 17 00:00:00 2001 From: Gopalakrishnan Nallasamy Date: Wed, 10 Jun 2026 15:34:48 -0700 Subject: [PATCH 3/3] Address STFT review feedback --- onnxruntime/core/providers/cpu/signal/dft.cc | 16 +++++------- .../providers/cpu/signal/signal_ops_test.cc | 26 +++++++++++++------ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/onnxruntime/core/providers/cpu/signal/dft.cc b/onnxruntime/core/providers/cpu/signal/dft.cc index 08ef8b6b180ad..f75c5fe12a6f4 100644 --- a/onnxruntime/core/providers/cpu/signal/dft.cc +++ b/onnxruntime/core/providers/cpu/signal/dft.cc @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -525,9 +524,9 @@ template static Status short_time_fourier_transform(OpKernelContext* ctx, bool is_onesided, bool /*inverse*/) { // Attr("onesided"): default = 1 // Input(0, "signal") type = T1 - // Input(1, "frame_length") type = T2 + // Input(1, "frame_step") type = T2 // Input(2, "window") type = T1, optional - // Input(3, "frame_step") type = T2 + // Input(3, "frame_length") type = T2 // Output(0, "output") type = T1 // Get signal @@ -594,16 +593,15 @@ static Status short_time_fourier_transform(OpKernelContext* ctx, bool is_oneside Tensor b_fft, chirp; InlinedVector> V; InlinedVector> temp_output; - const int64_t signal_frame_step_components = std::is_same::value ? signal_components : 1; // Run each dft of each batch as if it was a real-valued batch size 1 dft operation for (int64_t batch_idx = 0; batch_idx < batch_size; batch_idx++) { for (int64_t i = 0; i < n_dfts; i++) { const auto frame_start = i * frame_step; + // Defensive check before creating a non-owning tensor view. n_dfts derivation should keep this in bounds. ORT_RETURN_IF_NOT(frame_start <= signal_size - window_size, "STFT input frame is out of bounds."); - auto input_frame_begin = - signal_data + (batch_idx * signal_size * signal_frame_step_components) + - (frame_start * signal_frame_step_components); + // 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; auto output_frame_begin = Y_data + (batch_idx * n_dfts * dft_output_size * output_components) + (i * dft_output_size * output_components); @@ -625,9 +623,9 @@ static Status short_time_fourier_transform(OpKernelContext* ctx, bool is_oneside Status STFT::Compute(OpKernelContext* ctx) const { // Attr("onesided"): default = 1 // Input(0, "signal") type = T1 - // Input(1, "frame_length") type = T2 + // Input(1, "frame_step") type = T2 // Input(2, "window") type = T1, optional - // Input(3, "frame_step") type = T2 + // Input(3, "frame_length") type = T2 // Output(0, "output") type = T1 // Get signal shape diff --git a/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc b/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc index 8636cda765bbf..c87e5d7c4c6e1 100644 --- a/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc +++ b/onnxruntime/test/providers/cpu/signal/signal_ops_test.cc @@ -256,7 +256,8 @@ TEST(SignalOpsTest, STFTFrameStepMustBePositive) { TestSTFTInvalidFrameStep(-1); } -TEST(SignalOpsTest, STFTFloatComplexInputBatched) { +template +static void TestSTFTComplexInputBatched() { OpTester test("STFT", kMinOpsetVersion); test.AddAttribute("onesided", static_cast(false)); @@ -269,34 +270,43 @@ TEST(SignalOpsTest, STFTFloatComplexInputBatched) { constexpr int64_t dft_output_size = frame_length; constexpr int64_t output_components = 2; - vector signal(batch_size * signal_size * signal_components, 0.f); + vector signal(batch_size * signal_size * signal_components, static_cast(0)); for (int64_t batch_idx = 0; batch_idx < batch_size; ++batch_idx) { - const float signal_value = batch_idx == 0 ? 1.f : 99.f; + const T signal_value = batch_idx == 0 ? static_cast(1) : static_cast(99); for (int64_t sample_idx = 0; sample_idx < signal_size; ++sample_idx) { signal[(batch_idx * signal_size + sample_idx) * signal_components] = signal_value; } } - test.AddInput("signal", {batch_size, signal_size, signal_components}, signal); + test.AddInput("signal", {batch_size, signal_size, signal_components}, signal); test.AddInput("frame_step", {}, {frame_step}); - test.AddOptionalInputEdge(); + test.AddOptionalInputEdge(); test.AddInput("frame_length", {}, {frame_length}); vector output_shape = {batch_size, n_dfts, dft_output_size, output_components}; - vector expected_output(batch_size * n_dfts * dft_output_size * output_components, 0.f); + vector expected_output(batch_size * n_dfts * dft_output_size * output_components, static_cast(0)); for (int64_t batch_idx = 0; batch_idx < batch_size; ++batch_idx) { - const float dc_value = (batch_idx == 0 ? 1.f : 99.f) * frame_length; + const T dc_value = (batch_idx == 0 ? static_cast(1) : static_cast(99)) * static_cast(frame_length); for (int64_t frame_idx = 0; frame_idx < n_dfts; ++frame_idx) { expected_output[((batch_idx * n_dfts + frame_idx) * dft_output_size) * output_components] = dc_value; } } - test.AddOutput("output", output_shape, expected_output); + test.AddOutput("output", output_shape, expected_output); test.SetOutputAbsErr("output", 0.001f); + // DML does not consistently match these CPU STFT validation/regression paths in Windows GPU CI. test.ConfigExcludeEps({kDmlExecutionProvider}); test.RunWithConfig(); } +TEST(SignalOpsTest, STFTFloatComplexInputBatched) { + TestSTFTComplexInputBatched(); +} + +TEST(SignalOpsTest, STFTDoubleComplexInputBatched) { + TestSTFTComplexInputBatched(); +} + TEST(SignalOpsTest, HannWindowFloat) { OpTester test("HannWindow", kMinOpsetVersion);