Skip to content

fix(runtime): cut long hotwords to 10 characters and skip empty ones - #3882

Merged
LauraGPT merged 1 commit into
modelscope:mainfrom
tanishqbaweja:fix/runtime-long-hotwords
Nov 12, 2026
Merged

LauraGPT merged 1 commit into
modelscope:mainfrom
tanishqbaweja:fix/runtime-long-hotwords

Conversation

@tanishqbaweja

Copy link
Copy Markdown
Contributor

Summary

Fixes #3881

ContextualParaformer.proc_hotword in funasr_onnx and funasr_torch (SeacoParaformer inherits it) split the hotword string with split(" ") and padded each hotword into a row of 10 ids with pad_list(..., max_len=10):

  • A hotword longer than 10 characters did not fit in its row, so the whole call raised ValueError: could not broadcast input array from shape (12,) into shape (10,) (for 12 characters).
  • Repeated, leading or trailing spaces, and an empty hotword string, gave empty hotwords. Each became a row of ten 0 ids whose embedding was read at position -1 (the last of the 10), and went to the model as an extra hotword.

The exported embedding model takes exactly 10 ids per hotword: ContextualEmbedderExport (funasr/models/seaco_paraformer/export_meta.py, subclassed for the Contextual Paraformer export) makes only num_hotwords dynamic, so model_eb.onnx declares its input as ['num_hotwords', 10]. The C++ runtime keeps the first max_hotword_len = 10 tokens of each hotword and skips hotwords without tokens (CompileHotwordEmbedding in runtime/onnxruntime/src/paraformer.cpp:565-607 and paraformer-torch.cpp:530-572).

This PR does the same in proc_hotword (three lines of code and a two-line comment per file):

  • split() instead of split(" "), as generate_hotwords_list splits in funasr/models/seaco_paraformer/model.py (:688) and funasr/models/contextual_paraformer/model.py (:653). This drops the empty strings.
  • Each hotword keeps its first max_hotword_len = 10 characters, and pad_list uses the same constant. These wrappers map one character to one id, so that is 10 ids, as in C++.

