Repository navigation
fix(onnx): return one segment list per input from Fsmn_vad - #3831
Merged
LauraGPT merged 1 commit intoNov 2, 2026
Merged
Conversation
Fsmn_vad.__call__ started from [[]] * self.batch_size and added the segments of each batch to segments[batch_num] for every batch_num below batch_size. With the default batch_size=1, each file of a list input is its own batch, so the segments of all files ended up in the first list: model([a, b]) returned [a_segments + b_segments], and model([silence, b]) returned b's segments as the result of the silent file. With batch_size=2, a single file with speech raised IndexError, because E2EVadModel returns one segment list per waveform, and a file without speech returned two aliased empty lists. A batch of two or more files failed before that: np.array(waveform) could not stack files of different lengths, and int(feats_len) raised TypeError for files of equal length. E2EVadModel supports one waveform per call and prepare_cache builds the FSMN cache for one waveform, so score the inputs one at a time and add each input's segments to its own list. Fsmn_vad no longer uses batch_size; the argument is still accepted. A single input still returns [segments]. Add tests/test_funasr_onnx_vad_multiple_inputs.py, which builds the model without model files and stubs only the ONNX session, and run it in the funasr-onnx package workflow.
LauraGPT
approved these changes
Nov 2, 2026
LauraGPT
left a comment
Collaborator
There was a problem hiding this comment.
Reviewed the three changed files at 76b5189e0afb62e83131f3a0fe1c2c23530a4217. Per-input result allocation and indexing fix the attribution bug; sequential scoring matches the existing single-waveform scorer/cache contract. I also checked ordering, silent entries, duplicate inputs and repeated calls on the same instance.
Independent validation:
- The submitted regression reproduced on the current main: two methods failed and the single-input control passed.
- All 74 selected CPU/package regressions now pass. The first audit export omitted three unchanged support files, yielding 49 passes and 25 missing-file failures/errors. After recovering those exact Git blobs, only those 25 cases were rerun and passed. This was an audit-directory error, not a product fix or a hidden test exclusion.
- 42 real L20Z/CUDA contract cases: main failed 36; this head passed all 42. FP32, requested batch sizes 1/2/3, effective model batch 1. Actual ONNX Runtime profiles show 396 CUDA and zero CPU node events. The graph is an explicitly synthetic analytic scoring/cache fixture reused from prior validation, with actual wrapper/frontend/state machine; no pretrained weights or downloads. This does not establish pretrained VAD/ASR quality, Windows/NPU compatibility or a full-suite pass.
- Exact-head CI succeeded; actual Python 3.11/3.12 wheel/sdist source bytes and test logs, plus Python 3.10/3.12 runtime-evaluation artifacts, were inspected.
This is a source fix only. Existing official funasr-onnx 0.4.5 bytes are not overwritten and do not gain this fix merely because the PR merges. The next package release must use a new version. No protection or CI bypass.
This was referenced Nov 2, 2026
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
Fixes #3830
Fsmn_vad(runtime/python/onnxruntime/funasr_onnx/vad_bin.py) takesstr, np.ndarray, List[str]input (README), but for a list of files it put the segments of all files into the first result list, andbatch_sizeabove 1 failed.__call__started fromsegments = [[]] * self.batch_sizeand added the segments of each batch tosegments[batch_num]for everybatch_numbelowbatch_size. With the defaultbatch_size=1, each file is its own batch, so the segments of every file were added tosegments[0].With the files of the new test (
a: 3 s, noise at 0.5-1.5 s;b: 4.5 s, noise at 1-2 s and 3-3.5 s;silence: 2 s):mainmodel(a)[[[220, 1660]]]model(b)[[[720, 2160], [2720, 3660]]]model([a, b])[[[220, 1660], [720, 2160], [2720, 3660]]][[[220, 1660]], [[720, 2160], [2720, 3660]]]model([silence, b])[[[720, 2160], [2720, 3660]]][[], [[720, 2160], [2720, 3660]]]On
main, b's times start again from 0 after a's last segment, and in the last call b's segments are reported for the silent file while b gets no entry. Code that pairs the inputs with the results attributes every segment to the first file.ParaformerandSenseVoiceSmallin the same package return one result per input.batch_sizeabove 1 failed onmainin two ways (numpy 1.26.4):E2EVadModelreturns one segment list per waveform, and only when it found segments, but the loop readsegments_part[batch_num]for everybatch_numbelowbatch_size. Withbatch_size=2,model(a)raisedIndexError: list index out of range, andmodel(silence)returned two aliased empty lists,[[], []].np.array(waveform)raisedValueError(inhomogeneous shape) for files of different lengths, such as[a, b], andint(feats_len)raisedTypeError: only length-1 arrays can be converted to Python scalarsfor files of equal length, such as[a, a]. Past those lines,E2EVadModeltakes one waveform per call (assert len(self.scores) == 1, "only support batch_size = 1 now"), andprepare_cachebuilds the FSMN cache with a batch dimension of 1.This PR scores the inputs one at a time (
for beg_idx in range(0, waveform_nums)withend_idx = beg_idx + 1), creates one list per input (segments = [[] for _ in range(waveform_nums)]) and adds each input's segments to its own list (segments[beg_idx] += segments_part[0]). Each batch already started from a newE2EVadModeland the initial FSMN cache, so now each input does, and a file in a list gets the same segments as on its own. A single input still returns[segments], and with the defaultbatch_size=1the loop runs the same batches as onmain.Tests
tests/test_funasr_onnx_vad_multiple_inputs.pybuildsFsmn_vadwith__new__(no model files): a realWavFrontendwith thefrontend_confoffunasr/models/fsmn_vad_streaming/template.yamland no CMVN, the realE2EVadModel, andinferreplaced by a stub that scores loud frames as speech, with the same scoring as the stub oftests/test_funasr_onnx_vad_online_short_chunks.py. It writes the three files above to a temporary directory.test_single_input: a path or an array of each file returns[segments]. This passes onmaintoo.test_list_input:[],[a, b],[b, a],[silence, b]and[a, silence, b]return one segment list per file, in input order.test_batch_size_above_one: withbatch_size2 and 3, each file on its own and the lists[a, b],[a, a],[silence, b]and[a, silence, b]return thebatch_size=1results.I added the file to
test-funasr-onnx-package.yml(both path filters, and a step after the streaming VAD check), so CI runs it against the built wheel.Type of change
Validation
python -m compileall funasr examples testsWindows, Python 3.11.17 and 3.12.15, numpy 1.26.4, onnxruntime 1.31.1, kaldi-native-fbank 1.22.5, librosa 0.11.0, soundfile 0.14.0. I built the wheel from
runtime/python/onnxruntimeatmain(f122f6c) and the wheel and the sdist on this branch, installed them into Python 3.11 and 3.12 venvs and ran the test steps of the workflow:python tests/test_funasr_onnx_vad_multiple_inputs.py:main:FAILED (failures=7, errors=12)on 3.11 and 3.12.test_single_inputpasses. The five list cases fail as in the table ([]gives[[]]). Withbatch_size2 and 3,aandbraise theIndexError,silencereturns[[], []]and[[], [], []], and the four lists raise theValueErroror, for[a, a], theTypeError.Ran 3 tests ... OKon 3.11 and 3.12, from the wheel and from the sdist.tests/test_funasr_onnx_installed.py(6 tests, from the wheel and from the sdist),tests/test_funasr_onnx_paraformer_online_overlap.py(3),tests/test_funasr_onnx_paraformer_online_empty_final.py(12),tests/test_funasr_onnx_vad_online_short_chunks.py(6), the two rich transcription test files (36 passed),twine check,pip checkand the workflow's clean-install check, which asserts version 0.4.5.tests/test_funasr_onnx_release.py(8 tests) passes withPYTHONUTF8=1. Without it,test_readme_names_the_standalone_fix_releasecannot decodeREADME.mdhere (read_text()uses the Windows locale encoding, cp1252); this PR changes neither that test norREADME.md.black --line-length=100(24.4.0, the pre-commit pin) leaves the new test unchanged. Onvad_bin.pyit reports the same existing formatting as onmain, none of it in the changed lines.git diff --checkis clean.I did not run a real FSMN-VAD ONNX model. The stub replaces only the session call.
User impact
Users of
funasr_onnx.Fsmn_vadwho pass a list of files, as the README documents, get one segment list per file, in input order, instead of the segments of every file in the first list. Withbatch_sizeabove 1, a call returns the same segments as withbatch_size=1instead of raisingIndexError,ValueErrororTypeError, or returningbatch_sizeempty lists for a file without speech. Results for a single input with the defaultbatch_size=1are unchanged.Notes for reviewers
Fsmn_vadno longer usesbatch_size: it scores one input at a time, which is all thatE2EVadModelandprepare_cachesupport. Onmain, no call withbatch_sizeabove 1 returned a segment. The argument is still accepted, and I left the README'sbatch_sizeline forFsmn_vadas it is.__call__for concurrent calls and kept the aggregation.model([])now returns[]instead of[[]], asParaformerandSenseVoiceSmalldo for an empty list.