Skip to content

funasr_onnx streaming Paraformer repeats the last characters when chunk_size[0] is 0 #3789

Open

Description

@ychy7001

Description

funasr_onnx.paraformer_online_bin.Paraformer.add_overlap_chunk keeps the left context for the next chunk with

cache["feats"] = overlap_feats[:, -self.chunk_size[0] :, :]

When chunk_size[0] == 0, -0: is 0:, so the slice returns every frame instead of none. When the final call is split into two chunks, the second chunk is prefixed with all of the first chunk's features, so the tail is decoded twice and the last characters are repeated.

chunk_size=[0, 10, 5] is the streaming setting used in README.md, README_zh.md and docs/python_api*.md, so users following the docs with the ONNX runtime hit this. The package's own demo uses [5, 10, 5] and is not affected. The C++ runtime does not use this code path.

The non-final branch has the same pattern (-(chunk_size[0] + chunk_size[2]):), which only matters when both values are 0.

Reproduction

Environment: funasr and funasr_onnx from main (fbbfe01), onnxruntime 1.20.1, numpy 1.26.4, Python 3.12, CPU.

  1. Download and export the model (writes model.onnx and decoder.onnx next to the checkpoint):
from modelscope import snapshot_download
from funasr import AutoModel

model_dir = snapshot_download(
    "iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online", revision="v2.0.5"
)
AutoModel(model=model_dir, device="cpu", disable_update=True).export(type="onnx", quantize=True)
  1. Optional, to get deterministic output: add dither: 0.0 under frontend_conf: in model_dir/config.yaml. The config does not set it, so the frontend uses its default dither=1.0. The numbers below use 0.0; the duplication also appears with the default.

  2. Decode in 9600-sample (600 ms) pieces with chunk_size=[0, 10, 5]:

import soundfile as sf
from funasr_onnx.paraformer_online_bin import Paraformer

model = Paraformer(model_dir, batch_size=1, chunk_size=[0, 10, 5], quantize=False)
speech, _ = sf.read("BAC009S0764W0129.wav", dtype="float32")  # AISHELL-1 test
cache, out, step = {}, [], 9600
n = (len(speech) - 1) // step + 1
for i in range(n):
    res = model(speech[i * step : (i + 1) * step], param_dict={"cache": cache, "is_final": i == n - 1})
    out += [r["preds"][0] for r in res if r]
print("".join(out))

AISHELL-1 test examples:

utt reference current main with fix PyTorch AutoModel (same chunk_size, look-back 0)
BAC009S0764W0129 也助推了土地市场的火爆 也助推了土地市场的火爆爆爆 也助推了土地市场的火爆 也助推了土地市场的火爆
BAC009S0764W0126 因此土地储备至关重要 因此土地储备至关重要要要 因此土地储备至关重要要 因此土地储备至关重要要
BAC009S0764W0139 国家统计局的数据显示 国家统计局的数据显示是 国家统计局的数据显示 国家统计局的数据显示

The single repeat left in W0126 also appears in the PyTorch reference with the same settings, so the fix does not claim to remove every trailing repeat; it only removes the extra decoding of the previous chunk.

On the full AISHELL-1 test set (7176 utterances), 2170 outputs (30.2%) change after the fix, and CER (punctuation removed) goes from 17.23% to 15.18% with the FP32 export and from 17.15% to 15.08% with the quantized export. The PyTorch reference with the same settings is 14.81%.

Proposed fix

Compute the start index explicitly, feats[:, max(feats.shape[1] - n, 0):, :], for both cache updates. PR to follow.

Activity

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