What proc_hotword returns (each hotword's ids without padding, <s> = 1, and the positions where __call__ reads the embeddings), with the toy vocabulary of the script in the issue:

hotwords main this PR
'魔搭 达摩院' [[14, 15], [6, 7, 8], [1]], [1, 2, 0] unchanged
'魔搭 达摩院' [[14, 15], [], [6, 7, 8], [1]], [1, -1, 2, 0] [[14, 15], [6, 7, 8], [1]], [1, 2, 0]
'' [[], [1]], [-1, 0] [[1]], [0]
'魔搭 阿里巴巴达摩院语音实验室' (12 characters) ValueError [[14, 15], [3, 4, 5, 5, 6, 7, 8, 9, 10, 11], [1]], [1, 9, 0]

Type of change

  • Bug fix
  • Documentation
  • Example or demo
  • Runtime or deployment
  • Benchmark or evaluation
  • Model/training change

Validation

I added test_hotword_wrappers_skip_empty_and_cut_long_hotwords to tests/test_runtime_eval_native.py, for ContextualParaformer and SeacoParaformer in funasr_onnx and funasr_torch. It loads each wrapper with its real constructor from the hotword fixture graphs of test_hotword_wrappers_decode_every_batch (whose model_eb.onnx has the same dynamic axes as the export). It checks the hotword ids and embedding positions for " 魔搭 你好魔搭你好魔搭你好魔搭 " and "", and that both strings decode all three fixture files.

test-runtime-eval.yml runs this file for changes under runtime/python/onnxruntime/funasr_onnx/** and runtime/python/libtorch/funasr_torch/**, so no workflow change is needed. With the pins it installs (Python 3.12.15, torch 2.8.0+cpu, numpy 1.26.4, onnx 1.19.0, onnxruntime 1.23.2), on Windows 11:

python -m pytest -q tests/test_runtime_eval_cli.py tests/test_runtime_eval_native.py

Also:

  • The same command with Python 3.11.17, numpy 2.4.6, torch 2.10.0+cpu, onnxruntime 1.23.2: 4 failed, 55 passed on main source; 59 passed on this branch.

  • tests/test_rich_transcription_postprocess.py, tests/test_rich_transcription_event_segments.py and tests/test_paraformer_hotword_file.py: 40 passed.

  • The funasr_onnx package scripts pass from source: the release contract (with PYTHONUTF8=1 on Windows), overlap, empty_final, vad_online_short_chunks, vad_multiple_inputs and punctuation_empty_input. test_funasr_onnx_installed.py passes 9 of 10; the other one reads the metadata of an installed wheel.

  • tests/test_funasr_torch_package.py: 13 passed, 1 failed, the same as on main. The failure is test_wheel_contains_all_runtime_sources, which on Windows builds its expected file names with backslashes.

  • I exported a tiny embedder (random embedding and LSTM) with ContextualEmbedderExport itself, using its dummy input, input/output names and dynamic axes, and opset 14 (the default in funasr/utils/export_utils.py). ONNX Runtime runs a [3, 10] hotword matrix and rejects [3, 12] with Got invalid dimensions for input: hotword. So padding the rows to the longest hotword instead would fail with an exported model.

  • black --line-length=100 (24.4.0, as in pre-commit) leaves the test file unchanged and does not touch the changed lines of the two paraformer_bin.py files (it would reformat other lines of those files, on main too).

  • python -m compileall funasr examples tests

  • Docs or links checked

  • Runtime/deployment command tested (the wrappers with the synthetic fixture graphs, CPU)

No real model was run.

User impact

Users of funasr_onnx or funasr_torch ContextualParaformer and SeacoParaformer can pass hotwords longer than 10 characters: the model gets their first 10 characters, as with the C++ runtime, instead of the whole call failing. Extra spaces between, before or after hotwords, and an empty hotword string, no longer add a padding-only hotword. Hotword strings with single spaces and hotwords of up to 10 characters give the same model input as before.

Notes for reviewers

ContextualParaformer.proc_hotword in funasr_onnx and funasr_torch
(SeacoParaformer inherits it) split the hotword string with split(" ")
and padded each hotword into a row of 10 ids. A hotword longer than 10
characters did not fit, so the whole call raised "ValueError: could not
broadcast input array from shape (12,) into shape (10,)". Repeated,
leading or trailing spaces, and an empty hotword string, gave empty
hotwords: rows of padding whose embedding was read at position -1 and
passed to the model as extra hotwords.

The exported embedding model takes exactly 10 ids per hotword (only
num_hotwords is dynamic in ContextualEmbedderExport), and the C++
runtime (CompileHotwordEmbedding in paraformer.cpp and
paraformer-torch.cpp) keeps the first max_hotword_len = 10 tokens of
each hotword and skips empty ones. Do the same: split on whitespace, as
generate_hotwords_list in funasr/models does, and keep the first 10
characters of each hotword.

Extend tests/test_runtime_eval_native.py: for both classes in both
packages, built from the hotword fixture graphs, check the hotword ids
and embedding positions for a string with extra spaces and a
12-character hotword and for an empty string, and that both strings
decode every file.
LauraGPT added a commit that referenced this pull request Nov 12, 2026
Retain the original #3882 author commit and carry forward the exact accepted 1.4.26 main tree. Only the original three-file hotword patch differs from main.

Signed-off-by: LauraGPT <zhifu.gzf@alibaba-inc.com>
LauraGPT added a commit that referenced this pull request Nov 12, 2026
Preserve #3882 author ancestry. Current-main CPU and real CUDA synthetic-fixture contracts, exact-head CI artifacts and tested merge tree accepted. No pretrained-quality or full-suite claim.

Signed-off-by: LauraGPT <zhifu.gzf@alibaba-inc.com>
@LauraGPT
LauraGPT merged commit ae06bf3 into modelscope:main Nov 12, 2026
6 checks passed
LauraGPT added a commit that referenced this pull request Nov 13, 2026
Publish the already accepted #3869/#3871/#3882 standalone wrapper fixes with hosted regression coverage.

Signed-off-by: LauraGPT <zhifu.gzf@alibaba-inc.com>
LauraGPT added a commit that referenced this pull request Nov 13, 2026
Accept tested standalone package bytes and the already accepted exact-source #3869/#3871/#3882 runtime evidence.

Signed-off-by: LauraGPT <zhifu.gzf@alibaba-inc.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

funasr_onnx / funasr_torch ContextualParaformer and SeacoParaformer: a hotword longer than 10 characters raises ValueError

2 participants