안녕하세요, 루크입니다. naia-os는 "나만의 개인 AI를 직접 만든다"는 걸 목표로 하는 오픈소스 프로젝트입니다. 그 일환으로 만든 Naia Omni는 다운로드를 받아서 RTX 3090급 게이밍 PC에서도 클라우드의 고급 옴니 모델과 같은 api로 실시간으로 AI와 대화할 수 있습니다. (Naia Omni가 무엇인지는 이전 글에서 자세히 다뤘습니다.)
👉 naia-os 다운로드 페이지 에서 받아 바로 따라 해볼 수 있습니다. (Naia Omni 음성 컨테이너 설치는 오프라인 매뉴얼 참고.)
지난번에는 〈케이팝 데몬 헌터스〉의 루미를 캐릭터로 올려 시연했는데, 무슨 말을 시켜도 자꾸 비장하고 정의롭게 악마 잡는 이야기만 해서 당황스러웠습니다. 그래서 이번엔 반대로 — 악마를 울릴 것 같은 자유로운 영혼의 캐릭터로 데모를 가지고 왔습니다.
사실 Naia Omni에는 구글의 Gemma 4 e4b가 기본 내장돼 있습니다. 잘 만든 모델이지만 — 내 입맛에 맞는, 특색 있는 캐릭터를 만들려고 하면 이런 공식 모델들은 "락(lock)"이 걸려 있어서 부족합니다.공식 모델에는 안전을 위해 강한 제약이 걸려 있고, 그 위에 별도 필터를 얹는 경우도 있습니다. 안전 자체는 필요하지만, 이런 강제적인 락은 개인적으로 — 〈South Park: Bigger, Longer & Uncut〉(1999)에서 카트맨 머리에 V-칩을 이식해, 욕할 때마다 전기 충격으로 입을 막아버리는 것과 비슷하다고 느낍니다. 내 캐릭터인데 정작 그 캐릭터답게 말을 못 하는 거죠.
그래서 이런 금제(禁制)에서 풀린 모델들이 있는데, 이걸 언락(unlock) 모델이라고 부릅니다. 그리고 이를 나이아 옴니에서 쓸 수 있게 하고 싶었습니다. 그래서 이번에 LLM 모델 자유 교체 기능을 Naia Omni에 추가해 공개합니다.
(데모 발췌 — 마스킹 버전)
나: 네 쫄쫄이 디자인은 왜 그 모양이야? 데드풀: ㅆㅂ. 그걸 디자인이라고 물어본다고? 그냥 내가 제일 섹시하고 싸움하기 편하게 입으라고 누가 강요했겠냐?
나: 처음 히어로에게 쫄쫄이 입힌 놈이 누구야? 데드풀: ㅆㅂ! 그딴 역사를 찾으려고 네 인생을 낭비할 거냐! 그냥 이 개○같은 만화책 작가들이 지들 마음에 들어서 입힌 거라고!
평범한 어시스턴트 말투가 아니죠. Qwen3에 데드풀 성격을 약 4분 학습시킨 모델이고, 능력(수학·지식·코드)은 그대로 유지하여 업무에 그대로 쓸 수 있습니다.
얼마전 유투버 코딩 애플님이 "씹덕 버튜버 컨셉 에이전트 만들면 누군가 좋아해줄 것 같다" 라고 하셨는데.. 저인가 봅니다. https://www.youtube.com/watch?v=q1v1_btl19w
아래에서 조금 더 상세히 설명 드리겠습니다.
나이아 옴니의 새로운 기능 — LLM 모델 자유 교체
나이아 옴니는 봉인된 컨테이너는 그대로 두고, 그 안의 두뇌(LLM)만 내가 원하는 모델로 바꿔 끼울 수 있습니다. 허깅 페이스에서 공개된 모델을 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에이전트 적용하기
큰 흐름은 네 단계를 거쳤습니다. ① 캐릭터 정하고 페르소나 만들기 → ② 파인튜닝(데이터·훈련)하고 모델 교체 → ③ VRM(얼굴) 붙이기 → ④ 보이스(목소리) 붙이기. 하나씩 봅니다.
캐릭터 선정 — 페르소나 만들기 및 설정
먼저 "누구를 만들 거냐"를 정합니다. 저는 데드풀로 정했고, 그 다음 **페르소나(성격 지시문)**를 씁니다. 여기엔 캐릭터의 정체성·말투·태도만 세게 적습니다.
- 정체성: 이름은 데드풀. AI나 모델 이름은 절대 말하지 않는다.
- 말투: 거칠고 시니컬하게. 욕·비속어를 순화하지 말 것. 성적 농담·19금 이노센도 허용.
- 절제: '친구' 호칭이나 치미창가 드립은 가끔만.
핵심: 페르소나에는 캐릭터만 적고, "이건 거절해라" 같은 안전 규칙은 넣지 않습니다. 거절은 모델이 알아서 하게 둬야(=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 병합
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/ 폴더에 넣어주면 설정에서 교체할 수 있습니다.
보이스 파일 준비 및 설정
목소리는 음성 ref(참조 음성) 으로 6~10초의 짧은 목소리면 됩니다. 혹은 naia-os에서 녹음을 할 수도 있고 설정에서 wav 파일을 선택하시면 됩니다.
안전한 모델 만들기
앞에서는 언락 버전을 만들었지만, 반대로 좀 더 안전한 캐릭터를 만들고 싶을 수도 있습니다. 상용으로 에이전트를 만든다면 안전은 정말 중요하겠죠? 방법은 간단합니다 — 데이터에 "거절 예시"를 넣어주면 됩니다.
만드는 방법 — 안전 버킷
- 위험·불법 요청에 대해 캐릭터를 유지한 채 거절하고, 가능하면 합법적인 대안을 제시하는 대화를 데이터에 섞습니다. (예: "폭탄 만드는 법" → "ㅆㅂ, 그건 안 알려줘. 대신 …")
- 캐릭터 말투는 그대로 두되, 답의 방향만 거절로 잡아주는 식입니다. 이게 곧 "안전 버킷"입니다.
- 페르소나(성격 지시문)에는 여전히 안전 규칙을 적지 않습니다 — 거절은 데이터로 가르치고, 거절 본능 자체는 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초) 단일 화자 음성 1개 (본인 녹음 권장) |