From 562edaf73ce232f1bad0bf7a131e6c279730e59f Mon Sep 17 00:00:00 2001 From: ychy7001 <62779060+ychy7001@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:24:09 +0800 Subject: [PATCH] fix(onnx): return empty results for split-final Paraformer chunks --- .../workflows/test-funasr-onnx-package.yml | 5 ++ .../funasr_onnx/paraformer_online_bin.py | 2 +- ...nasr_onnx_paraformer_online_empty_final.py | 75 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/test_funasr_onnx_paraformer_online_empty_final.py diff --git a/.github/workflows/test-funasr-onnx-package.yml b/.github/workflows/test-funasr-onnx-package.yml index 796ede71ae..5049f5cda6 100644 --- a/.github/workflows/test-funasr-onnx-package.yml +++ b/.github/workflows/test-funasr-onnx-package.yml @@ -9,6 +9,7 @@ on: - "runtime/python/libtorch/funasr_torch/utils/postprocess_utils.py" - "tests/test_funasr_onnx_release.py" - "tests/test_funasr_onnx_installed.py" + - "tests/test_funasr_onnx_paraformer_online_empty_final.py" - "tests/test_rich_transcription_postprocess.py" - "tests/test_rich_transcription_event_segments.py" push: @@ -21,6 +22,7 @@ on: - "runtime/python/libtorch/funasr_torch/utils/postprocess_utils.py" - "tests/test_funasr_onnx_release.py" - "tests/test_funasr_onnx_installed.py" + - "tests/test_funasr_onnx_paraformer_online_empty_final.py" - "tests/test_rich_transcription_postprocess.py" - "tests/test_rich_transcription_event_segments.py" workflow_dispatch: @@ -95,6 +97,9 @@ jobs: - name: Verify installed wrapper error paths run: python tests/test_funasr_onnx_installed.py + - name: Verify empty split-final Paraformer result + run: python tests/test_funasr_onnx_paraformer_online_empty_final.py + - name: Verify source distribution error paths run: | python -m pip install --force-reinstall --no-deps dist/funasr-onnx/*.tar.gz diff --git a/runtime/python/onnxruntime/funasr_onnx/paraformer_online_bin.py b/runtime/python/onnxruntime/funasr_onnx/paraformer_online_bin.py index 81b991d4b5..fc8bbf0d1b 100644 --- a/runtime/python/onnxruntime/funasr_onnx/paraformer_online_bin.py +++ b/runtime/python/onnxruntime/funasr_onnx/paraformer_online_bin.py @@ -178,7 +178,7 @@ def __call__(self, audio_in: np.ndarray, **kwargs): res[key][1].extend(value[1]) else: res[key] = [value[0], value[1]] - return [res] + return [res] if res else [] else: feats = self.add_overlap_chunk(feats, cache) diff --git a/tests/test_funasr_onnx_paraformer_online_empty_final.py b/tests/test_funasr_onnx_paraformer_online_empty_final.py new file mode 100644 index 0000000000..30b01c0f0e --- /dev/null +++ b/tests/test_funasr_onnx_paraformer_online_empty_final.py @@ -0,0 +1,75 @@ +"""Final-result regressions; run with the standalone funasr-onnx dependencies.""" + +import unittest +from unittest.mock import Mock + +import numpy as np + +from funasr_onnx.paraformer_online_bin import Paraformer + + +class ParaformerOnlineEmptyFinalTest(unittest.TestCase): + def call_final(self, frames, results): + # Keep __call__, cache preparation and overlap splitting real; no model files. + model = Paraformer.__new__(Paraformer) + model.chunk_size = [5, 10, 5] + model.encoder_output_size = model.feats_dims = 1 + model.fsmn_layer = 0 + model.pe = Mock() + model.pe.forward.side_effect = lambda feats, start_idx: feats + model.extract_feat = Mock(return_value=( + np.zeros((1, frames, 1), dtype=np.float32), + np.array([frames], dtype=np.int32), + )) + model.infer = Mock(side_effect=results) + cache = {} + result = model( + np.zeros(9600, dtype=np.float32), + param_dict={"cache": cache, "is_final": True}, + ) + self.assertEqual(model.infer.call_count, len(results)) + self.assertTrue(cache["last_chunk"]) + return result + + def test_split_final_without_results(self): + result = self.call_final(10, [[], []]) + self.assertEqual(result, []) + # The official demo consumes any nonempty result this way. + final_result = "" + if len(result) > 0: + final_result += result[0]["preds"][0] + self.assertEqual(final_result, "") + + def test_split_final_with_only_one_result(self): + for results in ( + [[{"preds": ("hello", ["hello"])}], []], + [[], [{"preds": ("hello", ["hello"])}]], + ): + with self.subTest(results=results): + result = self.call_final(10, results) + self.assertEqual(result, [{"preds": ["hello", ["hello"]]}]) + self.assertIsInstance(result[0]["preds"], list) + + def test_split_final_merges_both_results(self): + result = self.call_final(10, [ + [{"preds": ("hello", ["hello"])}], + [{"preds": (" world", ["world"])}], + ]) + self.assertEqual(result, [{"preds": ["hello world", ["hello", "world"]]}]) + self.assertIsInstance(result[0]["preds"], list) + + def test_single_final_with_result(self): + expected = [{"preds": ("hello", ["hello"])}] + result = self.call_final(5, [expected]) + self.assertIs(result, expected) + self.assertIsInstance(result[0]["preds"], tuple) + + def test_single_final_with_empty_text(self): + expected = [{"preds": ("", [])}] + result = self.call_final(5, [expected]) + self.assertIs(result, expected) + self.assertIsInstance(result[0]["preds"], tuple) + + +if __name__ == "__main__": + unittest.main()