Qwen3-TTS API开发实战:构建语音生成微服务
Qwen3-TTS API开发实战:构建语音生成微服务
最近在折腾语音生成项目,发现Qwen3-TTS-12Hz-1.7B-Base这个模型确实有点意思。3秒就能克隆声音,支持10种语言,延迟还低到97毫秒,这性能放在开源TTS里算是相当能打了。
不过光有模型还不够,真正要用起来,得有个稳定可靠的API服务。想象一下,你的应用需要批量生成语音,或者多个用户同时请求,总不能每次都手动跑Python脚本吧?这时候就需要一个生产级的微服务。
今天我就来分享一下,怎么用FastAPI给Qwen3TS搭建一个完整的API服务。不只是简单的接口封装,还会讲到异步任务队列、负载测试、监控告警这些生产环境必备的东西。如果你正在考虑把语音生成能力集成到自己的产品里,这篇文章应该能给你不少实用的参考。
1. 环境准备与快速部署
1.1 系统要求
先说说硬件要求,这个很重要。Qwen3-TTS-1.7B模型对显存的需求大概在6-8GB左右,如果你用的是0.6B的轻量版,4-6GB显存就够了。
我测试用的环境是:
- Ubuntu 22.04 LTS
- NVIDIA RTX 4090 (24GB显存)
- Python 3.10
- CUDA 12.1
如果你的显卡是RTX 3090,也能跑得不错。要是用GTX 1080这种老卡,可能就得考虑0.6B版本了,1.7B模型会有点吃力。
1.2 基础环境搭建
第一步,创建个虚拟环境,避免包冲突:
# 创建虚拟环境
python -m venv qwen3-tts-api
source qwen3-tts-api/bin/activate
# 安装PyTorch(根据你的CUDA版本调整)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 安装Qwen3-TTS核心包
pip install qwen-tts transformers accelerate
这里有个小技巧,如果你想提升推理速度,可以装一下FlashAttention:
pip install flash-attn --no-build-isolation
装了之后速度能提升30-40%,不过Windows用户要注意,可能会有兼容性问题。
1.3 模型下载
模型可以从HuggingFace或者ModelScope下载。我习惯用HuggingFace,速度比较稳定:
# 创建模型目录
mkdir -p models/qwen3-tts
# 下载基础模型(用于语音克隆)
python -c "from transformers import AutoModel; AutoModel.from_pretrained('Qwen/Qwen3-TTS-12Hz-1.7B-Base', cache_dir='./models/qwen3-tts')"
# 下载分词器
python -c "from transformers import AutoTokenizer; AutoTokenizer.from_pretrained('Qwen/Qwen3-TTS-Tokenizer-12Hz', cache_dir='./models/qwen3-tts')"
下载需要点时间,模型大概3.5GB左右。如果网速慢,可以考虑用镜像源,或者直接去官网下载离线包。
2. 基础API服务搭建
2.1 FastAPI基础框架
现在来搭建最核心的API服务。我用FastAPI是因为它性能好,文档自动生成,用起来也简单。
先创建项目结构:
qwen3-tts-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI应用入口
│ ├── models.py # 数据模型定义
│ ├── services.py # 业务逻辑服务
│ └── utils.py # 工具函数
├── requirements.txt
└── config.yaml # 配置文件
先写个简单的FastAPI应用:
# app/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from contextlib import asynccontextmanager
from app.models import TTSRequest, TTSResponse
from app.services import TTSService
# 生命周期管理
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时加载模型
print("正在加载Qwen3-TTS模型...")
app.state.tts_service = TTSService()
yield
# 关闭时清理资源
print("正在清理资源...")
if hasattr(app.state, 'tts_service'):
del app.state.tts_service
# 创建FastAPI应用
app = FastAPI(
title="Qwen3-TTS API服务",
description="基于Qwen3-TTS-12Hz-1.7B-Base的语音生成API",
version="1.0.0",
lifespan=lifespan
)
# 添加CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境要限制具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
return {"message": "Qwen3-TTS API服务运行中", "status": "healthy"}
@app.get("/health")
async def health_check():
"""健康检查接口"""
return {"status": "healthy", "model_loaded": True}
if __name__ == "__main__":
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
reload=True,
workers=1 # 多worker需要共享模型,这里先单worker
)
2.2 TTS服务核心实现
接下来是实现真正的语音生成服务。这里要注意,模型加载比较耗内存,最好做成单例模式:
# app/services.py
import torch
import soundfile as sf
import numpy as np
from typing import Optional, Tuple
import io
import base64
from pathlib import Path
import logging
from qwen_tts import Qwen3TTSModel
logger = logging.getLogger(__name__)
class TTSService:
"""TTS服务核心类"""
def __init__(self, model_path: str = "Qwen/Qwen3-TTS-12Hz-1.7B-Base"):
self.model = None
self.model_path = model_path
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self._load_model()
def _load_model(self):
"""加载TTS模型"""
try:
logger.info(f"正在加载模型: {self.model_path}")
# 设置模型参数
torch_dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
self.model = Qwen3TTSModel.from_pretrained(
self.model_path,
device_map=self.device,
torch_dtype=torch_dtype,
attn_implementation="flash_attention_2" if self.device == "cuda" else None,
cache_dir="./models/qwen3-tts"
)
logger.info(f"模型加载完成,设备: {self.device}")
except Exception as e:
logger.error(f"模型加载失败: {str(e)}")
raise
def generate_voice_clone(
self,
text: str,
ref_audio: bytes,
ref_text: Optional[str] = None,
language: str = "Chinese",
sample_rate: int = 24000
) -> Tuple[np.ndarray, int]:
"""
生成语音克隆
Args:
text: 要合成的文本
ref_audio: 参考音频的字节数据
ref_text: 参考音频对应的文本(可选)
language: 语言代码
sample_rate: 采样率
Returns:
(音频数据, 采样率)
"""
try:
# 将字节数据保存为临时文件
import tempfile
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
tmp_file.write(ref_audio)
ref_audio_path = tmp_file.name
# 生成语音
wavs, sr = self.model.generate_voice_clone(
text=text,
language=language,
ref_audio=ref_audio_path,
ref_text=ref_text
)
# 清理临时文件
Path(ref_audio_path).unlink(missing_ok=True)
return wavs[0], sr
except Exception as e:
logger.error(f"语音生成失败: {str(e)}")
raise
def generate_base_tts(
self,
text: str,
language: str = "Chinese",
sample_rate: int = 24000
) -> Tuple[np.ndarray, int]:
"""
基础TTS生成(使用默认音色)
Args:
text: 要合成的文本
language: 语言代码
sample_rate: 采样率
Returns:
(音频数据, 采样率)
"""
try:
# 这里可以根据需要调用不同的生成方法
# 比如使用预设音色或者基础生成
wavs, sr = self.model.generate(
text=text,
language=language
)
return wavs[0], sr
except Exception as e:
logger.error(f"基础TTS生成失败: {str(e)}")
raise
def audio_to_base64(self, audio_data: np.ndarray, sample_rate: int) -> str:
"""将音频数据转换为base64字符串"""
# 将音频数据保存到内存中的WAV文件
buffer = io.BytesIO()
sf.write(buffer, audio_data, sample_rate, format='WAV')
buffer.seek(0)
# 编码为base64
audio_base64 = base64.b64encode(buffer.read()).decode('utf-8')
return audio_base64
def audio_to_bytes(self, audio_data: np.ndarray, sample_rate: int) -> bytes:
"""将音频数据转换为字节流"""
buffer = io.BytesIO()
sf.write(buffer, audio_data, sample_rate, format='WAV')
return buffer.getvalue()
2.3 API接口设计
有了核心服务,现在来设计API接口。我设计了几个常用的端点:
# 在app/main.py中添加
from fastapi import UploadFile, File, Form
from fastapi.responses import StreamingResponse, JSONResponse
import json
@app.post("/api/v1/tts/clone", response_model=TTSResponse)
async def voice_clone(
text: str = Form(..., description="要合成的文本"),
language: str = Form("Chinese", description="语言代码"),
ref_audio: UploadFile = File(..., description="参考音频文件"),
ref_text: Optional[str] = Form(None, description="参考音频对应的文本")
):
"""
语音克隆接口
- **text**: 要合成的文本内容
- **language**: 语言代码 (默认: Chinese)
- **ref_audio**: 参考音频文件 (WAV格式)
- **ref_text**: 参考音频对应的文本 (可选)
"""
try:
# 读取参考音频
audio_bytes = await ref_audio.read()
# 调用TTS服务
tts_service = app.state.tts_service
audio_data, sample_rate = tts_service.generate_voice_clone(
text=text,
ref_audio=audio_bytes,
ref_text=ref_text,
language=language
)
# 转换为base64
audio_base64 = tts_service.audio_to_base64(audio_data, sample_rate)
return TTSResponse(
success=True,
audio_data=audio_base64,
sample_rate=sample_rate,
duration=len(audio_data) / sample_rate,
message="语音生成成功"
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"语音生成失败: {str(e)}")
@app.post("/api/v1/tts/generate", response_model=TTSResponse)
async def generate_tts(
text: str = Form(..., description="要合成的文本"),
language: str = Form("Chinese", description="语言代码")
):
"""
基础TTS生成接口
- **text**: 要合成的文本内容
- **language**: 语言代码 (默认: Chinese)
"""
try:
tts_service = app.state.tts_service
audio_data, sample_rate = tts_service.generate_base_tts(
text=text,
language=language
)
audio_base64 = tts_service.audio_to_base64(audio_data, sample_rate)
return TTSResponse(
success=True,
audio_data=audio_base64,
sample_rate=sample_rate,
duration=len(audio_data) / sample_rate,
message="语音生成成功"
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"语音生成失败: {str(e)}")
@app.post("/api/v1/tts/stream")
async def stream_tts(
text: str = Form(..., description="要合成的文本"),
language: str = Form("Chinese", description="语言代码")
):
"""
流式返回音频文件
- **text**: 要合成的文本内容
- **language**: 语言代码 (默认: Chinese)
"""
try:
tts_service = app.state.tts_service
audio_data, sample_rate = tts_service.generate_base_tts(
text=text,
language=language
)
# 将音频数据转换为字节流
audio_bytes = tts_service.audio_to_bytes(audio_data, sample_rate)
# 流式返回
return StreamingResponse(
io.BytesIO(audio_bytes),
media_type="audio/wav",
headers={
"Content-Disposition": "attachment; filename=generated_audio.wav",
"Content-Length": str(len(audio_bytes))
}
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"语音生成失败: {str(e)}")
2.4 数据模型定义
数据模型用Pydantic来定义,这样能自动做数据验证:
# app/models.py
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class TTSRequest(BaseModel):
"""TTS请求模型"""
text: str = Field(..., description="要合成的文本内容")
language: str = Field("Chinese", description="语言代码")
ref_audio: Optional[str] = Field(None, description="参考音频的base64编码")
ref_text: Optional[str] = Field(None, description="参考音频对应的文本")
class TTSResponse(BaseModel):
"""TTS响应模型"""
success: bool = Field(..., description="请求是否成功")
audio_data: Optional[str] = Field(None, description="生成的音频数据(base64)")
sample_rate: Optional[int] = Field(None, description="音频采样率")
duration: Optional[float] = Field(None, description="音频时长(秒)")
message: str = Field(..., description="响应消息")
request_id: Optional[str] = Field(None, description="请求ID")
timestamp: datetime = Field(default_factory=datetime.now)
class BatchTTSRequest(BaseModel):
"""批量TTS请求模型"""
tasks: list[TTSRequest] = Field(..., description="TTS任务列表")
callback_url: Optional[str] = Field(None, description="回调URL")
class TaskStatus(BaseModel):
"""任务状态模型"""
task_id: str = Field(..., description="任务ID")
status: str = Field(..., description="任务状态")
progress: Optional[float] = Field(None, description="任务进度")
result: Optional[TTSResponse] = Field(None, description="任务结果")
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
3. 生产级功能增强
3.1 异步任务队列
在实际生产环境中,语音生成可能需要较长时间(特别是长文本),不能让用户一直等待。这时候就需要异步任务队列。
我用Celery + Redis来实现:
# app/celery_app.py
from celery import Celery
import os
from app.services import TTSService
import json
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
# 创建Celery应用
celery_app = Celery(
'qwen3_tts_tasks',
broker=os.getenv('REDIS_URL', 'redis://localhost:6379/0'),
backend=os.getenv('REDIS_URL', 'redis://localhost:6379/0')
)
# 配置Celery
celery_app.conf.update(
task_serializer='json',
accept_content=['json'],
result_serializer='json',
timezone='Asia/Shanghai',
enable_utc=True,
task_track_started=True,
task_time_limit=300, # 5分钟超时
task_soft_time_limit=240, # 4分钟软超时
)
# 全局TTS服务实例
tts_service = None
@celery_app.task(bind=True, name='generate_tts_task')
def generate_tts_task(self, task_data: dict):
"""异步TTS生成任务"""
global tts_service
try:
# 懒加载TTS服务
if tts_service is None:
tts_service = TTSService()
# 更新任务状态
self.update_state(
state='PROGRESS',
meta={'progress': 10, 'message': '开始处理任务'}
)
# 提取任务数据
text = task_data.get('text', '')
language = task_data.get('language', 'Chinese')
ref_audio_b64 = task_data.get('ref_audio')
ref_text = task_data.get('ref_text')
self.update_state(
state='PROGRESS',
meta={'progress': 30, 'message': '正在生成语音'}
)
# 生成语音
if ref_audio_b64:
# 语音克隆
import base64
ref_audio = base64.b64decode(ref_audio_b64)
audio_data, sample_rate = tts_service.generate_voice_clone(
text=text,
ref_audio=ref_audio,
ref_text=ref_text,
language=language
)
else:
# 基础TTS
audio_data, sample_rate = tts_service.generate_base_tts(
text=text,
language=language
)
self.update_state(
state='PROGRESS',
meta={'progress': 80, 'message': '正在编码音频'}
)
# 编码为base64
audio_base64 = tts_service.audio_to_base64(audio_data, sample_rate)
# 返回结果
result = {
'success': True,
'audio_data': audio_base64,
'sample_rate': sample_rate,
'duration': len(audio_data) / sample_rate,
'message': '语音生成成功',
'task_id': self.request.id
}
return result
except Exception as e:
logger.error(f"异步任务失败: {str(e)}")
return {
'success': False,
'error': str(e),
'task_id': self.request.id
}
然后在FastAPI中集成异步任务:
# app/main.py中添加
from app.celery_app import celery_app, generate_tts_task
from celery.result import AsyncResult
@app.post("/api/v1/tts/async")
async def async_tts(request: TTSRequest):
"""
异步TTS生成接口
- 提交异步任务,立即返回任务ID
- 客户端可以通过任务ID查询状态和结果
"""
try:
# 转换为字典
task_data = request.dict()
# 提交Celery任务
task = generate_tts_task.delay(task_data)
return {
"success": True,
"task_id": task.id,
"status_url": f"/api/v1/tasks/{task.id}/status",
"message": "任务已提交"
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"任务提交失败: {str(e)}")
@app.get("/api/v1/tasks/{task_id}/status")
async def get_task_status(task_id: str):
"""获取任务状态"""
try:
task_result = AsyncResult(task_id, app=celery_app)
response = {
"task_id": task_id,
"status": task_result.status,
"result": task_result.result if task_result.ready() else None
}
# 如果任务正在执行,添加进度信息
if task_result.state == 'PROGRESS':
response.update(task_result.info)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务状态失败: {str(e)}")
3.2 负载均衡与多实例部署
当用户量上来后,单实例可能扛不住。这时候需要考虑多实例部署和负载均衡。
我用Docker来容器化服务:
# Dockerfile
FROM python:3.10-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
g++ \
make \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY app/ ./app/
COPY config.yaml .
# 创建模型目录
RUN mkdir -p models/qwen3-tts
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
然后用docker-compose编排多个服务:
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
celery-worker:
build: .
command: celery -A app.celery_app worker --loglevel=info --concurrency=2
environment:
- REDIS_URL=redis://redis:6379/0
volumes:
- ./models:/app/models
depends_on:
- redis
deploy:
replicas: 2 # 启动2个worker实例
tts-api:
build: .
ports:
- "8000:8000"
- "8001:8000"
- "8002:8000"
environment:
- REDIS_URL=redis://redis:6379/0
volumes:
- ./models:/app/models
depends_on:
- redis
deploy:
replicas: 3 # 启动3个API实例
restart_policy:
condition: on-failure
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- tts-api
volumes:
redis_data:
Nginx配置负载均衡:
# nginx.conf
events {
worker_connections 1024;
}
http {
upstream tts_backend {
least_conn; # 最少连接负载均衡
server tts-api-1:8000;
server tts-api-2:8000;
server tts-api-3:8000;
}
server {
listen 80;
location / {
proxy_pass http://tts_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://tts_backend;
}
}
}
3.3 性能优化与参数调优
Qwen3-TTS的性能可以通过一些参数来优化。我在实际使用中发现,调整这些参数能显著提升效果:
# app/services.py中添加优化方法
class OptimizedTTSService(TTSService):
"""优化版的TTS服务"""
def __init__(self, model_path: str = "Qwen/Qwen3-TTS-12Hz-1.7B-Base"):
super().__init__(model_path)
self.optimization_config = self._get_optimization_config()
def _get_optimization_config(self):
"""获取优化配置"""
return {
"max_length": 500, # 最大生成长度
"temperature": 0.7, # 温度参数,控制随机性
"top_p": 0.9, # 核采样参数
"repetition_penalty": 1.1, # 重复惩罚
"length_penalty": 1.0, # 长度惩罚
}
def generate_with_optimization(
self,
text: str,
language: str = "Chinese",
use_cache: bool = True
) -> Tuple[np.ndarray, int]:
"""
使用优化参数生成语音
Args:
text: 要合成的文本
language: 语言代码
use_cache: 是否使用生成缓存
Returns:
(音频数据, 采样率)
"""
try:
# 这里可以根据需要调用模型的优化生成方法
# 实际参数需要根据模型API调整
generation_config = {
"max_length": self.optimization_config["max_length"],
"temperature": self.optimization_config["temperature"],
"top_p": self.optimization_config["top_p"],
"repetition_penalty": self.optimization_config["repetition_penalty"],
"do_sample": True,
}
# 实际调用代码会根据模型API有所不同
# 这里只是示例
wavs, sr = self.model.generate(
text=text,
language=language,
**generation_config
)
return wavs[0], sr
except Exception as e:
logger.error(f"优化生成失败: {str(e)}")
raise
def batch_generate(
self,
texts: List[str],
language: str = "Chinese",
batch_size: int = 4
) -> List[Tuple[np.ndarray, int]]:
"""
批量生成语音
Args:
texts: 文本列表
language: 语言代码
batch_size: 批处理大小
Returns:
音频数据列表
"""
results = []
for i in range(0, len(texts), batch_size):
batch_texts = texts[i:i + batch_size]
logger.info(f"处理批次 {i//batch_size + 1}, 文本数: {len(batch_texts)}")
for text in batch_texts:
try:
audio_data, sample_rate = self.generate_with_optimization(
text=text,
language=language
)
results.append((audio_data, sample_rate))
except Exception as e:
logger.error(f"批量生成失败,文本: {text[:50]}..., 错误: {str(e)}")
# 返回空数据占位
results.append((np.array([]), 24000))
return results
4. 监控与告警系统
4.1 性能监控
生产环境必须要有监控。我用Prometheus + Grafana来监控服务状态:
# app/monitoring.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from fastapi import Response
import time
import psutil
import GPUtil
# 定义监控指标
REQUEST_COUNT = Counter(
'tts_request_total',
'Total number of TTS requests',
['endpoint', 'method', 'status']
)
REQUEST_LATENCY = Histogram(
'tts_request_duration_seconds',
'TTS request latency in seconds',
['endpoint']
)
ACTIVE_REQUESTS = Gauge(
'tts_active_requests',
'Number of active TTS requests'
)
GPU_MEMORY_USAGE = Gauge(
'tts_gpu_memory_usage_bytes',
'GPU memory usage in bytes',
['gpu_id']
)
GPU_UTILIZATION = Gauge(
'tts_gpu_utilization_percent',
'GPU utilization percentage',
['gpu_id']
)
class MonitoringMiddleware:
"""监控中间件"""
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope['type'] != 'http':
await self.app(scope, receive, send)
return
start_time = time.time()
endpoint = scope['path']
method = scope['method']
# 增加活跃请求数
ACTIVE_REQUESTS.inc()
async def send_wrapper(message):
if message['type'] == 'http.response.start':
status = message['status']
# 记录请求
REQUEST_COUNT.labels(
endpoint=endpoint,
method=method,
status=status
).inc()
# 记录延迟
duration = time.time() - start_time
REQUEST_LATENCY.labels(endpoint=endpoint).observe(duration)
# 减少活跃请求数
ACTIVE_REQUESTS.dec()
await send(message)
await self.app(scope, receive, send_wrapper)
def update_gpu_metrics():
"""更新GPU监控指标"""
try:
gpus = GPUtil.getGPUs()
for gpu in gpus:
GPU_MEMORY_USAGE.labels(gpu_id=gpu.id).set(gpu.memoryUsed * 1024 * 1024)
GPU_UTILIZATION.labels(gpu_id=gpu.id).set(gpu.load * 100)
except Exception as e:
# GPU不可用或监控失败
pass
@app.get("/metrics")
async def metrics():
"""Prometheus metrics endpoint"""
# 更新GPU指标
update_gpu_metrics()
return Response(
content=generate_latest(),
media_type="text/plain"
)
4.2 日志系统
完善的日志系统能帮我们快速定位问题:
# app/logging_config.py
import logging
import sys
from logging.handlers import RotatingFileHandler
import json
from datetime import datetime
def setup_logging():
"""配置日志系统"""
# 创建JSON格式的Formatter
class JsonFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': datetime.utcnow().isoformat() + 'Z',
'level': record.levelname,
'logger': record.name,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno,
}
# 添加异常信息
if record.exc_info:
log_record['exception'] = self.formatException(record.exc_info)
# 添加额外字段
if hasattr(record, 'extra'):
log_record.update(record.extra)
return json.dumps(log_record, ensure_ascii=False)
# 创建根logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 控制台处理器
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
# 文件处理器(JSON格式)
file_handler = RotatingFileHandler(
'logs/tts_api.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
file_handler.setLevel(logging.INFO)
json_formatter = JsonFormatter()
file_handler.setFormatter(json_formatter)
# 错误文件处理器
error_handler = RotatingFileHandler(
'logs/tts_api_error.log',
maxBytes=10*1024*1024,
backupCount=5
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(json_formatter)
# 添加处理器
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(error_handler)
# 设置第三方库的日志级别
logging.getLogger('uvicorn').setLevel(logging.WARNING)
logging.getLogger('celery').setLevel(logging.WARNING)
# 请求日志中间件
async def log_requests(request, call_next):
"""记录请求日志"""
logger = logging.getLogger('api.request')
# 记录请求开始
start_time = time.time()
# 获取请求信息
request_info = {
'method': request.method,
'url': str(request.url),
'client_ip': request.client.host if request.client else None,
'user_agent': request.headers.get('user-agent'),
}
# 记录请求体(如果是TTS请求)
if request.method == 'POST' and 'tts' in request.url.path:
try:
body = await request.body()
request_info['body_size'] = len(body)
except:
pass
logger.info('Request started', extra={'request': request_info})
# 处理请求
response = await call_next(request)
# 记录响应信息
process_time = time.time() - start_time
response_info = {
'status_code': response.status_code,
'process_time': process_time,
'content_length': response.headers.get('content-length'),
}
logger.info('Request completed',
extra={'request': request_info, 'response': response_info})
return response
4.3 告警配置
当服务出现问题时,需要及时告警。我用Alertmanager来配置告警规则:
# alertmanager.yml
global:
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: 'alerts@yourdomain.com'
smtp_auth_username: 'your-email@gmail.com'
smtp_auth_password: 'your-password'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'email-alerts'
receivers:
- name: 'email-alerts'
email_configs:
- to: 'admin@yourdomain.com'
send_resolved: true
# prometheus告警规则
# prometheus_rules.yml
groups:
- name: tts_alerts
rules:
- alert: HighErrorRate
expr: rate(tts_request_total{status=~"5.."}[5m]) / rate(tts_request_total[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "高错误率告警"
description: "TTS API错误率超过5% (当前值: {{ $value }})"
- alert: HighLatency
expr: histogram_quantile(0.95, rate(tts_request_duration_seconds_bucket[5m])) > 5
for: 2m
labels:
severity: warning
annotations:
summary: "高延迟告警"
description: "95%的请求延迟超过5秒 (当前值: {{ $value }}s)"
- alert: GPUHighMemoryUsage
expr: tts_gpu_memory_usage_bytes / tts_gpu_memory_total_bytes > 0.9
for: 2m
labels:
severity: warning
annotations:
summary: "GPU内存使用率过高"
description: "GPU内存使用率超过90% (当前值: {{ $value }}%)"
5. 测试与验证
5.1 单元测试
写测试能保证代码质量,特别是API服务:
# tests/test_api.py
import pytest
from fastapi.testclient import TestClient
import base64
import io
import soundfile as sf
import numpy as np
from app.main import app
client = TestClient(app)
def test_health_check():
"""测试健康检查接口"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert data["model_loaded"] == True
def test_generate_tts():
"""测试基础TTS生成"""
response = client.post(
"/api/v1/tts/generate",
data={"text": "你好,这是一个测试", "language": "Chinese"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] == True
assert data["audio_data"] is not None
assert data["sample_rate"] == 24000
assert data["duration"] > 0
# 验证音频数据可以解码
audio_bytes = base64.b64decode(data["audio_data"])
audio_buffer = io.BytesIO(audio_bytes)
audio_data, sample_rate = sf.read(audio_buffer)
assert len(audio_data) > 0
assert sample_rate == data["sample_rate"]
def test_async_tts():
"""测试异步TTS生成"""
response = client.post(
"/api/v1/tts/async",
json={
"text": "这是一个异步测试",
"language": "Chinese"
}
)
assert response.status_code == 200
data = response.json()
assert data["success"] == True
assert "task_id" in data
assert "status_url" in data
# 检查任务状态
task_id = data["task_id"]
status_response = client.get(f"/api/v1/tasks/{task_id}/status")
assert status_response.status_code == 200
# 等待任务完成(在实际测试中可能需要轮询)
# 这里只是检查接口是否正常
def test_batch_requests():
"""测试批量请求"""
import concurrent.futures
def make_request(text):
response = client.post(
"/api/v1/tts/generate",
data={"text": text, "language": "Chinese"}
)
return response.status_code
texts = [f"测试文本{i}" for i in range(5)]
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(make_request, texts))
# 所有请求都应该成功
assert all(status == 200 for status in results)
@pytest.mark.performance
def test_performance():
"""性能测试"""
import time
start_time = time.time()
num_requests = 10
for i in range(num_requests):
response = client.post(
"/api/v1/tts/generate",
data={"text": f"性能测试请求{i}", "language": "Chinese"}
)
assert response.status_code == 200
total_time = time.time() - start_time
avg_time = total_time / num_requests
print(f"\n性能测试结果:")
print(f"总请求数: {num_requests}")
print(f"总耗时: {total_time:.2f}秒")
print(f"平均耗时: {avg_time:.2f}秒")
print(f"QPS: {num_requests/total_time:.2f}")
# 性能断言(根据实际情况调整)
assert avg_time < 5.0 # 平均响应时间应小于5秒
5.2 负载测试
用Locust做负载测试,看看服务能承受多大压力:
# locustfile.py
from locust import HttpUser, task, between
import random
class TTSUser(HttpUser):
wait_time = between(1, 3) # 用户等待时间
def on_start(self):
"""用户启动时执行"""
self.texts = [
"你好,欢迎使用语音生成服务",
"今天天气不错,适合出门散步",
"人工智能正在改变世界",
"语音合成技术越来越成熟了",
"这是一个测试文本,用于性能测试"
]
@task(3) # 权重为3,更频繁执行
def generate_tts(self):
"""测试TTS生成"""
text = random.choice(self.texts)
self.client.post(
"/api/v1/tts/generate",
data={"text": text, "language": "Chinese"}
)
@task(1) # 权重为1
def async_tts(self):
"""测试异步TTS"""
text = random.choice(self.texts)
response = self.client.post(
"/api/v1/tts/async",
json={"text": text, "language": "Chinese"}
)
if response.status_code == 200:
task_id = response.json()["task_id"]
# 可以继续查询任务状态
self.client.get(f"/api/v1/tasks/{task_id}/status")
@task(1)
def health_check(self):
"""健康检查"""
self.client.get("/health")
运行负载测试:
# 启动Locust
locust -f locustfile.py --host=http://localhost:8000
# 或者无头模式运行
locust -f locustfile.py --host=http://localhost:8000 \
--headless -u 100 -r 10 -t 5m
5.3 集成测试
最后写个完整的集成测试脚本:
# tests/integration_test.py
import requests
import time
import json
import base64
import soundfile as sf
import io
from concurrent.futures import ThreadPoolExecutor
class TTSAPITester:
"""TTS API集成测试器"""
def __init__(self, base_url="http://localhost:8000"):
self.base_url = base_url
self.session = requests.Session()
def test_full_workflow(self):
"""测试完整工作流"""
print("开始完整工作流测试...")
# 1. 健康检查
print("1. 测试健康检查...")
health_response = self.session.get(f"{self.base_url}/health")
assert health_response.status_code == 200
print(" 健康检查通过")
# 2. 同步TTS生成
print("2. 测试同步TTS生成...")
sync_response = self.session.post(
f"{self.base_url}/api/v1/tts/generate",
data={"text": "集成测试同步生成", "language": "Chinese"}
)
assert sync_response.status_code == 200
sync_data = sync_response.json()
assert sync_data["success"] == True
print(f" 同步生成成功,时长: {sync_data['duration']:.2f}秒")
# 3. 异步TTS生成
print("3. 测试异步TTS生成...")
async_response = self.session.post(
f"{self.base_url}/api/v1/tts/async",
json={"text": "集成测试异步生成", "language": "Chinese"}
)
assert async_response.status_code == 200
async_data = async_response.json()
task_id = async_data["task_id"]
print(f" 异步任务已提交,任务ID: {task_id}")
# 4. 轮询任务状态
print("4. 轮询任务状态...")
for i in range(10): # 最多轮询10次
time.sleep(2)
status_response = self.session.get(
f"{self.base_url}/api/v1/tasks/{task_id}/status"
)
status_data = status_response.json()
if status_data["status"] == "SUCCESS":
print(f" 异步任务完成,结果: {status_data['result']['message']}")
break
elif status_data["status"] == "FAILURE":
print(f" 异步任务失败: {status_data['result']}")
break
else:
print(" 异步任务超时")
# 5. 流式音频下载
print("5. 测试流式音频下载...")
stream_response = self.session.post(
f"{self.base_url}/api/v1/tts/stream",
data={"text": "流式音频测试", "language": "Chinese"}
)
assert stream_response.status_code == 200
assert stream_response.headers["content-type"] == "audio/wav"
print(" 流式下载成功")
# 6. 并发测试
print("6. 测试并发请求...")
self.test_concurrent_requests()
print("\n所有测试通过!")
def test_concurrent_requests(self, num_requests=10, max_workers=5):
"""测试并发请求"""
def make_request(i):
try:
response = self.session.post(
f"{self.base_url}/api/v1/tts/generate",
data={"text": f"并发测试请求{i}", "language": "Chinese"},
timeout=30
)
return response.status_code == 200
except Exception as e:
print(f" 请求{i}失败: {str(e)}")
return False
start_time = time.time()
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(executor.map(make_request, range(num_requests)))
total_time = time.time() - start_time
success_count = sum(results)
print(f" 并发测试完成:")
print(f" 总请求数: {num_requests}")
print(f" 成功数: {success_count}")
print(f" 失败数: {num_requests - success_count}")
print(f" 总耗时: {total_time:.2f}秒")
print(f" 平均QPS: {num_requests/total_time:.2f}")
assert success_count >= num_requests * 0.8 # 80%成功率
if __name__ == "__main__":
tester = TTSAPITester()
tester.test_full_workflow()
6. 总结
折腾了这么一圈,从模型部署到API开发,再到生产级的功能增强,算是把Qwen3-TTS的API服务给搭完整了。整个过程下来,有几点感受比较深。
首先是部署方面,Qwen3-TTS的模型确实对硬件有要求,但1.7B版本在RTX 4090上跑得挺流畅,实时生成没问题。如果显存紧张,可以考虑0.6B版本,效果略有下降但速度更快。
API设计这块,我分了同步和异步两种接口。短文本用同步接口,响应快;长文本或者批量任务用异步接口,避免阻塞。实际用下来,这种设计挺合理的,用户可以根据需要选择。
性能优化上,调整生成参数能明显改善效果。温度、top_p这些参数不是摆设,调好了生成的语音更自然。批处理功能也很有用,一次处理多个请求能提升吞吐量。
监控告警是生产环境必不可少的。有了Prometheus和Grafana,能实时看到服务状态,哪里有问题一目了然。告警规则要设得合理,太敏感了整天报警,太宽松了又发现不了问题。
测试环节不能省。单元测试保证代码质量,负载测试知道服务极限,集成测试验证整个流程。特别是并发测试,能提前发现性能瓶颈。
最后是部署架构,单实例肯定不够用,多实例加负载均衡才是正道。Docker容器化让部署变得简单,配合Nginx做负载均衡,服务稳定性和扩展性都好很多。
这套方案在实际项目中跑了一段时间,稳定性还不错。当然还有优化空间,比如模型缓存、请求排队、智能降级这些,可以根据具体需求再加。如果你正在考虑做语音生成服务,希望这篇文章能给你一些参考。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐

所有评论(0)