fix(tokenizer): treat special-token strings as text to prevent batch crashes (#3110)#3111
fix(tokenizer): treat special-token strings as text to prevent batch crashes (#3110)#311114790897 wants to merge 1 commit into
Conversation
…crashes ASR models such as Fun-ASR-Nano occasionally emit special-token strings (e.g. <|no|>, a language tag) as part of their transcription text. When that text is re-encoded via Tokenizer.encode for downstream tasks (forced alignment, loss computation), tiktoken rejects it by default (disallowed_special="all"), crashing the whole batch on a single bad sample (issue modelscope#3110). Tokenizer.encode now defaults disallowed_special=() so special-token strings are treated as ordinary text. Callers that explicitly pass allowed_special / disallowed_special are left untouched (setdefault), so decoding.py's encode(prompt, allowed_special="all") still encodes special tokens as their token ids, and any caller wanting the strict behaviour can still opt in. Adds tests/test_sensevoice_tokenizer_special_tokens.py with a minimal in-memory tiktoken encoding (no model download or GPU required). Fixes modelscope#3110
There was a problem hiding this comment.
Code Review
This pull request addresses issue #3110 by updating the encode method in the SenseVoice tokenizer to treat special-token strings as ordinary text by default. This is achieved by setting disallowed_special to an empty tuple in kwargs if not already specified, preventing crashes when tiktoken encounters special tokens in transcription text. Additionally, a comprehensive suite of unit tests has been added to verify this behavior and ensure regression protection. I have no further feedback to provide as there are no review comments to assess.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Summary
When transcribing long audio in a batch with
FunAudioLLM/Fun-ASR-Nano-2512(+vad_model=fsmn-vad,spk_model=campplus), a single sample can crash withEncountered text corresponding to disallowed special token '<|no|>'.and abort the entire batch, even though every other sample transcribes fine (stable RTF). Closes #3110.Root cause. The model occasionally emits a special-token string (e.g.
<|no|>, the Norwegian language tag) as part of its transcription text. That text is then re-encoded for forced alignment / loss computation:self.ctc_tokenizerresolves toSenseVoiceTokenizer(funasr/tokenizer/whisper_tokenizer.py:30) →funasr/models/sense_voice/whisper_lib/tokenizer.py::Tokenizer, whoseencodeforwards straight totiktoken.Encoding.encode:tiktoken defaults to
disallowed_special="all", so any text containing a special-token string raisesValueError, taking down the whole batch.model.py:921already strips<|nospeech|>from the CTC text — which confirms ASR output can leak special-token strings — but the allow-list isn't exhaustive (language tags like<|no|>slip through).Fix. Default
disallowed_special=()inTokenizer.encodeso special-token strings are treated as ordinary text unless a caller explicitly opts into stricter behaviour:Type of change
Validation
python -m compileall funasr examples testspython -m compileall -q funasr examples testsexits 0 (only two pre-existingSyntaxWarnings inexamples/industrial_data_pretraining/fun_asr_nano/tools/format5res.py, untouched by this PR).Adds
tests/test_sensevoice_tokenizer_special_tokens.py. It builds a minimal in-memorytiktoken.Encoding(byte-level BPE + the specialsTokenizer.__post_init__needs), so it runs without themultilingual.tiktokenvocab (ships with the model, not this repo), without a GPU and without a model download:<|no|>/<|nospeech|>no longer raises (regression for ASR Inference Failure: Disallowed special<|no|>` causes single-sample transcription crash in batch processing #3110)disallowed_special="all"still raises (back-compat)allowed_special="all"still returns the special-token id (back-compat fordecoding.py)Local exercise of the fix against the minimal encoding (
importlib-loadedtokenizer.pyto bypass the heavyfunasrpackage import chain on a machine withouttorch/kaldiio):User impact
Production deployers running Fun-ASR-Nano (and any model reusing the SenseVoice
Tokenizer) on batch / streaming ASR pipelines. Today a single bad sample aborts the whole batch; after this PR that sample is transcribed with the stray token treated as text and the batch completes. Researchers usingctc_tokenizer.encodefor forced alignment / loss hit the same crash.Notes for reviewers
Why the tokenizer, not the call sites. There are multiple call sites that re-encode ASR output —
fun_asr_nano/model.py:443, 495, 923, 928,inference_vllm.py:668,inference_vllm_pipeline.py:348. Patching the tokenizer covers all of them in one place, and also protects SenseVoice and any future model that reuses thisTokenizer.Backward compatibility.
setdefaultkeeps the change fully backward compatible:disallowed_special=...are untouched.decoding.py'stokenizer.encode(prompt, allowed_special="all")still encodes special tokens as their token ids (verified in case 4 above).non_speech_tokenscached_property callsself.encoding.encode(...)directly (notself.encode), so it's unaffected.Scope. One-line behavioural default in
Tokenizer.encode+ regression tests. No model weights, no config, no public API change.