Both package cmvn readers index line_item[0] and lines[i + 1] without
checking, so a blank or whitespace-only line anywhere in the file
(trailing newline at EOF, a separator between sections) aborts frontend
construction with IndexError, and a file whose tags the walker does not
recognize returns empty statistics that only fail much later at inference.
Skip blank lines, bound the lookahead, and raise a ValueError naming the
file when neither <AddShift> nor <Rescale> parsed.
SeACo-Paraformer and Contextual Paraformer turn every line of a hotword
.txt file into a hotword. A blank line (e.g. an extra newline at the end
of the file) became an empty hotword, and its zero length made
pack_padded_sequence raise "Length of all samples has to be greater
than 0" in the bias encoder, so inference failed.
The file was also opened with codecs.open() without an encoding, i.e.
the locale encoding. On Windows a UTF-8 Chinese hotword file decoded
into mojibake, so every hotword became <unk> (or reading raised
UnicodeDecodeError on GBK locales).
Read the local and downloaded hotword files as UTF-8 and skip blank
lines, matching parse_hotword_file() in funasr/utils/postprocess_hotwords.py.
A blank or whitespace-only line in a file list crashes prepare_data_iterator:
the id/data branch indexes lines[0] on an empty split (IndexError), and the
.jsonl branch feeds an empty string to json.loads (JSONDecodeError). Skip
blank lines so a trailing or separator line no longer aborts the whole batch.
Co-authored-by: Lesereingrape <[email protected]>
rich_transcription_postprocess() maps SenseVoice event tags through
event_dict, which used 🤧 for both <|Sneeze|> and <|Cough|>, so coughs
were displayed as sneezes. emoji_dict and event_set already use 😷 for
coughs; use it in event_dict too, in funasr and in the funasr_onnx and
funasr_torch runtime copies.
timestamp_sentence() stripped the trailing space from sentence_text_seg
on every token instead of once when the sentence is emitted, so the
per-sentence raw_text lost all separators ("iphone pro max" became
"iphonepromax"). Trim it only at the sentence boundary, as
timestamp_sentence_en() already does.
The fuzzy key was built with lazy_pinyin(errors="ignore"), which drops
every non-Chinese character. A window holding the hotword plus an
adjacent full stop, comma, digit or Latin letter got the same key as the
target and scored 1.0, and the tie-break on length then picked it, so
the replacement deleted that character. A Latin-only target such as
"FunASR" had an empty key and matched every Latin window of similar
length, overwriting the transcript.
Exact occurrences of a target were skipped but not reserved, so an
overlapping near match could still rewrite correct text and duplicate
the first character of the hotword.
Keep non-Chinese characters in the key, and drop a fuzzy candidate when
it overlaps an exact occurrence of the target it would write. Other
targets can still replace across that span, so a short target found
inside a longer one does not block the longer one.
Lower-cased Latin letters can spell pinyin, so a window with Latin
letters is only compared with targets that contain Latin letters, and a
window with Chinese characters only with targets that contain Chinese
characters. Targets that mix both scripts are compared with any window.
A window also may not start or end inside a run of ASCII letters or
digits, so a Latin target cannot rewrite part of a word or a number
(with target "Win11", "Win10" no longer becomes "Win110").
When punc_id_list is longer than timestamp_postprocessed (a mismatch
the function already logs a warning for), the entries past the end of
timestamp_postprocessed get timestamp=None from zip_longest. For the
sentence that starts on one of those entries, the is_sentence_start
flag was consumed unconditionally on the first iteration regardless of
whether timestamp was None, so sentence_start kept whatever value was
left over from the previous, unrelated sentence instead of being
reported as unknown.
The sibling function timestamp_sentence already handles this correctly
by resetting sentence_start to None after each flush and only setting
it once a real timestamp shows up. Mirror that logic here instead of
the one-shot is_sentence_start flag.
Before: a trailing sentence whose first word has no timestamp gets
start reused from the previous sentence (wrong, looks valid).
After: start is None for that sentence, matching timestamp_sentence.
* fix(amp): resolve autocast/GradScaler device type for non-CUDA accelerators
funasr/utils/amp.py pinned device_type="cuda" in the torch.amp compatibility shim, so on Ascend NPU (and XPU/MPS) builds torch.amp only warned "CUDA is not available ... Disabling autocast" and the context became a no-op: use_fp16/use_bf16 training silently ran in float32 and GradScaler(enabled=True) was silently disabled.
Resolve the device type from the running accelerator (torch.accelerator), the same policy funasr/models/fun_asr_nano/device_utils.py already applies to the inference path, and keep "cuda" as the historical no-op default when no accelerator is available so CPU behaviour is unchanged. GradScaler prefers an accelerator-specific implementation (e.g. torch.npu.amp.GradScaler) when one exists.
Signed-off-by: li-lizhe <[email protected]>
* fix(amp): honour an explicit device before choosing the GradScaler implementation
An explicit device (positional or keyword) now selects the scaler for that
device instead of the active accelerator, so GradScaler(device="cpu") no longer
reaches torch.npu.amp.GradScaler (its constructor has no device parameter and
raised TypeError) and GradScaler("cpu") no longer consumed "cpu" as init_scale.
The device argument is dropped before dispatching to an accelerator-specific
scaler and forwarded unchanged to the generic one; torch.cpu.amp is no longer
mistaken for a backend-specific scaler. Explicit keyword/positional/torch.device
coverage added.
Signed-off-by: li-lizhe <[email protected]>
* fix(amp): bind the generic GradScaler signature before native dispatch
The accelerator scalers do not share the generic torch.amp.GradScaler
positional order: torch_npu inserts `dynamic` before `enabled`, so
forwarding the caller's positional tail unchanged turned
GradScaler("npu", 1024.0, 2.0, 0.5, 2000, False) into an enabled static
scaler while the keyword form kept working. Bind a call written against
the generic signature to its parameter names first, drop the `device`
the accelerator constructor does not accept, and forward the rest by
name. Calls that do not match the generic signature keep the previous
plain re-export behaviour.
Tests: cover the positional enabled=False/True tail against a strict
torch_npu-signature stand-in (a *args/**kwargs recorder cannot detect a
shifted argument slot).
Signed-off-by: li-lizhe <[email protected]>
* docs(amp): narrow the _generic_arguments docstring claim
Signed-off-by: li-lizhe <[email protected]>
---------
Signed-off-by: li-lizhe <[email protected]>
Three modules use invalid escapes in non-raw string literals:
- fun_text_processing/text_normalization/normalize.py - \., \w
- funasr/models/fsmn_kws/encoder.py - \[
- funasr/models/fun_asr_nano/tools/format5res.py - \.
On Python 3.12+ each raises SyntaxWarning: invalid escape sequence on
import, and FunASR runs the 3.10-3.12 CI matrix. Escaping the
backslashes leaves every string value identical (ruff W605-style).
Claude-Session: https://claude.ai/code/session_014HBQNzAf5C2E3MiJC48HQw
Co-authored-by: Mehdi Boutayeb <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>