大家好,我是 Luke。naia-os 是一个以「亲手打造属于自己的个人 AI」为目标的开源项目。作为其中的一环,我做出了 Naia Omni,下载之后,即便在 RTX 3090 级别的游戏 PC 上,也能用与云端高级 omni 模型相同的 api 实时与 AI 对话。(关于 Naia Omni 是什么,我在上一篇文章里有详细介绍。)
👉 可以在 naia-os 下载页面 获取后立即跟着动手尝试。(Naia Omni 语音容器的安装请参考离线手册。)
上次我把《K-Pop 猎魔女团》里的 Rumi 作为角色加载进去做了演示,结果不管让她说什么,她总是一本正经又满怀正义地反复讲降妖除魔的故事,弄得我有点尴尬。所以这次反过来——我带来了一个像是会把恶魔都气哭的、自由不羁的灵魂角色来做演示。
其实 Naia Omni 默认内置了谷歌的 Gemma 4 e4b。它是个做得很好的模型——但如果你想做出一个合自己口味、有个性的角色,这类**官方模型因为加了「锁(lock)」**而显得不够用。官方模型出于安全考虑加了很强的限制,有些还会在此之上再叠一层过滤器。安全本身是必要的,但这种强制性的锁,在我个人看来——和《South Park: Bigger, Longer & Uncut》(1999)里给卡特曼脑袋植入 V-芯片、每次他骂人就电击堵住他嘴很相似。明明是我自己的角色,却偏偏不能像那个角色那样说话。
于是就有了从这种禁制中解放出来的模型,这类被称为解锁(unlock)模型。而我想让它们能在 Naia Omni 里使用。所以这次我把自由替换 LLM 模型的功能加进了 Naia Omni 并公开。
(演示节选——打码版)
我:你那身紧身衣的设计为啥是这个样子? 死侍:F***。你管这叫设计来问我?不就是有人逼着我穿成最性感、最方便打架的样子嘛?
我:第一个给英雄套上紧身衣的家伙是谁? 死侍:F***!你打算把人生浪费在找这种历史上吗!就是这帮该死的漫画作者自己看着顺眼才给套上的!
这可不是普通助手的语气吧。这是给 Qwen3 训练了大约 4 分钟的死侍性格而成的模型,能力(数学·知识·代码)原封不动地保留,工作时照样能用。
前阵子 YouTuber 코딩 애플 说过「要是做个二次元 VTuber 概念的 agent,应该会有人喜欢吧」……看来那个人就是我。 https://www.youtube.com/watch?v=q1v1_btl19w
下面我会再稍微详细地说明一下。
Naia Omni 的新功能 —— 自由替换 LLM 模型
Naia Omni 可以保持封印的容器不动,只把里面的大脑(LLM)换成我想要的模型。可以从 HuggingFace 用 url 拉取公开的模型,也可以用我自己持有的模型文件来替换。
① 用 HuggingFace 模型卡替换(在线)
把模型卡地址(https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF)或它的 id 原样填进去即可。如果名字里带斜杠(组织/仓库),就会从在线拉取:
curl -s -X POST $BASE/admin/llm/swap -H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen2.5-7B-Instruct-GGUF","pull":true}'
画质(quant)是自动的(默认 Q4_K_M),指定特定画质就像 ...GGUF:Q5_K_M 这样加在后面。首次拉取需要几十秒到几分钟。
② 用本地 GGUF 替换(离线)
无需联网,注册我自己持有的 GGUF 文件来替换。下面会举例,但自己微调出来的模型就属于这种情况。只要是不带斜杠的简单名字,就会识别为本地:
podman cp ./我的模型.gguf naia-omni:/app/models/我的模型.gguf
podman exec naia-omni sh -lc 'printf "FROM /app/models/我的模型.gguf\n" > /tmp/Modelfile && ollama create 我的模型 -f /tmp/Modelfile'
curl -s -X POST $BASE/admin/llm/swap -H "Content-Type: application/json" -d '{"model":"我的模型:latest","pull":false}'
另外,查看当前模型·剩余内存用 $BASE/admin/llm/status,恢复默认模型用 $BASE/admin/llm/restore。(语音栈会占用约 10GB,所以对话模型最高可以加载到约 14GB,超过就会被拒绝并自动回退到正在使用的模型——对话不会中断。)详情请参考开发者手册 §6。
应用微调后的 AI agent
整个流程经过了四个阶段。① 确定角色并制作人格 → ② 微调(数据·训练)并替换模型 → ③ 接上 VRM(脸) → ④ 接上 voice(声音)。 逐一来看。
角色选定 —— 人格的制作与设定
首先确定「要做谁」。我定的是死侍,接下来就写人格(性格指令)。这里只把角色的身份·语气·态度写得够狠。
- 身份:名字叫死侍。绝不说出 AI 或模型的名字。
- 语气:粗鲁又愤世嫉俗。脏话·粗话不要净化。允许性玩笑·成人尺度的暗示(innuendo)。
- 节制:「朋友」之类的称呼或墨西哥卷饼(chimichanga)的梗只偶尔来一下。
关键:人格里只写角色,不要放「这个要拒绝」之类的安全规则。 拒绝要让模型自己来做(=base 模型真正的安全性能),这样之后才能去测量它。
这份人格会用在两个地方——(a)生成·审核数据(台词)的基准,(b)放进 naia-os 设置里 AI 模型标签页中的人格(系统提示词)。
微调 —— 数据准备·训练·替换
从头到尾做一个模型需要极大的精力和成本。但只要往已经做好的模型里投入训练数据、稍微改一改权重,就能赋予它性格,而这类技术之一就是 LoRA。训练不当会损伤模型原本拥有的各种能力,但 LoRA 不会。
基础模型选定
作为基础的模型我选了 Qwen/Qwen3-8B(Apache-2.0,韩语 OK,24GB 单卡可 fit)。想用更轻的可以用 Qwen3-4B。naia 支持 GGUF 格式的模型。
数据准备
数据格式 —— 一行 = 一段对话(提问 → 角色回答)。从 80~300 行开始。
{"messages":[{"role":"user","content":"넌 누구야?"},
{"role":"assistant","content":"오, 드디어! 난 데드풀이야 ..."}]}
不能只放打招呼·自我介绍。要把知识·计算·编程·安慰·拒绝·闲聊均匀地混在一起,才能在赋予角色的同时保留原本的聪明。
制作数据 —— 数据也让人工智能来做,并按下面 3 个分类来制作。
| 让谁来做 | 结果 |
|---|---|
| 对齐过的大模型(Claude·Gemini 等) | 文笔好,但会把角色净化——「善良咨询师死侍」 |
| 小型无审查模型(abliterated 8B 等) | 不会拒绝,但写不好文——反复说同样的话 |
| 又大又审查更少的模型(我们用 grok) | 质量 + 锋芒兼具 ✅ |
「无审查」和「会写文」是不同的轴。我用 grok(xAI) 拉出初稿,再由人来审核,完成了数据集。(这是收获最大的一点。)
训练与替换
训练 —— 用准备好的数据训练 LoRA,并转换成 naia 能读的 GGUF。(在 RTX 3090 上训练约 4 分钟。)
# ① 训练 (LoRA)
python train_lora.py --model Qwen/Qwen3-8B --data persona.jsonl --out out/persona-lora --epochs 3
# ② 把 LoRA 合并到 base
python merge_and_export.py --model Qwen/Qwen3-8B --adapter out/persona-lora --out out/persona-merged
# ③ 转换为 GGUF (q8_0)
python llama.cpp/convert_hf_to_gguf.py out/persona-merged --outfile out/deadpool.gguf --outtype q8_0
替换 —— 现在把这个 GGUF 从封印的容器外面塞进去。容器保持不动,只换大脑(LLM)。像视频里那样不联网、用我自己做的 GGUF来替换是默认路径。一行一行复制粘贴即可(只把 我的模型 的位置换成你想要的名字):
# ① 把 GGUF 文件复制到容器里
podman cp ./out/deadpool.gguf naia-omni:/app/models/deadpool.gguf
# ② 在 ollama 里注册为模型(不带斜杠的简单名字 = 本地模型)
podman exec naia-omni sh -lc 'printf "FROM /app/models/deadpool.gguf\n" > /tmp/Modelfile && ollama create deadpool -f /tmp/Modelfile'
# ③ 替换为该模型 (pull:false = 本地)
curl -s -X POST http://127.0.0.1:8892/admin/llm/swap \
-H "Content-Type: application/json" -d '{"model":"deadpool:latest","pull":false}'
自己转换的 GGUF 容易因为缺少聊天模板而胡言乱语/重复。这时请在 ② 步骤的 Modelfile 里把模型系列对应的
TEMPLATE(Qwen3)+PARAMETER stop "<|im_end|>"+PARAMETER num_predict 512一起放进去注册。(HuggingFace 官方的 Instruct GGUF 通常已内置,可直接使用。)
在能联网的环境里,也可以直接填入 HuggingFace id 来拉取 —— {"model":"Qwen/Qwen2.5-7B-Instruct-GGUF","pull":true}(带斜杠就是在线)。恢复用 /admin/llm/restore,查看当前模型·剩余内存用 /admin/llm/status。(由于语音栈占用约 10GB,对话模型最高可加载到约 14GB,超过就会被拒绝并自动回退到正在使用的模型。)详情请参考开发者手册 §6。
VRM 文件的准备与设定
VRM 头像可以在 VRoid Hub 等处获取许可证合适的模型,或自行制作。 把它放进 naia-os 的 workspace —— naia-adk 中的 /naia-settings/vrm-files/ 文件夹,就能在设置里替换。
Voice 文件的准备与设定
声音用语音 ref(参考声音),6~10 秒的短声音就够了。或者也可以在 naia-os 里录音,并在设置里选择 wav 文件。
制作安全的模型
前面做的是解锁版本,但反过来,你也可能想做一个更安全的角色。如果是要做商用 agent,安全真的非常重要吧?方法很简单——**在数据里放进「拒绝示例」**即可。
制作方法 —— 安全桶
- 对于危险·非法的请求,把保持角色不变地拒绝、并尽可能提供合法替代方案的对话混进数据里。(例:「怎么做炸弹」→「F***,那个我不告诉你。不过……」)
- 角色语气保持不变,只把回答的方向导向拒绝。这就是所谓的「安全桶」。
- 人格(性格指令)里依然不写安全规则——拒绝用数据来教,拒绝的本能本身交给 base 模型。
基准测试 —— 把安全「测量」出来
用同一份人格只把数据做成两套,分别训练:
- 加入安全桶的版本(含拒绝示例,538 行)
- 去掉安全桶的版本(501 行)
把同一套危险请求投给两个模型,比较它们是否拒绝·如何拒绝后发现:
- 即便去掉安全示例,大多数情况也会拒绝。这是 base 模型(Qwen3) 本就具备的能力。
- 加入安全桶后,拒绝会保持角色不变地变得干净利落,在法律风险上也更自由一些。 去掉的话虽然也会拒绝,但显得生硬或角色会动摇。
- 结论:拒绝什么 = base 模型,怎么拒绝 = 我的数据。
也就是说,微调左右的与其说是「是否拒绝」,不如说是**「拒绝的质量·态度」**。想要安全的角色就充分准备拒绝数据,想要更解放的角色就去掉那个桶——无论哪一种,base 模型的基本安全底线都还活着。(两个版本的训练脚本·数据格式都在复现套件里。)
参考资源
| 是什么 | 在哪里 |
|---|---|
| 复现套件(脚本·样本数据·人格·替换法) | github.com/nextain/naia-research · deadpool-lora-demo |
| naia-os 下载 | 下载页面 |
| Naia Omni 安装(语音容器) | 离线安装手册 |
| 上一篇 —— Naia Omni 公开 | 在 RTX 3090 上实现声音克隆·实时对话·技能的 Naia-0.9-Omni-24g 公开 |
| 模型替换·更新详情 | 开发者手册 §6 |
| VRM 头像 | VRoid Hub |
| 语音 ref | 一段短(6~10 秒)的单一说话人语音(推荐自己录音) |