Fish Audio의 Drama 3는 텍스트를 음성으로 변환하는 350억 개의 파라미터 Mixture of Experts (MoE) 모델이며, 대괄호 안에 작성된 무대 지시(stage directions)까지 따릅니다. 문장 앞에 [속삭이면서] 또는 [화난 목소리로]와 같이 적으면 해당 감정으로 라인을 연기합니다. 이 모델은 가중치만으로도 약 41.5 GB가 필요하기 때문에 시중에 나와 있는 모든 소비자용 그래픽 카드를 사용하기 어렵습니다.
저는 이를 터키어로, RTX 5070 Ti (16GB)를 장착한 책상 로봇에 구현하고 싶었습니다. 이 게시물은 제가 성공적으로 작동시킨 방법과 측정 내용, 그리고 다른 사람들이 저보다 먼저 발표했던 부분들을 설명합니다.
샘플이 포함된 30초 데모는 huggingface.co/kasparovabi/drama-3-tr-speaker에서 확인할 수 있습니다.
읽기와 말하기는 다른 작업입니다
Drama 3와 같은 Text to Speech 모델은 두 가지 작업을 수행합니다. 첫째, 프롬프트(시스템 텍스트, 음성 레퍼런스 녹음, 발화할 문장)를 읽습니다. 둘째, 초당 약 21.5개의 오디오 프레임을 하나씩 생성합니다.
저는 터키어 음성에서 각 전문가(expert)가 어떻게 사용되는지 계산했습니다. '읽기'는 모든 레이어의 거의 모든 256개 전문가를 건드립니다. 반면, '말하기'는 훨씬 적은 세트에 집중합니다. 그래서 저는 읽기를 위한 원래 모델을 유지하고, 오디오 프레임마다 원본의 12.5%에 해당하는 단지 32개의 전문가만을 가진 별도의 스피커(speaker)를 훈련시켰습니다. 이 둘은 어텐션 캐시(attention cache)와 DeltaNet 순환 상태(recurrent states)를 통해 만나기 때문에, 스피커는 리더가 멈춘 바로 그 지점부터 정확하게 이어집니다.
나중에 저는 SlimWise (arXiv 2609.34117)에서 몇 주 전에 언어 모델에 대해 동일한 분할(split)을 발표했다는 것을 알게 되었습니다. 이 아이디어 자체도 마찬가지로, 저는 음성 분야에서 별도로 도달했습니다.
스피커를 자체 오류로 훈련시키기
스피커의 각 레이어는 자신 아래 스피커 레이어에서 생성된 은닉 상태(hidden states)를 기반으로 학습됩니다. 이 은닉 상태들은 이미 원래 모델과 약간 벗어나 있지만, 목표값은 깨끗한 경로(clean path)에서의 원래 모델의 다음 은닉 상태입니다. 제 제거 실험(ablation)에서는 모든 크기에서 단순 출력 매칭(plain output matching)보다 성능이 좋았으며 (32 전문가 기준 KL 0.109 대 0.153), GPTAQ (ICML 2025)도 양자화에 동일한 비대칭 목적 함수를 사용합니다.
ISSAI 터키어 음성 코퍼스(Turkish Speech Corpus)의 3,800개 발화로 약 85분 동안 학습이 진행되었습니다.
드라이브에서 스트리밍하는 전문가 모델 (Streaming experts from the drive)
여전히 전체 모델이 필요합니다. 41.5 GB를 RAM에 유지하는 대신, 각 3 MB의 전문가(expert)는 NVMe 드라이브에서 버퍼링되지 않은 I/O(unbuffered I/O)를 통해 고정 메모리(pinned memory)로 온디맨드(on demand) 로드됩니다. 이 과정은 생성 시 약 4.4 GB의 시스템 RAM을 필요로 하며, 로딩 초기에 최대 9.3 GB에 달합니다. 전문가를 디스크로 오프로딩하는 것은 확립된 기술이며, 저는 여기서 새로운 것을 주장하지 않습니다. 비용은 짧은 문장에서의 속도이며, 수치는 아래와 같습니다.
결과 (Results)
Freya-TR-Eval(Freya가 자체 점수 스크립트를 사용하는 공개 터키어 벤치마크)에서 시스템은 전문가를 RAM에 로드했을 때 495개 문장에 걸쳐 WER 2.68%와 CER 0.70%를 달성했으며, 전문가를 디스크에 두고 방향 안내(direction guidance)를 활성화했을 때는 각각 2.78%와 0.67%였습니다. FreyaTTS는 장치에서 실행되도록 구축된 183M 모델이므로 이는 동등한 비교가 아닙니다. 하지만 이 결과는 작은 스피커가 Drama 3의 터키어 발음을 이해할 수 있는 수준(intelligible)을 유지하고 있음을 보여줍니다.
전문가를 RAM에 로드했을 때 시스템은 짧은 문장에서 실시간 대비 1.2배, 단락에서는 1.7~1.9배로 작동하며, 스트리밍 코덱 디코딩을 통해 첫 오디오는 약 1.15초 후에 도착합니다. 디스크 모드는 짧은 문장에서 더 느립니다 (방향 안내 활성화 시 실시간 대비 0.86배). 이는 한 문장을 읽어들이는 과정에서 드라이브로부터 약 8 GB의 전문가를 가져오기 때문입니다. 이 트래픽이 제가 다음에 가장 줄이고 싶은 부분입니다.
예상치 못한 발견 (The finding I did not expect)
또한 리더를 축소하는 실험도 해봤습니다. 텍스트에 레이어당 16개의 전문가(experts)를 가진 리더는 단어를 명확하게 유지했지만, 목소리가 평면적으로 변했고, [속삭임] 같은 지시사항은 작동하지 않았습니다. 스피커만 축소하는 것은 이런 문제를 일으키지 않았습니다. 이는 듣기를 통해 확인한 단일 실험이므로 중요한 단서로 간주합니다. 이 모델에서는 감정과 스타일이 모델이 읽는 동안 캐시(cache)에 기록되는 것 같고, 이후의 프레임들이 주로 그것들을 전달합니다.
공개 후에야 발견한 버그
출시 다음 날 샘플을 다시 들어봤는데, 두 번째 샘플은 다른 사람 목소리처럼 들렸습니다. 처음 의심했던 것은 강도(strength) 4에서의 지시사항이었는데, 이것이 대괄호([])를 더 강하게 밀어붙이는 경향이 있었습니다. 이를 테스트하기 위해 여러 강도에서 동일한 문장들을 생성하고 스피커 임베딩(speaker embedding) (CAM++)을 사용하여 참조 목소리와 비교 점수를 매겼습니다. 유사도는 0.02에서 0.49 사이로 떨어져, 지시사항 탓이라고 하기에는 너무 낮았습니다.
원인은 제 엔진에 있었습니다. 단일 프로세스가 비지정(unguided) 생성에서 지정(guided) 생성으로 전환될 때, 어텐션 캐시(attention caches)가 배치 크기 2를 위해 재할당되면서 캐시된 참조 녹음이 지워졌고, 반면 제 프리픽스 캐시(prefix cache)는 여전히 그 자리에 있다고 믿었습니다. 모델은 아예 참조 목소리 없이 말하고 있었습니다. 수정 후에는 각 강도 실행을 개별 프로세스로 진행했을 때, 일반 문장은 모든 강도에서 0.810.87의 점수를 받았고, 대괄호가 포함된 문장은 0.560.78의 점수를 받았습니다. 강도 4에서의 지시사항은 괜찮았으며, 공개 샘플이 불운한 시드(seed)를 사용했던 것이었으므로, 모든 샘플을 시드 0으로 다시 생성했습니다. 가중치와 Freya 숫자는 영향을 받지 않았습니다.
Claude Code 작업하기
저는 전체 프로젝트를 Claude Code로 진행했습니다. 다음에 무엇을 시도할지, 그리고 어떤 것이 증거로 간주될지를 결정했고, Claude가 대부분의 코드를 작성하고 실험을 실행하며 문헌 조사를 수행했습니다. 가장 중요했던 두 가지 습관이 있었습니다. 모든 주장은 반드시 측정치와 함께 제시되어야 했고, 이것이 캐시 버그(cache bug)를 제가 추적할 수 있는 숫자로 나타내는 방식이었습니다. 그리고 어떤 아이디어가 새롭다고 부르기 전에, 저는 그 특정 아이디어에 초점을 맞춘 검색을 요청했는데, 이것이 이 게시물이 나가기 전에 SlimWise와 GPTAQ가 발견된 방식입니다. 스테어스피치(SteerSpeech)나 EmoSteer-TTS가 이미 다루고 있는 것처럼, 히든 스테이트(hidden states)에서 감정을 직접 조향하는 것과 같은 제가 좋아했던 몇 가지 아이디어들도 거기서 멈췄습니다.
링크
가중치(Weights)는 huggingface.co/kasparovabi/drama-3-tr-speaker에, 코드는 github.com/kasparovabi/drama-3-tr에 있습니다. Fish Audio로 구축되었습니다. 이 가중치는 Fish Audio 커뮤니티 라이선스(Community License) 하에 있습니다.
Ahmet Kazankaya