Skip to content

funasr_onnx streaming Paraformer returns inconsistent structures for empty results #3791

Open

Description

@ychy7001

Description

funasr_onnx.paraformer_online_bin.Paraformer.__call__ returns different structures when there is no recognition result, depending on which path is taken:

case return value
non-final call, no tokens (infer → asr_res = []) []
final call handled as one chunk, decoder runs but text is empty [{'preds': ['', []]}]
final call split into two chunks, neither chunk has a result (lines 172-186 merge into res = {}) [{}]

The last case means callers that read res[0]["preds"] after checking if res: hit KeyError: 'preds'; they have to check for an empty dict as well.

Reproduction

main (fbbfe01), iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online v2.0.5 exported to ONNX, onnxruntime 1.20.1, CPU. Silence is enough to trigger it; chunk_size=[5, 10, 5] (the package default) is used, so this is unrelated to #3789.

import numpy as np
from funasr_onnx.paraformer_online_bin import Paraformer

model = Paraformer(model_dir, batch_size=1, chunk_size=[5, 10, 5], quantize=False)
for n_final in (9600, 16000):
    cache = {}
    r1 = model(np.zeros(9600, np.float32), param_dict={"cache": cache, "is_final": False})
    r2 = model(np.zeros(n_final, np.float32), param_dict={"cache": cache, "is_final": True})
    print(n_final, r1, r2)

Output:

9600 [] [{'preds': ['', []]}]
16000 [] [{}]

Question

What is the intended return value when there is no result? Returning [] in the split-final path, consistent with the non-final path, looks simplest, but I am not sure whether existing callers rely on [{}] or on [{'preds': ['', []]}]. If you confirm the expected contract, I can send a small PR with a regression test.

Activity

  1. ychy7001 commented on Oct 24, 2026

    @ychy7001
    Author

    Follow-up after checking the unmodified fbbfe01 runtime with the official v2.0.5 FP32 export (onnxruntime 1.20.1 CPU, chunk_size=[5, 10, 5], no patches).

    Correction to the table in the description. The single-chunk empty-text result is [{"preds": ("", [])}] with a tuple under preds. The ['', []] I listed comes from the split-final path, and my 9600-sample final call in the original reproduction also takes that path, so it does not show the single-chunk type:

    for n in (4800, 9600):
        cache = {}
        r = model(np.zeros(n, np.float32), param_dict={"cache": cache, "is_final": True})
        print(n, cache["start_idx"], repr(r), type(r[0]["preds"]))
    # 4800 5 [{'preds': ('', [])}] <class 'tuple'>
    # 9600 10 [{'preds': ['', []]}] <class 'list'>

    Reproducible trigger. [{}] breaks the access pattern in runtime/python/onnxruntime/demo_paraformer_online.py (if len(rec_result) > 0: rec_result[0]["preds"][0]):

    cache = {}
    model(np.zeros(9600, np.float32), param_dict={"cache": cache, "is_final": False})
    rec_result = model(np.zeros(16000, np.float32), param_dict={"cache": cache, "is_final": True})
    print(rec_result)                 # [{}]
    rec_result[0]["preds"][0]         # KeyError: 'preds'

    I could only trigger it with all-silence input and a final chunk larger than the demo's usual step. AISHELL-1 speech followed by 0–5 s of silence, stepped at 9600 samples, did not trigger it, so I am not claiming that trailing silence generally crashes the demo.

    Proposed fix. Only return [res] if res else [] at the end of the split-final merge, with regression tests (PR to follow). Tuple/list normalization is out of scope. A caller could in theory use [{}] as a status signal; the change makes the no-result case consistent with infer(), which returns [].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions