本地 AI 赛博女友 NAS 部署:三容器、四条命令,从显卡到浏览器

阅读 -- 次

本文目标:照着做,大约 20~30 分钟(其中 13GB 模型下载占大头),你的 NAS 上就能跑起一个"本地 AI 赛博女友"——对着麦克风说话,她用你克隆的音色回答,嘴型跟着动。
全程本地运行,不需要 API Key,不花一分钱,断网也能用。
看之前先把这几个换成你自己的:
- <NAS_IP> —— 你的 NAS 内网地址(路由器后台看,或者 ping 一下别的设备反查)
- <项目目录> —— 你放这个项目的地方,示例用 /volume1/docker/cybergirl
- 端口 21790 / 21791 / 21792 —— 三个都在 .env 里可以改,撞了端口就改这里,不用动代码


0. 先看跑起来是什么样

赛博女友主界面

网页全屏是数字人,左边橙色语音球。你说话 → 她听见 → 大模型想 → 用你的音色说回来 → 嘴型同步。底部实时出字幕。

手机上一样能用:

手机端

它由三个容器组成(都在你的 NAS 上,不联网):

容器

干什么

用什么

cybergirl-llm

大脑

llama.cpp + Qwen3-8B

cybergirl-voice

耳朵 + 嘴巴 + 网页

Silero VAD → faster-whisper → Qwen3-TTS Base

cybergirl-livetalking

数字人 + 嘴型

LiveTalking + Wav2Lip256


1. 开始之前,确认三件事

要求

说明

NAS + NVIDIA 显卡

建议 12G 显存以上(16G 卡的实测约占用 12.5G)。没显卡也能装,但会慢到没法用

Docker + Compose

NAS 上装 Docker 是基本功了。绿色/黑色小窗里的 docker compose 命令都算

磁盘空 40GB

模型 13GB + 镜像约 12GB + 缓存约 2GB

⚠️ 如果你的 NAS 上还跑着 ComfyUI / Ollama:16G 卡上这几个一起开会显存不够,ComfyUI 一跑就 OOM。
装好之后有现成的让位脚本:bash scripts/14-显存让位.sh(停掉吃显存的),用完 bash scripts/15-显存复位.sh(开回来)。


2. 把文件传到 NAS

下载部署包解压,里面应该长这样:

赛博女友-部署包/
├── docker-compose.yml
├── .env.example
├── app/          # 网页前端 + 语音链路服务
├── docker/       # 两个镜像的 Dockerfile
├── persona/      # 人设(默认「小雅」)
├── scripts/      # 各种脚本,00~18
├── assets/       # 数字人形象底版视频
├── voice/ref/    # 参考音色
└── docs/         # 补充文档

上传方式三选一:

  • 网页文件管理器(最省事):把整个目录打包成 zip,上传到 NAS,解压。

  • scp(NAS 开了 SSH 时):

  scp -r ./赛博女友-部署包 <NAS用户名>@<NAS_IP>:<项目目录>
  • git:推到 GitHub/Gitee,NAS 上 git clone。

⚠️ 上传完一定要检查有没有多套一层

这是最容易踩的坑,不报错,就是"文件不存在":

cd <项目目录>
ls -a

你要看到 scripts/、app/、docker-compose.yml 都在这一层。

如果看到的是 赛博女友-部署包/scripts/...,说明多了一层,把里面的东西提上来:

