Qwen3-TTS 내 목소리 학습: 맥미니에서 미세조정한 과정과 결과
Qwen3-TTS는 알리바바 큐원 팀이 2026년 1월 아파치 2.0으로 공개한 오픈소스 음성 합성 모델입니다. 통합 메모리 24GB 맥미니에서 녹음 13초로 제로샷 복제를 하고, 31분 녹음으로 0.6B 모델을 미세조정해 참조 음성 없이 내 목소리를 내게 만든 과정과 원리, 필요한 사양, 감정 표현을 넣는 방법을 정리했습니다.

3줄 요약
이번 방문에서 한 편은 바로 볼 수 있습니다.
Qwen3-TTS는 알리바바 큐원 팀이 2026년 1월 아파치 2.0 라이선스로 공개한 오픈소스 음성 합성(Text-to-Speech) 모델입니다. 공식 모델 카드에 따르면 10개 언어 500만 시간이 넘는 음성으로 학습했고, 한국어가 그 10개 언어에 들어 있으며, 녹음 3초 정도만 참조로 주면 그 목소리로 새 문장을 읽는 음성 복제를 지원합니다. 쉽게 말해 내 목소리 견본을 옆에 두고 어떤 문장이든 그 필체로 옮겨 적어 주는 프로그램입니다.
목소리를 복제해 주는 서비스가 많다는 것은 이제 다들 알고 있습니다. 다만 그 서비스들은 내 녹음을 남의 서버에 올려야 하고, 월 요금이 붙고, 내 목소리로 만든 음성이 어디에 남는지 알기 어렵습니다. 내 컴퓨터 안에서만 학습하고 합성할 수 있는지, 그게 엔비디아 그래픽카드가 없는 맥에서도 되는지, 학습이라는 것이 정확히 무엇을 하는 일인지는 막상 해 보기 전에는 알 수 없었습니다.
원리는 세 단계로 요약됩니다. 소리를 초당 12개의 토큰, 즉 글자 같은 조각으로 바꾸고, 언어 모델이 다음 글자를 이어 쓰듯 다음 소리 토큰을 예측하고, 디코더가 그 토큰을 다시 파형으로 되돌립니다. 목소리 복제는 이 예측기에 어떤 사람의 필체를 알려 주는 일이고, 알려 주는 방법이 두 가지입니다. 견본 한 장을 옆에 놓고 따라 쓰게 하는 것이 제로샷 복제이고, 그 사람의 글씨를 수십 분치 베껴 써서 필체를 몸에 익히게 하는 것이 미세조정입니다.
이 글에서는 통합 메모리 24GB 맥미니 한 대로 두 방법을 모두 해 본 과정을 정리하겠습니다. 결과를 먼저 들려드리고, 에포크 같은 학습 용어와 필요한 컴퓨터 사양을 풀고, 실제 절차와 중간에 만난 공식 스크립트의 버그, 감정과 말투를 넣는 방법의 한계까지 순서대로 적습니다.
실제 녹음과 미세조정 결과를 먼저 듣기
글로만 설명하면 어느 정도 닮았는지 가늠이 되지 않아서 결과부터 둡니다. 첫 번째는 학습에 쓴 미팅 녹음 가운데 한 토막이고, 음량을 맞춘 것 말고는 가공하지 않은 제 실제 목소리입니다. 두 번째는 미세조정한 모델이 참조 음성 없이 화자 이름만 받아 합성한 문장이고, 학습 데이터에 없던 문장입니다.
실제 녹음 (7초): "최대한 간결하게 해서 한번 진행을 해 보겠습니다. 화면 공유를 먼저 해 드릴게요."
미세조정 모델 합성 (8.6초): "안녕하세요, 준이아빠블로그를 운영하는 홍승협입니다. 오늘은 클로드 코드로 GA4 리포트를 자동화하는 방법을 정리해 봤습니다."
실제 녹음은 대화 중이라 말이 빠르고 합성은 읽는 말투에 가까워서 속도가 다르게 들립니다. 음색은 두 개를 이어서 들어 보시면 판단이 되고, 속도와 억양의 차이가 어디서 오는지는 뒤의 감정 표현 절에서 다룹니다. 여기까지 오는 데 걸린 것은 제로샷 30분과 미세조정 세 차례였고, 그 가운데 한 차례는 실패였습니다.
소리를 글자처럼 다루는 구조
Qwen3-TTS가 어떻게 목소리를 배우는지 알면 뒤에 나오는 용어와 사양이 왜 그런 값인지 이해가 됩니다. 아래는 공식 저장소의 구조도입니다.
그림 아래쪽의 Qwen3 LM은 글을 쓰는 언어 모델과 같은 종류입니다. 다만 입력이 두 줄인데, 한 줄은 읽을 문장의 텍스트 토큰이고 다른 한 줄은 소리를 잘게 나눈 코덱 토큰입니다. 모델 이름에 붙은 12Hz는 이 코덱 토큰이 초당 12개라는 뜻이라서, 10초짜리 문장은 토큰 120개로 표현됩니다. 언어 모델이 다음 단어를 고르듯 다음 코덱 토큰을 하나씩 고르고, 오른쪽 위 MTP 모듈이 그 토큰 하나에 세부 음질을 담당하는 토큰 15개를 덧붙이며, 마지막에 디코더가 이것을 파형으로 되돌립니다.
목소리는 어디에 들어가는지가 핵심입니다. 입력 줄 앞부분에 화자 임베딩(speaker embedding)이라는 칸이 하나 있고, 참조 음성에서 뽑은 목소리 특징을 숫자 묶음으로 만들어 그 칸에 넣습니다. 제로샷 복제는 합성할 때마다 이 칸을 참조 음성으로 채우는 방식이고, 미세조정은 학습 데이터 전체로 모델의 가중치 자체를 바꾼 뒤 그 칸에 들어갈 값을 화자 이름과 함께 모델 안에 저장해 두는 방식입니다.
공식 저장소에는 모델이 다섯 개 올라와 있고 이름 끝에 붙은 단어로 용도가 나뉩니다. 내 목소리를 쓰려면 Base가 붙은 모델을 받아야 합니다.
| 모델 | 크기 | 하는 일 | 내 목소리 복제 |
|---|---|---|---|
| Qwen3-TTS-12Hz-1.7B-Base | 1.7B | 참조 음성으로 목소리 복제, 미세조정 출발점 | 됩니다 |
| Qwen3-TTS-12Hz-0.6B-Base | 0.6B | 위와 같고 더 작습니다 | 됩니다 |
| Qwen3-TTS-12Hz-1.7B-CustomVoice | 1.7B | 미리 들어 있는 화자들로 합성하고 말투를 글로 지시 | 되지 않습니다 |
| Qwen3-TTS-12Hz-0.6B-CustomVoice | 0.6B | 위와 같습니다 | 되지 않습니다 |
| Qwen3-TTS-12Hz-1.7B-VoiceDesign | 1.7B | 글로 목소리를 묘사해 새 목소리를 만듭니다 | 되지 않습니다 |
이 글에서는 제로샷 복제에 1.7B Base를, 미세조정에 0.6B Base를 썼습니다. 1.7B와 0.6B는 언어 모델 부분의 크기 차이이고, 뒤의 사양 절에서 보듯 맥에서는 이 차이가 미세조정 가능 여부를 결정합니다.
제로샷 복제와 미세조정이 다른 점
두 방법은 필요한 재료와 결과의 성격이 다릅니다. 견본 한 장을 옆에 놓고 따라 쓰는 것과 필체 자체를 몸에 익히는 것이 다르듯, 제로샷 복제는 합성할 때마다 참조 음성을 보고 따라 하고 미세조정은 모델 안에 목소리를 새겨 둡니다.
| 항목 | 제로샷 복제 | 미세조정 |
|---|---|---|
| 필요한 녹음 | 3초에서 15초 한 토막과 그 전사문 | 10분 이상, 문장 단위로 자른 클립과 전사문 |
| 학습 | 없음 | 있음. 이 글에서는 에포크당 약 95초 |
| 합성할 때 | 매번 참조 음성을 함께 넣음 | 화자 이름만 지정 |
| 결과의 성격 | 참조 토막의 말 빠르기를 어느 정도 따라가고 문장마다 목소리가 조금씩 달라질 수 있음 | 목소리가 일정하고 억양은 학습 데이터의 평균에 가까움 |
| 먼저 해 볼 쪽 | 제로샷 | 제로샷 결과가 아쉬울 때 |
공식 문서도 Base 모델을 제로샷 복제와 미세조정 두 용도의 출발점으로 안내합니다. 먼저 제로샷으로 들어 보고, 더 일관된 목소리가 필요할 때 같은 모델을 미세조정하는 순서가 자연스럽습니다.
에포크, 학습률, 손실: 미세조정에 나오는 용어
미세조정 명령에는 낯선 숫자가 여럿 붙습니다. 학습 과정을 연습장 베껴 쓰기에 비추면 각 숫자가 무엇을 정하는지 잡힙니다.
| 용어 | 뜻 | 이 글에서 쓴 값 |
|---|---|---|
| 에포크(epoch) | 학습 데이터 전체를 처음부터 끝까지 한 번 다 보는 단위. 연습장 한 권을 한 번 다 베껴 쓰면 1 에포크 | 6. 에포크마다 217개 클립을 한 번씩 봄 |
| 배치(batch)와 누적 | 한 번에 같이 보는 클립 수와, 몇 번을 모아서 가중치를 한 번 고칠지 | 배치 2, 누적 4. 클립 8개를 보고 한 번 고침 |
| 학습률(learning rate) | 한 번 고칠 때 가중치를 얼마나 크게 바꿀지 | 5e-6. 크면 원래 알던 것까지 망가지고 작으면 느림 |
| 손실(loss) | 모델이 다음 소리 토큰을 얼마나 틀리는지 나타내는 점수. 낮을수록 좋음 | 4.5에서 시작해 3.6으로 내려감 |
| 체크포인트(checkpoint) | 에포크가 끝날 때 저장하는 모델 사본 | 2.3GB짜리 2개 |
| 화자 임베딩 | 목소리 특징을 숫자 묶음으로 만든 것. 참조 음성에서 뽑음 | 13초 녹음 하나에서 뽑아 전 샘플에 같은 값 사용 |
에포크는 많을수록 좋은 것이 아닙니다. 이 글에서는 4 에포크 판이 6 에포크 판보다 자연스러웠고, 6 에포크 판은 말이 조금 느려지고 문장이 길어졌습니다. 연습장을 여러 번 베끼면 글씨는 견본을 닮아 가지만 어느 순간부터는 견본에 있던 버릇까지 과하게 따라 하는 것과 비슷합니다. 손실도 마찬가지로 낮아지는 것만으로는 안심할 수 없는데, 그 이유는 뒤의 실패 사례에서 숫자로 보여드리겠습니다.
학습에 필요한 컴퓨터 사양
공식 문서는 엔비디아 GPU와 CUDA만 상정합니다. 그래서 맥에서는 되지 않는다고 생각하기 쉬운데, 실제로는 애플 실리콘의 MPS(Metal Performance Shaders)로 파이토치를 돌리면 제로샷과 0.6B 미세조정까지 됩니다. 아래는 M4 Pro 맥미니, 통합 메모리 24GB에서 직접 확인한 값입니다.
| 하려는 일 | 이 글에서 확인한 것 | 필요한 사양 |
|---|---|---|
| 1.7B 제로샷 복제 | 모델 로드 68초, 15초 음성 생성 14초 | 모델 파일 약 4GB. 통합 메모리 24GB에서 여유가 있었고, 16GB에서도 될 가능성이 높지만 직접 확인하지는 않았습니다 |
| 0.6B 미세조정 | 학습 프로세스가 19GB에서 23GB까지 커짐. 클립 217개 기준 에포크당 약 95초 | 통합 메모리 24GB가 사실상 하한. 학습 중 다른 큰 프로그램을 닫아야 합니다 |
| 1.7B 미세조정 | 시도하지 않음 | 가중치와 기울기, 옵티마이저 상태만 14GB 안팎이라 24GB에서는 넘칠 가능성이 큽니다. 통합 메모리 48GB 이상이나 엔비디아 GPU가 있어야 현실적입니다 |
| 디스크 | 모델 2.3GB와 4GB, 체크포인트 하나 2.3GB, 스왑 파일 최대 40GB | 30GB 이상 비워 두는 편이 안전합니다 |
| 윈도우와 리눅스 | 확인하지 않음 | 엔비디아 GPU가 있으면 공식 문서의 CUDA 경로를 그대로 따르면 됩니다 |
메모리가 이렇게 많이 드는 이유는 미세조정이 모델의 가중치 전체를 고치는 방식이기 때문입니다. 가중치 자체에 더해 각 가중치를 어느 방향으로 고칠지 적는 기울기와 옵티마이저 상태가 같은 크기로 두 벌 더 붙고, 파이토치의 MPS 할당자가 배치마다 길이가 다른 텐서를 새로 잡으면서 사용량이 계속 늘어납니다. 이 글의 0.6B 학습은 5GB 정도로 시작해 23GB까지 자랐습니다.
이 글에서 쓴 환경은 아래와 같습니다.
- 컴퓨터: M4 Pro 맥미니, 통합 메모리 24GB. 같은 용량의 맥에서 Qwen3.8 27B를 돌렸을 때는 CPU로 떨어졌는데, 이번 모델은 훨씬 작아서 여유가 있습니다.
- 파이썬: 3.12. 공식 문서가 권하는 버전이고 uv로 가상 환경을 만들었습니다.
- 녹음 파일: 이전에 다른 용도로 잘라 둔 45초에서 60초짜리 WAV 20개(17.7분)와 미팅 녹음 51분 한 개. 미팅 녹음에는 상대방 목소리가 섞여 있어서 뒤에서 화자를 가려내는 단계가 필요했습니다.
- 전사 도구: 위스퍼(Whisper)의 맥용 판인 mlx-whisper. 참조 음성의 전사문과 학습 클립의 문장을 여기서 얻습니다.
설치는 세 줄입니다. 공식 문서는 flash-attn 설치를 함께 안내하지만 그 패키지는 엔비디아 GPU 전용이라 맥에서는 건너뜁니다.
uv venv --python 3.12 .venv && source .venv/bin/activate
uv pip install -U qwen-tts torch torchaudio
python -c "import torch; print(torch.backends.mps.is_available())"마지막 줄이 True를 찍으면 준비가 끝납니다. 모델은 처음 실행할 때 허깅페이스에서 자동으로 내려받습니다.
1단계: 녹음 13초로 제로샷 복제
먼저 참조로 쓸 토막을 고릅니다. 상대방 목소리가 섞이지 않고 문장이 끊기지 않는 13초 구간을 잘라 24kHz 모노 WAV로 만들고, mlx-whisper로 그 구간의 전사문을 받아 텍스트 파일에 넣었습니다. 전사문이 실제 발화와 다르면 복제 품질이 바로 나빠지므로 한 번 읽어 보고 틀린 낱말을 고칩니다.
ffmpeg -ss 13.18 -to 26.98 -i 원본.wav -ar 24000 -ac 1 ref.wav
mlx_whisper --model mlx-community/whisper-large-v3-turbo --language ko ref.wav합성 코드는 공식 예제에서 두 곳만 바꿨습니다. device_map을 cuda:0 대신 mps로, attn_implementation을 flash_attention_2 대신 sdpa로 지정합니다. 공식 문서에는 맥에 관한 언급이 없는데 이 두 값만 바꾸면 그대로 동작했습니다.
import torch, soundfile as sf
from qwen_tts import Qwen3TTSModel
model = Qwen3TTSModel.from_pretrained(
"Qwen/Qwen3-TTS-12Hz-1.7B-Base",
device_map="mps", dtype=torch.bfloat16, attn_implementation="sdpa",
)
prompt = model.create_voice_clone_prompt(
ref_audio="ref.wav", ref_text=open("ref.txt").read().strip(),
)
wavs, sr = model.generate_voice_clone(
text=["안녕하세요, 준이아빠블로그를 운영하는 홍승협입니다."],
language=["Korean"], voice_clone_prompt=prompt,
)
sf.write("out.wav", wavs[0], sr)제가 직접 듣기 전에 합성 결과를 위스퍼로 다시 전사해 원문과 맞는지부터 확인했는데, 한국어 발음은 원문과 일치했습니다. 여기까지는 학습이 없으니 30분이면 끝납니다. 목소리가 얼마나 닮았는지는 결국 본인이 들어 봐야 알 수 있고, 저는 이 단계에서 톤은 비슷한데 문장마다 목소리가 조금씩 달라지는 점이 아쉬워 미세조정으로 넘어갔습니다.
2단계: 학습 데이터 만들기
미세조정에 넣는 데이터는 문장 단위 WAV 클립과 그 전사문, 그리고 화자 참조 음성 한 개입니다. 공식 문서는 세 가지를 JSONL 한 줄에 담고, 화자 참조 음성은 모든 샘플에 같은 파일을 쓰라고 강하게 권합니다. 1단계의 13초 파일을 그대로 참조로 썼습니다.
{"audio": "clips/001.wav", "text": "이 문장의 전사문입니다.", "ref_audio": "ref.wav"}클립은 45초짜리 원본을 그대로 넣지 않고 위스퍼의 문장 타임스탬프로 4초에서 15초 사이로 다시 잘랐습니다. 처음 만든 데이터는 20개 원본에서 나온 클립 97개, 18.6분이었습니다. 이 과정에서 두 가지를 걸러 냈습니다.
- 길이가 0인 클립: 위스퍼가 파일 끝을 넘긴 시각을 찍는 일이 있어서 잘라 보면 빈 파일이 나옵니다. 길이를 확인해 4초 미만은 뺍니다.
- 환각 문장: 무음 구간에서 위스퍼가 같은 문장을 반복해 만들어 냅니다. 같은 문장이 세 번 이상 나오면 전부 제외했습니다. 미팅 녹음에서는 이런 문장이 150건이었습니다.
미팅 녹음은 상대방 목소리가 섞여 있어서 화자를 가려내야 했는데, 별도 도구를 설치하지 않고 Qwen3-TTS 안에 들어 있는 화자 인코더를 그대로 썼습니다. 문장마다 화자 임베딩을 뽑아 참조 음성의 임베딩과 코사인 유사도를 재면 값이 두 무리로 나뉩니다. 제 목소리는 0.94에서 0.97, 상대방은 0.88에서 0.92에 모였고 그 사이 0.935를 경계로 잘랐습니다. 이렇게 51분 녹음에서 제 발화 29.7분을 얻었습니다.
클립을 만든 뒤에는 공식 데이터 준비 스크립트로 오디오를 12Hz 코덱 토큰으로 바꿉니다. 이 스크립트도 --device mps만 넘기면 그대로 돌아갑니다.
python prepare_data.py --device mps \
--input_jsonl train_raw.jsonl --output_jsonl train_with_codes.jsonl3단계: 미세조정 스크립트를 맥에서 돌리기
공식 저장소의 sft_12hz.py는 순수 파이토치와 Accelerate로 짜여 있어서 CUDA 전용 라이브러리는 없습니다. 맥에서 돌리려면 flash_attention_2를 sdpa로 바꾸는 것으로 충분하고, 0.6B 모델은 여기에 수정이 하나 더 필요합니다.
첫 실행은 차원 불일치 오류로 바로 멈췄습니다. 0.6B 모델은 텍스트 임베딩이 2048차원이고 코덱 임베딩이 1024차원이라 그 사이를 잇는 text_projection 층을 거쳐야 하는데, 공식 스크립트가 이 층을 건너뜁니다. 1.7B는 두 차원이 같아서 드러나지 않던 문제이고, 저장소 이슈에도 같은 보고가 여러 건 있습니다. 추론 코드와 같은 순서로 투사 층을 넣으면 학습이 시작됩니다.
python sft_12hz_mac.py \
--init_model_path models/Qwen3-TTS-12Hz-0.6B-Base \
--train_jsonl train_with_codes.jsonl \
--batch_size 2 --grad_accum 4 --lr 5e-6 --num_epochs 6 \
--speaker_name hongsh학습 자체는 순조로웠습니다. 클립 97개 기준으로 에포크 하나에 약 95초가 걸렸고 손실은 12.2에서 3.6까지 내려갔습니다. 여섯 에포크가 끝나고 체크포인트로 합성을 돌렸는데, 결과는 말소리가 아니라 잡음이었고 문장이 끝나도 멈추지 않았습니다.
손실은 줄었는데 잡음만 나온 이유
학습이 가중치를 망가뜨린 것인지 추론 경로가 잘못된 것인지부터 구분해야 했습니다. 학습된 가중치를 제로샷 복제 모드로 강제로 열어 참조 음성을 주고 합성해 보니 그래도 잡음이었고, 원본 0.6B 모델은 같은 조건에서 정확한 문장을 냈습니다. 가중치 자체가 망가진 상태였습니다.
원인은 공식 학습 스크립트에 있는 손실 정렬 버그였고, 저장소에 올라온 수정 요청(Pull Request)에 정리되어 있었습니다. 스크립트가 입력과 라벨을 한 칸 미리 밀어 넣는데 트랜스포머스(Transformers) 라이브러리의 손실 함수가 안에서 또 한 칸을 밀어서, 모델이 다음 토큰이 아니라 다다음 토큰을 배우게 됩니다. 견본의 다다음 글자를 보며 필체 연습을 한 셈이라 아무리 열심히 연습해도 실제로 쓸 때는 어긋납니다. 앞의 구조도에서 본 MTP 모듈, 학습 코드에서는 서브 토커(sub-talker)라고 부르는 보조 예측기에도 정답이 입력으로 새어 들어가는 문제가 하나 더 있었습니다.
이 버그는 학습 없이도 확인할 수 있습니다. 원본 모델에 실제 학습 배치를 넣고 옛 목적함수와 수정한 목적함수로 손실을 각각 재면 됩니다. 어휘 크기가 2048인 코덱에서 무작위 추측의 손실은 ln(2048), 약 7.62입니다.
| 목적함수 | 원본 모델의 손실 | 판정 |
|---|---|---|
| 공식 스크립트 그대로 | 10.17 | 무작위 추측보다 나쁨 |
| 수정본 | 4.33 | 정상 범위 |
정상적으로 학습된 모델이라면 자기 데이터에서 무작위보다 나쁜 손실이 나올 수 없습니다. 이 표 하나로 버그가 확정됐고, 수정을 적용한 두 번째 학습은 첫 스텝 손실이 4.5에서 시작해 3.6으로 내려갔으며 네 에포크째에 저장한 첫 체크포인트에서 정확한 문장이 나왔습니다. 앞에서 손실이 낮아지는 것만으로는 안심할 수 없다고 한 이유가 이것입니다. 학습 전에 이 검증을 한 번 하는 편이 여섯 에포크를 버리는 것보다 낫습니다.
어미를 흐리는 습관을 데이터에서 걸러내기
두 번째 학습 결과를 들어 보니 문장 끝이 기어들어 가는 습관까지 그대로 배워 있었습니다. 실제로 제가 그렇게 말하기는 하는데, 내레이션으로 쓸 음성이라면 어미가 들려야 합니다. 이 부분은 실제 말버릇과 달라도 되니 학습 데이터 쪽에서 고치기로 했습니다.
먼저 문장 끝 0.5초의 음량이 문장 중앙부보다 얼마나 낮은지를 재는 값을 하나 만들었습니다. 이 낙폭이 6dB를 넘는 문장, 그러니까 끝이 중간의 절반 크기 아래로 꺼지는 문장은 학습에서 뺐습니다. 또박또박 쓴 페이지만 골라 연습장에 끼우는 것과 같아서, 문장 다섯 개 가운데 하나꼴로 여기서 빠졌습니다. 남은 문장에는 가벼운 압축기(acompressor)를 걸어 끝부분을 조금 올리고, 끝에 0.4초 무음을 붙여 문장이 깔끔하게 끝나는 것도 함께 배우게 했습니다.
이렇게 만든 세 번째 데이터는 클립 217개, 31.4분이었습니다. 미팅 녹음이 더해져 늘어난 숫자이고, 어미 필터가 없었다면 클립이 더 남았겠지만 어미가 들리는 문장만 남기는 쪽을 택했습니다. 같은 세 문장을 합성해 어미 낙폭을 재 보면 차이가 수치로 보입니다.
| 문장 | 두 번째 모델 | 세 번째 모델 (4 에포크) |
|---|---|---|
| 1 | -10.1 dB | -6.9 dB |
| 2 | -10.5 dB | -4.3 dB |
| 3 | -9.5 dB | -4.3 dB |
0에 가까울수록 어미가 또렷합니다. 두 번째 모델은 문장 끝이 중간의 3분의 1 크기로 꺼졌고, 세 번째 모델은 절반에서 3분의 2 수준으로 남습니다. 직접 들어 본 느낌도 세 번째 모델 쪽이 깔끔했고, 글 앞머리에서 들려드린 합성이 이 모델의 4 에포크 판입니다.
감정과 말투를 넣는 방법과 그 한계
앞머리의 두 음성을 비교하면 합성 쪽이 읽는 말투로 평평하게 들립니다. 미세조정한 모델이 학습 데이터의 평균을 배우기 때문인데, 제 데이터는 미팅에서 설명하는 대화 한 종류였습니다. 그러면 감정이나 말투를 나중에 얹을 수 있는지가 궁금해지는데, 결론부터 말하면 미세조정한 모델에는 얹을 수 없었고 방법은 데이터 쪽에 있습니다.
위 공식 소개 그림의 왼쪽 아래 Control이 말투를 글로 지시하는 기능입니다. "뉴스 앵커 톤으로 읽어 달라"는 식의 지시문을 붙이는 방식인데, 이 기능은 표에서 본 CustomVoice와 VoiceDesign 모델의 것입니다. 미세조정한 제 모델에도 같은 지시문 인자가 있어서 실제로 시험했습니다. 같은 문장에 지시문 없음, 차분하게, 밝게, 슬프게 네 가지를 붙여 합성했더니 네 결과가 길이와 음높이는 물론 바이트 단위까지 완전히 같았습니다. 지시문이 무시된 것이고, 공식 미세조정 스크립트가 지시문 없는 텍스트와 오디오 쌍만 학습하기 때문으로 보입니다.
제로샷 복제는 참조 토막의 말투를 어느 정도 따라갑니다. 초당 3.2음절로 느리게 말한 토막과 7.4음절로 빠르게 말한 토막을 각각 참조로 넣고 같은 문장을 만들어 보니, 결과는 초당 5.9음절과 6.4음절이었습니다. 방향은 따라가지만 폭은 참조 사이의 차이보다 훨씬 좁습니다. 참조 하나로 말투를 크게 바꾸기는 어렵다고 보는 편이 맞습니다.
그래서 감정과 말투를 넣으려면 학습 데이터에 그 상황의 녹음을 섞는 쪽이 현실적입니다.
- 읽는 말투가 필요하면 읽은 녹음을 넣습니다. 내레이션용이라면 실제 대본 몇 편을 또박또박 읽어 10분에서 15분을 더하는 쪽이 지시문보다 확실합니다. 모델은 데이터의 평균을 배우므로 원하는 말투가 데이터의 절반 이상은 차지해야 결과에 드러납니다.
- 감정별로 따로 학습하는 방법도 있습니다. 공식 스크립트는 화자 하나만 지원하므로, 차분한 녹음과 밝은 녹음을 각각 다른 화자 이름으로 따로 미세조정하고 문장에 따라 모델을 바꿔 쓰는 식입니다. 모델이 그만큼 늘어나고 학습도 두 번이라 시도해 볼 만한 방법이지 검증된 것은 아닙니다.
- 문장 쪽에서 조절합니다. 긴 문장을 나누고 쉼표와 마침표를 조정하면 호흡이 달라집니다. 감정을 바꾸지는 못하지만, 긴 문장을 한 번에 읽힐 때 더 평평하게 들리는 경향이 있었습니다.
나온 결과와 매번 같은 목소리를 내는 설정
미세조정이 끝난 모델은 참조 음성 없이 화자 이름만 지정해 합성합니다. 합성은 표본 추출(sampling) 방식이라 같은 문장도 돌릴 때마다 조금씩 달라지는데, 시드를 고정하면 같은 명령에서 바이트 단위로 같은 결과가 나옵니다. 마음에 드는 톤이 나왔을 때 그 설정을 묶어 두면 앞으로 만드는 음성이 그 톤으로 고정됩니다.
torch.manual_seed(7)
model = Qwen3TTSModel.from_pretrained("output/checkpoint-epoch-3", device_map="mps",
dtype=torch.bfloat16, attn_implementation="sdpa")
wavs, sr = model.generate_custom_voice(
text=sentences, language=["Korean"] * len(sentences), speaker="hongsh",
temperature=0.75, subtalker_temperature=0.75, max_new_tokens=600,
)실제로 쓰는 스크립트는 여기에 세 가지를 더했습니다. 마침표 기준으로 문장을 나눠 따로 합성하고, 문장마다 앞뒤 무음을 정리한 뒤 0.35초 간격으로 이어 붙이고, max_new_tokens에 상한을 둡니다. 마지막 항목은 안전장치인데, 기본값 8192 그대로 두면 모델이 종료 토큰을 내지 못했을 때 문장 하나에 11분짜리 잡음을 만들고 나서야 멈춥니다.
속도는 0.6B 모델 기준으로 실시간의 절반 정도입니다. 10분짜리 내레이션이면 5분 안팎이 걸립니다. 톤은 문장마다 조금씩 달라서 어떤 문장은 어미가 다시 흐려지기도 하는데, 그 문장만 시드를 바꿔 다시 만들면 되고 대본 전체를 다시 만들 필요는 없습니다.
학습 중에 메모리와 디스크에서 막힌 것
사양 절에서 말한 대로 0.6B 학습 프로세스는 23GB까지 자랐습니다. 통합 메모리 24GB 컴퓨터에서 다른 프로그램이 함께 떠 있으면 스왑이 시작되고, 에포크 시간이 95초에서 300초, 심할 때는 28분까지 늘어났습니다.
- 학습 전에 큰 프로그램을 닫습니다. 제 경우 백그라운드에 떠 있던 프로그램들이 33GB를 차지하고 있었고, 그것들을 닫자 스왑이 38GB에서 19GB로 줄고 속도가 돌아왔습니다.
- 체크포인트 하나가 2.3GB입니다. 공식 스크립트는 에포크마다 모델 폴더 전체를 복사해 저장하므로 여섯 에포크면 14GB입니다. 저장할 에포크를 골라 두고 저장 직전에 디스크 여유를 확인합니다.
- 스왑이 디스크를 차지합니다. 학습 도중 디스크 여유가 14GB에서 3GB로 줄어 있었는데 원인은 스왑 파일이었습니다. 체크포인트 저장이 실패하면 그 에포크가 통째로 날아가므로 여유를 두는 편이 안전합니다.
녹음은 몇 분이나 있어야 하나요?
제로샷 복제는 공식 문서 기준 3초면 되고, 실제로는 문장이 끊기지 않는 10초에서 15초 한 토막이 안정적이었습니다. 미세조정은 이 글에서 18분으로도 결과가 나왔지만 저장소 이슈의 사례들을 보면 30분에서 1시간 사이가 현실적인 목표로 보입니다. 분량보다 중요한 것은 종류인데, 미팅 대화 한 가지로만 학습하면 억양이 그 상황의 평균으로 평평해집니다. 혼자 또박또박 읽은 내레이션이나 강의하듯 말한 녹음이 섞이면 폭이 넓어집니다.
1.7B 모델도 맥미니에서 미세조정이 되나요?
24GB 통합 메모리에서는 어렵습니다. 1.7B의 전체 미세조정은 가중치와 기울기, 옵티마이저 상태만으로 14GB 안팎이고, 0.6B가 실제로 23GB까지 자란 것을 보면 1.7B는 배치 1로도 넘칠 가능성이 큽니다. 제로샷 복제는 1.7B도 문제없이 돌아가므로, 맥에서는 제로샷은 1.7B로 미세조정은 0.6B로 나누는 편이 맞습니다. 통합 메모리 48GB 이상이라면 1.7B 미세조정도 시도해 볼 만합니다.
미세조정한 목소리가 제로샷보다 항상 나은가요?
그렇지는 않습니다. 저장소 이슈에는 미세조정 결과가 제로샷보다 단조롭게 들렸다는 보고가 여럿 있고, 제 결과도 억양 폭은 제로샷 쪽이 넓었습니다. 미세조정이 나은 점은 문장을 많이 만들어도 목소리가 흔들리지 않는 일관성과 참조 음성을 매번 넣지 않아도 되는 편의입니다. 짧은 문장 몇 개라면 제로샷으로 충분하고, 긴 내레이션을 정기적으로 만든다면 미세조정이 맞습니다.
3줄 요약
- Qwen3-TTS는 알리바바 큐원 팀이 2026년 1월 아파치 2.0으로 공개한 음성 합성 모델이고, Base 모델은 녹음 몇 초만 참조로 주면 한국어를 포함한 10개 언어로 그 목소리를 냅니다. 소리를 초당 12개 토큰으로 바꿔 언어 모델이 다음 토큰을 예측하는 구조라서, 공식 문서는 CUDA만 다루지만
device_map을mps로 바꾸면 맥에서 그대로 동작합니다. - 통합 메모리 24GB 맥미니에서 1.7B 제로샷 복제는 실시간에 가까운 속도로 동작했고, 0.6B 미세조정은 학습 프로세스가 23GB까지 커지며 31분 녹음 217개 클립으로 에포크당 약 95초가 걸렸습니다. 다른 프로그램을 닫고 체크포인트 2.3GB가 들어갈 디스크를 비워 둡니다.
- 미세조정한 모델은 말투 지시문에 반응하지 않으므로 감정과 말투는 학습 데이터에 그 상황의 녹음을 섞는 방법으로만 넣을 수 있습니다. 공식 학습 스크립트는 손실 정렬 버그가 있어 커뮤니티 수정을 적용하고, 원본 모델의 손실이 무작위 추측(7.62)보다 낮게 나오는지 확인한 뒤에 학습을 시작합니다.
Sources
이 글이 도움이 되셨다면 공유해 주세요
메신저로 바로 보내거나 링크를 복사할 수 있습니다.

