3D-Speaker-MT.Axera / utils /ax_model_bin.py
wangli
Upload folder using huggingface_hub
9b44474 verified
Raw
History Blame Contribute Delete
12.7 kB
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
# Copyright FunASR (https://github.com/FunAudioLLM/SenseVoice). All Rights Reserved.
# MIT License (https://opensource.org/licenses/MIT)
import os.path
from pathlib import Path
from typing import List, Union, Tuple
TORCH_AVAILABLE = False
import numpy as np
import axengine as axe
try:
import librosa
except ImportError:
print("Warning: librosa not found. Please install it using 'pip install librosa'.")
# Provide a fallback implementation if needed
def load_wav_fallback(path, sr=None):
import wave
import numpy as np
with wave.open(path, 'rb') as wf:
num_frames = wf.getnframes()
frames = wf.readframes(num_frames)
return np.frombuffer(frames, dtype=np.int16).astype(np.float32) / 32768.0, wf.getframerate()
from utils.infer_utils import (
CharTokenizer,
get_logger,
read_yaml,
)
from utils.frontend import WavFrontend
logging = get_logger()
def sequence_mask_np(lengths, maxlen=None, dtype=np.float32):
lengths = np.asarray(lengths).astype(np.int64)
if maxlen is None:
maxlen = int(lengths.max())
row_vector = np.arange(0, maxlen, 1)
matrix = lengths.reshape(-1, 1)
mask = row_vector < matrix
return mask.astype(dtype)
def unique_consecutive_np(arr: np.ndarray) -> np.ndarray:
if arr.size == 0:
return arr
out = [arr[0]]
for v in arr[1:]:
if v != out[-1]:
out.append(v)
return np.asarray(out, dtype=arr.dtype)
class AX_SenseVoiceSmall:
"""
Author: Speech Lab of DAMO Academy, Alibaba Group
Paraformer: Fast and Accurate Parallel Transformer for Non-autoregressive End-to-End Speech Recognition
https://arxiv.org/abs/2206.08317
"""
def __init__(
self,
model_dir: Union[str, Path] = None,
batch_size: int = 1,
seq_len: int = 68
):
model_file = os.path.join(model_dir, "sensevoice.axmodel")
config_file = os.path.join(model_dir, "sensevoice/config.yaml")
cmvn_file = os.path.join(model_dir, "sensevoice/am.mvn")
config = read_yaml(config_file)
self.model_dir = model_dir
# token_list = os.path.join(model_dir, "tokens.json")
# with open(token_list, "r", encoding="utf-8") as f:
# token_list = json.load(f)
# self.converter = TokenIDConverter(token_list)
self.tokenizer = CharTokenizer()
config["frontend_conf"]['cmvn_file'] = cmvn_file
self.frontend = WavFrontend(**config["frontend_conf"])
# self.ort_infer = OrtInferSession(
# model_file, device_id, intra_op_num_threads=intra_op_num_threads
# )
self.session = axe.InferenceSession(model_file)
self.batch_size = batch_size
self.blank_id = 0
self.seq_len = seq_len
self.lid_dict = {"auto": 0, "zh": 3, "en": 4, "yue": 7, "ja": 11, "ko": 12, "nospeech": 13}
self.lid_int_dict = {24884: 3, 24885: 4, 24888: 7, 24892: 11, 24896: 12, 24992: 13}
self.textnorm_dict = {"withitn": 14, "woitn": 15}
self.textnorm_int_dict = {25016: 14, 25017: 15}
self.emo_dict = {"unk": 25009, "happy": 25001, "sad": 25002, "angry": 25003, "neutral": 25004}
def __call__(self,
wav_content: Union[str, np.ndarray, List[str]],
language: str,
withitn: bool,
position_encoding: np.ndarray,
tokenizer=None,
**kwargs) -> List:
"""Enhanced model inference with additional features from model.py
Args:
wav_content: Audio data or path
language: Language code for processing
withitn: Whether to use ITN (inverse text normalization)
position_encoding: Position encoding tensor
tokenizer: Tokenizer for text conversion
**kwargs: Additional arguments
"""
# Start time tracking for metadata
import time
meta_data = {}
time_start = time.perf_counter()
# Load waveform data
waveform_list = self.load_data(wav_content, self.frontend.opts.frame_opts.samp_freq)
waveform_nums = len(waveform_list)
time_load = time.perf_counter()
meta_data["load_data"] = f"{time_load - time_start:0.3f}"
# Get key for result identification
key = kwargs.get("key", ["wav_file"])
if isinstance(wav_content, str):
wav_name = os.path.splitext(os.path.basename(wav_content))[0]
if key == ["wav_file"]:
key = [wav_name]
# Load queries from saved numpy files
language_query = np.load(os.path.join(self.model_dir, f"{language}.npy"))
textnorm_query = np.load(os.path.join(self.model_dir, "withitn.npy") if withitn
else os.path.join(self.model_dir, "woitn.npy"))
event_emo_query = np.load(os.path.join(self.model_dir, "event_emo.npy"))
# Concatenate queries to form input_query
input_query = np.concatenate((language_query, event_emo_query, textnorm_query), axis=1)
# Setup dataset directories for saving intermediate files
dataset = "dataset"
os.makedirs(dataset, exist_ok=True)
speech_dir = os.path.join(dataset, "speech", language, "withitn" if withitn else "woitn")
mask_dir = os.path.join(dataset, "masks", language, "withitn" if withitn else "woitn")
pe_dir = os.path.join(dataset, "position_encoding", language, "withitn" if withitn else "woitn")
os.makedirs(speech_dir, exist_ok=True)
os.makedirs(mask_dir, exist_ok=True)
os.makedirs(pe_dir, exist_ok=True)
# Process features
results = []
output_timestamp = kwargs.get("output_timestamp", False)
ban_emo_unk = kwargs.get("ban_emo_unk", False)
ibest_writer = None
# 添加时间偏移变量,用于跟踪连续的时间戳
time_offset = 0.0
# 添加合并时间戳变量,用于存储同一音频文件的所有时间戳,仅保留必要功能
merged_timestamps = []
merged_words = []
# Handle output_dir without using DatadirWriter (which is not available)
output_dir = kwargs.get("output_dir")
slice_len = self.seq_len - 4
time_pre = time.perf_counter()
meta_data["preprocess"] = f"{time_pre - time_load:0.3f}"
for beg_idx in range(0, waveform_nums, self.batch_size):
end_idx = min(waveform_nums, beg_idx + self.batch_size)
feats, feats_len = self.extract_feat(waveform_list[beg_idx:end_idx])
time_feat = time.perf_counter()
meta_data["extract_feat"] = f"{time_feat - time_pre:0.3f}"
for i in range(int(np.ceil(feats.shape[1] / slice_len))):
sub_feats = np.concatenate([input_query, feats[:, i*slice_len : (i+1)*slice_len, :]], axis=1)
feats_len[0] = sub_feats.shape[1]
# 计算当前片段的实际长度(帧数)
actual_slice_length = min(slice_len, feats.shape[1] - i*slice_len)
if feats_len[0] < self.seq_len:
sub_feats = np.concatenate([sub_feats, np.zeros((1, self.seq_len - feats_len[0], 560), dtype=np.float32)], axis=1)
masks = sequence_mask_np([self.seq_len], maxlen=self.seq_len, dtype=np.float32)[:, None, :]
# Run inference
ctc_logits, encoder_out_lens = self.infer(sub_feats, masks, position_encoding)
# Ban emotion unknown token if requested
if ban_emo_unk:
ctc_logits[:, :, self.emo_dict["unk"]] = -float("inf")
# Process results for each batch
b, n, d = ctc_logits.shape
if isinstance(key, (list, tuple)) and len(key) < b:
key = key * b
for j in range(b):
enc_len = int(encoder_out_lens[j]) if not hasattr(encoder_out_lens[j], "item") else int(encoder_out_lens[j].item())
x = ctc_logits[j, : enc_len, :]
yseq = np.argmax(x, axis=-1)
yseq = unique_consecutive_np(yseq)
mask = yseq != self.blank_id
token_int = yseq[mask].tolist()
# Convert tokens to text
text = tokenizer.decode(token_int) if tokenizer is not None else str(token_int)
# 文本处理
# 简化文本处理,不执行合并操作
# Write to output directory if provided
if ibest_writer is not None:
ibest_writer["text"][key[j]] = text
if output_timestamp:
# Torch-free build: skip timestamp generation
output_timestamp = False
result_i = {"key": key[j] if j < len(key) else f"result_{j}", "text": text}
# 直接添加结果,重复处理将在export.py中进行
results.append(result_i)
time_offset = round(time_offset + (actual_slice_length * 60 ) / 1000, 2)
# 结果处理完成
time_end = time.perf_counter()
meta_data["total_time"] = f"{time_end - time_start:0.3f}"
meta_data["inference_time"] = f"{time_end - time_feat:0.3f}"
# 简化元数据处理,只保留时间戳
if len(results) > 0 and output_timestamp and merged_timestamps:
# 只保留时间戳数据,以便在export.py中处理
meta_data["merged_timestamps"] = merged_timestamps
meta_data["merged_words"] = merged_words
return results, meta_data
def load_data(self, wav_content: Union[str, np.ndarray, List[str]], fs: int = None) -> List:
def load_wav(path: str) -> np.ndarray:
try:
# Use librosa if available
if 'librosa' in globals():
waveform, _ = librosa.load(path, sr=fs)
else:
# Use fallback implementation
waveform, native_sr = load_wav_fallback(path)
if fs is not None and native_sr != fs:
# Implement resampling if needed
print(f"Warning: Resampling from {native_sr} to {fs} is not implemented in fallback mode")
return waveform
except Exception as e:
print(f"Error loading audio file {path}: {e}")
# Return empty audio in case of error
return np.zeros(1600, dtype=np.float32)
if isinstance(wav_content, np.ndarray):
return [wav_content]
if isinstance(wav_content, str):
return [load_wav(wav_content)]
if isinstance(wav_content, list):
return [load_wav(path) for path in wav_content]
raise TypeError(f"The type of {wav_content} is not in [str, np.ndarray, list]")
def extract_feat(self, waveform_list: List[np.ndarray]) -> Tuple[np.ndarray, np.ndarray]:
feats, feats_len = [], []
for waveform in waveform_list:
speech, _ = self.frontend.fbank(waveform)
feat, feat_len = self.frontend.lfr_cmvn(speech)
feats.append(feat)
feats_len.append(feat_len)
feats = self.pad_feats(feats, np.max(feats_len))
feats_len = np.array(feats_len).astype(np.int32)
return feats, feats_len
@staticmethod
def pad_feats(feats: List[np.ndarray], max_feat_len: int) -> np.ndarray:
def pad_feat(feat: np.ndarray, cur_len: int) -> np.ndarray:
pad_width = ((0, max_feat_len - cur_len), (0, 0))
return np.pad(feat, pad_width, "constant", constant_values=0)
feat_res = [pad_feat(feat, feat.shape[0]) for feat in feats]
feats = np.array(feat_res).astype(np.float32)
return feats
def infer(self,
feats: np.ndarray,
masks: np.ndarray,
position_encoding: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray]:
#outputs = self.ort_infer([feats, masks, position_encoding])
outputs =self.session.run(None, {
'speech': feats,
'masks': masks,
'position_encoding': position_encoding
})
return outputs