本文档介绍基于 FunASR 框架,将 FSMN VAD + SenseVoice-Small + CAM++ 三个本地模型组合服务化部署为 OpenAI 兼容 HTTP API,并基于官方 AISHELL-1 test 数据集完成 SenseVoice-Small 精度复现的实践过程。
本实践服务接口:
POST /v1/audio/transcriptions评测目标:
模型信息:
| 模型 | ModelScope 地址 | 作用 |
|---|---|---|
| FSMN VAD | https://modelscope.cn/models/iic/speech_fsmn_vad_zh-cn-16k-common-pytorch | 语音活动检测,将长音频切分为有效语音片段 |
| SenseVoice-Small | https://modelscope.cn/models/iic/SenseVoiceSmall | 多语种语音识别、情绪/事件富文本识别 |
| CAM++ | https://modelscope.cn/models/iic/speech_campplus_sv_zh-cn_16k-common | 说话人识别/说话人标记 |
| 项目 | 版本 / 规格 |
|---|---|
| 操作系统 / 架构 | Ubuntu 22.04.5 LTS / aarch64 |
| 驱动 / 固件 | 25.2.0 / 7.0.3.220 |
| CANN | 8.5.1 |
| Python | 3.11.14 |
| torch / torch_npu | 2.9.0+cpu / 2.9.0.post1+gitee7ba04 |
在项目目录执行:
python -m pip install -r requirements.txtrequirements.txt:
fastapi
uvicorn
python-multipart
funasr
modelscope
torch
torch-npu
soundfile
librosa
pytest
httpx所有模型路径和推理参数均通过命令行参数传入。以下示例使用占位符表示本地模型目录,请按实际部署路径替换。
python -m funasr_openai_service.main \
--host 0.0.0.0 \
--port 8000 \
--asr-model <MODEL_ROOT>/SenseVoiceSmall \
--vad-model <MODEL_ROOT>/speech_fsmn_vad_zh-cn-16k-common-pytorch \
--spk-model <MODEL_ROOT>/speech_campplus_sv_zh-cn_16k-common \
--device npu:0 \
--batch-size-s 60 \
--merge-vad true \
--merge-length-s 15 \
--max-single-segment-time 30000 \
--max-upload-size-mb 100 \
--lazy-load false| 参数 | 默认值 | 说明 |
|---|---|---|
--host | 0.0.0.0 | 服务监听地址 |
--port | 8000 | 服务监听端口 |
--asr-model | SenseVoice-Small 本地模型目录 | SenseVoice-Small 模型路径 |
--vad-model | FSMN VAD 本地模型目录 | FSMN VAD 模型路径 |
--spk-model | CAM++ 本地模型目录 | CAM++ 模型路径 |
--device | npu:0 | 推理设备 |
--batch-size-s | 60 | 传给 FunASR generate() 的动态 batch 参数,单位为秒。表示内部一次 batch 推理中累计音频时长的目标规模或上限约为 60 秒;它不是 HTTP 请求数量,也不会把多个 API 请求合并成 batch。 |
--merge-vad | true | 传给 FunASR generate() 的 VAD 片段合并开关。开启后,FunASR 会尝试合并相邻 VAD 语音片段,减少过碎切分,使 ASR 获得更完整上下文。 |
--merge-length-s | 15 | 传给 FunASR generate() 的 VAD 合并目标长度,单位秒,仅在 --merge-vad true 时有意义。表示合并后的语音片段目标长度约为 15 秒。 |
--max-single-segment-time | 30000 | 传给 FunASR AutoModel 的 vad_kwargs 参数,单位毫秒。表示 VAD 单个语音片段允许的最大时长,30000 即 30 秒,用于避免 VAD 输出过长片段。 |
--max-upload-size-mb | 100 | 上传音频文件大小上限,单位 MiB |
--lazy-load | false | 是否延迟加载 FunASR 模型。默认 false,服务启动阶段即加载模型;设置为 true 时,模型会在第一次转写请求中加载。 |
其中 --batch-size-s、--merge-vad、--merge-length-s、--max-single-segment-time 均由当前服务解析后透传给 FunASR 框架处理。服务代码不自行实现 VAD 切分、片段合并或内部 batch 组织逻辑。
curl http://127.0.0.1:8000/health返回示例:
{
"status": "ok",
"model_id": "sensevoice-small-vad-campp",
"settings": {
"asr_model": "<MODEL_ROOT>/SenseVoiceSmall",
"vad_model": "<MODEL_ROOT>/speech_fsmn_vad_zh-cn-16k-common-pytorch",
"spk_model": "<MODEL_ROOT>/speech_campplus_sv_zh-cn_16k-common",
"device": "npu:0"
},
"transcriber": {
"loaded": true,
"device": "npu:0",
"npu": {
"requested": true,
"available": true,
"count": 8
},
"load_error": null
}
}curl http://127.0.0.1:8000/v1/models返回:
{
"object": "list",
"data": [
{
"id": "sensevoice-small-vad-campp",
"object": "model",
"created": 0,
"owned_by": "local"
}
]
}接口:
POST /v1/audio/transcriptions请求类型:
multipart/form-data请求参数:
| 参数 | 必填 | 默认值 | 当前行为 |
|---|---|---|---|
file | 是 | 无 | 上传音频文件 |
language | 否 | auto | 传给 FunASR 作为语言提示 |
response_format | 否 | json | 支持 json、text、srt、vtt、verbose_json |
timestamp_granularities[] | 否 | 无 | 仅 verbose_json 支持;当前支持 segment,word 明确返回 400 |
调用示例:
curl http://127.0.0.1:8000/v1/audio/transcriptions \
-F language=auto \
-F response_format=verbose_json \
-F "timestamp_granularities[]=segment" \
-F file=@<AUDIO_FILE>当前服务代码结构:
.
├── README.md
├── requirements.txt
└── funasr_openai_service
├── __init__.py
├── main.py
├── config.py
├── api.py
├── transcriber.py
├── formatter.py
├── run_result.py
├── benchmark.py
├── evaluation.py
├── datasets.py
└── model_profiles.py
核心模块职责:
| 文件 | 说明 |
|---|---|
config.py | 启动参数、模型路径、设备、上传大小等配置 |
main.py | CLI 参数解析与 Uvicorn 启动入口 |
api.py | FastAPI 路由与 OpenAI 兼容接口 |
transcriber.py | FunASR AutoModel 封装、模型加载、NPU 探测、推理调用 |
formatter.py | SenseVoice 标签清理、OpenAI 响应格式转换、SRT/VTT 生成 |
run_result.py | 单次推理结构化指标 |
benchmark.py | HTTP / service-sdk / funasr-direct 三种模式的评测 CLI |
evaluation.py | CER/WER、评测汇总、官方精度基线对比、manifest 读取 |
datasets.py | AISHELL-1 / AISHELL-4 manifest 构建 |
model_profiles.py | 三模型组件画像和官方公开精度基线 |
官方 FunASR OpenAI API 示例流程:
FastAPI 接收上传文件 → 写临时文件 → AutoModel.generate → 返回 OpenAI 风格响应当前适配后流程:
启动参数解析
→ FastAPI 服务启动
→ 默认加载 FunASR AutoModel
→ /health、/v1/models、/v1/model_profiles 可用
→ 转写请求
→ 接收上传文件并写入临时文件
→ 使用 VAD + SenseVoice-Small + CAM++ 推理
→ 清理 SenseVoice 富文本标签
→ 转换为 OpenAI json/text/srt/vtt/verbose_json 响应
→ 删除临时上传文件核心推理参数:
model.generate(
input=str(audio_path),
cache={},
language=language or "auto",
use_itn=True,
batch_size_s=settings.batch_size_s,
merge_vad=settings.merge_vad,
merge_length_s=settings.merge_length_s,
)关键说明:
| 参数 | 作用 |
|---|---|
use_itn=True | 将数字、日期、时间等转换为书面形式;适合服务输出,但可能影响官方 CER 对齐 |
batch_size_s | FunASR 内部按音频时长控制动态 batch,不等于 HTTP 请求级 batch |
merge_vad / merge_length_s | 控制 VAD 分段合并 |
spk_model | CAM++ 说话人模型,用于输出 speaker 字段 |
当前服务化请求是单文件请求,不会把多个 HTTP 请求自动合并为 batch。--direct-batch-size 只用于 benchmark.py --mode funasr-direct,不影响当前 HTTP API。
当前支持:
response_format | 返回类型 |
|---|---|
json | {"text": "..."} |
text | 纯文本 |
srt | SubRip 字幕 |
vtt | WebVTT 字幕 |
verbose_json | OpenAI 风格结构化 JSON |
verbose_json 示例:
{
"task": "transcribe",
"language": "auto",
"duration": 5.622,
"text": "开饭时间早上9点至下午5点。",
"segments": [
{
"id": 0,
"seek": 0,
"start": 0.42,
"end": 5.6,
"text": "开饭时间早上9点至下午5点。",
"tokens": [],
"temperature": 0.0,
"avg_logprob": 0.0,
"compression_ratio": 0.0,
"no_speech_prob": 0.0,
"speaker": "speaker_0"
}
]
}格式化规则:
| 规则 | 说明 |
|---|---|
| SenseVoice 标签清理 | 删除 `< |
| 时间单位转换 | FunASR 毫秒时间戳转为秒 |
| 说话人字段 | spk: 0 转为 speaker_0 |
| timestamp fallback | 无 sentence_info 时合并 timestamp 范围,避免重复整段文本 |
本实践使用 AISHELL-1 的 test split 进行 SenseVoice-Small 精度复现。
ModelScope 数据集地址:
https://www.modelscope.cn/datasets/OmniData/AISHELL-1当前用于官方精度对齐的是 AISHELL-1 的 test split,不包含 train 和 dev。
数据统计:
| 指标 | 数值 |
|---|---|
| 样本数 | 7176 |
| 总时长 | 36108.919s |
| 总时长 | 10.03h |
| 平均单条时长 | 5.032s |
| 中位数 | 4.743s |
| 最短 | 1.859s |
| 最长 | 14.700s |
| P95 | 8.153s |
时长分布:
| 区间 | 数量 |
|---|---|
< 3s | 378 |
3s - 5s | 3616 |
5s - 10s | 3111 |
>= 10s | 71 |
python -m funasr_openai_service.datasets \
aishell1 \
--root <AISHELL1_ROOT> \
--split test \
--output <AISHELL1_TEST_MANIFEST>manifest 格式:
{"id":"BAC009S0764W0121","audio":"wav/test/S0764/BAC009S0764W0121.wav","text":"参考文本","language":"zh"}| 指标 | 含义 | 方向 |
|---|---|---|
| CER | Character Error Rate,字符错误率,中文 ASR 主指标 | 越低越好 |
| WER | Word Error Rate,词错误率,英文/空格分词语言更适合 | 越低越好 |
中文 AISHELL-1 主要看 CER。WER 因中文参考文本通常无空格,数值可解释性较弱。
为对齐官方 SenseVoice-Small 在 AISHELL-1 上的精度,使用 funasr-direct 模式测试 SenseVoice-Small ASR 本体。
关键配置:
| 配置 | 值 |
|---|---|
| 模式 | funasr-direct |
| 模型 | SenseVoice-Small |
| VAD | 关闭 |
| CAM++ | 关闭 |
use_itn | False |
| batch | 50 |
| 数据集 | AISHELL-1 test 全量 7176 条 |
完整测试命令:
python -u -m funasr_openai_service.benchmark \
--manifest <AISHELL1_TEST_MANIFEST> \
--mode funasr-direct \
--official-dataset aishell1_test \
--warmup 1 \
--device npu:0 \
--direct-batch-size 50 \
--progress-every 500 \
--progress-interval-s 60 \
--output <AISHELL1_BATCH50_REPORT> \
--quiet全量结果:
| 指标 | 当前结果 |
|---|---|
| 样本数 | 7176 |
| CER | 3.0115% |
| 官方 AISHELL-1 CER | 2.96% |
| CER 差值 | +0.0515% |
结果说明:
本实践完成了 FSMN VAD、SenseVoice-Small 和 CAM++ 三个本地模型的服务化封装,并通过 OpenAI 兼容的 POST /v1/audio/transcriptions 接口对外提供转写能力。
在精度复现方面,使用 AISHELL-1 test 全量 7176 条样本、funasr-direct 模式、关闭 VAD 和 CAM++、direct_batch_size=50 的配置,SenseVoice-Small 得到 3.0115% CER,与官方 2.96% 基线差值为 +0.0515%,整体与官方公开结果基本对齐。