SenseVoice-small-onnx部署实战:Ansible自动化部署ONNX语音识别服务集群
SenseVoice-small-onnx部署实战:Ansible自动化部署ONNX语音识别服务集群
1. 引言
你有没有遇到过这样的场景?团队需要部署一个语音识别服务,支持中文、粤语、英语等多种语言,还要能自动检测说话人的情感。手动在一台服务器上部署已经够麻烦了,如果要部署到多台服务器组成集群,那工作量简直让人头疼。
今天我要分享的,就是如何用Ansible这个自动化工具,把SenseVoice-small-onnx语音识别服务一键部署到多台服务器上。SenseVoice-small是一个经过ONNX量化处理的多语言语音识别模型,只有230MB大小,但能力却很强——10秒的音频,推理时间只要70毫秒,还支持50多种语言的自动检测。
这篇文章不是那种只讲理论的教程,而是我实际部署过多次的实战经验总结。我会带你从零开始,一步步搭建起一个完整的语音识别服务集群。无论你是运维工程师、后端开发,还是对AI服务部署感兴趣的技术爱好者,都能跟着做下来。
2. 环境准备与规划
2.1 我们需要准备什么
在开始自动化部署之前,我们先要搞清楚整个架构是什么样的。简单来说,我们需要:
- 一台控制机:用来运行Ansible,控制所有的部署操作
- 多台目标服务器:实际运行语音识别服务的机器
- 模型文件:SenseVoice-small的ONNX量化模型
- 部署脚本:Ansible Playbook,告诉Ansible怎么部署
这里有个很重要的点:模型文件有230MB,如果每台服务器都从网上下载,既慢又浪费带宽。所以我们要用缓存机制,让第一台服务器下载后,其他服务器直接从内网拉取。
2.2 服务器配置建议
根据我的经验,下面这样的配置就够用了:
| 组件 | 最低要求 | 推荐配置 |
|---|---|---|
| CPU | 2核 | 4核或以上 |
| 内存 | 4GB | 8GB |
| 磁盘 | 20GB | 50GB |
| 系统 | Ubuntu 20.04/22.04 | Ubuntu 22.04 |
| Python | 3.8+ | 3.9+ |
如果你只是测试,用2核4GB的服务器也能跑起来。但如果是生产环境,建议至少4核8GB,因为语音识别还是挺吃CPU的。
2.3 网络规划
网络这块要提前规划好:
- 控制机到目标机:要能SSH免密登录
- 目标机之间:如果要做模型缓存同步,需要内网互通
- 服务端口:7860端口要对外开放(或者通过Nginx反向代理)
3. Ansible基础配置
3.1 安装Ansible
首先在控制机上安装Ansible。如果你用的是Ubuntu,很简单:
sudo apt update
sudo apt install -y ansible
安装完成后检查一下版本:
ansible --version
你应该能看到类似这样的输出:
ansible [core 2.15.0]
3.2 配置SSH免密登录
这是自动化部署的关键一步。Ansible需要通过SSH连接到目标服务器执行命令,如果每次都要输密码,那就不是自动化了。
第一步,生成SSH密钥(如果还没有的话):
ssh-keygen -t rsa -b 4096
直接按回车,使用默认设置就行。
第二步,把公钥复制到所有目标服务器:
假设你的目标服务器IP是192.168.1.101到192.168.1.103,用户名都是ubuntu:
for ip in 192.168.1.{101..103}; do
ssh-copy-id ubuntu@$ip
done
这会要求你输入每台服务器的密码,输一次就好,以后就不用了。
第三步,测试连接:
ansible all -i "192.168.1.101,192.168.1.102,192.168.1.103," -m ping -u ubuntu
如果看到每台服务器都返回"ping": "pong",那就说明配置成功了。
3.3 创建Ansible项目结构
一个好的项目结构能让后续维护轻松很多。我建议这样组织:
sensevoice-ansible/
├── inventories/
│ ├── production/
│ │ └── hosts
│ └── staging/
│ └── hosts
├── group_vars/
│ └── all.yml
├── roles/
│ └── sensevoice/
│ ├── tasks/
│ │ └── main.yml
│ ├── handlers/
│ │ └── main.yml
│ ├── templates/
│ │ └── app.py.j2
│ └── files/
│ └── requirements.txt
└── playbooks/
└── deploy.yml
我来解释一下每个目录是干什么的:
- inventories/:存放服务器列表,production是生产环境,staging是测试环境
- group_vars/:存放所有服务器共用的变量
- roles/sensevoice/:SenseVoice服务的具体部署逻辑
- playbooks/:部署的入口文件
4. 编写Ansible部署脚本
4.1 定义服务器清单
先在inventories/production/hosts文件中定义你的服务器:
[sensevoice_servers]
server1 ansible_host=192.168.1.101 ansible_user=ubuntu
server2 ansible_host=192.168.1.102 ansible_user=ubuntu
server3 ansible_host=192.168.1.103 ansible_user=ubuntu
[sensevoice_servers:vars]
ansible_python_interpreter=/usr/bin/python3
model_cache_server=192.168.1.101
这里我指定了192.168.1.101作为模型缓存服务器,其他服务器会从它那里拉取模型,而不是各自从网上下载。
4.2 配置全局变量
在group_vars/all.yml中定义一些全局配置:
# SenseVoice服务配置
sensevoice_port: 7860
sensevoice_host: "0.0.0.0"
sensevoice_model_path: "/opt/sensevoice/models"
sensevoice_app_path: "/opt/sensevoice/app"
# 模型配置
sensevoice_model_repo: "danieldong/sensevoice-small-onnx-quant"
sensevoice_model_file: "model_quant.onnx"
# 系统配置
system_user: "sensevoice"
system_group: "sensevoice"
4.3 创建部署任务
这是最核心的部分,在roles/sensevoice/tasks/main.yml中定义所有部署步骤:
---
- name: 创建系统用户和组
user:
name: "{{ system_user }}"
group: "{{ system_group }}"
system: yes
create_home: no
- name: 创建应用目录
file:
path: "{{ sensevoice_app_path }}"
state: directory
owner: "{{ system_user }}"
group: "{{ system_group }}"
mode: '0755'
- name: 创建模型目录
file:
path: "{{ sensevoice_model_path }}"
state: directory
owner: "{{ system_user }}"
group: "{{ system_group }}"
mode: '0755'
- name: 安装系统依赖
apt:
name:
- python3-pip
- python3-venv
- ffmpeg
- libsndfile1
state: present
update_cache: yes
- name: 复制requirements文件
copy:
src: files/requirements.txt
dest: "{{ sensevoice_app_path }}/requirements.txt"
owner: "{{ system_user }}"
group: "{{ system_group }}"
- name: 创建Python虚拟环境
pip:
requirements: "{{ sensevoice_app_path }}/requirements.txt"
virtualenv: "{{ sensevoice_app_path }}/venv"
virtualenv_python: python3.9
- name: 部署模型文件(缓存服务器)
block:
- name: 下载模型文件
shell: |
cd {{ sensevoice_model_path }}
huggingface-cli download {{ sensevoice_model_repo }} {{ sensevoice_model_file }} --local-dir .
become: yes
become_user: "{{ system_user }}"
when: inventory_hostname == groups['sensevoice_servers'][0]
- name: 创建模型同步服务
template:
src: templates/model-sync.service.j2
dest: /etc/systemd/system/model-sync.service
when: inventory_hostname == groups['sensevoice_servers'][0]
when: inventory_hostname == groups['sensevoice_servers'][0]
- name: 从缓存服务器拉取模型(其他服务器)
synchronize:
src: "{{ sensevoice_model_path }}/"
dest: "{{ sensevoice_model_path }}"
mode: pull
rsync_opts:
- "--exclude=*.tmp"
delegate_to: "{{ model_cache_server }}"
when: inventory_hostname != groups['sensevoice_servers'][0]
- name: 复制应用代码
template:
src: templates/app.py.j2
dest: "{{ sensevoice_app_path }}/app.py"
owner: "{{ system_user }}"
group: "{{ system_group }}"
mode: '0644'
- name: 创建systemd服务文件
template:
src: templates/sensevoice.service.j2
dest: /etc/systemd/system/sensevoice.service
- name: 重载systemd配置
systemd:
daemon_reload: yes
- name: 启用并启动服务
systemd:
name: sensevoice
state: started
enabled: yes
daemon_reload: yes
- name: 检查服务状态
shell: systemctl status sensevoice --no-pager
register: service_status
changed_when: false
- name: 显示服务状态
debug:
msg: "{{ service_status.stdout_lines }}"
这个脚本做了很多事情,我挑几个重点说说:
- 创建专用用户:为了安全,不用root用户运行服务
- 安装依赖:包括Python包和系统库(ffmpeg处理音频必须)
- 智能模型部署:第一台服务器从Hugging Face下载,其他服务器从第一台拉取
- 创建系统服务:用systemd管理,服务崩溃了会自动重启
4.4 创建应用模板
我们需要一个FastAPI应用来提供REST API。在roles/sensevoice/templates/app.py.j2中:
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
import uvicorn
import soundfile as sf
import numpy as np
import tempfile
import os
from funasr_onnx import SenseVoiceSmall
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="SenseVoice语音识别服务")
# 初始化模型
MODEL_PATH = "{{ sensevoice_model_path }}"
model = None
@app.on_event("startup")
async def startup_event():
"""服务启动时加载模型"""
global model
try:
logger.info(f"正在加载模型,路径: {MODEL_PATH}")
model = SenseVoiceSmall(
MODEL_PATH,
batch_size=10,
quantize=True
)
logger.info("模型加载成功")
except Exception as e:
logger.error(f"模型加载失败: {e}")
raise
@app.get("/health")
async def health_check():
"""健康检查接口"""
return {
"status": "healthy",
"model_loaded": model is not None,
"service": "sensevoice-onnx"
}
@app.post("/api/transcribe")
async def transcribe_audio(
file: UploadFile = File(...),
language: str = "auto",
use_itn: bool = True
):
"""
语音转写接口
Args:
file: 音频文件
language: 语言代码 (auto, zh, en, yue, ja, ko)
use_itn: 是否使用逆文本正则化
"""
if model is None:
raise HTTPException(status_code=503, detail="模型未加载")
# 检查文件类型
allowed_extensions = ['.wav', '.mp3', '.m4a', '.flac']
file_ext = os.path.splitext(file.filename)[1].lower()
if file_ext not in allowed_extensions:
raise HTTPException(
status_code=400,
detail=f"不支持的文件格式。支持: {', '.join(allowed_extensions)}"
)
try:
# 保存上传的文件
with tempfile.NamedTemporaryFile(delete=False, suffix=file_ext) as tmp_file:
content = await file.read()
tmp_file.write(content)
tmp_path = tmp_file.name
# 读取音频文件
audio, sample_rate = sf.read(tmp_path)
# 如果是单声道,确保是1D数组
if len(audio.shape) > 1:
audio = audio.mean(axis=1)
# 执行语音识别
result = model([tmp_path], language=language, use_itn=use_itn)
# 清理临时文件
os.unlink(tmp_path)
return JSONResponse({
"text": result[0],
"language": language,
"success": True,
"audio_duration": len(audio) / sample_rate if sample_rate else 0
})
except Exception as e:
logger.error(f"语音识别失败: {e}")
raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
@app.get("/docs", include_in_schema=False)
async def custom_docs():
"""自定义API文档重定向"""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/docs")
if __name__ == "__main__":
uvicorn.run(
app,
host="{{ sensevoice_host }}",
port={{ sensevoice_port }},
log_level="info"
)
这个应用提供了两个主要接口:
/health:健康检查,监控服务状态/api/transcribe:核心的语音转写接口
4.5 创建系统服务文件
在roles/sensevoice/templates/sensevoice.service.j2中:
[Unit]
Description=SenseVoice ONNX语音识别服务
After=network.target
[Service]
Type=simple
User={{ system_user }}
Group={{ system_group }}
WorkingDirectory={{ sensevoice_app_path }}
Environment="PATH={{ sensevoice_app_path }}/venv/bin"
ExecStart={{ sensevoice_app_path }}/venv/bin/python app.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
4.6 创建依赖文件
在roles/sensevoice/files/requirements.txt中:
funasr-onnx==0.0.1
gradio==4.19.0
fastapi==0.104.1
uvicorn[standard]==0.24.0
soundfile==0.12.1
jieba==0.42.1
numpy==1.24.3
python-multipart==0.0.6
5. 执行部署与验证
5.1 创建部署Playbook
在playbooks/deploy.yml中:
---
- name: 部署SenseVoice语音识别集群
hosts: sensevoice_servers
become: yes
gather_facts: yes
roles:
- sensevoice
handlers:
- name: restart sensevoice
systemd:
name: sensevoice
state: restarted
daemon_reload: yes
5.2 执行部署
现在一切准备就绪,执行部署命令:
cd sensevoice-ansible
ansible-playbook -i inventories/production/hosts playbooks/deploy.yml
你会看到Ansible开始执行任务,输出类似这样:
PLAY [部署SenseVoice语音识别集群] **********************************************
TASK [Gathering Facts] *********************************************************
ok: [server1]
ok: [server2]
ok: [server3]
TASK [sensevoice : 创建系统用户和组] *******************************************
changed: [server1]
changed: [server2]
changed: [server3]
TASK [sensevoice : 创建应用目录] ***********************************************
changed: [server1]
changed: [server2]
changed: [server3]
...(更多任务输出)...
PLAY RECAP *********************************************************************
server1 : ok=15 changed=12 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
server2 : ok=14 changed=11 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
server3 : ok=14 changed=11 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
整个过程大概需要5-10分钟,主要时间花在下载模型和安装Python包上。
5.3 验证部署结果
部署完成后,我们来验证一下服务是否正常。
首先检查服务状态:
# 在控制机上执行
ansible sensevoice_servers -i inventories/production/hosts -m shell -a "systemctl status sensevoice --no-pager"
应该看到所有服务器的服务都是active (running)状态。
测试健康检查接口:
# 测试第一台服务器
curl http://192.168.1.101:7860/health
应该返回:
{
"status": "healthy",
"model_loaded": true,
"service": "sensevoice-onnx"
}
测试语音识别接口:
先准备一个测试音频文件(可以用手机录一段话保存为test.wav),然后:
curl -X POST "http://192.168.1.101:7860/api/transcribe" \
-F "file=@test.wav" \
-F "language=auto" \
-F "use_itn=true"
如果一切正常,你会得到转写结果:
{
"text": "这是一段测试语音,用于验证语音识别服务是否正常工作。",
"language": "zh",
"success": true,
"audio_duration": 3.5
}
5.4 测试多语言支持
SenseVoice支持多种语言,我们可以测试一下:
# 测试英语
curl -X POST "http://192.168.1.101:7860/api/transcribe" \
-F "file=@english.wav" \
-F "language=en"
# 测试粤语
curl -X POST "http://192.168.1.102:7860/api/transcribe" \
-F "file=@cantonese.wav" \
-F "language=yue"
# 让模型自动检测语言
curl -X POST "http://192.168.1.103:7860/api/transcribe" \
-F "file=@japanese.wav" \
-F "language=auto"
6. 生产环境优化建议
6.1 负载均衡配置
现在我们有3台服务器都运行着语音识别服务,可以通过Nginx做负载均衡。创建一个Nginx配置:
upstream sensevoice_backend {
server 192.168.1.101:7860;
server 192.168.1.102:7860;
server 192.168.1.103:7860;
# 最少连接数负载均衡
least_conn;
}
server {
listen 80;
server_name voice-api.yourdomain.com;
location / {
proxy_pass http://sensevoice_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 健康检查
location /health {
proxy_pass http://sensevoice_backend;
}
}
6.2 监控与告警
生产环境必须要有监控。我建议用Prometheus + Grafana:
- 在每台服务器上部署Node Exporter,监控系统资源
- 添加自定义指标,比如:
- 语音识别请求数
- 平均处理时间
- 错误率
- 模型加载状态
可以在app.py中添加Prometheus指标:
from prometheus_client import Counter, Histogram, generate_latest
# 定义指标
REQUEST_COUNT = Counter('sensevoice_requests_total', 'Total requests')
REQUEST_LATENCY = Histogram('sensevoice_request_latency_seconds', 'Request latency')
ERROR_COUNT = Counter('sensevoice_errors_total', 'Total errors')
@app.post("/api/transcribe")
async def transcribe_audio(...):
REQUEST_COUNT.inc()
start_time = time.time()
try:
# ... 处理逻辑 ...
duration = time.time() - start_time
REQUEST_LATENCY.observe(duration)
return result
except Exception as e:
ERROR_COUNT.inc()
raise
@app.get("/metrics")
async def metrics():
return Response(generate_latest(), media_type="text/plain")
6.3 日志收集
用ELK Stack(Elasticsearch, Logstash, Kibana)收集和分析日志:
- 在每台服务器上安装Filebeat
- 配置收集/var/log/syslog和journalctl日志
- 在Kibana中创建仪表板,监控错误日志和性能指标
6.4 自动扩缩容
如果流量波动比较大,可以考虑自动扩缩容。用Kubernetes的话很简单,但如果是纯虚拟机,可以用Ansible + 监控脚本:
#!/bin/bash
# auto-scale.sh
# 获取当前CPU使用率
CPU_USAGE=$(ssh user@server1 "top -bn1 | grep 'Cpu(s)' | awk '{print \$2}'")
# 如果CPU使用率超过80%,增加一台服务器
if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then
echo "CPU使用率过高 ($CPU_USAGE%),开始扩容..."
# 调用云服务商API创建新服务器
# 这里以AWS为例
NEW_INSTANCE_ID=$(aws ec2 run-instances \
--image-id ami-xxxxxxxx \
--instance-type t3.medium \
--key-name my-key \
--security-group-ids sg-xxxxxxxx \
--subnet-id subnet-xxxxxxxx \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sensevoice-auto-scale}]' \
--query 'Instances[0].InstanceId' \
--output text)
# 等待实例启动
aws ec2 wait instance-running --instance-ids $NEW_INSTANCE_ID
# 获取新实例IP
NEW_IP=$(aws ec2 describe-instances \
--instance-ids $NEW_INSTANCE_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
# 添加到Ansible inventory
echo "server_new ansible_host=$NEW_IP ansible_user=ubuntu" >> inventories/production/hosts
# 部署服务
ansible-playbook -i inventories/production/hosts playbooks/deploy.yml --limit server_new
# 更新负载均衡配置
# ... 更新Nginx配置并重载 ...
echo "扩容完成,新增服务器: $NEW_IP"
fi
然后把这个脚本加到crontab里,每分钟执行一次。
7. 常见问题与解决方案
7.1 模型下载失败
问题:部署时模型下载很慢或者失败。
解决方案:
- 提前下载模型到本地,然后通过内网分发
- 使用国内镜像源
修改部署脚本,使用本地模型文件:
- name: 从本地复制模型文件
copy:
src: "/local/path/to/model_quant.onnx"
dest: "{{ sensevoice_model_path }}/model_quant.onnx"
owner: "{{ system_user }}"
group: "{{ system_group }}"
when: inventory_hostname == groups['sensevoice_servers'][0]
7.2 内存不足
问题:语音识别时内存占用过高。
解决方案:
- 调整batch_size参数,减少批处理大小
- 增加swap空间
- 使用内存限制
在app.py中调整:
model = SenseVoiceSmall(
MODEL_PATH,
batch_size=5, # 从10减少到5
quantize=True
)
7.3 并发性能问题
问题:高并发时响应变慢。
解决方案:
- 增加服务器数量
- 使用异步处理
- 添加请求队列
使用FastAPI的异步特性:
from concurrent.futures import ThreadPoolExecutor
import asyncio
executor = ThreadPoolExecutor(max_workers=10)
@app.post("/api/transcribe")
async def transcribe_audio(...):
# 使用线程池处理CPU密集型任务
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
executor,
lambda: model([tmp_path], language=language, use_itn=use_itn)
)
return result
7.4 音频格式不支持
问题:上传的音频文件格式不被支持。
解决方案:
- 使用ffmpeg进行格式转换
- 添加更详细的错误提示
在代码中添加格式转换:
import subprocess
def convert_audio(input_path, output_path):
"""转换音频格式为wav"""
cmd = [
'ffmpeg', '-i', input_path,
'-acodec', 'pcm_s16le',
'-ar', '16000',
'-ac', '1',
output_path,
'-y'
]
subprocess.run(cmd, check=True, capture_output=True)
8. 总结
通过这篇文章,我们完成了一个完整的SenseVoice-small-onnx语音识别服务集群的自动化部署。让我简单回顾一下关键点:
第一,Ansible让部署变得简单。我们只需要写一次部署脚本,就可以在任何数量的服务器上重复执行。今天部署3台,明天部署30台,工作量几乎是一样的。
第二,模型缓存机制很重要。230MB的模型文件,如果每台服务器都从网上下载,既慢又浪费。我们让第一台服务器下载,其他服务器从内网拉取,速度提升了几十倍。
第三,生产环境要考虑周全。负载均衡、监控告警、日志收集、自动扩缩容,这些都不是可有可无的。特别是语音识别这种CPU密集型的服务,监控系统资源使用情况特别重要。
第四,SenseVoice-small真的很实用。支持50多种语言自动检测,10秒音频只要70毫秒就能识别,还有情感识别功能。无论是做客服系统、会议记录,还是内容审核,都能用得上。
我建议你根据自己的实际需求调整这个方案。如果只是内部使用,可能不需要那么复杂的监控和扩缩容。如果是面向公众的服务,那么安全性、稳定性和性能就都要考虑进去。
最后,自动化部署不是一劳永逸的。随着业务发展,你可能需要调整配置、更新模型、优化性能。但有了Ansible这个基础,这些后续的维护工作也会轻松很多。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)