Written by
데이터로 설명하는 마케터
Qwen3-TTS 공식 미세조정 스크립트로 학습했더니 손실은 12에서 3.6까지 내려갔는데 합성 결과가 잡음뿐입니다. 가장 먼저 의심할 곳은 어디일까요?
이 글이 도움이 되었나요?
다음 단계
이어서 읽기 좋은 글
맥미니 한 대에 tmux 세션 두고 맥북과 아이폰에서 SSH로 AI 작업 이어가기
tmux는 터미널 세션을 컴퓨터 안에 남겨 두었다가 다른 기기에서 그대로 이어 받게 해 주는 터미널 멀티플렉서입니다. 집 맥 한 대에 세션을 켜 두고 테일스케일로 묶인 맥북과 아이폰에서 SSH로 들어와 클로드 코드 화면을 이어 쓰는 설정 순서를 정리했습니다.
같이 보면 좋은 글

RTX 스파크는 엔비디아가 2026년 10월 출시를 예고한 윈도우 PC용 슈퍼칩으로, 20코어 Grace CPU와 블랙웰 RTX GPU에 통합 메모리를 최대 128GB까지 얹었습니다. 로컬 AI 에이전트를 집 컴퓨터에서 돌리는 조건을 맥 스튜디오, RTX 5090과 비교해 정리했습니다.
2026. 9. 7.
테일스케일은 집과 회사와 가방 속 기기를 하나의 사설 네트워크로 묶어 이름만으로 서로 부르게 하는 VPN 도구입니다. 집 컴퓨터 한 대를 24시간 AI 작업 서버로 두고 노트북과 회사 컴퓨터, 스마트폰에서 이어 쓰는 구성과 설치 순서를 정리했습니다.
2026. 9. 6.
intent.md는 앤트로픽 AI 네이티브 SDLC 플레이북에서 코드를 쓰기 전에 무엇을 왜 만들지 적어 두는 첫 번째 문서입니다. 클로드에게 질문을 시켜 만드는 절차와 그대로 가져다 쓸 수 있는 예시 세 가지, PRD와 TRD에서 무엇이 달라졌는지, 이어지는 spec.md와 plan.md의 역할까지 다룹니다.
2026. 9. 5.
GPT-6 아스트라는 OpenAI가 2026년 9월 3일 공개한, 컴퓨터를 사람처럼 직접 조작하는 데 초점을 둔 모델입니다. 이번 출시가 왜 예전 모델 발표와 다른 반응을 얻었는지, 클로드 페이블 5.1과 제미나이 3.8 플래시와 견주면 어디에 서는지, ARC-AGI-3 99.9%와 Critical 사이버 등급이 무엇을 상징하는지 정리하고 사무직과 AI 전환기의 중장기 방향을 조심스럽게 전망했습니다.
2026. 9. 5.ADVERTISEMENT