cd <项目目录>
mv 赛博女友-部署包/* 赛博女友-部署包/.env.example 赛博女友-部署包/.gitignore . 2>/dev/null
rm -rf 赛博女友-部署包
ls -a    # 再确认一次

3. 四条命令,跑起来

3.1 生成配置

cd <项目目录>
cp .env.example .env

默认配置就能用,一般不用改。真要改就改这几个:

vi .env

改什么

什么时候改

VOICE_PORT / LIVETALKING_PORT / LLM_PORT

端口跟 NAS 上别的服务撞了

LLM_FILE

显存不够就把 Qwen3-8B-Q4_K_M.gguf 换成 Qwen3-4B-Q4_K_M.gguf

AVATAR_ID

形象名,见第 5 步

PERSONA_NAME / PERSONA_FILE

换人设

端口选号建议:别用 80/443/3000/5000/8000/8080/8888/9000,也别用 32768 以上(那是内核临时端口段,容易偶发冲突)。

3.2 预检(强烈建议跑)

bash scripts/00-预检.sh

它会告诉你:Docker 能不能用、容器里能不能看到显卡、三个端口空不空、磁盘够不够、模型还缺哪些。

输出长这样:

项目目录:/volume1/docker/cybergirl
── 1/5 Docker ──
✓ docker 正常
✓ docker compose 正常
── 2/5 GPU ──
✓ docker 已注册 nvidia runtime
✓ 容器里能看到显卡
── 3/5 端口 ──
✓ 端口 21790(网页)空闲
✓ 端口 21791(数字人)空闲
✓ 端口 21792(大模型)空闲
── 4/5 磁盘 ──
   可用空间:XX GB(模型 + 镜像 + 缓存大约要 40 GB)

看到"容器里看不到显卡"就先别往下走,去确认 nvidia-container-toolkit 装没装。
如果你 NAS 上已经有 ComfyUI 容器能跑 nvidia-smi,说明机制是好的,只是这台还没注册。

3.3 下载模型(约 13GB)

bash scripts/01-下载模型.sh

全走 hf-mirror 国内镜像,断了重跑会续传,不用怕。

下完会打印体积清单:

2.9G	models/asr
4.7G	models/llm
4.3G	models/tts
86M	livetalking/models/s3fd.pth
205M	livetalking/models/wav2lip.pth
372K	assets/idle.mp4

嫌一次下太多太慢?bash scripts/01-下载模型.sh lip 只下嘴型那部分,其余按需再下。

3.4 构建镜像 + 启动

bash scripts/02-构建镜像.sh

脚本会自动挑一个能拉到的 CUDA 基础镜像(pytorch 官方 → nvidia 官方 → 你 NAS 上已有的 ComfyUI 镜像),首次 10~25 分钟。之后改代码不用重建(代码是挂载进去的)。

三个候选都拉不下来?在 1Panel → 容器 → 镜像加速里配个国内加速地址,然后手动 docker pull 任意一个含 torch 2.5+/cu124 的镜像,把 .env 的 BASE_IMAGE 指过去。

bash scripts/04-启动.sh

看到这三个就成了:

── 等待服务就绪 ──
✓ 大模型 就绪
✓ 数字人引擎 就绪
✓ 语音服务 就绪

4. 打开网页

http://<NAS_IP>:21790/

进去点「开始」,浏览器会要麦克风权限,点允许。

首次进入

然后就可以跟她说话了:

对话中

⚠️ 如果点了"开始"完全没反应

这是 90% 的人都会卡住的地方。 原因不是代码:Chrome 规定"非 localhost 的 http 页面"不给麦克风权限。

你在 Mac/手机上用 http://192.168.x.x:21790 打开,属于"不安全上下文",浏览器直接不给权限。表现就是"点了开始一点动静没有,日志里一条识别记录都没有"。

解法(选一个):

a) 自己签个 https 证书(推荐,一次搞定,手机也能用)

bash scripts/09-生成自签证书.sh
bash scripts/05-停止.sh && bash scripts/04-启动.sh

然后用 https://<NAS_IP>:21790/ 打开,浏览器提示证书不受信任时点「继续访问」。

b) 让 Chrome 放行这个 http 源(仅桌面)

Chrome 快捷方式目标后面加:

--unsafely-treat-insecure-origin-as-secure=http://<NAS_IP>:21790

另外记得:进设置把 NOISE GATE 拉到最左边(0)。这个滑块拉高会把正常说话的音量直接掐掉,症状跟上面一模一样。

想用手机在公网上玩

套一条 Cloudflare Tunnel:

cloudflared tunnel --url http://localhost:21790

拿到 https 域名后手机直接开,麦克风权限也正常。


5. 换成你自己的

5.1 数字人形象

最省事:直接用网页右上角的「形象」,上传一张正面照或一段 5~10 秒的小视频,生成完点「使用」就切过去了,不用重启容器。

形象上传

素材要求(很重要,选错了效果会很怪):

  • 视频(推荐):5~10 秒、嘴唇闭合、固定机位、单人、光线均匀、半身或全身入镜、有轻微呼吸/眨眼

  • 照片:正面、单人、脸占画面 1/3 左右、背景干净

⚠️ 手托腮、手压下巴的姿势不要用 —— 口型模型只替换嘴部区域,手越过分界线会出鬼影。
⚠️ 脸太小也不行 —— 人脸在画面里占比不够,生成出来的人脸图会小于 256×256,Wav2Lip256 会直接报错
Kernel size can't be greater than actual input size。

用命令行的话(从 assets/idle.mp4 生成):

bash scripts/03-准备形象.sh --from-idle
# 然后把 .env 里的 AVATAR_ID 改成生成出来的名字
bash scripts/05-停止.sh && bash scripts/04-启动.sh

5.2 声音(音色)

部署包里已经带了一份可直接用的参考音色(voice/ref/ref.wav + voice/ref/ref_text.txt)。想换成自己的:

  1. 录 5~15 秒:干净、无背景音、单人、语气自然(别念稿)

  2. 存成 voice/ref/ref.wav

  3. 把录音里说的每个字写进 voice/ref/ref_text.txt(语气词「嗯」「啊」也要写)

  4. 统一格式成 16k / 单声道:

docker run --rm -v "$PWD/voice/ref:/r" linuxserver/ffmpeg \
  -i /r/ref.wav -ac 1 -ar 16000 -c:a pcm_s16le -y /r/ref_16k.wav
mv voice/ref/ref_16k.wav voice/ref/ref.wav

也可以用预置音色现场生成:

bash scripts/12-生成参考音色.sh Serena   # 温柔年轻女声

情绪会一起被克隆。参考音频里带委屈和撒娇,克隆出来就全是这个基调。想要中性一点,就录一段平铺直叙的。

5.3 人设

设置抽屉里的 INSTRUCTIONS 就是人设,改完点「保存」即时生效,不用重启。

人设三条硬规则(照抄就行):

  1. 每次两三句 —— 语音场景说太长听着累,合成时间也线性增加

  2. 不用 Markdown / 列表 / 表情符号 —— 这些会被逐字念出来(* 会念成"星号")

  3. 口语化 + /no_think —— 关掉思考模式,回复更直接

设置

6. 自检(跑完看一眼心里有底)

bash scripts/06-分层自检.sh

四关:

关

检查什么

通过说明

①

llama.cpp /v1/models + 一次真实对话

大脑能用

②

LiveTalking :21791/index.html

数字人引擎能用

③

容器内合成一句 + 再识别回来

声音克隆能用

④

/health

网页服务能用

第 ③ 关会产出一段 data/logs/tts_sample.wav,下载下来听:像你的声音就成了。


7. 出问题看这里

现象

原因 / 处理

网页点「开始」没反应

麦克风权限(http 非 localhost 被禁)→ 见第 4 步 a);其次确认 NOISE GATE 在最左

有字幕没声音、也不动嘴

docker logs -f cybergirl-voice 看 humanaudio 报错;检查 AVATAR_ID 和 livetalking/data/avatars/ 里的目录名是否一致

有声有画,但嘴不动

wav2lip.pth 位置不对(必须在 livetalking/models/wav2lip.pth);或形象人脸不是 256×256

报 Kernel size can't be greater than actual input size

形象人脸图小于 256×256,换示例形象或用脸更大的素材重做

报 CUDA out of memory

显存被占。先停 ComfyUI(bash scripts/14-显存让位.sh),或把 LLM_FILE 换成 4B

报 could not select device driver ""

没装 nvidia-container-toolkit,装完重启 Docker

容器显示 (unhealthy) 但能正常用

健康检查在 https 模式下误报,不影响使用

音色不像你录的

ref.wav 不是 16k 单声道,或 ref_text.txt 和录音不是逐字一致

第一次回答特别慢

模型首次加载 1~3 分钟,之后常驻显存

抢答(还没说完她就接话)

.env 里 MIN_SILENCE_MS 调到 1200,改完重启容器

端口被占

改 .env 三个端口,重启

改了人设不生效

设置里点「保存」;或改 persona/*.md 后 docker compose restart voice

日志位置:data/logs/voice.log(语音链路)、docker logs -f cybergirl-livetalking(数字人)。


8. 常用命令

docker compose ps                 # 状态
docker compose logs -f voice      # 语音链路日志
docker logs -f cybergirl-livetalking   # 数字人日志(看 fps)
docker logs -f cybergirl-llm      # 大模型日志(看 token 速度)

bash scripts/05-停止.sh           # 停止
docker compose up -d              # 再启动
bash scripts/04-启动.sh           # 启动(带就绪等待,输出打开地址)

docker compose down               # 卸载(模型还在目录里,删不删随你)

升级/改代码:代码是挂载进容器的,改完 docker compose restart voice 就行,不用重建镜像。


9. 目录结构(知道东西在哪,排查方便)

<项目目录>/
├── docker-compose.yml     三个容器的编排
├── .env                   你的配置(端口、模型、路径都在这)
├── app/                   语音链路服务 + 网页前端(挂载,改完即生效)
├── persona/               人设文件
├── scripts/               各种脚本(00 预检 / 01 下模型 / 02 构建 / 04 启动 / 06 自检 …)
├── assets/                数字人形象底版视频 idle.mp4
├── voice/ref/             参考音色 ref.wav + ref_text.txt
├── models/                模型(脚本下载,不进镜像,删容器不丢)
├── livetalking/           数字人引擎的权重和形象
│   ├── models/            wav2lip.pth、s3fd.pth
│   └── data/avatars/      形象目录(full_imgs/ + face_imgs/ + coords.pkl)
├── data/                  日志、缓存
└── certs/                 自签证书(跑 09 脚本后生成)

10. 进阶:全身出镜 + 跳舞

实时层用的是 Wav2Lip256,只替换嘴部区域,所以实时状态她不会做大幅动作。

想要「远离镜头、全身入镜」:用 ComfyUI 生成一段全身 idle 底版,重新做形象即可(做法见包内 docs/全身与舞蹈复刻-设计.md)。

想要「跳舞」:ComfyUI + Wan Animate 2 做动作迁移,或者 Wan Dancer 自己编舞,生成 mp4 放进 library/dances/,网页「表演」里点播。

bash scripts/11-下载ComfyUI模型.sh      # 补 ComfyUI 侧权重(约 25.6GB)
bash scripts/10-复刻舞蹈.sh --video 你的舞.mp4 --ref 她的全身图 --name 我的舞

生成期间 GPU 占满,实时聊天会卡 —— 先 bash scripts/05-停止.sh,生成完再 bash scripts/04-启动.sh。


收尾

跑成功的话,你现在有了一个:

  • 不联网、不要 Key、不花钱的本地 AI 女友

  • 你说一句她回一句,首响 1~2.5 秒

  • 声音是克隆的,形象是能换的

  • NAS 重启自动拉起(三个容器都设了 restart: unless-stopped)

遇到问题,把 bash scripts/06-分层自检.sh 的输出贴出来,问题基本都能定位。

说明:整套由开源组件组成(llama.cpp / Qwen3 / faster-whisper / Qwen3-TTS / LiveTalking / Wav2Lip),不涉及任何破解或付费资源。默认形象和参考音色都是脚本生成的。换自己的形象和声音时,也请用自己的素材。


致谢:本文部署思路参考了零度博客的《AI 赛博女友!本地部署教程》,并结合 NAS 与 Docker Compose 环境进行了改造和实测,特此感谢原作者的分享。

消息盒子

# 暂无消息 #

只显示最新10条未读和已读信息