Duix-Avatar 去 Docker Desktop 本地化完整复盘
背景与思路
Duix-Avatar 由三个服务组成,原本通过 Docker Desktop 运行。目标是完全摆脱 Docker Desktop,直接用 WSL2 承载这三个服务。
核心判断:
项目的视频合成核心全部编译成了 .cpython-38-x86_64-linux-gnu.so,只能在 Linux x86_64 + Python 3.8 下运行,Windows native 无法加载,因此 WSL2 是唯一可行的本地化路径。
三个服务说明
【保姆级教程】Windows + Podman 从零部署 Duix-Avatar 数字人项目
【笔记】Podman Desktop 部署 开源数字人 HeyGem.ai
【笔记】在 Podman Machine(Fedora 42)中安装 NVIDIA Container Toolkit 使镜像能使用GPU
Windows 开发环境部署指南:WSL、Docker Desktop、Podman Desktop 部署顺序与存储路径迁移指南
在WSL-podman-machine-default (Fedora Linux 42) 中安装 CUDA 13.0、cuDNN 9.14、Anaconda 2025.06、PyTorch 2.10
用Podman Desktop创建自用的WSL-Fedora Linux子系统
新!在 podman-machine-default 中安装 CUDA、cuDNN、Anaconda、PyTorch 等并验证安装
PyCharm 链接 Podman Desktop 的 podman-machine-default Linux 虚拟环境
Podman Desktop:现代轻量容器管理利器(Podman与Docker)
来自 Podman Desktop 拉取完毕的镜像
| 服务 | 镜像 | 端口 | 职责 |
|---|---|---|---|
| duix-avatar-gen-video | guiji2025/duix.avatar | 8383 | lip-sync 视频合成(核心) |
| duix-avatar-tts | guiji2025/fish-speech-ziming | 18180 | 声音克隆 + TTS |
| duix-avatar-asr | guiji2025/fun-asr | 10095 | 语音识别(ASR) |
第一步:确认镜像大小和磁盘空间
podman image inspect guiji2025/duix.avatar --format "{{.Size}}"
podman image inspect guiji2025/fish-speech-ziming --format "{{.Size}}"
podman image inspect guiji2025/fun-asr --format "{{.Size}}"
Get-PSDrive J # 确认目标盘剩余空间
三个镜像合计约 64GB,导出 tar + 导入 vhdx 峰值需要约 130GB 空闲空间。
第二步:建立目录结构
New-Item -ItemType Directory -Path J:\duix_export -Force
New-Item -ItemType Directory -Path J:\wsl\duix-avatar -Force
New-Item -ItemType Directory -Path J:\wsl\duix-fish -Force
New-Item -ItemType Directory -Path J:\wsl\duix-asr -Force
第三步:逐个导出容器并导入 WSL2
每次导出一个、验证后删除 tar 再导下一个,节省磁盘峰值占用。
duix-avatar(最小,先练手)
podman create --name tmp_avatar guiji2025/duix.avatar sleep 999
podman export tmp_avatar -o J:\duix_export\avatar.tar
podman rm tmp_avatar
wsl --import duix-avatar J:\wsl\duix-avatar J:\duix_export\avatar.tar --version 2
# 验证成功后删除
Remove-Item J:\duix_export\avatar.tar
duix-asr
podman create --name tmp_asr guiji2025/fun-asr sleep 999
podman export tmp_asr -o J:\duix_export\asr.tar
podman rm tmp_asr
wsl --import duix-asr J:\wsl\duix-asr J:\duix_export\asr.tar --version 2
Remove-Item J:\duix_export\asr.tar
duix-fish(最大,最后)
podman create --name tmp_fish guiji2025/fish-speech-ziming sleep 999
podman export tmp_fish -o J:\duix_export\fish.tar
podman rm tmp_fish
wsl --import duix-fish J:\wsl\duix-fish J:\duix_export\fish.tar --version 2
Remove-Item J:\duix_export\fish.tar
第四步:修复 GPU 直通(三个发行版都要做)
容器镜像内 /lib/x86_64-linux-gnu/ 里有大量空占位 .so 文件,会干扰 WSL2 的 GPU 桥接库,需要清除并重建软链。
# 对 duix-avatar、duix-asr、duix-fish 各执行一次,替换发行版名称
wsl -d duix-avatar -- bash -c "
find /lib/x86_64-linux-gnu/ -name 'libnvidia-*.so*' -empty -delete &&
find /lib/x86_64-linux-gnu/ -name 'libcuda*.so*' -empty -delete &&
find /lib/x86_64-linux-gnu/ -name 'libcudadebugger*.so*' -empty -delete &&
echo '/usr/lib/wsl/lib' > /etc/ld.so.conf.d/wsl.conf &&
ldconfig 2>/dev/null &&
rm /usr/bin/nvidia-smi 2>/dev/null &&
ln -s /usr/lib/wsl/lib/nvidia-smi /usr/bin/nvidia-smi &&
nvidia-smi | head -5
"
验证标准:能看到 NVIDIA-SMI 输出和 GPU 型号即成功。
第五步:建立数据目录软链
客户端实际使用的数据目录是 D:\heygem_data\(注意:不是 README 里写的 duix_avatar_data,要以客户端 asar 解包后的实际路径为准)。
# avatar:face2face 数据目录
wsl -d duix-avatar -- bash -c "
rm -rf /code/data &&
ln -s /mnt/d/heygem_data/face2face /code/data &&
ls /code/data/
"
# fish:voice/data 目录
wsl -d duix-fish -- bash -c "
rm -rf /code/data &&
ln -s /mnt/d/heygem_data/voice/data /code/data &&
ls /code/data/origin_audio/ | tail -3
"

