Repository navigation
fix(runtime): cut long hotwords to 10 characters and skip empty ones - #3882
Merged
LauraGPT merged 1 commit intoNov 12, 2026
Merged
Conversation
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
added a commit
that referenced
this pull request
Nov 13, 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 #3881
ContextualParaformer.proc_hotwordinfunasr_onnxandfunasr_torch(SeacoParaformerinherits it) split the hotword string withsplit(" ")and padded each hotword into a row of 10 ids withpad_list(..., max_len=10):ValueError: could not broadcast input array from shape (12,) into shape (10,)(for 12 characters).-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 onlynum_hotwordsdynamic, somodel_eb.onnxdeclares its input as['num_hotwords', 10]. The C++ runtime keeps the firstmax_hotword_len = 10tokens of each hotword and skips hotwords without tokens (CompileHotwordEmbeddinginruntime/onnxruntime/src/paraformer.cpp:565-607andparaformer-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 ofsplit(" "), asgenerate_hotwords_listsplits infunasr/models/seaco_paraformer/model.py(:688) andfunasr/models/contextual_paraformer/model.py(:653). This drops the empty strings.max_hotword_len = 10characters, andpad_listuses the same constant. These wrappers map one character to one id, so that is 10 ids, as in C++.What
proc_hotwordreturns (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:main'魔搭 达摩院'[[14, 15], [6, 7, 8], [1]],[1, 2, 0]'魔搭 达摩院'[[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
Validation
I added
test_hotword_wrappers_skip_empty_and_cut_long_hotwordstotests/test_runtime_eval_native.py, forContextualParaformerandSeacoParaformerinfunasr_onnxandfunasr_torch. It loads each wrapper with its real constructor from the hotword fixture graphs oftest_hotword_wrappers_decode_every_batch(whosemodel_eb.onnxhas 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.ymlruns this file for changes underruntime/python/onnxruntime/funasr_onnx/**andruntime/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:mainsource (0e52c3c): 4 failed, 55 passed. All four fail withValueError: could not broadcast input array from shape (12,) into shape (10,).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
mainsource; 59 passed on this branch.tests/test_rich_transcription_postprocess.py,tests/test_rich_transcription_event_segments.pyandtests/test_paraformer_hotword_file.py: 40 passed.The
funasr_onnxpackage scripts pass from source: the release contract (withPYTHONUTF8=1on Windows),overlap,empty_final,vad_online_short_chunks,vad_multiple_inputsandpunctuation_empty_input.test_funasr_onnx_installed.pypasses 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 onmain. The failure istest_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
ContextualEmbedderExportitself, using its dummy input, input/output names and dynamic axes, and opset 14 (the default infunasr/utils/export_utils.py). ONNX Runtime runs a[3, 10]hotword matrix and rejects[3, 12]withGot 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 twoparaformer_bin.pyfiles (it would reformat other lines of those files, onmaintoo).python -m compileall funasr examples testsDocs 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_onnxorfunasr_torchContextualParaformerandSeacoParaformercan 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
logging.warningfor cut hotwords if you prefer.split()also splits on tabs and newlines, asgenerate_hotwords_listdoes.split(" ")kept them inside a hotword, where they became<unk>ids with an OOV warning. The C++ runtime splits on spaces only.<unk>with a warning (the C++ runtime skips such hotwords). This PR does not change that.git merge-tree --write-treeof this branch with the heads of fix(runtime): trim batch padding from CIF peaks before funasr_onnx and funasr_torch Paraformer timestamps #3871 (which also edits bothparaformer_bin.pyfiles and the test file), Fix/security hardening #3877, fix(runtime): honor use_itn in funasr_onnx and funasr_torch SenseVoiceSmall #3860 and fix(runtime): honor SenseVoice ITN and preserve mixed batch query tags #3861 gives no conflicts.