b0b81a5d60
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 8s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 43s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 53s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 59s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 49s
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 32s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m33s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 46s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 3m36s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m43s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m15s
CI/CD Pipeline / Validate - Style (push) Successful in 4m22s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m22s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m48s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m55s
AI Code Review / AI Code Review (pull_request) Successful in 6m38s
CI/CD Pipeline / Validate - Security (push) Successful in 7m26s
CI/CD Pipeline / Unit Tests (push) Successful in 1h52m21s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: backend-dev <dev@xiaoxiajianji.com> Co-committed-by: backend-dev <dev@xiaoxiajianji.com>
218 lines
8.3 KiB
Markdown
218 lines
8.3 KiB
Markdown
# MuseTalk GPU Worker 部署指南
|
||
|
||
本目录包含两个组件:
|
||
|
||
1. **gpu_worker.py**:反向轮询客户端,部署在 RTX2060 本地,轮询 SaaS API 拉取口型任务,调用本地 MuseTalk 服务推理,上传结果回 SaaS。
|
||
2. **musetalk_server.py**:MuseTalk Flask HTTP 服务端,接收 gpu_worker.py 的推理请求,调用 MuseTalk 模型生成口型同步视频。
|
||
|
||
---
|
||
|
||
## 一、环境准备
|
||
|
||
### 1.1 硬件要求
|
||
|
||
- GPU: NVIDIA RTX 2060 或更高(显存 ≥ 6GB)
|
||
- CUDA: 11.8+
|
||
- Python: 3.10+
|
||
- ffmpeg: 需安装并加入 PATH
|
||
|
||
### 1.2 安装依赖
|
||
|
||
```bash
|
||
cd deploy/gpu_worker
|
||
python3 -m venv venv
|
||
source venv/bin/activate
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
---
|
||
|
||
## 二、MuseTalk 服务端部署(musetalk_server.py)
|
||
|
||
### 2.1 配置环境变量
|
||
|
||
复制 `.env.example` 为 `.env`,修改配置:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
vim .env
|
||
```
|
||
|
||
关键配置:
|
||
|
||
| 变量 | 说明 | 默认值 |
|
||
|------|------|--------|
|
||
| `MUSE_PORT` | 监听端口 | `7861` |
|
||
| `MUSE_INFERENCE_TIMEOUT` | 推理超时秒数 | `600` |
|
||
| `MUSE_VIDEO_MAX_MB` | 视频上传大小限制 MB | `100` |
|
||
| `MUSE_AUDIO_MAX_MB` | 音频上传大小限制 MB | `20` |
|
||
| `MUSE_DEFAULT_FPS` | 视频 fps 兜底值 | `25.0` |
|
||
| `MUSE_TEMP_DIR` | 临时文件目录 | `/tmp/musetalk_$$` |
|
||
| `MUSE_VIDEO_ENCODER` | 循环视频时的编码器:`auto`(优先 h264_nvenc,失败回退 libx264)/`h264_nvenc`/`libx264` | `auto` |
|
||
| `MUSE_ENABLE_VIDEO_LOOP` | 驱动音频比视频长时循环视频补齐画面,`0` 关闭 | `1` |
|
||
|
||
### 2.2 更新部署(音轨修复,必做)
|
||
|
||
> ⚠️ 2026-09-20 修复严重 bug:旧版封装保留了源视频音轨,结果口型配的是原声而不是 TTS 驱动音频。RTX2060 机器必须重新拉取 `musetalk_server.py` 并重启:
|
||
|
||
```bash
|
||
# 在 RTX2060 上备份旧文件并拉取新版本(按实际部署路径调整)
|
||
cp ~/projects/MuseTalk/musetalk_server.py ~/projects/MuseTalk/musetalk_server.py.bak
|
||
# 从仓库 raw 地址下载最新版(替换为你的仓库地址/分支)
|
||
wget -O ~/projects/MuseTalk/musetalk_server.py \
|
||
"https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/raw/branch/develop/deploy/gpu_worker/musetalk_server.py"
|
||
|
||
# 重启服务
|
||
sudo systemctl restart musetalk-server
|
||
sudo systemctl status musetalk-server
|
||
curl http://127.0.0.1:7861/health
|
||
```
|
||
|
||
修复后封装逻辑:
|
||
|
||
- 最终 mux 强制 `-map 0:v -map 1:a`:视频流只取 MuseTalk 无声画面,音轨只取 TTS 驱动音频,杜绝 ffmpeg 默认行为带入源视频音轨
|
||
- 驱动音频不长于视频时:`-c:v copy -c:a aac -shortest`,无损秒封装
|
||
- 驱动音频长于视频时(如 TTS 15s vs 视频 9s):`-stream_loop -1` 循环画面,RTX2060 走 `h264_nvenc` 硬件重编码(NVENC 失败自动回退 libx264),`-t` 精确卡到音频时长
|
||
|
||
### 2.3 启动服务
|
||
|
||
```bash
|
||
# 前台运行(调试用)
|
||
python musetalk_server.py
|
||
|
||
# 后台运行(生产用 systemd)
|
||
sudo systemctl start musetalk-server
|
||
sudo systemctl enable musetalk-server
|
||
```
|
||
|
||
### 2.4 验证健康检查
|
||
|
||
```bash
|
||
curl http://127.0.0.1:7861/health
|
||
```
|
||
|
||
应返回:
|
||
|
||
```json
|
||
{
|
||
"status": "healthy",
|
||
"gpu": {
|
||
"gpu_name": "NVIDIA GeForce RTX 2060",
|
||
"memory_total_mb": 6144,
|
||
"memory_used_mb": 1024,
|
||
"memory_free_mb": 5120
|
||
},
|
||
"current_task": {
|
||
"task_id": null,
|
||
"running": false,
|
||
"elapsed_seconds": 0.0
|
||
},
|
||
"timestamp": 1700000000.0
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 三、GPU Worker 客户端部署(gpu_worker.py)
|
||
|
||
### 3.1 配置环境变量
|
||
|
||
复制 `.env.example` 为 `.env`,修改配置:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
vim .env
|
||
```
|
||
|
||
关键配置:
|
||
|
||
| 变量 | 说明 | 默认值 |
|
||
|------|------|--------|
|
||
| `API_BASE_URL` | SaaS API 基础 URL | `https://staging-api.xiaoxiajianji.com` |
|
||
| `GPU_WORKER_TOKEN` | 长期 API Token(与服务端一致) | - |
|
||
| `MUSE_TALK_URL` | 本地 MuseTalk 服务地址 | `http://127.0.0.1:7861` |
|
||
| `POLL_INTERVAL` | 轮询间隔秒 | `5` |
|
||
| `HEARTBEAT_INTERVAL` | 空闲心跳间隔秒 | `15` |
|
||
| `REQUEST_TIMEOUT` | HTTP 请求超时秒 | `900` |
|
||
| `TASK_MAX_RETRY` | 本地最大重试次数 | `1` |
|
||
| `TASK_HEARTBEAT_INTERVAL` | 推理期间任务心跳间隔秒 | `30` |
|
||
| `MIN_VIDEO_DURATION_SECONDS` | 最短输入视频时长秒 | `3` |
|
||
|
||
### 3.2 启动 Worker
|
||
|
||
```bash
|
||
# 前台运行(调试用)
|
||
python gpu_worker.py
|
||
|
||
# 后台运行(生产用 systemd)
|
||
sudo systemctl start xiaoxia-gpu-worker
|
||
sudo systemctl enable xiaoxia-gpu-worker
|
||
```
|
||
|
||
### 3.3 验证启动日志
|
||
|
||
应看到:
|
||
|
||
```
|
||
============================================================
|
||
MuseTalk GPU Worker 启动
|
||
worker_id = rtx2060-xxxx
|
||
api_base = https://staging-api.xiaoxiajianji.com
|
||
muse_talk = http://127.0.0.1:7861
|
||
poll = 5.0s / heartbeat = 15.0s
|
||
============================================================
|
||
MuseTalk 健康检查通过: {...}
|
||
注册/心跳成功
|
||
```
|
||
|
||
---
|
||
|
||
## 四、常见问题排查
|
||
|
||
| 现象 | 可能原因 / 排查 |
|
||
|---|---|
|
||
| 日志 401 `Invalid GPU worker token` | `.env` 的 `GPU_WORKER_TOKEN` 与服务端不一致 |
|
||
| 日志 `MuseTalk 健康检查未通过` | 本地 MuseTalk 没启动,或端口不是 7861;`curl http://127.0.0.1:7861/health` 验证 |
|
||
| 任务长时间不被拉取 | Worker 和服务端连不上;检查 API_BASE_URL 是否可达、Token 是否正确 |
|
||
| 推理后上传 OSS 失败 | 本地出口网络被防火墙拦截 OSS 域名(oss-cn-hangzhou.aliyuncs.com) |
|
||
| 服务端看到任务回退到 pending 重试 | 任务心跳真正超时(默认 900s):Worker 进程崩溃/断网,或推理彻底卡死;正常长推理期间心跳线程每 30s 续期,不会回退 |
|
||
| 日志 `MuseTalk 推理超时或连接失败` | 视频太长或显存不足;可临时调大 REQUEST_TIMEOUT(服务端 GPU_TASK_TIMEOUT_SECONDS 需同步调大),或限制输入视频时长 |
|
||
| 日志 `视频过短(x.xxs < 3s)` | 输入视频不足 3s,MuseTalk 对短视频会 division by zero,已在本地直接上报失败;可用 MIN_VIDEO_DURATION_SECONDS 调整阈值 |
|
||
| MuseTalk 服务端 503 `GPU 正在处理其他任务` | 并发请求被锁拒绝,等当前推理完成即可 |
|
||
| MuseTalk 服务端 504 `推理超时` | 推理超过 MUSE_INFERENCE_TIMEOUT,客户端会调 /cancel 终止服务端任务 |
|
||
|
||
---
|
||
|
||
## 五、安全注意事项
|
||
|
||
- `.env` 包含长期 Token,文件权限设为 600(`chmod 600 .env`)
|
||
- Token 泄露要立即在服务端更换 `GPU_WORKER_TOKEN` 并重启 Worker
|
||
- Worker 只需要出站访问 SaaS API 和 OSS,不需要开放任何入站端口
|
||
- MuseTalk 服务端只监听本地 127.0.0.1(或 0.0.0.0 但通过防火墙限制),不暴露到公网
|
||
- 临时文件自动清理(推理完成/失败后),无需手动维护
|
||
|
||
---
|
||
|
||
## 六、工程改进记录(musetalk_server.py)
|
||
|
||
相比原 `worker.py`,修复了以下 8 个 bug:
|
||
|
||
1. **Flask 单线程阻塞**:`app.run(threaded=True)`,推理时 `/health` 仍可响应
|
||
2. **fps=0 除零崩溃**:`_get_video_fps()` 兜底 `MUSE_DEFAULT_FPS`
|
||
3. **ffmpeg 不检查返回码**:`subprocess.run(check=True)` + 超时检查,失败立即报错
|
||
4. **无并发锁**:`threading.Lock` 控制并发,第二请求立即 503
|
||
5. **无推理超时**:线程 join timeout,超时返回 504 并调 `/cancel`
|
||
6. **结果文件不清理**:推理完成/失败后自动删除临时目录
|
||
7. **无人脸检测兜底**:MuseTalk 推理内部处理(TODO: 可在 `_run_inference` 前置检查)
|
||
8. **上传无大小限制**:`_check_file_size()` 校验,超限返回 413
|
||
|
||
新增:
|
||
- `/cancel` 端点:终止当前推理任务,清理临时文件
|
||
- `/health` 端点:返回 GPU 显存信息和当前任务状态
|
||
|
||
2026-09-20 追加修复(音轨正确性,上线阻断级):
|
||
|
||
9. **音轨未替换(严重)**:旧最终封装让 ffmpeg 默认选流,结果保留了源视频自带音轨(与画面相关系数 0.9998,与 TTS 无关)。改为 `_mux_video_with_audio()` 统一封装,强制 `-map 0:v:0 -map 1:a:0`,画面取 MuseTalk 无声产物、音轨只取驱动音频
|
||
10. **音视频时长不对齐**:TTS 长于原视频时 `-shortest` 会截短语音。改为探测双方时长,音频更长时 `-stream_loop -1` 循环画面 + `h264_nvenc` 硬件重编码(`MUSE_VIDEO_ENCODER=auto`,失败回退 libx264)+ `-t <音频时长>`;不循环时 `-c:v copy` 秒封装
|
||
- 开关 `MUSE_ENABLE_VIDEO_LOOP=0` 可关闭循环;请求也支持 form 参数 `enable_video_loop` 单任务覆盖
|