SenseVoice-small部署教程:Docker Compose编排多模型语音服务集群
SenseVoice-small部署教程:Docker Compose编排多模型语音服务集群
1. 引言
你有没有遇到过这样的场景?手机上的语音助手反应迟钝,总是要等它“思考”几秒;开视频会议时,实时字幕跟不上说话速度;或者处理一些敏感的客户录音时,总担心数据上传到云端不安全。
今天要介绍的SenseVoice-small,就是为解决这些问题而生的。它是一个轻量级的语音识别模型,专门为资源有限的场景设计——无论是你的手机、平板,还是那些没有强大GPU的服务器,甚至是嵌入式设备,它都能流畅运行。
更棒的是,我们这次要部署的不是单个服务,而是一个多模型语音服务集群。想象一下,你可以同时运行多个语音识别实例,有的处理中文会议录音,有的处理英文客服对话,还有的专门做情感分析,所有服务都通过一个统一的Web界面来管理。
这篇文章将带你一步步完成这个集群的部署。我会用最直白的方式讲解,即使你之前没怎么接触过Docker,也能跟着操作成功。我们不仅会部署服务,还会探讨如何在实际业务中用好它。
2. 为什么选择SenseVoice-small?
在开始动手之前,我们先搞清楚为什么要选这个方案。市面上语音识别的工具不少,但SenseVoice-small有几个独特的优势,让它特别适合我们今天要搭建的场景。
2.1 轻量级但功能强大
SenseVoice-small是ONNX量化版本,这意味着它被“压缩”过,体积更小,运行更快,但功能一点没打折。它支持超过50种语言,包括中文、英文、日文、韩文、粤语等,还能识别说话人的情感——是开心、悲伤还是愤怒。
量化技术简单解释:你可以把它想象成把一张高清照片转换成适合手机浏览的版本。照片看起来还是很清晰,但文件大小小了很多,加载速度也快了很多。ONNX量化就是对AI模型做类似的处理。
2.2 真正的离线运行
很多语音服务都需要联网,把音频上传到云端处理。SenseVoice-small可以在本地设备上完全离线运行,这对隐私敏感的场景特别重要:
- 医疗场景:病人的问诊录音不需要离开医院网络
- 金融场景:客户的财务咨询录音在本地处理
- 企业内部会议:敏感的商业讨论内容不外泄
2.3 资源需求极低
这是它最大的亮点之一:
| 资源类型 | 传统语音服务需求 | SenseVoice-small需求 |
|---|---|---|
| GPU | 必须 | 不需要(纯CPU运行) |
| 内存 | 通常8GB+ | 最低2GB就能跑 |
| 存储 | 模型文件几GB | 量化后几百MB |
| 网络 | 必须联网 | 完全离线 |
这意味着你可以在树莓派、旧手机、甚至一些工控设备上运行它。
2.4 多场景适用性
根据你的输入描述,这个方案特别适合:
- 端侧应用:手机、平板上的离线语音助手,实时字幕生成
- 边缘计算:没有GPU的服务器的语音转写、客服质检、会议纪要
- 隐私敏感场景:医疗、金融等需要本地处理语音数据的业务
- 低资源环境:带宽有限或算力不足的设备
3. 环境准备与Docker基础
在开始部署之前,我们需要确保环境准备就绪。如果你已经熟悉Docker和Docker Compose,可以跳过这部分直接看下一节。
3.1 系统要求
首先确认你的系统满足以下要求:
- 操作系统:Ubuntu 20.04/22.04, CentOS 7/8, 或者任何支持Docker的Linux发行版
- 内存:至少4GB(如果要运行多个实例,建议8GB+)
- 存储:至少10GB可用空间
- 网络:能正常访问Docker Hub(或者配置了国内镜像)
3.2 Docker安装与配置
如果你还没有安装Docker,跟着下面的步骤操作:
# 1. 卸载旧版本(如果有)
sudo apt-get remove docker docker-engine docker.io containerd runc
# 2. 安装依赖包
sudo apt-get update
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release
# 3. 添加Docker官方GPG密钥
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 4. 设置存储库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 5. 安装Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 6. 验证安装
sudo docker run hello-world
如果看到“Hello from Docker!”的提示,说明安装成功了。
3.3 Docker Compose安装
新版本的Docker已经包含了Compose插件,但如果你需要单独安装:
# 下载最新版本的Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version
4. 单服务部署:快速体验
在搭建集群之前,我们先部署一个单实例的服务,确保一切正常。这样如果出现问题,排查起来也简单。
4.1 创建项目目录
首先,我们创建一个专门的项目目录:
# 创建项目目录
mkdir -p ~/sensevoice-cluster
cd ~/sensevoice-cluster
# 创建必要的子目录
mkdir -p config logs models
4.2 编写单服务Docker Compose文件
创建一个名为docker-compose-single.yml的文件:
version: '3.8'
services:
sensevoice-webui:
image: sensevoice/sensevoice-small-webui:latest
container_name: sensevoice-single
restart: unless-stopped
ports:
- "7860:7860"
volumes:
- ./models:/app/models
- ./logs:/app/logs
- ./config:/app/config
environment:
- MODEL_PATH=/app/models/sensevoice-small-onnx
- LANGUAGE=auto
- ENABLE_ITN=true
networks:
- sensevoice-net
networks:
sensevoice-net:
driver: bridge
让我解释一下这个配置文件的关键部分:
- image: 使用的Docker镜像,我们用了官方的最新版本
- ports: 把容器的7860端口映射到主机的7860端口
- volumes: 把本地的目录挂载到容器内,这样数据不会丢失
- environment: 设置环境变量,比如模型路径和默认语言
4.3 启动单服务
现在启动这个单实例服务:
# 拉取镜像并启动服务
docker-compose -f docker-compose-single.yml up -d
# 查看服务状态
docker-compose -f docker-compose-single.yml ps
# 查看日志(确认服务正常启动)
docker-compose -f docker-compose-single.yml logs -f
如果一切正常,你会看到服务启动的日志,最后显示服务已经在7860端口监听。
4.4 测试单服务
打开浏览器,访问 http://你的服务器IP:7860(如果在本地就是 http://localhost:7860)。
你应该能看到一个简洁的Web界面,包含:
- 文件上传区域
- 录音按钮
- 语言选择
- 开始识别按钮
上传一个音频文件测试一下,比如一个MP3格式的录音。选择语言(或者用自动检测),点击“开始识别”,几秒钟后就能看到转写结果。
5. 多模型集群部署
现在进入正题——部署多模型集群。我们的目标是同时运行多个SenseVoice实例,每个实例可以配置不同的参数,服务于不同的业务场景。
5.1 集群架构设计
在开始编写配置文件之前,我们先看看集群的整体架构:
┌─────────────────────────────────────────────────────────┐
│ 负载均衡器 (Nginx) │
│ 端口: 80 │
└──────────────────────────┬──────────────────────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 实例1 │ │ 实例2 │ │ 实例3 │
│ 中文专用 │ │ 英文专用 │ │ 情感分析 │
│ 端口: 7861 │ │ 端口: 7862 │ │ 端口: 7863 │
└──────────────┘ └──────────────┘ └──────────────┘
这样的设计有几个好处:
- 负载均衡:多个请求可以分发到不同实例
- 专业化:不同实例可以针对特定语言或功能优化
- 高可用:一个实例挂了,其他实例还能继续服务
- 灵活扩展:需要更多处理能力时,只需增加实例
5.2 编写集群Docker Compose文件
创建主配置文件 docker-compose-cluster.yml:
version: '3.8'
services:
# 实例1:中文优化版
sensevoice-zh:
image: sensevoice/sensevoice-small-webui:latest
container_name: sensevoice-zh
restart: unless-stopped
ports:
- "7861:7860"
volumes:
- ./models/zh:/app/models
- ./logs/zh:/app/logs
environment:
- MODEL_PATH=/app/models
- LANGUAGE=zh
- ENABLE_ITN=true
- INSTANCE_NAME=中文专用实例
networks:
- sensevoice-cluster-net
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
# 实例2:英文优化版
sensevoice-en:
image: sensevoice/sensevoice-small-webui:latest
container_name: sensevoice-en
restart: unless-stopped
ports:
- "7862:7860"
volumes:
- ./models/en:/app/models
- ./logs/en:/app/logs
environment:
- MODEL_PATH=/app/models
- LANGUAGE=en
- ENABLE_ITN=true
- INSTANCE_NAME=英文专用实例
networks:
- sensevoice-cluster-net
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
# 实例3:日语专用版
sensevoice-ja:
image: sensevoice/sensevoice-small-webui:latest
container_name: sensevoice-ja
restart: unless-stopped
ports:
- "7863:7860"
volumes:
- ./models/ja:/app/models
- ./logs/ja:/app/logs
environment:
- MODEL_PATH=/app/models
- LANGUAGE=ja
- ENABLE_ITN=true
- INSTANCE_NAME=日语专用实例
networks:
- sensevoice-cluster-net
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
# 负载均衡器
nginx-lb:
image: nginx:alpine
container_name: nginx-loadbalancer
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- sensevoice-cluster-net
depends_on:
- sensevoice-zh
- sensevoice-en
- sensevoice-ja
networks:
sensevoice-cluster-net:
driver: bridge
5.3 配置Nginx负载均衡
创建Nginx配置目录和文件:
# 创建Nginx配置目录
mkdir -p ~/sensevoice-cluster/nginx/conf.d
# 创建主配置文件
cat > ~/sensevoice-cluster/nginx/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
EOF
# 创建负载均衡配置
cat > ~/sensevoice-cluster/nginx/conf.d/sensevoice.conf << 'EOF'
upstream sensevoice_backend {
# 负载均衡策略:轮询
least_conn;
# 后端服务实例
server sensevoice-zh:7860 max_fails=3 fail_timeout=30s;
server sensevoice-en:7860 max_fails=3 fail_timeout=30s;
server sensevoice-ja:7860 max_fails=3 fail_timeout=30s;
# 健康检查
keepalive 32;
}
server {
listen 80;
server_name localhost;
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_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 启用WebSocket支持(如果未来需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 状态页面(需要nginx status模块)
location /nginx_status {
stub_status;
access_log off;
allow 127.0.0.1;
deny all;
}
}
EOF
5.4 创建管理脚本
为了方便管理集群,我们创建几个实用的脚本:
# 创建启动脚本
cat > ~/sensevoice-cluster/start-cluster.sh << 'EOF'
#!/bin/bash
echo "正在启动 SenseVoice 集群..."
echo "========================================"
# 创建必要的目录
mkdir -p models/{zh,en,ja}
mkdir -p logs/{zh,en,ja}
mkdir -p nginx/conf.d
# 启动集群
docker-compose -f docker-compose-cluster.yml up -d
# 等待服务启动
echo "等待服务启动..."
sleep 10
# 检查服务状态
echo "检查服务状态..."
docker-compose -f docker-compose-cluster.yml ps
echo "========================================"
echo "集群启动完成!"
echo ""
echo "访问地址:"
echo "- 负载均衡器:http://localhost"
echo "- 中文实例:http://localhost:7861"
echo "- 英文实例:http://localhost:7862"
echo "- 日文实例:http://localhost:7863"
echo ""
echo "查看日志:./logs-cluster.sh"
EOF
chmod +x ~/sensevoice-cluster/start-cluster.sh
# 创建停止脚本
cat > ~/sensevoice-cluster/stop-cluster.sh << 'EOF'
#!/bin/bash
echo "正在停止 SenseVoice 集群..."
docker-compose -f docker-compose-cluster.yml down
echo "集群已停止"
EOF
chmod +x ~/sensevoice-cluster/stop-cluster.sh
# 创建日志查看脚本
cat > ~/sensevoice-cluster/logs-cluster.sh << 'EOF'
#!/bin/bash
echo "选择要查看的日志:"
echo "1) 所有服务日志"
echo "2) 中文实例日志"
echo "3) 英文实例日志"
echo "4) 日文实例日志"
echo "5) Nginx日志"
echo "6) 实时跟踪所有日志"
read -p "请输入选项 (1-6): " choice
case $choice in
1)
docker-compose -f docker-compose-cluster.yml logs
;;
2)
docker-compose -f docker-compose-cluster.yml logs sensevoice-zh
;;
3)
docker-compose -f docker-compose-cluster.yml logs sensevoice-en
;;
4)
docker-compose -f docker-compose-cluster.yml logs sensevoice-ja
;;
5)
docker-compose -f docker-compose-cluster.yml logs nginx-lb
;;
6)
docker-compose -f docker-compose-cluster.yml logs -f
;;
*)
echo "无效选项"
;;
esac
EOF
chmod +x ~/sensevoice-cluster/logs-cluster.sh
5.5 启动集群并测试
现在一切准备就绪,启动集群:
# 进入项目目录
cd ~/sensevoice-cluster
# 启动集群
./start-cluster.sh
启动完成后,你可以通过不同的方式访问服务:
- 通过负载均衡器访问:
http://localhost- 请求会自动分发到不同的实例 - 直接访问特定实例:
- 中文实例:
http://localhost:7861 - 英文实例:
http://localhost:7862 - 日文实例:
http://localhost:7863
- 中文实例:
测试一下负载均衡是否工作正常:
# 发送多个请求,观察它们被分配到不同的实例
for i in {1..10}; do
curl -s http://localhost/health | grep -o "Instance:.*" || echo "Request $i"
sleep 1
done
6. 高级配置与优化
基本的集群已经运行起来了,但要让它在生产环境中稳定高效地工作,还需要一些优化配置。
6.1 资源限制与监控
在Docker Compose文件中,我们已经为每个服务设置了资源限制。但你可能需要根据实际情况调整:
# 在docker-compose-cluster.yml中,每个服务的deploy部分可以这样调整
deploy:
resources:
limits:
memory: 4G # 根据实际内存调整
cpus: '2.0' # 根据CPU核心数调整
reservations:
memory: 2G
cpus: '1.0'
创建资源监控脚本:
cat > ~/sensevoice-cluster/monitor.sh << 'EOF'
#!/bin/bash
echo "=== SenseVoice 集群资源监控 ==="
echo "监控时间: $(date)"
echo ""
# 查看容器状态
echo "1. 容器状态:"
docker-compose -f docker-compose-cluster.yml ps
echo ""
# 查看资源使用情况
echo "2. 资源使用情况:"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
echo ""
# 查看日志文件大小
echo "3. 日志文件大小:"
du -sh logs/* 2>/dev/null || echo "日志目录为空"
echo ""
# 检查端口监听
echo "4. 端口监听情况:"
netstat -tlnp | grep -E ":80|:786[0-9]" | sort
EOF
chmod +x ~/sensevoice-cluster/monitor.sh
6.2 数据持久化配置
为了确保数据安全,我们需要配置持久化存储:
# 创建数据备份脚本
cat > ~/sensevoice-cluster/backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="./backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="sensevoice_backup_${TIMESTAMP}.tar.gz"
echo "开始备份 SenseVoice 集群数据..."
echo "备份时间: $(date)"
echo ""
# 创建备份目录
mkdir -p "${BACKUP_DIR}"
# 备份配置
echo "备份配置文件..."
tar -czf "${BACKUP_DIR}/config_${BACKUP_NAME}" config/
# 备份日志(可选,日志通常不备份)
# echo "备份日志文件..."
# tar -czf "${BACKUP_DIR}/logs_${BACKUP_NAME}" logs/
# 备份Docker Compose文件
echo "备份Docker Compose文件..."
cp docker-compose-cluster.yml "${BACKUP_DIR}/docker-compose-cluster.yml.${TIMESTAMP}"
cp docker-compose-single.yml "${BACKUP_DIR}/docker-compose-single.yml.${TIMESTAMP}"
# 备份脚本
echo "备份管理脚本..."
tar -czf "${BACKUP_DIR}/scripts_${BACKUP_NAME}" *.sh
echo ""
echo "备份完成!"
echo "备份文件保存在: ${BACKUP_DIR}"
echo "总大小: $(du -sh ${BACKUP_DIR} | cut -f1)"
EOF
chmod +x ~/sensevoice-cluster/backup.sh
6.3 健康检查与自动恢复
在Docker Compose中配置健康检查,确保服务异常时能自动恢复:
# 修改docker-compose-cluster.yml,在每个sensevoice服务中添加健康检查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:7860/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
同时,我们可以创建一个自动恢复脚本:
cat > ~/sensevoice-cluster/auto-recover.sh << 'EOF'
#!/bin/bash
echo "开始检查集群健康状态..."
echo "检查时间: $(date)"
echo ""
# 定义要检查的服务
SERVICES=("sensevoice-zh" "sensevoice-en" "sensevoice-ja" "nginx-lb")
for service in "${SERVICES[@]}"; do
echo "检查服务: ${service}"
# 检查容器是否在运行
if docker ps --filter "name=${service}" --format "{{.Names}}" | grep -q "${service}"; then
echo " ✓ 容器正在运行"
# 检查服务是否可访问(对于sensevoice服务)
if [[ "${service}" == sensevoice-* ]]; then
PORT=$(docker port "${service}" 7860/tcp | cut -d: -f2)
if curl -s --max-time 5 "http://localhost:${PORT}/health" > /dev/null; then
echo " ✓ 服务健康检查通过"
else
echo " ✗ 服务健康检查失败,尝试重启..."
docker-compose -f docker-compose-cluster.yml restart "${service}"
fi
fi
else
echo " ✗ 容器未运行,尝试启动..."
docker-compose -f docker-compose-cluster.yml up -d "${service}"
fi
echo ""
done
echo "健康检查完成"
EOF
chmod +x ~/sensevoice-cluster/auto-recover.sh
# 添加到crontab,每5分钟检查一次
(crontab -l 2>/dev/null; echo "*/5 * * * * cd /root/sensevoice-cluster && ./auto-recover.sh >> logs/health-check.log 2>&1") | crontab -
6.4 性能优化配置
根据你的使用场景,可能需要调整一些性能参数。创建一个配置文件:
cat > ~/sensevoice-cluster/config/performance.conf << 'EOF'
# SenseVoice 性能优化配置
# 根据实际硬件配置调整以下参数
# CPU核心数(根据你的服务器调整)
CPU_CORES=4
# 内存限制(单位:MB)
MEMORY_LIMIT=4096
# 并发处理数
CONCURRENT_PROCESSES=2
# 音频处理参数
AUDIO_CHUNK_SIZE=16000 # 音频块大小
MAX_AUDIO_LENGTH=300 # 最大音频长度(秒)
# 缓存配置
ENABLE_CACHE=true
CACHE_SIZE=1000 # 缓存条目数
CACHE_TTL=3600 # 缓存有效期(秒)
# 日志级别
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR
# 模型加载优化
PRELOAD_MODELS=true # 启动时预加载模型
MODEL_WARMUP=true # 模型预热
EOF
然后在Docker Compose中引用这个配置:
environment:
- MODEL_PATH=/app/models
- LANGUAGE=zh
- ENABLE_ITN=true
- INSTANCE_NAME=中文专用实例
- CONCURRENT_PROCESSES=2
- MAX_AUDIO_LENGTH=300
- PRELOAD_MODELS=true
7. 实际应用场景示例
现在集群已经部署好了,我们来看看在实际业务中怎么使用它。
7.1 场景一:多语言客服系统
假设你有一个跨境电商平台,客服需要处理中文、英文、日文三种语言的客户咨询电话。
传统方案的问题:
- 需要购买三个不同的语音识别服务
- 数据需要在不同服务间同步
- 成本高,管理复杂
使用SenseVoice集群的方案:
# customer_service.py - 多语言客服语音处理示例
import requests
import json
import os
class MultiLanguageCustomerService:
def __init__(self, base_url="http://localhost"):
self.base_url = base_url
self.instance_map = {
"zh": "中文客服",
"en": "英文客服",
"ja": "日文客服"
}
def transcribe_customer_call(self, audio_file, language="auto"):
"""
转录客户通话录音
"""
# 上传音频文件
with open(audio_file, 'rb') as f:
files = {'file': f}
data = {
'language': language,
'enable_itn': 'true'
}
# 发送到负载均衡器,自动分配到合适的实例
response = requests.post(
f"{self.base_url}/api/transcribe",
files=files,
data=data
)
if response.status_code == 200:
result = response.json()
return {
'text': result.get('text', ''),
'language': result.get('language', 'unknown'),
'emotion': result.get('emotion', 'neutral'),
'instance': result.get('instance_name', '')
}
else:
raise Exception(f"转录失败: {response.text}")
def batch_process_calls(self, call_records):
"""
批量处理通话录音
"""
results = []
for record in call_records:
try:
# 根据客户ID判断语言偏好
language = self.detect_language_preference(record['customer_id'])
# 转录通话
result = self.transcribe_customer_call(
record['audio_path'],
language
)
# 分析情感,标记紧急程度
urgency = self.analyze_urgency(result['emotion'], result['text'])
results.append({
'customer_id': record['customer_id'],
'transcription': result['text'],
'language': result['language'],
'emotion': result['emotion'],
'urgency': urgency,
'processed_by': result['instance']
})
except Exception as e:
print(f"处理失败 {record['customer_id']}: {str(e)}")
results.append({
'customer_id': record['customer_id'],
'error': str(e)
})
return results
def detect_language_preference(self, customer_id):
"""
根据客户历史数据检测语言偏好
"""
# 这里可以连接数据库查询客户历史
# 简化示例:随机返回一种语言
import random
return random.choice(['zh', 'en', 'ja'])
def analyze_urgency(self, emotion, text):
"""
分析紧急程度
"""
urgent_keywords = ['紧急', 'urgent', 'すぐに', '投诉', 'complain', '苦情']
# 检查情感
if emotion in ['angry', 'sad']:
urgency = 'high'
elif emotion == 'happy':
urgency = 'low'
else:
urgency = 'medium'
# 检查关键词
for keyword in urgent_keywords:
if keyword in text.lower():
urgency = 'high'
break
return urgency
# 使用示例
if __name__ == "__main__":
service = MultiLanguageCustomerService()
# 模拟一批通话录音
calls = [
{'customer_id': 'C001', 'audio_path': '/path/to/call1.wav'},
{'customer_id': 'C002', 'audio_path': '/path/to/call2.wav'},
{'customer_id': 'C003', 'audio_path': '/path/to/call3.wav'}
]
results = service.batch_process_calls(calls)
# 输出结果
for result in results:
print(f"客户 {result['customer_id']}:")
print(f" 语言: {result.get('language', 'N/A')}")
print(f" 情感: {result.get('emotion', 'N/A')}")
print(f" 紧急程度: {result.get('urgency', 'N/A')}")
print(f" 处理实例: {result.get('processed_by', 'N/A')}")
print(f" 转录文本: {result.get('transcription', 'N/A')[:100]}...")
print()
7.2 场景二:会议纪要自动生成
对于经常开跨国会议的企业,自动生成多语言会议纪要有很大价值。
# meeting_minutes.py - 会议纪要自动生成
import requests
import json
from datetime import datetime
class MeetingMinutesGenerator:
def __init__(self, cluster_url="http://localhost"):
self.cluster_url = cluster_url
self.instances = {
7861: "中文会议",
7862: "英文会议",
7863: "日文会议"
}
def transcribe_meeting(self, audio_file, participants, meeting_topic):
"""
转录会议录音并生成纪要
"""
# 第一步:语音转文字
transcription = self.transcribe_audio(audio_file)
# 第二步:语言识别
language = transcription.get('language', 'zh')
# 第三步:生成会议纪要
minutes = self.generate_minutes(
transcription['text'],
language,
participants,
meeting_topic
)
# 第四步:情感分析(可选)
emotion_analysis = self.analyze_meeting_emotion(transcription['text'])
return {
'meeting_topic': meeting_topic,
'date': datetime.now().strftime('%Y-%m-%d %H:%M'),
'participants': participants,
'language': language,
'transcription': transcription['text'],
'minutes': minutes,
'emotion_analysis': emotion_analysis,
'key_points': self.extract_key_points(transcription['text'], language)
}
def transcribe_audio(self, audio_file):
"""
使用集群转录音频
"""
# 可以指定使用某个实例,或者让负载均衡器自动分配
instance_port = 7861 # 默认使用中文实例
with open(audio_file, 'rb') as f:
files = {'file': f}
data = {'language': 'auto', 'enable_itn': 'true'}
response = requests.post(
f"http://localhost:{instance_port}/api/transcribe",
files=files,
data=data
)
if response.status_code == 200:
return response.json()
else:
# 失败时尝试其他实例
for port in [7862, 7863]:
try:
response = requests.post(
f"http://localhost:{port}/api/transcribe",
files=files,
data=data,
timeout=30
)
if response.status_code == 200:
return response.json()
except:
continue
raise Exception("所有实例都失败了")
def generate_minutes(self, text, language, participants, topic):
"""
生成结构化会议纪要
"""
# 这里可以集成大模型来生成更专业的纪要
# 简化示例:只做基本格式化
minutes_template = f"""
会议主题:{topic}
会议时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}
参会人员:{', '.join(participants)}
会议语言:{language}
会议内容摘要:
{text[:500]}...
主要讨论点:
1. [自动提取的第一个要点]
2. [自动提取的第二个要点]
3. [自动提取的第三个要点]
行动计划:
- [行动项1]
- [行动项2]
下次会议时间:[待定]
"""
return minutes_template
def analyze_meeting_emotion(self, text):
"""
分析会议情感倾向
"""
# 简化示例:基于关键词的情感分析
positive_words = ['好', '同意', '支持', 'great', 'agree', '支持']
negative_words = ['问题', '困难', '不同意', 'problem', 'difficult', '反对']
positive_count = sum(1 for word in positive_words if word in text)
negative_count = sum(1 for word in negative_words if word in text)
if positive_count > negative_count:
return "积极"
elif negative_count > positive_count:
return "需要关注"
else:
return "中性"
def extract_key_points(self, text, language):
"""
提取关键要点(简化示例)
"""
# 实际应用中可以使用文本分析算法
sentences = text.split('。')[:5] # 取前5个句子作为关键点
return [s.strip() for s in sentences if s.strip()]
# 使用示例
if __name__ == "__main__":
generator = MeetingMinutesGenerator()
# 模拟会议数据
meeting_data = {
'audio_file': '/path/to/meeting.wav',
'participants': ['张三', '李四', '王五', 'John Smith'],
'meeting_topic': 'Q3产品规划会议'
}
try:
minutes = generator.transcribe_meeting(**meeting_data)
print("=== 会议纪要 ===")
print(f"主题: {minutes['meeting_topic']}")
print(f"时间: {minutes['date']}")
print(f"语言: {minutes['language']}")
print(f"情感分析: {minutes['emotion_analysis']}")
print("\n关键要点:")
for i, point in enumerate(minutes['key_points'], 1):
print(f"{i}. {point}")
# 保存到文件
with open('meeting_minutes.txt', 'w', encoding='utf-8') as f:
f.write(minutes['minutes'])
print("\n会议纪要已保存到 meeting_minutes.txt")
except Exception as e:
print(f"生成会议纪要失败: {str(e)}")
7.3 场景三:实时字幕生成系统
对于在线教育、视频会议等需要实时字幕的场景:
# realtime_subtitle.py - 实时字幕生成
import pyaudio
import wave
import threading
import queue
import requests
import json
from datetime import datetime
class RealtimeSubtitleSystem:
def __init__(self, instance_port=7861, language='zh'):
self.instance_port = instance_port
self.language = language
self.audio_queue = queue.Queue()
self.subtitle_queue = queue.Queue()
self.is_recording = False
# 音频参数
self.CHUNK = 1024
self.FORMAT = pyaudio.paInt16
self.CHANNELS = 1
self.RATE = 16000 # SenseVoice推荐采样率
def start_recording(self):
"""开始录音"""
self.is_recording = True
self.recording_thread = threading.Thread(target=self._record_audio)
self.recording_thread.start()
# 启动处理线程
self.processing_thread = threading.Thread(target=self._process_audio)
self.processing_thread.start()
# 启动字幕显示线程
self.display_thread = threading.Thread(target=self._display_subtitles)
self.display_thread.start()
print("实时字幕系统已启动,开始录音...")
def stop_recording(self):
"""停止录音"""
self.is_recording = False
self.recording_thread.join()
self.processing_thread.join()
self.display_thread.join()
print("实时字幕系统已停止")
def _record_audio(self):
"""录音线程"""
p = pyaudio.PyAudio()
stream = p.open(
format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
input=True,
frames_per_buffer=self.CHUNK
)
print("录音中... (按Ctrl+C停止)")
while self.is_recording:
try:
# 读取音频数据
data = stream.read(self.CHUNK, exception_on_overflow=False)
self.audio_queue.put(data)
except KeyboardInterrupt:
break
except Exception as e:
print(f"录音错误: {e}")
break
# 清理
stream.stop_stream()
stream.close()
p.terminate()
def _process_audio(self):
"""处理音频线程"""
audio_buffer = bytearray()
buffer_duration = 3 # 每3秒处理一次
while self.is_recording or not self.audio_queue.empty():
try:
# 收集音频数据
chunk = self.audio_queue.get(timeout=1)
audio_buffer.extend(chunk)
# 每3秒处理一次
if len(audio_buffer) >= self.RATE * 2 * buffer_duration: # 采样率 * 字节宽度 * 秒数
# 保存临时文件
temp_file = self._save_temp_audio(audio_buffer)
# 发送到语音识别服务
subtitle = self._transcribe_audio(temp_file)
if subtitle:
self.subtitle_queue.put({
'timestamp': datetime.now().strftime('%H:%M:%S'),
'text': subtitle
})
# 清空缓冲区(保留最后1秒的数据用于衔接)
keep_samples = self.RATE * 2 * 1 # 保留1秒
audio_buffer = audio_buffer[-keep_samples:] if len(audio_buffer) > keep_samples else bytearray()
except queue.Empty:
continue
except Exception as e:
print(f"处理错误: {e}")
def _save_temp_audio(self, audio_data):
"""保存临时音频文件"""
import tempfile
import os
temp_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
with wave.open(temp_file.name, 'wb') as wf:
wf.setnchannels(self.CHANNELS)
wf.setsampwidth(2) # 16位 = 2字节
wf.setframerate(self.RATE)
wf.writeframes(audio_data)
return temp_file.name
def _transcribe_audio(self, audio_file):
"""调用语音识别服务"""
try:
with open(audio_file, 'rb') as f:
files = {'file': f}
data = {
'language': self.language,
'enable_itn': 'true'
}
response = requests.post(
f'http://localhost:{self.instance_port}/api/transcribe',
files=files,
data=data,
timeout=5
)
if response.status_code == 200:
result = response.json()
return result.get('text', '')
else:
print(f"识别失败: {response.status_code}")
return None
except requests.exceptions.Timeout:
print("识别超时")
return None
except Exception as e:
print(f"识别错误: {e}")
return None
finally:
# 删除临时文件
import os
if os.path.exists(audio_file):
os.unlink(audio_file)
def _display_subtitles(self):
"""显示字幕线程"""
while self.is_recording or not self.subtitle_queue.empty():
try:
subtitle = self.subtitle_queue.get(timeout=1)
print(f"[{subtitle['timestamp']}] {subtitle['text']}")
except queue.Empty:
continue
def save_transcript(self, filename='transcript.txt'):
"""保存完整转录文本"""
subtitles = []
while not self.subtitle_queue.empty():
subtitles.append(self.subtitle_queue.get())
with open(filename, 'w', encoding='utf-8') as f:
for sub in subtitles:
f.write(f"[{sub['timestamp']}] {sub['text']}\n")
print(f"转录文本已保存到 {filename}")
# 使用示例
if __name__ == "__main__":
# 需要先安装pyaudio: pip install pyaudio
import sys
print("实时字幕生成系统")
print("=" * 50)
# 选择语言
print("请选择语言:")
print("1. 中文 (默认)")
print("2. 英文")
print("3. 日文")
choice = input("请输入选择 (1-3): ").strip()
language_map = {'1': 'zh', '2': 'en', '3': 'ja'}
port_map = {'1': 7861, '2': 7862, '3': 7863}
language = language_map.get(choice, 'zh')
port = port_map.get(choice, 7861)
# 创建系统实例
system = RealtimeSubtitleSystem(instance_port=port, language=language)
print(f"\n使用 {language} 语言实例 (端口: {port})")
print("即将开始录音,请说话...")
print("按 Ctrl+C 停止录音\n")
try:
# 开始录音和字幕生成
system.start_recording()
# 等待用户中断
import time
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n正在停止...")
system.stop_recording()
# 保存转录文本
save = input("是否保存转录文本? (y/n): ").lower()
if save == 'y':
system.save_transcript()
print("系统已关闭")
8. 故障排查与维护
即使部署得很完美,在实际运行中也可能遇到问题。这里提供一些常见问题的解决方法。
8.1 常见问题与解决方案
问题1:服务启动失败
症状:docker-compose up 失败,容器无法启动
排查步骤:
# 1. 检查Docker服务状态
sudo systemctl status docker
# 2. 检查端口占用
sudo netstat -tlnp | grep :786
# 3. 查看详细错误日志
docker-compose logs --tail=50
# 4. 检查镜像是否存在
docker images | grep sensevoice
# 5. 尝试单独启动一个容器测试
docker run --rm -p 7860:7860 sensevoice/sensevoice-small-webui:latest
常见原因:
- 端口被占用:修改docker-compose.yml中的端口映射
- 镜像拉取失败:检查网络连接,或使用国内镜像源
- 权限问题:确保当前用户有Docker执行权限
问题2:Web界面无法访问
症状:浏览器打不开 http://localhost:7860
排查步骤:
# 1. 检查容器是否运行
docker ps | grep sensevoice
# 2. 检查容器日志
docker logs <容器ID>
# 3. 进入容器内部检查
docker exec -it <容器ID> /bin/bash
# 在容器内检查服务状态
ps aux | grep python
netstat -tlnp
# 4. 从容器内部访问服务
curl http://localhost:7860
# 5. 检查防火墙
sudo ufw status
问题3:语音识别结果不准确
症状:识别错误率高,或者完全识别不出来
解决方法:
-
检查音频格式:
# 使用ffmpeg检查音频信息 ffmpeg -i your_audio.wav # SenseVoice推荐格式 # 采样率:16kHz或8kHz # 声道:单声道 # 格式:WAV、MP3、M4A等 -
优化音频质量:
# 转换音频格式 ffmpeg -i input.mp3 -ar 16000 -ac 1 output.wav # 降低背景噪音(需要sox) sox input.wav output.wav noisered noise-profile.prof 0.21 -
调整识别参数:
# 在docker-compose环境变量中添加 environment: - AUDIO_SAMPLE_RATE=16000 - VAD_THRESHOLD=0.5 - BEAM_SIZE=5
问题4:性能问题(响应慢)
症状:识别速度慢,CPU/内存占用高
优化建议:
-
调整资源配置:
# 在docker-compose中增加资源限制 deploy: resources: limits: memory: 4G cpus: '2.0' reservations: memory: 2G cpus: '1.0' -
启用模型缓存:
# 在环境变量中设置 environment: - MODEL_CACHE_SIZE=1000 - ENABLE_CACHE=true -
批量处理优化:
# 使用异步处理 import asyncio import aiohttp async def batch_transcribe(audio_files): async with aiohttp.ClientSession() as session: tasks = [] for file in audio_files: task = transcribe_audio(session, file) tasks.append(task) results = await asyncio.gather(*tasks) return results
8.2 监控与告警
创建监控脚本,定期检查服务状态:
cat > ~/sensevoice-cluster/monitor-service.sh << 'EOF'
#!/bin/bash
# 监控配置
ALERT_EMAIL="admin@example.com"
LOG_FILE="./logs/monitor.log"
STATUS_FILE="./logs/status.json"
# 检查服务状态
check_service() {
local service_name=$1
local port=$2
# 检查端口是否监听
if ! nc -z localhost $port 2>/dev/null; then
echo "ERROR: $service_name (port $port) is not listening" >> "$LOG_FILE"
send_alert "$service_name 服务异常" "端口 $port 未监听"
return 1
fi
# 检查HTTP服务
if ! curl -s --max-time 5 "http://localhost:$port/health" > /dev/null; then
echo "ERROR: $service_name health check failed" >> "$LOG_FILE"
send_alert "$service_name 健康检查失败" "服务无响应"
return 1
fi
echo "INFO: $service_name is healthy" >> "$LOG_FILE"
return 0
}
# 发送告警
send_alert() {
local subject=$1
local message=$2
# 这里可以集成邮件、短信、钉钉、企业微信等告警方式
echo "[$(date)] ALERT: $subject - $message" >> "$LOG_FILE"
# 示例:发送邮件(需要配置邮件服务器)
# echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
# 示例:记录到文件(实际使用时替换为真正的告警)
echo "ALERT: $subject - $message" >> "./logs/alerts.log"
}
# 检查所有服务
services=(
"sensevoice-zh:7861"
"sensevoice-en:7862"
"sensevoice-ja:7863"
"nginx-lb:80"
)
# 创建状态文件
status="{"
first=true
for service in "${services[@]}"; do
IFS=':' read -r name port <<< "$service"
if check_service "$name" "$port"; then
status_code="healthy"
else
status_code="unhealthy"
fi
if [ "$first" = true ]; then
first=false
else
status+=","
fi
status+="\"$name\":\"$status_code\""
done
status+="}"
# 保存状态
echo "$status" > "$STATUS_FILE"
# 检查资源使用
check_resources() {
echo "=== 资源使用情况 ===" >> "$LOG_FILE"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" >> "$LOG_FILE"
# 检查磁盘空间
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$disk_usage" -gt 80 ]; then
send_alert "磁盘空间不足" "磁盘使用率: ${disk_usage}%"
fi
# 检查内存使用
mem_usage=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')
if [ "$mem_usage" -gt 85 ]; then
send_alert "内存使用过高" "内存使用率: ${mem_usage}%"
fi
}
check_resources
echo "监控完成于: $(date)" >> "$LOG_FILE"
EOF
chmod +x ~/sensevoice-cluster/monitor-service.sh
# 添加到crontab,每分钟检查一次
(crontab -l 2>/dev/null; echo "* * * * * cd /root/sensevoice-cluster && ./monitor-service.sh >> /dev/null 2>&1") | crontab -
8.3 日志管理
合理的日志管理能帮助快速定位问题:
# 创建日志管理脚本
cat > ~/sensevoice-cluster/log-rotate.sh << 'EOF'
#!/bin/bash
# 日志轮转配置
LOG_DIR="./logs"
MAX_SIZE="100M" # 单个日志文件最大大小
KEEP_DAYS=7 # 保留最近7天的日志
# 轮转应用日志
for instance_dir in "$LOG_DIR"/*/; do
if [ -d "$instance_dir" ]; then
# 压缩旧日志
find "$instance_dir" -name "*.log" -mtime +$KEEP_DAYS -exec gzip {} \;
# 删除过旧的压缩日志
find "$instance_dir" -name "*.log.gz" -mtime +30 -delete
# 检查当前日志大小
for logfile in "$instance_dir"/*.log; do
if [ -f "$logfile" ]; then
size=$(stat -c%s "$logfile")
size_mb=$((size/1024/1024))
max_size_mb=$(echo "$MAX_SIZE" | sed 's/M//')
if [ "$size_mb" -gt "$max_size_mb" ]; then
# 轮转日志
timestamp=$(date +%Y%m%d_%H%M%S)
mv "$logfile" "${logfile}.${timestamp}"
touch "$logfile"
echo "轮转日志: $logfile (大小: ${size_mb}MB)" >> "$LOG_DIR/rotate.log"
fi
fi
done
fi
done
# 清理Docker日志
docker system prune -f --filter "until=24h"
echo "日志轮转完成: $(date)" >> "$LOG_DIR/rotate.log"
EOF
chmod +x ~/sensevoice-cluster/log-rotate.sh
# 每天凌晨执行日志轮转
(crontab -l 2>/dev/null; echo "0 2 * * * cd /root/sensevoice-cluster && ./log-rotate.sh >> /dev/null 2>&1") | crontab -
9. 总结
通过这篇文章,我们完成了一个完整的SenseVoice-small多模型语音服务集群的部署。让我们回顾一下关键要点:
9.1 部署成果
我们成功搭建了一个包含以下组件的语音服务集群:
- 多语言实例:中文、英文、日文三个专用实例,每个实例针对特定语言优化
- 负载均衡:Nginx作为负载均衡器,自动分配请求到不同实例
- 高可用架构:单个实例故障不影响整体服务
- 完整的管理工具:启动、停止、监控、备份脚本
9.2 核心优势
这个方案相比单实例部署有几个明显优势:
- 性能提升:多个实例并行处理,吞吐量大幅提高
- 专业化服务:不同实例可以针对特定语言优化参数
- 资源隔离:一个实例的资源问题不会影响其他实例
- 灵活扩展:需要时随时增加新的实例
- 易于维护:统一的Docker Compose管理
9.3 实际应用价值
根据你的使用场景描述,这个集群特别适合:
- 端侧应用:可以在手机、平板上部署轻量级实例,实现离线语音助手
- 边缘计算:在没有GPU的服务器上运行,处理客服质检、会议纪要
- 隐私敏感场景:医疗、金融数据完全在本地处理,不出本地网络
- 低资源环境:在带宽有限或算力不足的设备上也能运行
9.4 后续优化建议
如果你在生产环境使用这个集群,还可以考虑以下优化:
- 容器编排:使用Kubernetes替代Docker Compose,获得更好的伸缩性和管理能力
- 监控告警:集成Prometheus + Grafana,实现更细致的监控
- 自动扩缩容:基于负载自动调整实例数量
- 模型更新:定期更新模型文件,获得更好的识别效果
- 安全加固:添加身份验证、访问控制、数据加密
9.5 开始使用
现在你的集群已经准备就绪,可以开始:
- 测试基本功能:访问Web界面,上传音频文件测试识别效果
- 集成到应用:使用提供的Python示例代码,将服务集成到你的业务系统中
- 监控运行状态:使用监控脚本确保服务稳定运行
- 根据业务调整:根据实际使用情况调整资源配置和实例数量
语音识别技术正在改变我们与设备交互的方式,而SenseVoice-small这样的轻量级方案让这项技术变得更加普及和实用。希望这个部署教程能帮助你快速搭建自己的语音服务,在实际业务中创造价值。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐




所有评论(0)