chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:10 +08:00
commit c397331b1e
3684 changed files with 990993 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python
import argparse
import os
import sys
import unittest
from fnmatch import fnmatch
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
def gather_test_cases(test_dir, pattern, list_tests):
case_list = []
for dirpath, dirnames, filenames in os.walk(test_dir):
for file in filenames:
if fnmatch(file, pattern):
case_list.append(file)
test_suite = unittest.TestSuite()
for case in case_list:
test_case = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern=case)
test_suite.addTest(test_case)
if hasattr(test_case, "__iter__"):
for subcase in test_case:
if list_tests:
print(subcase)
else:
if list_tests:
print(test_case)
return test_suite
def main(args):
runner = unittest.TextTestRunner()
test_suite = gather_test_cases(os.path.abspath(args.test_dir), args.pattern, args.list_tests)
if not args.list_tests:
result = runner.run(test_suite)
if len(result.failures) > 0:
sys.exit(len(result.failures))
if len(result.errors) > 0:
sys.exit(len(result.errors))
if __name__ == "__main__":
parser = argparse.ArgumentParser("test runner")
parser.add_argument("--list_tests", action="store_true", help="list all tests")
parser.add_argument("--pattern", default="test_*.py", help="test file pattern")
parser.add_argument("--test_dir", default="tests", help="directory to be tested")
parser.add_argument("--disable_profile", action="store_true", help="disable profiling")
args = parser.parse_args()
print(f"working dir: {os.getcwd()}")
main(args)
+610
View File
@@ -0,0 +1,610 @@
import unittest
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestConformerInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_aishell1(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_conformer_asr_nat-zh-cn-16k-aishell1-vocab4234-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_aishell2(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_conformer_asr_nat-zh-cn-16k-aishell2-vocab5212-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
class TestData2vecInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_transformer(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_data2vec_pretrain-zh-cn-aishell2-16k-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://modelscope.oss-cn-beijing.aliyuncs.com/test/audios/asr_example.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "每一天都要快乐喔"
def test_paraformer(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_data2vec_pretrain-paraformer-zh-cn-aishell2-16k",
)
rec_result = inference_pipeline(
audio_in="https://modelscope.oss-cn-beijing.aliyuncs.com/test/audios/asr_example.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "每一天都要快乐喔"
class TestMfccaInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_alimeeting(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="NPU-ASLP/speech_mfcca_asr-zh-cn-16k-alimeeting-vocab4950",
model_revision="v3.0.0",
)
rec_result = inference_pipeline(
audio_in="https://pre.modelscope.cn/api/v1/models/NPU-ASLP/speech_mfcca_asr-zh-cn-16k-alimeeting-vocab4950/repo?Revision=master&FilePath=example/asr_example_mc.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
class TestParaformerInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_paraformer_large_contextual_common(self):
param_dict = dict()
param_dict["hotword"] = (
"https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/hotword.txt"
)
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404",
param_dict=param_dict,
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_hotword.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "国务院发展研究中心市场经济研究所副所长邓郁松认为"
def test_paraformer_large_aishell1(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-large_asr_nat-zh-cn-16k-aishell1-vocab8404-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "欢迎大家来体验达摩院推出的语音识别模型"
def test_paraformer_large_aishell2(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-large_asr_nat-zh-cn-16k-aishell2-vocab8404-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "欢迎大家来体验达摩院推出的语音识别模型"
def test_paraformer_large_common(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "欢迎大家来体验达摩院推出的语音识别模型"
def test_paraformer_large_online_common(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online",
model_revision="v1.0.6",
update_model=False,
mode="paraformer_fake_streaming",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "欢迎大家来体验达摩院推出的语音识别模型"
def test_paraformer_online_common(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer_asr_nat-zh-cn-16k-common-vocab8404-online",
model_revision="v1.0.6",
update_model=False,
mode="paraformer_fake_streaming",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
assert rec_result["text"] == "欢迎大家来体验达摩院推出的语音识别模型"
def test_paraformer_tiny_commandword(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-tiny-commandword_asr_nat-zh-cn-16k-vocab544-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh_command.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_paraformer_8k(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer_asr_nat-zh-cn-8k-common-vocab8358-tensorflow1",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_8K.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_paraformer_aishell1(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer_asr_nat-zh-cn-16k-aishell1-vocab4234-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_paraformer_aishell2(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer_asr_nat-zh-cn-16k-aishell2-vocab5212-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
class TestParaformerBertInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_aishell1(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformerbert_asr_nat-zh-cn-16k-aishell1-vocab4234-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://modelscope.oss-cn-beijing.aliyuncs.com/test/audios/asr_example.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_aishell2(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformerbert_asr_nat-zh-cn-16k-aishell2-vocab5212-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://modelscope.oss-cn-beijing.aliyuncs.com/test/audios/asr_example.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
class TestUniasrInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_uniasr_2pass_cantonese_chs_16k_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-cantonese-CHS-16k-common-vocab1468-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_cantonese-CHS.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_cantonese_chs_16k_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-cantonese-CHS-16k-common-vocab1468-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_cantonese-CHS.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_cn_dialect_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-cn-dialect-16k-vocab8358-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_cn_dialect_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-cn-dialect-16k-vocab8358-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_de_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-de-16k-common-vocab3690-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_de.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_de_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-de-16k-common-vocab3690-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_de.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_en_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-en-16k-common-vocab1080-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_en.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_en_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-en-16k-common-vocab1080-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_en.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_es_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-es-16k-common-vocab3445-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_es.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_es_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-es-16k-common-vocab3445-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_es.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_fa_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-fa-16k-common-vocab1257-pytorch-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_fa.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_fa_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-fa-16k-common-vocab1257-pytorch-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_fa.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_fr_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-fr-16k-common-vocab3472-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_fr.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_fr_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-fr-16k-common-vocab3472-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_fr.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_id_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-id-16k-common-vocab1067-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_id.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_id_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-id-16k-common-vocab1067-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_id.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_ja_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-ja-16k-common-vocab93-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_ja.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_ja_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-ja-16k-common-vocab93-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_ja.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_ko_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-ko-16k-common-vocab6400-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_ko.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_ko_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-ko-16k-common-vocab6400-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_ko.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_minnan_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-minnan-16k-common-vocab3825",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_pt_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-pt-16k-common-vocab1617-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_pt.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_pt_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-pt-16k-common-vocab1617-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_pt.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_ru_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-ru-16k-common-vocab1664-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_ru.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_ru_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-ru-16k-common-vocab1664-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_ru.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_vi_common_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-vi-16k-common-vocab1001-pytorch-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_vi.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_vi_common_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-vi-16k-common-vocab1001-pytorch-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_vi.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_zhcn_8k_common_vocab3445_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-zh-cn-8k-common-vocab3445-pytorch-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_zhcn_8k_common_vocab3445_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-zh-cn-8k-common-vocab3445-pytorch-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_zhcn_8k_common_vocab8358_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-zh-cn-8k-common-vocab8358-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_zhcn_8k_common_vocab8358_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-zh-cn-8k-common-vocab8358-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_zhcn_16k_common_vocab8358_offline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-zh-cn-16k-common-vocab8358-tensorflow1-offline",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "offline"},
)
logger.info("asr inference result: {0}".format(rec_result))
def test_uniasr_2pass_zhcn_16k_common_vocab8358_online(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_UniASR_asr_2pass-zh-cn-16k-common-vocab8358-tensorflow1-online",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav",
param_dict={"decoding_model": "normal"},
)
logger.info("asr inference result: {0}".format(rec_result))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,36 @@
import unittest
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestParaformerInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_inference_pipeline(self):
inference_pipeline = pipeline(
task=Tasks.auto_speech_recognition,
model="damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
model_revision="v1.2.1",
vad_model="damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
vad_model_revision="v1.1.8",
punc_model="damo/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
punc_model_revision="v1.1.6",
ngpu=1,
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"
)
logger.info("asr_vad_punc inference result: {0}".format(rec_result))
assert rec_result["text"] == "欢迎大家来体验达摩院推出的语音识别模型。"
if __name__ == "__main__":
unittest.main()
+154
View File
@@ -0,0 +1,154 @@
import importlib
from pathlib import Path
import sys
import tempfile
import unittest
import numpy as np
import torch
import funasr
from funasr.auto.auto_model import AutoModel
class TestAutoModel(unittest.TestCase):
def setUp(self):
self.base_kwargs = {
"model": "damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
"vad_model": "fsmn-vad",
"punc_model": "ct-punc",
"device": "cpu",
"batch_size": 1,
"disable_update": True,
}
def test_merge_thr_in_cb_model(self):
kwargs = self.base_kwargs.copy()
kwargs["spk_model"] = "cam++"
merge_thr = 0.5
kwargs["spk_kwargs"] = {"cb_kwargs": {"merge_thr": merge_thr}}
model = AutoModel(**kwargs)
self.assertEqual(model.cb_model.model_config['merge_thr'], merge_thr)
# res = model.generate(input="/test.wav",
# batch_size_s=300)
def test_progress_callback_called(self):
class DummyModel:
def __init__(self):
self.param = torch.nn.Parameter(torch.zeros(1))
def parameters(self):
return iter([self.param])
def eval(self):
pass
def inference(self, data_in=None, **kwargs):
results = [{"text": str(d)} for d in data_in]
return results, {"batch_data_time": 1}
am = AutoModel.__new__(AutoModel)
am.model = DummyModel()
am.kwargs = {"batch_size": 2, "disable_pbar": True}
progress = []
res = AutoModel.inference(
am,
["a", "b", "c"],
progress_callback=lambda idx, total: progress.append((idx, total)),
)
self.assertEqual(len(progress), 2)
self.assertEqual(progress, [(2, 3), (3, 3)])
def test_import_submodules_records_failures(self):
old_errors = dict(funasr._IMPORT_ERRORS)
old_tracebacks = dict(funasr._IMPORT_ERROR_TRACEBACKS)
package_name = "funasr_import_probe_pkg"
with tempfile.TemporaryDirectory() as tmp_dir:
package_dir = Path(tmp_dir) / package_name
package_dir.mkdir()
(package_dir / "__init__.py").write_text("", encoding="utf-8")
(package_dir / "broken.py").write_text(
'raise RuntimeError("boom optional dependency")\n', encoding="utf-8"
)
sys.path.insert(0, tmp_dir)
try:
funasr._IMPORT_ERRORS.clear()
funasr._IMPORT_ERROR_TRACEBACKS.clear()
package = importlib.import_module(package_name)
imported = funasr.import_submodules(package)
failed_module = f"{package_name}.broken"
self.assertEqual(imported, {})
self.assertIn(failed_module, funasr._IMPORT_ERRORS)
self.assertIn("boom optional dependency", funasr._IMPORT_ERRORS[failed_module])
self.assertIn(failed_module, funasr._IMPORT_ERROR_TRACEBACKS)
finally:
sys.path.remove(tmp_dir)
for module_name in list(sys.modules):
if module_name == package_name or module_name.startswith(package_name + "."):
sys.modules.pop(module_name, None)
funasr._IMPORT_ERRORS.clear()
funasr._IMPORT_ERRORS.update(old_errors)
funasr._IMPORT_ERROR_TRACEBACKS.clear()
funasr._IMPORT_ERROR_TRACEBACKS.update(old_tracebacks)
def test_import_submodules_strict_import_raises(self):
old_strict_import = funasr._STRICT_IMPORT
package_name = "funasr_import_strict_probe_pkg"
with tempfile.TemporaryDirectory() as tmp_dir:
package_dir = Path(tmp_dir) / package_name
package_dir.mkdir()
(package_dir / "__init__.py").write_text("", encoding="utf-8")
(package_dir / "broken.py").write_text(
'raise RuntimeError("strict optional dependency")\n', encoding="utf-8"
)
sys.path.insert(0, tmp_dir)
try:
funasr._STRICT_IMPORT = True
package = importlib.import_module(package_name)
with self.assertRaisesRegex(RuntimeError, "strict optional dependency"):
funasr.import_submodules(package)
finally:
funasr._STRICT_IMPORT = old_strict_import
sys.path.remove(tmp_dir)
for module_name in list(sys.modules):
if module_name == package_name or module_name.startswith(package_name + "."):
sys.modules.pop(module_name, None)
def test_unregistered_model_reports_import_failures(self):
old_errors = dict(funasr._IMPORT_ERRORS)
try:
funasr._IMPORT_ERRORS.clear()
funasr._IMPORT_ERRORS["funasr.models.fake_model"] = (
"ModuleNotFoundError: No module named 'fake_dep'"
)
with self.assertRaises(RuntimeError) as context:
AutoModel.build_model(
model="missing_model",
model_conf={},
device="cpu",
ncpu=1,
)
message = str(context.exception)
self.assertIn("missing_model", message)
self.assertIn("funasr.models.fake_model", message)
self.assertIn("fake_dep", message)
self.assertIn("FUNASR_IMPORT_DEBUG", message)
self.assertIn("FUNASR_STRICT_IMPORT", message)
finally:
funasr._IMPORT_ERRORS.clear()
funasr._IMPORT_ERRORS.update(old_errors)
if __name__ == '__main__':
unittest.main()
+41
View File
@@ -0,0 +1,41 @@
import io
import sys
import types
from contextlib import redirect_stdout
from unittest.mock import patch
from funasr import cli
class DummyAutoModel:
instances = []
def __init__(self, **kwargs):
self.kwargs = kwargs
self.instances.append(self)
def generate(self, **kwargs):
return [{"text": "hello"}]
def test_cli_passes_hub_to_auto_model(tmp_path):
audio_path = tmp_path / "sample.wav"
audio_path.write_bytes(b"not a real wav")
fake_torch = types.SimpleNamespace(
cuda=types.SimpleNamespace(is_available=lambda: False),
)
DummyAutoModel.instances = []
argv = ["funasr", "--hub", "hf", str(audio_path)]
with (
patch.object(sys, "argv", argv),
patch.dict(sys.modules, {"torch": fake_torch}),
patch("funasr.AutoModel", DummyAutoModel),
redirect_stdout(io.StringIO()) as stdout,
):
cli.main()
assert DummyAutoModel.instances[0].kwargs["hub"] == "hf"
assert stdout.getvalue().strip() == "hello"
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
import unittest
from types import SimpleNamespace
from unittest.mock import patch
import torch
from funasr.models.fsmn_vad_streaming import model as vad_model
class TestFsmnVadDynamicSilence(unittest.TestCase):
def _run_inference(self, **kwargs):
vad = vad_model.FsmnVADStreaming.__new__(vad_model.FsmnVADStreaming)
vad.vad_opts = SimpleNamespace(speech_to_sil_time_thres=100)
vad.forward = lambda **batch: []
cache = {
"frontend": {},
"prev_samples": torch.empty(0),
"encoder": {},
"stats": SimpleNamespace(
vad_state_machine=vad_model.VadStateMachine.kVadInStateInSpeechSegment,
max_end_sil_frame_cnt_thresh=200,
speech_noise_thres=0.6,
),
}
frontend = SimpleNamespace(fs=16000, frame_shift=10, lfr_n=1)
def fake_extract_fbank(*args, **kwargs):
cache["frontend"]["waveforms"] = torch.zeros(1, 16000)
return torch.zeros(1, 1, 80), torch.tensor([100])
with (
patch.object(
vad_model,
"load_audio_text_image_video",
return_value=[torch.zeros(16000)],
),
patch.object(vad_model, "extract_fbank", side_effect=fake_extract_fbank),
):
vad_model.FsmnVADStreaming.inference(
vad,
torch.zeros(16000),
frontend=frontend,
cache=cache,
key=["utt"],
chunk_size=1000,
is_final=False,
device="cpu",
**kwargs,
)
return cache
def test_explicit_max_end_silence_time_keeps_fixed_threshold_by_default(self):
cache = self._run_inference(max_end_silence_time=300)
self.assertEqual(cache["stats"].max_end_sil_frame_cnt_thresh, 200)
self.assertNotIn("_dynamic_accumulated_ms", cache)
def test_explicit_dynamic_silence_still_enables_schedule(self):
cache = self._run_inference(max_end_silence_time=300, dynamic_silence=True)
self.assertEqual(cache["stats"].max_end_sil_frame_cnt_thresh, 1900)
self.assertEqual(cache["_dynamic_accumulated_ms"], 1000)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,54 @@
import importlib.util
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
DEVICE_UTILS_PATH = REPO_ROOT / "funasr" / "models" / "fun_asr_nano" / "device_utils.py"
PACKAGE_MODEL_PATH = REPO_ROOT / "funasr" / "models" / "fun_asr_nano" / "model.py"
EXAMPLE_MODEL_PATH = (
REPO_ROOT / "examples" / "industrial_data_pretraining" / "fun_asr_nano" / "model.py"
)
def _load_device_utils():
spec = importlib.util.spec_from_file_location("fun_asr_nano_device_utils", DEVICE_UTILS_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_resolve_autocast_device_type_keeps_npu_backend():
device_utils = _load_device_utils()
assert device_utils.resolve_autocast_device_type("npu:1") == "npu"
assert device_utils.resolve_autocast_device_type("npu") == "npu"
assert device_utils.resolve_autocast_device_type("cuda:0") == "cuda"
assert device_utils.resolve_autocast_device_type("xpu:0") == "xpu"
assert device_utils.resolve_autocast_device_type("mps") == "mps"
assert device_utils.resolve_autocast_device_type("cpu") == "cpu"
assert device_utils.resolve_autocast_device_type("unknown:0") == "cpu"
def test_device_type_from_value_treats_none_as_cpu():
device_utils = _load_device_utils()
assert device_utils._device_type_from_value(None) == "cpu"
def test_resolve_autocast_device_type_accepts_device_like_objects():
device_utils = _load_device_utils()
class DeviceLike:
type = "npu"
assert device_utils.resolve_autocast_device_type(DeviceLike()) == "npu"
def test_fun_asr_nano_autocast_calls_use_shared_resolver():
old_inline_fallback = 'device_type if device_type in ["cuda", "xpu", "mps"] else "cpu"'
for path in (PACKAGE_MODEL_PATH, EXAMPLE_MODEL_PATH):
source = path.read_text(encoding="utf-8")
assert old_inline_fallback not in source
assert source.count("resolve_autocast_device_type(") >= 2
@@ -0,0 +1,103 @@
from types import SimpleNamespace
import torch
from funasr.models.fun_asr_nano import model as nano_model
class _FakeLLM:
def __init__(self):
self.config = SimpleNamespace(pad_token_id=None, eos_token_id=0)
self.calls = 0
def to(self, _dtype):
return self
def generate(self, **_kwargs):
self.calls += 1
return torch.tensor([[10 + self.calls]], dtype=torch.long)
class _FakeTokenizer:
def batch_decode(self, generated_ids, **_kwargs):
return [f"text-{int(generated_ids[0, -1])}"]
class _FakeCTCDecoder:
def __call__(self, encoder_out, encoder_out_lens):
logits = torch.tensor(
[[[0.0, 2.0, 0.0], [0.0, 0.0, 3.0]]],
dtype=torch.float32,
)
return logits, encoder_out_lens
class _FakeCTC:
def log_softmax(self, decoder_out):
return decoder_out
class _FakeCTCTokenizer:
def decode(self, token_ids):
return "".join(str(token_id) for token_id in token_ids)
def encode(self, text):
return [1] if text else []
def test_ctc_decoder_multi_segment_input_uses_single_segment_fallback(monkeypatch):
instance = object.__new__(nano_model.FunASRNano)
instance.ctc_decoder = _FakeCTCDecoder()
instance.ctc = _FakeCTC()
instance.ctc_tokenizer = _FakeCTCTokenizer()
instance.blank_id = 0
instance.llm = _FakeLLM()
prepare_calls = []
def fake_inference_prepare(
data_in,
data_lengths=None,
key=None,
tokenizer=None,
frontend=None,
**_kwargs,
):
if len(data_in) > 1:
raise NotImplementedError("batch decoding is not implemented")
prepare_calls.append((data_in[0], key[0]))
return (
torch.zeros(1, 2, 4),
{"assistant": [f"label-{data_in[0]}"]},
{"attention_mask": torch.ones(1, 2, dtype=torch.long)},
torch.empty(1, 0, dtype=torch.long),
{
"encoder_out": torch.zeros(1, 2, 3),
"encoder_out_lens": torch.tensor([2]),
"batch_data_time": 1.0,
},
)
monkeypatch.setattr(instance, "inference_prepare", fake_inference_prepare)
monkeypatch.setattr(
nano_model,
"forced_align",
lambda _logits, target_ids, _blank_id: [
{"token": int(target_ids[0]), "start_time": 0.0, "end_time": 1.0}
],
)
results, meta = instance.inference_llm(
["seg-a", "seg-b"],
key=["key-a", "key-b"],
tokenizer=_FakeTokenizer(),
frontend=None,
device="cpu",
llm_dtype="fp32",
)
assert prepare_calls == [("seg-a", "key-a"), ("seg-b", "key-b")]
assert [result["key"] for result in results] == ["key-a", "key-b"]
assert [result["text"] for result in results] == ["text-11", "text-12"]
assert all("timestamps" in result for result in results)
assert meta["batch_data_time"] == 2.0
+153
View File
@@ -0,0 +1,153 @@
import importlib.util
import sys
import types
from pathlib import Path
import numpy as np
REPO_ROOT = Path(__file__).resolve().parents[1]
SERVICE_PATH = (
REPO_ROOT
/ "examples"
/ "industrial_data_pretraining"
/ "fun_asr_nano"
/ "serve_vllm.py"
)
def load_service_module(monkeypatch):
fastapi_stub = types.ModuleType("fastapi")
class FastAPIStub:
def __init__(self, *args, **kwargs):
pass
def on_event(self, *args, **kwargs):
return lambda func: func
def post(self, *args, **kwargs):
return lambda func: func
def websocket(self, *args, **kwargs):
return lambda func: func
fastapi_stub.FastAPI = FastAPIStub
fastapi_stub.File = lambda *args, **kwargs: None
fastapi_stub.Form = lambda *args, **kwargs: None
fastapi_stub.UploadFile = object
fastapi_stub.WebSocket = object
fastapi_stub.WebSocketDisconnect = Exception
responses_stub = types.ModuleType("fastapi.responses")
class JSONResponseStub:
def __init__(self, content=None):
self.content = content
responses_stub.JSONResponse = JSONResponseStub
funasr_stub = types.ModuleType("funasr")
funasr_stub.AutoModel = object
nano_stub = types.ModuleType("funasr.models.fun_asr_nano.inference_vllm")
nano_stub.FunASRNanoVLLM = object
vad_stub = types.ModuleType("funasr.models.fsmn_vad_streaming.dynamic_vad")
vad_stub.DynamicStreamingVAD = object
monkeypatch.setitem(sys.modules, "fastapi", fastapi_stub)
monkeypatch.setitem(sys.modules, "fastapi.responses", responses_stub)
monkeypatch.setitem(sys.modules, "uvicorn", types.ModuleType("uvicorn"))
monkeypatch.setitem(sys.modules, "soundfile", types.ModuleType("soundfile"))
monkeypatch.setitem(sys.modules, "torch", types.ModuleType("torch"))
monkeypatch.setitem(sys.modules, "funasr", funasr_stub)
monkeypatch.setitem(sys.modules, "funasr.models", types.ModuleType("funasr.models"))
monkeypatch.setitem(
sys.modules,
"funasr.models.fun_asr_nano",
types.ModuleType("funasr.models.fun_asr_nano"),
)
monkeypatch.setitem(
sys.modules, "funasr.models.fun_asr_nano.inference_vllm", nano_stub
)
monkeypatch.setitem(
sys.modules,
"funasr.models.fsmn_vad_streaming",
types.ModuleType("funasr.models.fsmn_vad_streaming"),
)
monkeypatch.setitem(
sys.modules, "funasr.models.fsmn_vad_streaming.dynamic_vad", vad_stub
)
module_name = "serve_vllm_under_test"
sys.modules.pop(module_name, None)
spec = importlib.util.spec_from_file_location(module_name, SERVICE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_openai_verbose_json_keeps_segment_speaker(monkeypatch):
module = load_service_module(monkeypatch)
response = module.build_openai_verbose_json(
{
"duration": 1.2,
"text": "hello",
"segments": [
{
"start": 0.0,
"end": 1.2,
"text": "hello",
"words": [{"word": "hello", "start": 0.0, "end": 1.2}],
"speaker": "SPK0",
}
],
},
language="en",
)
assert response["segments"][0]["speaker"] == "SPK0"
def test_process_audio_downmixes_stereo_before_resampling(monkeypatch):
module = load_service_module(monkeypatch)
captured = {}
librosa_stub = types.ModuleType("librosa")
def fake_resample(audio, orig_sr, target_sr):
assert audio.ndim == 1
captured["resample_input"] = audio.copy()
assert orig_sr == 8000
assert target_sr == 16000
return np.repeat(audio, 2)
librosa_stub.resample = fake_resample
monkeypatch.setitem(sys.modules, "librosa", librosa_stub)
class EngineStub:
def generate(self, inputs, **kwargs):
captured["engine_input"] = inputs[0]
return [{"text": "ok"}]
module._engine = EngineStub()
stereo_audio = np.column_stack(
[np.zeros(8000, dtype=np.float32), np.ones(8000, dtype=np.float32)]
)
result = module.process_audio(
stereo_audio,
sr=8000,
use_vad=False,
use_spk=False,
use_timestamp=False,
)
assert np.allclose(captured["resample_input"], 0.5)
assert captured["engine_input"].shape == (16000,)
assert np.allclose(captured["engine_input"], 0.5)
assert result["duration"] == 1.0
assert result["text"] == "ok"
@@ -0,0 +1,74 @@
"""Unit tests for Fun-ASR-Nano vLLM repetition-penalty handling.
Regression guard for issue #2948: a repetition penalty other than 1.0 is
incompatible with vLLM prompt-embeds mode and aborts the engine with a CUDA
"scatter gather index out of bounds" assertion. The serving paths must never
forward such a value to ``SamplingParams`` while ``enable_prompt_embeds=True``.
The helper is dependency-free, so these tests run without a GPU or vLLM.
"""
import logging
import unittest
from funasr.models.fun_asr_nano import vllm_utils
from funasr.models.fun_asr_nano.vllm_utils import (
NEUTRAL_REPETITION_PENALTY,
resolve_repetition_penalty,
)
class TestResolveRepetitionPenalty(unittest.TestCase):
def setUp(self):
# Reset the once-per-process warning flag so each test is independent.
vllm_utils._warned_prompt_embeds = False
def test_neutral_value_passes_through(self):
self.assertEqual(resolve_repetition_penalty(1.0), 1.0)
def test_none_maps_to_neutral(self):
self.assertEqual(
resolve_repetition_penalty(None), NEUTRAL_REPETITION_PENALTY
)
def test_nonneutral_is_clamped_in_prompt_embeds_mode(self):
# The exact value that triggers the #2948 crash.
self.assertEqual(
resolve_repetition_penalty(1.3, prompt_embeds=True),
NEUTRAL_REPETITION_PENALTY,
)
def test_nonneutral_preserved_for_token_prompts(self):
# Regular token-prompt decoding can safely apply the penalty.
self.assertEqual(
resolve_repetition_penalty(1.3, prompt_embeds=False), 1.3
)
def test_warns_once_in_prompt_embeds_mode(self):
with self.assertLogs(vllm_utils.logger, level=logging.WARNING) as cm:
resolve_repetition_penalty(1.3, prompt_embeds=True)
# Subsequent clamps must not emit additional warnings.
resolve_repetition_penalty(1.5, prompt_embeds=True)
self.assertEqual(len(cm.records), 1)
self.assertIn("2948", cm.output[0])
def test_no_warning_when_value_is_safe(self):
# Capture records directly (assertNoLogs is only available on 3.10+).
records = []
class _Collect(logging.Handler):
def emit(self, record):
records.append(record)
handler = _Collect(level=logging.WARNING)
vllm_utils.logger.addHandler(handler)
try:
resolve_repetition_penalty(1.0, prompt_embeds=True)
resolve_repetition_penalty(1.3, prompt_embeds=False)
finally:
vllm_utils.logger.removeHandler(handler)
self.assertEqual(records, [])
if __name__ == "__main__":
unittest.main()
+115
View File
@@ -0,0 +1,115 @@
import importlib.util
import sys
import types
from pathlib import Path
def load_generate_subtitle_module():
script_path = Path(__file__).resolve().parents[1] / "examples" / "subtitle" / "generate_subtitle.py"
spec = importlib.util.spec_from_file_location("generate_subtitle", script_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_generate_subtitle_requests_sentence_timestamps_and_writes_segmented_srt(
monkeypatch, tmp_path
):
module = load_generate_subtitle_module()
captured = {}
class FakeAutoModel:
def __init__(self, **kwargs):
captured["model_kwargs"] = kwargs
def generate(self, **kwargs):
captured["generate_kwargs"] = kwargs
if kwargs.get("sentence_timestamp") and kwargs.get("output_timestamp"):
return [
{
"text": "<|zh|>First segment Second segment",
"sentence_info": [
{"start": 0, "end": 3500, "text": "<|zh|>First segment"},
{"start": 3500, "end": 7200, "sentence": "Second segment", "spk": 1},
],
}
]
return [{"text": "<|zh|>First segment Second segment"}]
fake_funasr = types.ModuleType("funasr")
fake_funasr.AutoModel = FakeAutoModel
monkeypatch.setitem(sys.modules, "funasr", fake_funasr)
input_path = tmp_path / "input.mp4"
input_path.write_bytes(b"fake media")
output_path = tmp_path / "sub.srt"
monkeypatch.setattr(
sys,
"argv",
[
"generate_subtitle.py",
str(input_path),
"-o",
str(output_path),
"--lang",
"zh",
"--device",
"cpu",
"--spk",
],
)
module.main()
assert captured["model_kwargs"]["punc_model"] == "ct-punc"
assert captured["generate_kwargs"]["language"] == "zh"
assert captured["generate_kwargs"]["sentence_timestamp"] is True
assert captured["generate_kwargs"]["output_timestamp"] is True
assert captured["generate_kwargs"]["return_time_stamps"] is True
assert output_path.read_text(encoding="utf-8") == (
"1\n"
"00:00:00,000 --> 00:00:03,500\n"
"First segment\n\n"
"2\n"
"00:00:03,500 --> 00:00:07,200\n"
"[Speaker 1] Second segment\n\n"
)
def test_generate_subtitle_falls_back_to_timestamp_bounds_when_sentence_info_missing(
monkeypatch, tmp_path
):
module = load_generate_subtitle_module()
class FakeAutoModel:
def __init__(self, **kwargs):
pass
def generate(self, **kwargs):
return [
{
"text": "<|zh|>Full text",
"timestamp": [[250, 1000], [1000, 3200]],
}
]
fake_funasr = types.ModuleType("funasr")
fake_funasr.AutoModel = FakeAutoModel
monkeypatch.setitem(sys.modules, "funasr", fake_funasr)
input_path = tmp_path / "input.mp4"
input_path.write_bytes(b"fake media")
output_path = tmp_path / "sub.srt"
monkeypatch.setattr(
sys,
"argv",
["generate_subtitle.py", str(input_path), "-o", str(output_path), "--device", "cpu"],
)
module.main()
assert output_path.read_text(encoding="utf-8") == (
"1\n"
"00:00:00,250 --> 00:00:03,200\n"
"Full text\n\n"
)
+124
View File
@@ -0,0 +1,124 @@
"""Unit tests for GLM-ASR vLLM result-key deduplication.
These tests exercise ``GLMASRVLLMEngine.generate`` without a GPU or a real
vLLM installation: the vLLM entry points are stubbed in ``sys.modules`` and the
audio/encoder/engine collaborators are mocked, so only the result-key wiring is
under test. The pure ``_dedup_keys`` helper is also tested directly.
"""
import sys
import types
import unittest
from unittest import mock
def _install_vllm_stub():
"""Install a minimal ``vllm`` stub so ``inference_vllm`` imports without GPU."""
class _SamplingParams:
def __init__(self, **kwargs):
self.kwargs = kwargs
class _EmbedsPrompt:
def __init__(self, **kwargs):
self.kwargs = kwargs
vllm_mod = types.ModuleType("vllm")
vllm_mod.SamplingParams = _SamplingParams
vllm_mod.LLM = object
inputs_mod = types.ModuleType("vllm.inputs")
inputs_mod.EmbedsPrompt = _EmbedsPrompt
data_mod = types.ModuleType("vllm.inputs.data")
data_mod.EmbedsPrompt = _EmbedsPrompt
sys.modules["vllm"] = vllm_mod
sys.modules["vllm.inputs"] = inputs_mod
sys.modules["vllm.inputs.data"] = data_mod
class DedupKeysHelperTest(unittest.TestCase):
def setUp(self):
_install_vllm_stub()
from funasr.models.glm_asr import inference_vllm
self.inference_vllm = inference_vllm
# Reset the warn-once flag so warning-related assertions are deterministic.
inference_vllm._warned_dup_keys = False
def test_collision_free_input_unchanged(self):
keys = ["a", "b", "c"]
self.assertEqual(self.inference_vllm._dedup_keys(keys), ["a", "b", "c"])
def test_repeated_keys_get_deterministic_suffix(self):
keys = ["seg", "seg", "other", "seg"]
self.assertEqual(
self.inference_vllm._dedup_keys(keys),
["seg", "seg_1", "other", "seg_2"],
)
def test_suffix_does_not_clash_with_existing_key(self):
# A naive "seg" -> "seg_1" scheme would re-collide with the literal
# "seg_1" input; the result must stay globally unique.
keys = ["seg", "seg_1", "seg"]
result = self.inference_vllm._dedup_keys(keys)
self.assertEqual(result, ["seg", "seg_1", "seg_2"])
self.assertEqual(len(set(result)), len(result))
def test_empty_input(self):
self.assertEqual(self.inference_vllm._dedup_keys([]), [])
def test_sentinel_keys_preserved(self):
keys = ["sample_0", "sample_1", "sample_0"]
self.assertEqual(
self.inference_vllm._dedup_keys(keys),
["sample_0", "sample_1", "sample_0_1"],
)
def test_does_not_mutate_input(self):
keys = ["seg", "seg"]
self.inference_vllm._dedup_keys(keys)
self.assertEqual(keys, ["seg", "seg"])
class DedupResultKeysTest(unittest.TestCase):
def setUp(self):
_install_vllm_stub()
from funasr.models.glm_asr.inference_vllm import GLMASRVLLMEngine
# Build an engine without running __init__ (no model load / GPU needed).
engine = GLMASRVLLMEngine.__new__(GLMASRVLLMEngine)
engine.device = "cpu"
engine._encode_audio = mock.Mock(return_value="audio_embeds")
engine._build_prompt_embeds = mock.Mock(
return_value=mock.Mock(float=lambda: "embeds")
)
engine.tokenizer = mock.Mock()
engine.tokenizer.decode = mock.Mock(return_value="hello world")
self.engine = engine
def _set_outputs(self, n):
token_out = types.SimpleNamespace(token_ids=[1, 2, 3])
outs = [types.SimpleNamespace(outputs=[token_out]) for _ in range(n)]
self.engine.vllm_engine = mock.Mock()
self.engine.vllm_engine.generate = mock.Mock(return_value=outs)
def test_duplicate_basenames_get_unique_keys(self):
self._set_outputs(2)
results = self.engine.generate(["spk1/segment.wav", "spk2/segment.wav"])
self.assertEqual([r["key"] for r in results], ["segment", "segment_1"])
# No transcript is dropped when results are folded into a {key: text} dict.
self.assertEqual(len({r["key"] for r in results}), len(results))
def test_distinct_basenames_unchanged(self):
self._set_outputs(2)
results = self.engine.generate(["a.wav", "b.wav"])
self.assertEqual([r["key"] for r in results], ["a", "b"])
def test_single_input_key(self):
self._set_outputs(1)
results = self.engine.generate("only.wav")
self.assertEqual(results, [{"key": "only", "text": "hello world"}])
if __name__ == "__main__":
unittest.main()
+60
View File
@@ -0,0 +1,60 @@
"""Unit tests for the GLM-ASR vLLM fp16 degraded-output guard.
Regression guard: ``fp16`` can produce degraded or garbage transcription for
GLM-ASR (numerical overflow in the audio embedding path), mirroring the
documented Fun-ASR-Nano behaviour. Requesting it must warn once so users are
not silently handed poor output, while leaving the requested value untouched.
The helper is dependency-free, so these tests run without a GPU, torch, or
vLLM.
"""
import logging
import unittest
from funasr.models.glm_asr import vllm_utils
from funasr.models.glm_asr.vllm_utils import warn_if_degraded_dtype
class TestWarnIfDegradedDtype(unittest.TestCase):
def setUp(self):
# Reset the once-per-process warning flag so each test is independent.
vllm_utils._warned_fp16 = False
def test_returns_value_unchanged(self):
for dtype in ("bf16", "fp16", "fp32", "something-else"):
self.assertEqual(warn_if_degraded_dtype(dtype), dtype)
def test_fp16_warns(self):
with self.assertLogs(vllm_utils.logger, level=logging.WARNING) as cm:
warn_if_degraded_dtype("fp16")
self.assertEqual(len(cm.records), 1)
self.assertIn("fp16", cm.output[0])
def test_fp16_warns_only_once(self):
with self.assertLogs(vllm_utils.logger, level=logging.WARNING) as cm:
warn_if_degraded_dtype("fp16")
# Subsequent calls must not emit additional warnings.
warn_if_degraded_dtype("fp16")
self.assertEqual(len(cm.records), 1)
def test_safe_values_do_not_warn(self):
# Capture records directly (assertNoLogs is only available on 3.10+).
records = []
class _Collect(logging.Handler):
def emit(self, record):
records.append(record)
handler = _Collect(level=logging.WARNING)
vllm_utils.logger.addHandler(handler)
try:
warn_if_degraded_dtype("bf16")
warn_if_degraded_dtype("fp32")
finally:
vllm_utils.logger.removeHandler(handler)
self.assertEqual(records, [])
if __name__ == "__main__":
unittest.main()
+119
View File
@@ -0,0 +1,119 @@
"""Unit tests for GLM-ASR vLLM sampling-parameter handling.
These tests exercise ``GLMASRVLLMEngine.generate`` without a GPU or a real
vLLM installation: the vLLM entry points are stubbed in ``sys.modules`` and the
audio/encoder/engine collaborators are mocked, so only the sampling-parameter
wiring is under test.
"""
import re
import sys
import types
import unittest
from unittest import mock
def _install_vllm_stub():
"""Install a minimal ``vllm`` stub whose SamplingParams records kwargs."""
captured = {}
class _RecordingSamplingParams:
def __init__(self, **kwargs):
captured.clear()
captured.update(kwargs)
class _EmbedsPrompt:
def __init__(self, **kwargs):
self.kwargs = kwargs
vllm_mod = types.ModuleType("vllm")
vllm_mod.SamplingParams = _RecordingSamplingParams
vllm_mod.LLM = object
inputs_mod = types.ModuleType("vllm.inputs")
inputs_mod.EmbedsPrompt = _EmbedsPrompt
data_mod = types.ModuleType("vllm.inputs.data")
data_mod.EmbedsPrompt = _EmbedsPrompt
sys.modules["vllm"] = vllm_mod
sys.modules["vllm.inputs"] = inputs_mod
sys.modules["vllm.inputs.data"] = data_mod
return captured
class GLMASRSamplingParamsTest(unittest.TestCase):
def setUp(self):
self.captured = _install_vllm_stub()
from funasr.models.glm_asr.inference_vllm import GLMASRVLLMEngine
# Build an engine without running __init__ (no model load / GPU needed).
engine = GLMASRVLLMEngine.__new__(GLMASRVLLMEngine)
engine.device = "cpu"
engine._encode_audio = mock.Mock(return_value="audio_embeds")
engine._build_prompt_embeds = mock.Mock(
return_value=mock.Mock(float=lambda: "embeds")
)
token_out = types.SimpleNamespace(token_ids=[1, 2, 3])
vllm_output = types.SimpleNamespace(outputs=[token_out])
engine.vllm_engine = mock.Mock()
engine.vllm_engine.generate = mock.Mock(return_value=[vllm_output])
engine.tokenizer = mock.Mock()
engine.tokenizer.decode = mock.Mock(return_value="hello world")
self.engine = engine
def test_defaults_preserve_greedy_behavior(self):
results = self.engine.generate("a.wav")
self.assertEqual(results, [{"key": "a", "text": "hello world"}])
self.assertEqual(self.captured["temperature"], 0.0)
self.assertEqual(self.captured["top_p"], 1.0)
self.assertEqual(self.captured["top_k"], -1)
self.assertEqual(self.captured["repetition_penalty"], 1.0)
self.assertEqual(self.captured["max_tokens"], 500)
def test_caller_sampling_params_are_forwarded(self):
self.engine.generate(
"a.wav", max_new_tokens=128, temperature=0.7, top_p=0.9, top_k=20
)
self.assertEqual(self.captured["max_tokens"], 128)
self.assertEqual(self.captured["temperature"], 0.7)
self.assertEqual(self.captured["top_p"], 0.9)
self.assertEqual(self.captured["top_k"], 20)
def test_non_positive_top_k_is_normalized_to_disabled(self):
self.engine.generate("a.wav", top_k=0)
self.assertEqual(self.captured["top_k"], -1)
def test_repetition_penalty_is_forced_neutral_in_prompt_embeds_mode(self):
# A non-neutral repetition_penalty would crash vLLM prompt-embeds mode
# (issue #2948), so it must be coerced back to 1.0 rather than forwarded.
self.engine.generate("a.wav", repetition_penalty=1.3)
self.assertEqual(self.captured["repetition_penalty"], 1.0)
def test_neutral_repetition_penalty_passes_through(self):
self.engine.generate("a.wav", repetition_penalty=1.0)
self.assertEqual(self.captured["repetition_penalty"], 1.0)
class SafeRepetitionPenaltyTest(unittest.TestCase):
def setUp(self):
_install_vllm_stub()
import funasr.models.glm_asr.inference_vllm as mod
self.mod = mod
# Reset the process-wide warn-once flag between tests.
mod._warned_rep_penalty = False
def test_neutral_and_none_map_to_one(self):
self.assertEqual(self.mod._safe_repetition_penalty(1.0), 1.0)
self.assertEqual(self.mod._safe_repetition_penalty(None), 1.0)
def test_non_neutral_is_coerced_and_warns_once(self):
with self.assertLogs(self.mod.logger, level="WARNING") as cm:
self.assertEqual(self.mod._safe_repetition_penalty(1.5), 1.0)
self.assertTrue(any("2948" in line for line in cm.output))
self.assertTrue(self.mod._warned_rep_penalty)
if __name__ == "__main__":
unittest.main()
+23
View File
@@ -0,0 +1,23 @@
import re
import unittest
from funasr.utils.kws_utils import query_token_set, split_mixed_label, symbol_str
class KwsUtilsTest(unittest.TestCase):
def test_symbol_regex_strips_punctuation_and_whitespace(self):
self.assertEqual(re.sub(symbol_str, "", "abc!@# 中文\t"), "abc中文")
def test_split_mixed_label_lowercases_ascii_words(self):
self.assertEqual(split_mixed_label("Hello中文"), ["hello", "", ""])
def test_query_token_set_strips_symbols_before_character_lookup(self):
symbol_table = {"a": 1, "": 2, "<unk>": 0}
tokens, token_ids = query_token_set("A!中", symbol_table, {})
self.assertEqual(tokens, ("a", ""))
self.assertEqual(token_ids, (1, 2))
if __name__ == "__main__":
unittest.main()
+27
View File
@@ -0,0 +1,27 @@
import unittest
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestTransformerInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_inference_pipeline(self):
inference_pipeline = pipeline(
task=Tasks.language_score_prediction,
model="damo/speech_transformer_lm_zh-cn-common-vocab8404-pytorch",
)
rec_result = inference_pipeline(text_in="hello 大 家 好 呀")
logger.info("lm inference result: {0}".format(rec_result))
if __name__ == "__main__":
unittest.main()
+53
View File
@@ -0,0 +1,53 @@
import json
from pathlib import Path
def test_mcp_server_example_has_glama_ready_dockerfile():
example_dir = Path(__file__).resolve().parents[1] / "examples" / "mcp_server"
dockerfile = example_dir / "Dockerfile"
assert dockerfile.is_file()
dockerfile_text = dockerfile.read_text()
assert "pip install" in dockerfile_text
assert "funasr" in dockerfile_text
assert "COPY funasr_mcp.py /app/funasr_mcp.py" in dockerfile_text
assert 'CMD ["python", "/app/funasr_mcp.py"]' in dockerfile_text
def test_mcp_server_example_has_glama_metadata():
example_dir = Path(__file__).resolve().parents[1] / "examples" / "mcp_server"
metadata_path = example_dir / "glama.json"
metadata = json.loads(metadata_path.read_text())
assert metadata["repository"] == "https://github.com/modelscope/FunASR"
assert metadata["runtime"] == "python"
assert metadata["license"] == "MIT"
assert metadata["mcpServers"]["funasr"]["command"] == "python"
assert metadata["mcpServers"]["funasr"]["args"] == ["/app/funasr_mcp.py"]
def test_repository_root_has_glama_metadata_for_mcp_directory_scanners():
repo_root = Path(__file__).resolve().parents[1]
metadata = json.loads((repo_root / "glama.json").read_text())
assert metadata["repository"] == "https://github.com/modelscope/FunASR"
assert metadata["runtime"] == "python"
assert metadata["license"] == "MIT"
assert metadata["mcpServers"]["funasr"]["command"] == "python"
assert metadata["mcpServers"]["funasr"]["args"] == [
"examples/mcp_server/funasr_mcp.py"
]
def test_mcp_server_readme_has_glama_submission_checklist():
example_dir = Path(__file__).resolve().parents[1] / "examples" / "mcp_server"
readme = (example_dir / "README.md").read_text()
assert "### Glama submission checklist" in readme
assert "https://github.com/modelscope/FunASR" in readme
assert "`examples/mcp_server`" in readme
assert "`python /app/funasr_mcp.py`" in readme
assert "`transcribe_audio`" in readme
assert "https://glama.ai/mcp/servers/modelscope/FunASR/badges/score.svg" in readme
+72
View File
@@ -0,0 +1,72 @@
import importlib.util
import sys
import types
from pathlib import Path
def _install_fake_rapidfuzz():
rapidfuzz = types.ModuleType("rapidfuzz")
distance = types.ModuleType("rapidfuzz.distance")
class Levenshtein:
@staticmethod
def distance(left, right):
left = list(left)
right = list(right)
dp = list(range(len(right) + 1))
for i, lval in enumerate(left, 1):
prev = dp[0]
dp[0] = i
for j, rval in enumerate(right, 1):
old = dp[j]
dp[j] = min(
dp[j] + 1,
dp[j - 1] + 1,
prev + (0 if lval == rval else 1),
)
prev = old
return dp[-1]
distance.Levenshtein = Levenshtein
rapidfuzz.distance = distance
sys.modules.setdefault("rapidfuzz", rapidfuzz)
sys.modules.setdefault("rapidfuzz.distance", distance)
def _load_metrics_common():
_install_fake_rapidfuzz()
module_path = Path(__file__).resolve().parents[1] / "funasr" / "metrics" / "common.py"
spec = importlib.util.spec_from_file_location("metrics_common_probe", module_path)
module = importlib.util.module_from_spec(spec)
sys.modules["metrics_common_probe"] = module
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_cer_and_wer_return_none_when_reference_length_is_zero():
common = _load_metrics_common()
calc = common.ErrorCalculator(
char_list=["<blank>", "a", "b", "c", "h", "e", "l", "o", "w", "r", "d", " "],
sym_space=" ",
sym_blank="<blank>",
report_cer=True,
report_wer=True,
)
assert calc.calculate_cer(["abc"], [" "]) is None
assert calc.calculate_wer(["hello world"], [" "]) is None
def test_cer_and_wer_keep_normal_levenshtein_semantics():
common = _load_metrics_common()
calc = common.ErrorCalculator(
char_list=["<blank>", "a", "b", "c", "x", "y", " "],
sym_space=" ",
sym_blank="<blank>",
report_cer=True,
report_wer=True,
)
assert calc.calculate_cer(["abc"], ["axc"]) == 1 / 3
assert calc.calculate_wer(["foo bar baz"], ["foo qux baz"]) == 1 / 3
+163
View File
@@ -0,0 +1,163 @@
import importlib.util
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
_MODULE_PATH = Path(__file__).resolve().parents[1] / "funasr" / "utils" / "postprocess_hotwords.py"
_SPEC = importlib.util.spec_from_file_location("postprocess_hotwords", _MODULE_PATH)
postprocess_hotwords = importlib.util.module_from_spec(_SPEC)
sys.modules["postprocess_hotwords"] = postprocess_hotwords
assert _SPEC.loader is not None
_SPEC.loader.exec_module(postprocess_hotwords)
HotwordMatch = postprocess_hotwords.HotwordMatch
PostprocessHotwordMatcher = postprocess_hotwords.PostprocessHotwordMatcher
apply_postprocess_hotwords_to_results = postprocess_hotwords.apply_postprocess_hotwords_to_results
build_postprocess_hotword_matcher = postprocess_hotwords.build_postprocess_hotword_matcher
parse_hotword_file = postprocess_hotwords.parse_hotword_file
parse_postprocess_hotwords = postprocess_hotwords.parse_postprocess_hotwords
class TestPostprocessHotwordParsing(unittest.TestCase):
def test_parse_list_and_dict(self):
explicit, fuzzy = parse_postprocess_hotwords(["科大讯飞", "东方财富"])
self.assertEqual(explicit, {})
self.assertEqual(fuzzy, ["科大讯飞", "东方财富"])
explicit, fuzzy = parse_postprocess_hotwords({"科大迅飞": "科大讯飞", "东方财富": "东方财富"})
self.assertEqual(explicit, {"科大迅飞": "科大讯飞"})
self.assertEqual(fuzzy, ["东方财富"])
def test_parse_inline_mapping(self):
explicit, fuzzy = parse_postprocess_hotwords(["撒贝你=>撒贝宁", "康辉"])
self.assertEqual(explicit, {"撒贝你": "撒贝宁"})
self.assertEqual(fuzzy, ["康辉"])
def test_parse_hotword_file(self):
with tempfile.NamedTemporaryFile("w", encoding="utf-8", delete=False) as f:
f.write("# comment\n")
f.write("科大讯飞\n")
f.write("科大迅飞=>科大讯飞\n")
path = f.name
try:
explicit, fuzzy = parse_hotword_file(path)
self.assertEqual(explicit, {"科大迅飞": "科大讯飞"})
self.assertEqual(fuzzy, ["科大讯飞"])
finally:
os.unlink(path)
class TestPostprocessHotwordMatcher(unittest.TestCase):
def test_explicit_replace(self):
matcher = PostprocessHotwordMatcher(
explicit_map={"撒贝你": "撒贝宁"},
enable_fuzzy=False,
)
text, matches = matcher.apply_text("我非常喜欢撒贝你说的新闻")
self.assertEqual(text, "我非常喜欢撒贝宁说的新闻")
self.assertEqual(len(matches), 1)
self.assertEqual(matches[0].replacement, "撒贝宁")
self.assertEqual(matches[0].score, 1.0)
def test_fuzzy_replace_with_optional_deps(self):
try:
import pypinyin # noqa: F401
import rapidfuzz # noqa: F401
except ImportError:
self.skipTest("pypinyin and rapidfuzz are required for fuzzy tests")
matcher = PostprocessHotwordMatcher(
fuzzy_targets=["科大讯飞"],
threshold=0.75,
)
text, matches = matcher.apply_text("科大迅飞的语音识别很强")
self.assertIn("科大讯飞", text)
self.assertTrue(matches)
self.assertEqual(matches[0].replacement, "科大讯飞")
def test_missing_fuzzy_dependency_raises(self):
with mock.patch.object(
postprocess_hotwords,
"_require_pypinyin",
side_effect=ImportError("missing pypinyin"),
):
with self.assertRaises(ImportError):
PostprocessHotwordMatcher(
fuzzy_targets=["科大讯飞"],
threshold=0.85,
)
def test_invalid_threshold_raises(self):
with self.assertRaises(ValueError):
PostprocessHotwordMatcher(explicit_map={"a": "b"}, threshold=1.5)
def test_sentence_info_and_timestamp_preserved(self):
matcher = PostprocessHotwordMatcher(
explicit_map={"撒贝你": "撒贝宁"},
enable_fuzzy=False,
)
result = {
"text": "撒贝你主持节目",
"timestamp": [[0, 100], [100, 200]],
"sentence_info": [
{"text": "撒贝你主持", "sentence": "撒贝你主持", "start": 0, "end": 1000},
{"text": "节目", "sentence": "节目", "start": 1000, "end": 1500},
],
}
matcher.apply_result(result, return_matches=True)
self.assertEqual(result["text"], "撒贝宁主持节目")
self.assertEqual(result["sentence_info"][0]["text"], "撒贝宁主持")
self.assertEqual(result["sentence_info"][0]["sentence"], "撒贝宁主持")
self.assertEqual(result["timestamp"], [[0, 100], [100, 200]])
self.assertEqual(result["postprocess_hotword_matches"][0]["replacement"], "撒贝宁")
def test_matcher_reused_for_multiple_results(self):
build_calls = {"count": 0}
original_build = build_postprocess_hotword_matcher
def counting_build(*args, **kwargs):
build_calls["count"] += 1
return original_build(*args, **kwargs)
results = [
{"text": "撒贝你主持", "timestamp": [1]},
{"text": "康灰播报", "timestamp": [2]},
]
cfg = {
"postprocess_hotwords": {"撒贝你": "撒贝宁", "康灰": "康辉"},
"return_postprocess_hotword_matches": True,
}
with mock.patch.object(
postprocess_hotwords,
"build_postprocess_hotword_matcher",
side_effect=counting_build,
):
updated = apply_postprocess_hotwords_to_results(results, cfg)
self.assertEqual(build_calls["count"], 1)
self.assertEqual(updated[0]["text"], "撒贝宁主持")
self.assertEqual(updated[1]["text"], "康辉播报")
self.assertEqual(len(updated[0]["postprocess_hotword_matches"]), 1)
def test_noop_when_not_configured(self):
results = [{"text": "不变", "timestamp": [1]}]
updated = apply_postprocess_hotwords_to_results(results, {})
self.assertIs(updated, results)
self.assertEqual(updated[0]["text"], "不变")
class TestHotwordMatch(unittest.TestCase):
def test_as_dict(self):
match = HotwordMatch("a", "b", 0.9, 1, 2)
self.assertEqual(
match.as_dict(),
{"original": "a", "replacement": "b", "score": 0.9, "start": 1, "end": 2},
)
if __name__ == "__main__":
unittest.main()
+119
View File
@@ -0,0 +1,119 @@
"""Tests for issue #2839: punc_model=None should not cause UnboundLocalError."""
import unittest
from unittest.mock import MagicMock, patch
import numpy as np
class TestPuncModelNone(unittest.TestCase):
"""Test that inference_with_vad works when punc_model is None."""
def _make_auto_model(self, punc_model=None, spk_model=None, spk_mode=None):
"""Create a minimal AutoModel instance with mocked dependencies."""
from funasr.auto.auto_model import AutoModel
am = AutoModel.__new__(AutoModel)
am.model = MagicMock()
am.vad_model = MagicMock()
am.punc_model = punc_model
am.punc_kwargs = {}
am.spk_model = spk_model
am.cb_model = None
am.spk_mode = spk_mode
am.vad_kwargs = {}
am.kwargs = {
"batch_size_s": 300,
"batch_size_threshold_s": 60,
"device": "cpu",
"disable_pbar": True,
"frontend": MagicMock(fs=16000),
"fs": 16000,
}
am._reset_runtime_configs = MagicMock()
return am
def _setup_mocks(self, am, mock_slice, mock_load, mock_prep):
"""Configure standard mocks for a single-segment VAD + ASR flow."""
# VAD returns one segment [0, 16000ms]
vad_result = [{"key": "test_utt", "value": [[0, 16000]]}]
# ASR returns text with timestamps
asr_result = [{"text": "hello world", "timestamp": [[0, 500], [500, 1000]]}]
call_count = [0]
results_seq = [vad_result, asr_result]
def mock_inference(data, input_len=None, model=None, kwargs=None, **cfg):
idx = call_count[0]
call_count[0] += 1
if idx < len(results_seq):
return results_seq[idx]
return [{"text": ""}]
am.inference = MagicMock(side_effect=mock_inference)
mock_prep.return_value = (["test_utt"], [np.zeros(16000, dtype=np.float32)])
mock_load.return_value = np.zeros(16000, dtype=np.float32)
mock_slice.return_value = ([np.zeros(16000, dtype=np.float32)], [16000])
@patch("funasr.auto.auto_model.slice_padding_audio_samples")
@patch("funasr.auto.auto_model.load_audio_text_image_video")
@patch("funasr.auto.auto_model.prepare_data_iterator")
def test_punc_model_none_basic(self, mock_prep, mock_load, mock_slice):
"""Basic inference with punc_model=None should not raise UnboundLocalError."""
am = self._make_auto_model(punc_model=None)
self._setup_mocks(am, mock_slice, mock_load, mock_prep)
results = am.inference_with_vad("dummy_input")
self.assertEqual(len(results), 1)
self.assertEqual(results[0]["text"], "hello world")
self.assertEqual(results[0]["key"], "test_utt")
@patch("funasr.auto.auto_model.slice_padding_audio_samples")
@patch("funasr.auto.auto_model.load_audio_text_image_video")
@patch("funasr.auto.auto_model.prepare_data_iterator")
def test_sentence_timestamp_with_punc_model_none(self, mock_prep, mock_load, mock_slice):
"""sentence_timestamp=True with punc_model=None should not crash."""
am = self._make_auto_model(punc_model=None)
self._setup_mocks(am, mock_slice, mock_load, mock_prep)
# This path previously caused UnboundLocalError on punc_res
results = am.inference_with_vad("dummy_input", sentence_timestamp=True)
self.assertEqual(len(results), 1)
# sentence_info should be empty list since punc_res is unavailable
self.assertEqual(results[0].get("sentence_info"), [])
@patch("funasr.auto.auto_model.slice_padding_audio_samples")
@patch("funasr.auto.auto_model.load_audio_text_image_video")
@patch("funasr.auto.auto_model.prepare_data_iterator")
def test_punc_model_with_value_still_works(self, mock_prep, mock_load, mock_slice):
"""When punc_model is provided, punc_res should still be used normally."""
punc_mock = MagicMock()
am = self._make_auto_model(punc_model=punc_mock)
vad_result = [{"key": "test_utt", "value": [[0, 16000]]}]
asr_result = [{"text": "hello world", "timestamp": [[0, 500], [500, 1000]]}]
punc_result = [{"text": "Hello, world.", "punc_array": [1, 2]}]
call_count = [0]
results_seq = [vad_result, asr_result, punc_result]
def mock_inference(data, input_len=None, model=None, kwargs=None, **cfg):
idx = call_count[0]
call_count[0] += 1
return results_seq[idx]
am.inference = MagicMock(side_effect=mock_inference)
mock_prep.return_value = (["test_utt"], [np.zeros(16000, dtype=np.float32)])
mock_load.return_value = np.zeros(16000, dtype=np.float32)
mock_slice.return_value = ([np.zeros(16000, dtype=np.float32)], [16000])
results = am.inference_with_vad("dummy_input")
self.assertEqual(len(results), 1)
# Text should be updated with punctuated version
self.assertEqual(results[0]["text"], "Hello, world.")
if __name__ == "__main__":
unittest.main()
+43
View File
@@ -0,0 +1,43 @@
import unittest
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestTransformerInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_inference_pipeline(self):
inference_pipeline = pipeline(
task=Tasks.punctuation,
model="damo/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
model_revision="v1.1.7",
)
inputs = "./egs_modelscope/punctuation/punc_ct-transformer_zh-cn-common-vocab272727-pytorch/data/punc_example.txt"
rec_result = inference_pipeline(text_in=inputs)
logger.info("punctuation inference result: {0}".format(rec_result))
def test_vadrealtime_inference_pipeline(self):
inference_pipeline = pipeline(
task=Tasks.punctuation,
model="damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727",
)
inputs = "跨境河流是养育沿岸|人民的生命之源长期以来为帮助下游地区防灾减灾中方技术人员|在上游地区极为恶劣的自然条件下克服巨大困难甚至冒着生命危险|向印方提供汛期水文资料处理紧急事件中方重视印方在跨境河流问题上的关切|愿意进一步完善双方联合工作机制|凡是|中方能做的我们|都会去做而且会做得更好我请印度朋友们放心中国在上游的|任何开发利用都会经过科学|规划和论证兼顾上下游的利益"
vads = inputs.split("|")
rec_result_all = "outputs:"
param_dict = {"cache": []}
for vad in vads:
rec_result = inference_pipeline(text_in=vad, param_dict=param_dict)
rec_result_all += rec_result["text"]
logger.info("punctuation inference result: {0}".format(rec_result_all))
if __name__ == "__main__":
unittest.main()
+91
View File
@@ -0,0 +1,91 @@
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
from funasr.models.qwen3_asr import model as qwen3_model
def test_qwen3_asr_dependency_check_reports_transformers_mismatch(monkeypatch):
def fake_version(package_name):
versions = {
"qwen-asr": "0.0.6",
"transformers": "5.5.0",
}
return versions[package_name]
def fake_requires(package_name):
assert package_name == "qwen-asr"
return ["transformers==4.57.6", "accelerate==1.12.0"]
monkeypatch.setattr(qwen3_model, "_package_version", fake_version)
monkeypatch.setattr(qwen3_model, "_package_requires", fake_requires)
with pytest.raises(ImportError) as exc_info:
qwen3_model._check_qwen3_asr_dependencies()
message = str(exc_info.value)
assert "Qwen3-ASR dependency mismatch" in message
assert "qwen-asr==0.0.6 requires transformers==4.57.6" in message
assert "active environment has transformers==5.5.0" in message
assert 'pip install -U "qwen-asr==0.0.6" "transformers==4.57.6" accelerate' in message
def test_qwen3_asr_dependency_check_accepts_matching_transformers(monkeypatch):
def fake_version(package_name):
versions = {
"qwen-asr": "0.0.6",
"transformers": "4.57.6",
}
return versions[package_name]
def fake_requires(package_name):
assert package_name == "qwen-asr"
return ["transformers==4.57.6", "accelerate==1.12.0"]
monkeypatch.setattr(qwen3_model, "_package_version", fake_version)
monkeypatch.setattr(qwen3_model, "_package_requires", fake_requires)
qwen3_model._check_qwen3_asr_dependencies()
def test_qwen3_asr_dependency_check_matches_requirement_names_case_insensitively(monkeypatch):
def fake_version(package_name):
versions = {
"qwen-asr": "0.0.6",
"transformers": "5.5.0",
}
return versions[package_name]
def fake_requires(package_name):
assert package_name == "qwen-asr"
return ["Transformers==4.57.6"]
monkeypatch.setattr(qwen3_model, "_package_version", fake_version)
monkeypatch.setattr(qwen3_model, "_package_requires", fake_requires)
with pytest.raises(ImportError) as exc_info:
qwen3_model._check_qwen3_asr_dependencies()
assert "requires transformers==4.57.6" in str(exc_info.value)
def test_qwen3_asr_dependency_check_does_not_crash_on_non_pep440_transformers(monkeypatch):
def fake_version(package_name):
versions = {
"qwen-asr": "0.0.6",
"transformers": "custom-main",
}
return versions[package_name]
def fake_requires(package_name):
assert package_name == "qwen-asr"
return ["transformers==4.57.6"]
monkeypatch.setattr(qwen3_model, "_package_version", fake_version)
monkeypatch.setattr(qwen3_model, "_package_requires", fake_requires)
qwen3_model._check_qwen3_asr_dependencies()
+56
View File
@@ -0,0 +1,56 @@
import ast
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EXAMPLE_DIR = ROOT / "examples" / "industrial_data_pretraining" / "qwen3_asr"
SERVER_PATH = EXAMPLE_DIR / "serve_qwen3_asr_ws.py"
NOTE_PATHS = [
EXAMPLE_DIR / "serve_qwen3_asr_ws_notes.md",
EXAMPLE_DIR / "serve_qwen3_asr_ws_notes_en.md",
]
class Qwen3AsrWebsocketExampleTest(unittest.TestCase):
def test_notes_do_not_contain_review_placeholders(self):
forbidden_phrases = ["以下是我的个人理解", "The following is my personal understanding"]
for note_path in NOTE_PATHS:
text = note_path.read_text(encoding="utf-8")
for phrase in forbidden_phrases:
with self.subTest(path=note_path.name, phrase=phrase):
self.assertNotIn(phrase, text)
def test_handler_logs_unexpected_exceptions(self):
tree = ast.parse(SERVER_PATH.read_text(encoding="utf-8"), filename=str(SERVER_PATH))
handle_client = next(
node
for node in tree.body
if isinstance(node, ast.AsyncFunctionDef) and node.name == "handle_client"
)
catches_generic_exception = False
logs_exception = False
for node in ast.walk(handle_client):
if not isinstance(node, ast.ExceptHandler):
continue
if isinstance(node.type, ast.Name) and node.type.id == "Exception":
catches_generic_exception = True
for child in ast.walk(node):
if not isinstance(child, ast.Call):
continue
func = child.func
if (
isinstance(func, ast.Attribute)
and isinstance(func.value, ast.Name)
and func.value.id == "logging"
and func.attr == "exception"
):
logs_exception = True
self.assertTrue(catches_generic_exception)
self.assertTrue(logs_exception)
if __name__ == "__main__":
unittest.main()
+64
View File
@@ -0,0 +1,64 @@
import asyncio
import importlib.util
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
BENCHMARK_PATH = (
REPO_ROOT
/ "examples"
/ "industrial_data_pretraining"
/ "fun_asr_nano"
/ "realtime_ws_benchmark.py"
)
def load_benchmark_module():
module_name = "realtime_ws_benchmark_under_test"
sys.modules.pop(module_name, None)
spec = importlib.util.spec_from_file_location(module_name, BENCHMARK_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_final_message_does_not_contribute_to_response_lag(monkeypatch):
module = load_benchmark_module()
messages = iter(
[
{"partial": "hello", "duration_ms": 1000},
{"is_final": True, "sentences": [{"text": "hello"}], "duration_ms": 1200},
{"event": "stopped"},
]
)
timestamps = iter([1.2, 2.0, 2.1])
async def fake_receive_message(ws, timeout):
return next(messages)
monkeypatch.setattr(module, "receive_message", fake_receive_message)
monkeypatch.setattr(module.time, "perf_counter", lambda: next(timestamps))
metrics = {
"messages": 0,
"result_messages": 0,
"partial_messages": 0,
"final_messages": 0,
"events": {},
"first_update_ms": None,
"final_update_ms": None,
"final_after_stop_ms": None,
"response_lag_ms": [],
"stopped": False,
"errors": [],
}
asyncio.run(module.recv_results(object(), metrics, 0.0, {"value": 1.5}, 1.0))
assert metrics["result_messages"] == 2
assert metrics["partial_messages"] == 1
assert metrics["final_messages"] == 1
assert metrics["response_lag_ms"] == [200.0]
assert metrics["final_update_ms"] == 2000.0
assert metrics["final_after_stop_ms"] == 500.0
+157
View File
@@ -0,0 +1,157 @@
import importlib.util
import sys
import types
from pathlib import Path
import numpy as np
REPO_ROOT = Path(__file__).resolve().parents[1]
SERVICE_PATH = REPO_ROOT / "examples" / "industrial_data_pretraining" / "fun_asr_nano" / "serve_realtime_ws.py"
def load_service_module():
websockets_stub = types.SimpleNamespace(
exceptions=types.SimpleNamespace(ConnectionClosed=Exception),
serve=lambda *args, **kwargs: None,
)
sys.modules.setdefault("websockets", websockets_stub)
module_name = "serve_realtime_ws_under_test"
sys.modules.pop(module_name, None)
spec = importlib.util.spec_from_file_location(module_name, SERVICE_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_cli_defaults_disable_speaker_and_bound_partial_window():
module = load_service_module()
assert hasattr(module, "build_arg_parser")
args = module.build_arg_parser().parse_args([])
assert args.enable_spk is False
assert args.partial_window_sec == 15.0
def test_websocket_keepalive_kwargs_are_configurable():
module = load_service_module()
args = module.build_arg_parser().parse_args(
[
"--ws-ping-interval", "35",
"--ws-ping-timeout", "70",
"--ws-close-timeout", "12",
"--ws-max-size", "12345",
]
)
assert module.build_websocket_serve_kwargs(args) == {
"max_size": 12345,
"ping_interval": 35.0,
"ping_timeout": 70.0,
"close_timeout": 12.0,
}
def test_websocket_keepalive_kwargs_can_disable_ping():
module = load_service_module()
args = module.build_arg_parser().parse_args(
[
"--ws-ping-interval", "0",
"--ws-ping-timeout", "0",
]
)
kwargs = module.build_websocket_serve_kwargs(args)
assert kwargs["ping_interval"] is None
assert kwargs["ping_timeout"] is None
def test_create_speaker_tracker_skips_spk_when_disabled():
module = load_service_module()
args = types.SimpleNamespace(enable_spk=False, device="cuda:0")
assert module.create_speaker_tracker(object(), args) is None
def test_partial_decode_audio_uses_recent_window():
module = load_service_module()
class DummyVad:
current_speech_start = 0
def reset(self):
pass
sample_rate = 16000
session = module.RealtimeASRSession(
vllm_engine=object(),
asr_kwargs={},
vad=DummyVad(),
spk_tracker=None,
sample_rate=sample_rate,
chunk_ms=960,
partial_window_sec=2.0,
)
session.audio_buffer = np.arange(sample_rate * 5, dtype=np.float32)
audio, start_ms = session.get_partial_decode_audio()
assert start_ms == 3000
np.testing.assert_array_equal(audio, session.audio_buffer[-sample_rate * 2:])
def test_partial_decode_audio_keeps_short_current_segment():
module = load_service_module()
class DummyVad:
current_speech_start = 1500
def reset(self):
pass
sample_rate = 16000
session = module.RealtimeASRSession(
vllm_engine=object(),
asr_kwargs={},
vad=DummyVad(),
spk_tracker=None,
sample_rate=sample_rate,
chunk_ms=960,
partial_window_sec=10.0,
)
session.audio_buffer = np.arange(sample_rate * 5, dtype=np.float32)
audio, start_ms = session.get_partial_decode_audio()
assert start_ms == 1500
np.testing.assert_array_equal(audio, session.audio_buffer[int(sample_rate * 1.5):])
def test_empty_partial_response_preserves_zero_speech_start():
module = load_service_module()
class DummyVad:
current_speech_start = 0
def reset(self):
pass
sample_rate = 16000
session = module.RealtimeASRSession(
vllm_engine=object(),
asr_kwargs={},
vad=DummyVad(),
spk_tracker=None,
sample_rate=sample_rate,
chunk_ms=960,
)
session.audio_buffer = np.zeros(sample_rate, dtype=np.float32)
session.last_partial_text = ""
response = session._build_response(is_final=False)
assert response["partial_start_ms"] == 0
@@ -0,0 +1,110 @@
"""Unit tests for SenseVoice Tokenizer handling of special-token strings.
Regression guard for issue #3110: ASR models such as Fun-ASR-Nano occasionally
emit special-token strings (e.g. ``<|no|>``, a language tag) as part of the
transcription text. Re-encoding that text via ``tiktoken.Encoding.encode``
crashes by default (``disallowed_special="all"``), which takes down the whole
batch on a single bad sample during forced alignment / loss computation.
``Tokenizer.encode`` now treats special-token strings as ordinary text unless
the caller explicitly opts into a stricter policy.
These tests build a minimal in-memory tiktoken encoding, so they run without
the Fun-ASR-Nano ``multilingual.tiktoken`` vocab (which ships with the model,
not this repo) and without a GPU or model download.
"""
import unittest
import tiktoken
from funasr.models.sense_voice.whisper_lib.tokenizer import Tokenizer
def _build_test_encoding() -> tiktoken.Encoding:
"""A minimal byte-level BPE encoding with the specials Tokenizer needs.
Every single byte is its own token, so arbitrary text can be encoded
without the model's multilingual.tiktoken vocab file.
"""
mergeable_ranks = {bytes([b]): b for b in range(256)}
n_vocab = len(mergeable_ranks)
# Specials required by Tokenizer.__post_init__ (startoftranscript/translate/
# transcribe) plus a few language tags. "<|no|>" is the Norwegian tag that
# triggered #3110.
required_specials = [
"<|startoftranscript|>",
"<|no|>",
"<|zh|>",
"<|en|>",
"<|translate|>",
"<|transcribe|>",
"<|startoflm|>",
"<|startofprev|>",
"<|nospeech|>",
"<|notimestamps|>",
"<|0.00|>",
]
special_tokens = {}
for tok in required_specials:
special_tokens[tok] = n_vocab
n_vocab += 1
return tiktoken.Encoding(
name="test-encoding",
pat_str=(
r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| """
r"""?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
),
mergeable_ranks=mergeable_ranks,
special_tokens=special_tokens,
)
def _build_test_tokenizer() -> Tokenizer:
return Tokenizer(encoding=_build_test_encoding(), num_languages=1)
class TestTokenizerEncodeSpecialTokens(unittest.TestCase):
def setUp(self):
self.tokenizer = _build_test_tokenizer()
self.no_id = self.tokenizer.special_tokens["<|no|>"]
def test_plain_text_encodes(self):
ids = self.tokenizer.encode("hello world")
self.assertIsInstance(ids, list)
self.assertTrue(ids)
def test_language_tag_in_text_does_not_raise(self):
# The exact failure from issue #3110: ASR output containing "<|no|>"
# used to crash with "disallowed special token '<|no|>'".
ids = self.tokenizer.encode("hello <|no|> world")
self.assertIsInstance(ids, list)
self.assertTrue(ids)
def test_nospeech_tag_in_text_does_not_raise(self):
ids = self.tokenizer.encode("<|nospeech|> something")
self.assertIsInstance(ids, list)
self.assertTrue(ids)
def test_language_tag_alone_does_not_raise(self):
ids = self.tokenizer.encode("<|no|>")
self.assertIsInstance(ids, list)
self.assertTrue(ids)
def test_caller_disallowed_special_is_respected(self):
# A caller that explicitly wants the strict behaviour must still get
# it (setdefault must not override an explicit kwargs).
with self.assertRaises(ValueError):
self.tokenizer.encode("<|no|>", disallowed_special="all")
def test_allowed_special_all_keeps_special_token_ids(self):
# funasr/models/sense_voice/whisper_lib/decoding.py calls
# tokenizer.encode(prompt, allowed_special="all"); the special token
# must still be encoded as its token id, not as raw bytes.
ids = self.tokenizer.encode("<|no|>", allowed_special="all")
self.assertEqual(ids, [self.no_id])
if __name__ == "__main__":
unittest.main()
+48
View File
@@ -0,0 +1,48 @@
import unittest
import numpy as np
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestXVectorInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_inference_pipeline(self):
inference_sv_pipline = pipeline(
task=Tasks.speaker_verification,
model="damo/speech_xvector_sv-zh-cn-cnceleb-16k-spk3465-pytorch",
)
# the same speaker
rec_result = inference_sv_pipline(
audio_in=(
"https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/sv_example_enroll.wav",
"https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/sv_example_same.wav",
)
)
assert (
abs(rec_result["scores"][0] - 0.85) < 0.1 and abs(rec_result["scores"][1] - 0.14) < 0.1
)
logger.info(f"Similarity {rec_result['scores']}")
# different speaker
rec_result = inference_sv_pipline(
audio_in=(
"https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/sv_example_enroll.wav",
"https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/sv_example_different.wav",
)
)
assert abs(rec_result["scores"][0] - 0.0) < 0.1 and abs(rec_result["scores"][1] - 1.0) < 0.1
logger.info(f"Similarity {rec_result['scores']}")
if __name__ == "__main__":
unittest.main()
+58
View File
@@ -0,0 +1,58 @@
import unittest
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestTimestampPredictionPipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_inference_pipeline(self):
inference_pipeline = pipeline(
task=Tasks.speech_timestamp,
model="damo/speech_timestamp_prediction-v1-16k-offline",
model_revision="v1.1.0",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_timestamps.wav",
text_in="一 个 东 太 平 洋 国 家 为 什 么 跑 到 西 太 平 洋 来 了 呢",
)
print(rec_result)
logger.info("punctuation inference result: {0}".format(rec_result))
assert rec_result == {
"text": "<sil> 0.000 0.380;一 0.380 0.560;个 0.560 0.800;东 0.800 0.980;太 0.980 1.140;平 1.140 1.260;洋 1.260 1.440;国 1.440 1.680;家 1.680 1.920;<sil> 1.920 2.040;为 2.040 2.200;什 2.200 2.320;么 2.320 2.500;跑 2.500 2.680;到 2.680 2.860;西 2.860 3.040;太 3.040 3.200;平 3.200 3.380;洋 3.380 3.500;来 3.500 3.640;了 3.640 3.800;呢 3.800 4.150;<sil> 4.150 4.440;",
"timestamp": [
[380, 560],
[560, 800],
[800, 980],
[980, 1140],
[1140, 1260],
[1260, 1440],
[1440, 1680],
[1680, 1920],
[2040, 2200],
[2200, 2320],
[2320, 2500],
[2500, 2680],
[2680, 2860],
[2860, 3040],
[3040, 3200],
[3200, 3380],
[3380, 3500],
[3500, 3640],
[3640, 3800],
[3800, 4150],
],
}
if __name__ == "__main__":
unittest.main()
+85
View File
@@ -0,0 +1,85 @@
import unittest
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
class TestFSMNInferencePipelines(unittest.TestCase):
def test_funasr_path(self):
import funasr
import os
logger.info("run_dir:{0} ; funasr_path: {1}".format(os.getcwd(), funasr.__file__))
def test_8k(self):
inference_pipeline = pipeline(
task=Tasks.voice_activity_detection,
model="damo/speech_fsmn_vad_zh-cn-8k-common",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/vad_example_8k.wav"
)
logger.info("vad inference result: {0}".format(rec_result))
assert rec_result["text"] == [
[0, 1960],
[2870, 6730],
[7960, 10180],
[12140, 14830],
[15740, 19400],
[20220, 24230],
[25540, 27290],
[30070, 30970],
[32070, 34280],
[35990, 37050],
[39400, 41020],
[41810, 47320],
[48120, 52150],
[53560, 58310],
[59290, 62210],
[63110, 66420],
[67300, 68280],
[69670, 71770],
[73100, 75550],
[76850, 78500],
[79380, 83280],
[85000, 92320],
[93560, 94110],
[94990, 95620],
[96940, 97590],
[98400, 100530],
[101600, 104890],
[108780, 110900],
[112020, 113460],
[114210, 115030],
]
def test_16k(self):
inference_pipeline = pipeline(
task=Tasks.voice_activity_detection,
model="damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
)
rec_result = inference_pipeline(
audio_in="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/vad_example.wav"
)
logger.info("vad inference result: {0}".format(rec_result))
assert rec_result["text"] == [
[70, 2340],
[2620, 6200],
[6480, 23670],
[23950, 26250],
[26780, 28990],
[29950, 31430],
[31750, 37600],
[38210, 46900],
[47310, 49630],
[49910, 56460],
[56740, 59540],
[59820, 70450],
]
if __name__ == "__main__":
unittest.main()