第六步:修复 fish 服务的 ASR 主机名配置
fish 服务内部通过 WebSocket 连接 ASR,配置文件里主机名是 Docker 容器名 duix-avatar-asr,在 WSL2 环境中无法解析,需要改为 127.0.0.1。
"
f = open('/code/config/config.py', 'r')
content = f.read()
f.close()
content = content.replace('duix-avatar-asr', '127.0.0.1')
f = open('/code/config/config.py', 'w')
f.write(content)
f.close()
print('done')
" | wsl -d duix-fish -- bash -c "cat > /tmp/fix_config.py && python3 /tmp/fix_config.py"
# 验证
wsl -d duix-fish -- bash -c "grep fun_asr_host /code/config/config.py"
期望输出:fun_asr_host = '127.0.0.1'
第七步:设置环境变量
wsl -d duix-avatar -- bash -c "echo 'export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512' >> /root/.bashrc"
第八步:写一键启动脚本
fish 服务需要直接监听 18180(WSL2 没有 Docker 的端口映射层),不能用 8080。
@'
# Duix Avatar 一键启动脚本(WSL2 版,无需 Docker Desktop)
Write-Host "启动 duix-asr (ASR服务 port:10095)..." -ForegroundColor Cyan
Start-Process wsl -ArgumentList "-d duix-asr -- bash -c `"cd /workspace/FunASR/runtime && sh /run.sh`"" -WindowStyle Normal
Start-Sleep 2
Write-Host "启动 duix-fish (TTS服务 port:18180)..." -ForegroundColor Cyan
Start-Process wsl -ArgumentList "-d duix-fish -- bash -c `"cd /code && /opt/conda/envs/python310/bin/python3 tools/api_server.py --listen 0.0.0.0:18180`"" -WindowStyle Normal
Start-Sleep 2
Write-Host "启动 duix-avatar (视频合成服务 port:8383)..." -ForegroundColor Cyan
Start-Process wsl -ArgumentList "-d duix-avatar -- bash -c `"export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512 && cd /code && python3 app_local.py`"" -WindowStyle Normal
Write-Host ""
Write-Host "服务启动中,等待约 30 秒后可打开客户端..." -ForegroundColor Green
Write-Host " ASR: ws://127.0.0.1:10095" -ForegroundColor Gray
Write-Host " TTS: http://127.0.0.1:18180" -ForegroundColor Gray
Write-Host " Video: http://127.0.0.1:8383" -ForegroundColor Gray
'@ | Out-File -FilePath D:\Program\start-duix.ps1 -Encoding UTF8
一键停止脚本
@'
Write-Host "停止所有 Duix 服务..." -ForegroundColor Yellow
wsl -d duix-avatar -- bash -c "pkill -f app_local.py 2>/dev/null; echo done"
wsl -d duix-fish -- bash -c "pkill -f api_server.py 2>/dev/null; echo done"
wsl -d duix-asr -- bash -c "pkill -f run_server.sh 2>/dev/null; pkill -f funasr 2>/dev/null; echo done"
wsl --terminate duix-avatar
wsl --terminate duix-fish
wsl --terminate duix-asr
Write-Host "所有服务已停止" -ForegroundColor Green
'@ | Out-File -FilePath D:\Program\stop-duix.ps1 -Encoding UTF8
踩坑记录
| 坑 | 原因 | 解法 |
|---|---|---|
nvidia-smi 无输出 |
镜像内有空占位 .so 覆盖了 WSL 桥接库 |
删除空文件 + 软链到 /usr/lib/wsl/lib/nvidia-smi |
TTS 返回 500 NoneType has no attribute send |
config.py 里 ASR 主机名是 Docker 容器名 duix-avatar-asr |
改为 127.0.0.1 |
| TTS 端口 18180 不通 | WSL2 没有 Docker 的端口映射,服务监听 8080 但客户端访问 18180 | 直接让服务监听 18180 |
file not exists |
软链指向了错误的数据目录 duix_avatar_data,客户端实际写入的是 heygem_data |
重建软链指向 heygem_data |
| SQLite 绑定错误 | train() 失败返回 false,被当作 voiceId 写入数据库 |
根因是以上几个问题,逐一修复后自然解决 |
最终验证
Test-NetConnection -ComputerName 127.0.0.1 -Port 8383 # 视频合成 ✅
Test-NetConnection -ComputerName 127.0.0.1 -Port 18180 # TTS ✅
Test-NetConnection -ComputerName 127.0.0.1 -Port 10095 # ASR ✅
GPU 合成期间 sm 83%,完全 GPU 推理,速度与原 Docker 版本一致。
