# 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` | ### 2.2 更新部署(v2 性能修复,必做) > ⚠️ 2026-09-20 v2 架构:修复 16 倍性能回归。旧版在推理前 loop 视频导致 MuseTalk 处理帧数翻倍、RTX2060 推理 >200s、nginx 504。**必须重新拉取并重启**: ```bash # 在 RTX2060 上备份旧文件并拉取新版本 cp ~/projects/MuseTalk/musetalk_server.py ~/projects/MuseTalk/musetalk_server.py.bak 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 ``` v2 架构核心变化: - **MuseTalk 直传全量音频**:不再在推理前用 ffmpeg 循环视频。MuseTalk 原生支持长音频输入,内部自动循环视频帧。推理时间不变(~14s/5s 视频) - **ffmpeg 只做快速封装**:`-c:v copy -c:a aac -shortest`,秒级完成,不重编码 - **循环仅兜底**:仅当 MuseTalk 输出画面短于音频时(极端情况),才 `-stream_loop` + NVENC 兜底 - **删除 `MUSE_ENABLE_VIDEO_LOOP`**:不再需要此开关,MuseTalk 原生处理 ### 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` 单任务覆盖 2026-09-20 v2 架构重构(性能回归修复,上线阻断级): 11. **16 倍性能回归**:#9/#10 的实现虽然音轨正确,但在某些集成场景下(推理前 loop 视频再喂 MuseTalk)导致推理帧数 ×2.2 + 叠加 ffmpeg 软编码预处理,5s 视频 +11s 音频推理 >200s,nginx 60s 超时 504 - **正确架构**:MuseTalk 原生支持长音频输入,内部自动循环视频帧。把【原视频】+【全量音频】直传 MuseTalk,输出时长=音频时长 - **ffmpeg 后置快速封装**:`-c:v copy -c:a aac -shortest` 秒级完成,不重编码 - **循环仅兜底**:仅当 MuseTalk 输出画面短于音频时(极端情况),才 `-stream_loop` + NVENC 兜底补齐 - **业务侧异步化**:POST /lipsync/jobs 创建 GPU 任务后立即返回 `job.status="processing"`,Celery 异步等待结果回写。前端 GET /jobs/{id} 轮询。避免同步阻塞 HTTP 请求 >200s - **删除 `MUSE_ENABLE_VIDEO_LOOP`**:不再需要此开关 --- ## 七、自动部署 从 2026-09-20 起,GPU 节点配置文件和脚本全部入库到 `deploy/gpu_worker/`,支持一键初始化新节点 + develop 分支 push 后 30 秒内自动拉取更新。 ### 7.1 服务架构 每个 GPU 渲染节点运行三个 systemd 单元: | 单元 | 类型 | 作用 | |---|---|---| | `musetalk-worker.service` | simple(常驻) | MuseTalk Flask 推理 API(监听 127.0.0.1:7861) | | `xiaoxia-gpu-worker.service` | simple(常驻) | 反向轮询 SaaS API 拉口型任务的 Worker 客户端 | | `gpu-poll.timer` + `gpu-poll.service` | timer(每 30s 触发 oneshot) | 轮询 Gitea `deploy/gpu_worker/` 最新 commit,有变更自动执行 update 脚本 | 脚本目录(节点本地): | 路径 | 来源 | 作用 | |---|---|---| | `~/projects/update-gpu-worker.sh` | `scripts/update-gpu-worker.sh` | 备份 → 拉代码 → 重启两个服务 → 健康检查 → 失败回滚 | | `~/projects/gpu-webhook/poll_and_update.sh` | `scripts/poll_and_update.sh` | 轮询 Gitea API 比对 SHA,有新 commit 时触发 update | ### 7.2 新节点部署步骤 **前置准备**(手动,首次部署必做): 1. 安装 NVIDIA 驱动 + CUDA 11.8+,`nvidia-smi` 能看到 GPU 2. 克隆 MuseTalk 代码到 `~/projects/MuseTalk/`,下载模型权重到 `~/projects/MuseTalk/models/musetalk/`(权重约几 GB,不适合自动下载) 3. 创建 Python 虚拟环境 `~/projects/MuseTalk/venv/` 并安装 MuseTalk 依赖(PyTorch CUDA 版等) 4. 创建 Worker 虚拟环境 `/opt/xiaoxia-gpu-worker/venv/` 并 `pip install -r requirements.txt` 5. 准备 `.env` 文件(Worker 端):`/opt/xiaoxia-gpu-worker/.env`,填好 `API_BASE_URL`、`GPU_WORKER_TOKEN`、`MUSE_TALK_URL` 等(参考 `.env.example`) > ⚠️ 模型权重和 Python 虚拟环境(含 CUDA 版 PyTorch)体积大、安装慢,首次部署必须手动准备;后续脚本只更新 `.py` 文件和配置,不碰权重和 venv。 **一键初始化**: ```bash # 从仓库拉取 setup 脚本并执行(在全新 GPU 机器上以 ying 用户执行) wget -q -O /tmp/setup-gpu-node.sh \ "https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/raw/branch/develop/deploy/gpu_worker/scripts/setup-gpu-node.sh" bash /tmp/setup-gpu-node.sh ``` 脚本自动完成: 1. apt 安装系统依赖(python3、ffmpeg、wget、curl、git) 2. 创建必要目录(`~/projects/MuseTalk`、`~/projects/gpu-webhook`、`/opt/xiaoxia-gpu-worker`) 3. 从仓库拉取三个 systemd 单元文件 + update/poll 脚本到本地 4. 安装 systemd 服务到 `/etc/systemd/system/` 5. 配置 sudo 免密(仅允许 `ying` 用户免密 restart 两个服务、status、journalctl、cp、chmod、tee) 6. 首次执行 update 脚本拉取最新 `musetalk_server.py` 和 `gpu_worker.py` 7. `systemctl daemon-reload` + enable + start 三个单元 **初始化后检查**: ```bash sudo systemctl status musetalk-worker # 应 active (running) sudo systemctl status xiaoxia-gpu-worker # 应 active (running) sudo systemctl status gpu-poll.timer # 应 active (waiting) curl http://127.0.0.1:7861/health # 应返回 healthy + GPU 显存信息 ``` ### 7.3 自动更新机制 push 到 `develop` 分支且修改了 `deploy/gpu_worker/` 下任何文件后: 1. `gpu-poll.timer` 每 30 秒触发 `gpu-poll.service` 2. `poll_and_update.sh` 调用 Gitea API 取 `deploy/gpu_worker/` 路径最新 commit SHA 3. 与本地 `~/projects/gpu-webhook/.last_commit` 比对,无变更直接退出 4. 有变更:写入新 SHA → 执行 `update-gpu-worker.sh` 5. `update-gpu-worker.sh` 执行流程: - 备份当前 `musetalk_server.py` / `gpu_worker.py`(带时间戳后缀) - wget 拉取最新 `musetalk_server.py`、`gpu_worker.py` - 比对 `requirements.txt`,有变化则 pip install - `sudo systemctl restart musetalk-worker`,等 5 秒 - `sudo systemctl restart xiaoxia-gpu-worker`,等 8 秒 - `curl http://127.0.0.1:7861/health` 健康检查 - 健康 → 写日志退出 0 - 不健康 → 回滚到最新备份 → 重启 → 退出 1(日志记录 rolled back) 端到端延迟:从 push 到节点拉到新代码并重启,约 30~60 秒。 ### 7.4 手动更新命令 ```bash # 立即手动触发一次更新(不依赖 timer) bash ~/projects/update-gpu-worker.sh # 查看更新日志 tail -f /tmp/gpu-worker-update.log # 查看轮询日志 tail -f /tmp/gpu-poll.log # 查看服务运行日志 journalctl -u musetalk-worker -f # MuseTalk 推理服务日志 journalctl -u xiaoxia-gpu-worker -f # GPU Worker 客户端日志 journalctl -u gpu-poll.service -f # 轮询/更新触发日志 ``` ### 7.5 仓库文件清单(自动部署相关) ``` deploy/gpu_worker/ ├── musetalk-worker.service # MuseTalk 推理 API 的 systemd 服务 ├── gpu-poll.service # 自动更新轮询 oneshot service ├── gpu-poll.timer # 每 30 秒触发轮询的 timer ├── xiaoxia-gpu-worker.service # GPU Worker 客户端 systemd 服务(已有) ├── gpu_worker.py # GPU Worker 客户端脚本(已有,自动更新) ├── musetalk_server.py # MuseTalk Flask 服务端(已有,自动更新) ├── requirements.txt # Worker Python 依赖(已有) ├── .env.example # Worker 环境变量模板(已有) ├── README.md # 本文档 └── scripts/ ├── update-gpu-worker.sh # 更新脚本:备份→拉取→重启→健康检查→回滚 ├── poll_and_update.sh # 轮询脚本:SHA 比对→触发更新 └── setup-gpu-node.sh # 新节点一键初始化脚本 ``` ### 7.6 注意事项 - **首次部署必须手动准备**:MuseTalk 代码仓库、模型权重(`models/musetalk/`,几 GB)、MuseTalk 的 Python 虚拟环境(`venv/`,含 CUDA 版 PyTorch)。这些体积大、安装耗时长,不在自动更新范围内。 - **脚本路径写死**:当前脚本路径固定为 `/home/ying/projects/` 和 `/opt/xiaoxia-gpu-worker/`,用户名固定 `ying`。后续如有多节点/多用户需求再做参数化。 - **sudo 免密范围最小化**:setup 脚本写入 `/etc/sudoers.d/ying-gpu-update`,仅放行 restart/status 两个 GPU 相关服务、daemon-reload、journalctl、cp、chmod、tee,不开放全量 root。 - **回滚只回滚 .py 文件**:健康检查失败只回滚 `musetalk_server.py` 和 `gpu_worker.py`,不回滚 pip 依赖(requirements.txt 变化概率低,且 pip 操作本身可能失败)。如需完全回滚,手动 `pip install -r requirements.txt` 指定旧版本。 - **poll 脚本容错**:Gitea API 请求失败直接跳过,不触发更新,不会因为网络抖动误重启服务。