Repository navigation
Description
ContextualParaformer.__call__ in runtime/python/onnxruntime/funasr_onnx/paraformer_bin.py (inherited by SeacoParaformer) returns results only for the first batch when a call has more files than batch_size:
model = SeacoParaformer(model_dir, batch_size=1)
res = model([wav1, wav2, wav3], "魔搭 你好") # len(res) == 1With batch_size=2, the third file is missing. Nothing is printed: each lost batch goes through the except ONNXRuntimeError branch, which logs input wav is silence or noise at WARNING and adds no result, and the package's logger setup (logging.basicConfig(level=logging.ERROR)) hides that warning in a plain script.
Cause: __call__ computes the hotword embedding once per call ([num_hotwords, 512]), but the batch loop overwrites it:
bias_embed = np.expand_dims(bias_embed, axis=0)
bias_embed = np.repeat(bias_embed, feats.shape[0], axis=0)So the second batch sends a 4-D bias_embed, and ONNX Runtime rejects it: Invalid rank for input: bias_embed Got: 4 Expected: 3. ContextualParaformer / SeacoParaformer in runtime/python/libtorch/funasr_torch/paraformer_bin.py have the same loop (bias_embed = torch.unsqueeze(bias_embed, 0).repeat(feats.shape[0], 1, 1)) and raise RuntimeError: Number of dimensions of repeat dims can not be smaller than number of dimensions of tensor on the second batch.
Repro on main (1125860) without downloading a model: tiny ONNX exports with the input/output names of funasr/models/contextual_paraformer/export_meta.py (no pretrained weights), the real WavFrontend and ONNX Runtime. Run it with runtime/python/onnxruntime on PYTHONPATH:
Script
import json
import wave
from pathlib import Path
import numpy as np
import torch
work = Path("hotword_repro")
work.mkdir(exist_ok=True)
tokens = ["<blank>", "<s>", "</s>", "你", "好", "魔", "搭", "<unk>"]
class Embedder(torch.nn.Module): # hotword [N, 10] -> hw_embed [10, N, 512]
def forward(self, hotword):
weight = torch.ones(len(tokens), 512)
return torch.nn.functional.embedding(hotword.long(), weight).transpose(0, 1)
class Backbone(torch.nn.Module): # bias_embed [B, N, 512]; every frame predicts "你"
def forward(self, speech, speech_lengths, bias_embed):
bias = torch.tensor([0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0])
logits = speech[..., :1] * 0.0 + bias_embed.sum((1, 2), keepdim=True) * 0.0 + bias
return logits, speech_lengths // 4
hotword = torch.zeros(3, 10, dtype=torch.int32)
speech, lengths = torch.zeros(2, 12, 4), torch.tensor([12, 8], dtype=torch.int32)
torch.onnx.export(
Embedder(), (hotword,), str(work / "model_eb.onnx"), dynamo=False,
input_names=["hotword"], output_names=["hw_embed"],
dynamic_axes={"hotword": {0: "N"}, "hw_embed": {1: "N"}},
)
torch.onnx.export(
Backbone(), (speech, lengths, torch.zeros(2, 3, 512)), str(work / "model.onnx"), dynamo=False,
input_names=["speech", "speech_lengths", "bias_embed"], output_names=["logits", "token_num"],
dynamic_axes={"speech": {0: "B", 1: "T"}, "speech_lengths": {0: "B"},
"bias_embed": {0: "B", 1: "N"}, "logits": {0: "B", 1: "T"}, "token_num": {0: "B"}},
)
(work / "tokens.json").write_text(json.dumps(tokens))
(work / "config.yaml").write_text(json.dumps({
"frontend_conf": {"n_mels": 4, "lfr_m": 1, "lfr_n": 1, "dither": 0.0},
"model_conf": {"predictor_bias": 1},
}))
(work / "am.mvn").write_text(
"<AddShift> 4 4\n<LearnRateCoef> 1 [ 0 0 0 0 ]\n<Rescale> 4 4\n<LearnRateCoef> 1 [ 1 1 1 1 ]\n"
)
wavs = []
for index, samples in enumerate([4000, 8000, 12000]): # 0.25 s, 0.5 s, 0.75 s
tone = (np.sin(np.arange(samples) * 2 * np.pi * 440 / 16000) * 3000).astype("<i2")
with wave.open(str(work / f"{index}.wav"), "wb") as output:
output.setnchannels(1)
output.setsampwidth(2)
output.setframerate(16000)
output.writeframes(tone.tobytes())
wavs.append(str(work / f"{index}.wav"))
from funasr_onnx import ContextualParaformer
model = ContextualParaformer(str(work), batch_size=1)
print([model(wav, "魔搭 你好")[0]["preds"][0] for wav in wavs]) # one call per file
print([row["preds"][0] for row in model(wavs, "魔搭 你好")]) # one call with all three filesOutput:
['你你你你', '你你你你你你你你你你你', '你你你你你你你你你你你你你你你你你']
['你你你你']
Expected: the second line equals the first, one result per file.
Environment: Windows 11; Python 3.11.17 with torch 2.10.0+cpu and numpy 2.4.6, and Python 3.12.15 with torch 2.8.0+cpu and numpy 1.26.4; onnx 1.19.0, onnxruntime 1.23.2, kaldi-native-fbank 1.22.5, librosa 0.11.0; funasr_onnx from runtime/python/onnxruntime on main.
Activity
LauraGPT commentedon Nov 4, 2026
The fix is merged in #3838 as
50c3b97bfb7e124f1d83958cf7211b04f70bfe68. Independent checks reproduced all eight original multi-batch failures, then passed 52 selected CPU tests and 48 real L20Z FP32 ONNX/TorchScript contract cases. Exact-head and merged-main CI artifacts were accepted separately, including package source bytes and both runtime test matrices.This establishes the batching fix with analytic fixed-weight graphs and the real wrappers/frontend; it is not pretrained recognition-quality, SeACo timestamp-output, Windows or NPU validation.
Delivery distinction: this is now a source fix. The already-published official
funasr-onnx 0.4.6andfunasr-torch 0.1.4remain unchanged and do not contain it; the CI packages using those version strings are candidate build artifacts, not replacement PyPI releases. Standalone package delivery requires new versions.