TrendShift 딥다이브 · 2026-07-01

KittenTTS 딥다이브
— GPU 없이 CPU에서 도는 25MB급 초경량 ONNX TTS

KittenTTS는 "최고 품질"이 아니라 "어디서든 돌아가는 크기"를 목표로 설계된 텍스트 음성 변환(TTS) 라이브러리다. 파라미터 1,500만~8,000만 개, 디스크 용량 25~80MB짜리 신경망 하나가 ONNX Runtime (신경망 추론 전용 경량 실행기) 위에서 GPU 없이 CPU만으로 사람 목소리를 합성한다. 텍스트 정규화(숫자·통화·날짜를 읽는 말로 변환)부터 음소화(phonemization, 글자를 발음 기호로 변환), 8가지 내장 보이스, int8 양자화까지 전체 파이프라인이 순수 Python + ONNX 한 장으로 끝난다. Python 100% · ONNX Runtime · phonemizer(espeak-NG) · Apache-2.0 위에서 동작한다. (저장소: KittenML/KittenTTS · Python · ★14.3k · Apache-2.0 · 최신 릴리스 0.8.1 "developer preview")
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

이 저장소가 대체 무엇인가.

핵심 메시지

"거대한 모델을 작게 압축하는 게 아니라, 처음부터 ‘스마트폰 한 대에서도 돌아가는 크기’를 목표로 설계한 TTS 모델."

대부분의 고품질 TTS(문자를 사람 목소리로 바꾸는 기술)는 수억~수십억 파라미터짜리 신경망을 GPU 서버에 올려 서비스한다. KittenTTS는 정반대 전략을 택했다 — 15M(1,500만)~80M(8,000만) 파라미터, 디스크에서 25~80MB밖에 안 되는 모델을 만들고, 이걸 범용 딥러닝 프레임워크(PyTorch 등)가 아니라 추론 전용으로 최적화된 ONNX Runtime 위에서 CPU만으로 돌린다.

비유하면: 일반 TTS 서비스는 공장에 발전기 한 대를 통째로 들여놓고 돌리는 것이고, KittenTTS는 손바닥만 한 건전지로 돌아가는 라디오를 만드는 것에 가깝다. 소리의 ‘최고급 오디오 감동’은 덜하지만, 서버도 GPU도 인터넷도 없이 노트북·라즈베리파이·브라우저(WebGPU) 어디서든 즉시 소리를 낼 수 있다는 게 핵심 가치다.

README의 자기소개는 "an open-source, lightweight text-to-speech library built on ONNX" — "ONNX 위에 지어진 오픈소스 경량 TTS 라이브러리"다. GitHub 저장소 설명은 더 직설적으로 "State-of-the-art TTS model under 25MB"라고 못박는다. 2026년 2월 v0.8 릴리스로 15M/40M/80M 세 가지 크기의 모델이 한꺼번에 공개됐고, int8 양자화 버전은 25MB까지 더 줄어든다.

단, README 최상단에 "Developer preview -- APIs may change between releases"라고 명시되어 있다. 아직 API가 안정화되지 않은 초기 단계 프로젝트이며, README도 "일부 사용자가 int8 nano 모델에서 문제를 겪고 있다"는 점을 숨기지 않고 공개해 둔다. 상업 지원(Stellon Labs)이 별도로 제공되는 것도 특징이다.

용어
TTS (Text-to-Speech, 텍스트 음성 변환)
글자로 된 텍스트를 사람이 말하는 것처럼 들리는 음성 파형(waveform)으로 바꾸는 기술. Siri·구글 어시스턴트의 답변 음성, 오디오북 자동 생성, 스크린 리더 등에 쓰인다. 최근 몇 년은 자연스러움을 극대화한 대형 모델(수억 파라미터, GPU 필수) 경쟁이었는데, KittenTTS는 "충분히 자연스러우면서 어디서든 돌아가는" 쪽으로 방향을 튼 사례다.

2왜 주목받는가

트렌딩 이유 · 대형 GPU TTS·클라우드 TTS API 대비 장점.

2026년 상반기 TTS 생태계는 두 축으로 갈려 있다. 한쪽은 ElevenLabs·OpenAI TTS처럼 클라우드 API로 초고품질 음성을 파는 서비스, 다른 한쪽은 Coqui-TTS·VITS·StyleTTS2 계열처럼 연구용으로 공개됐지만 GPU 없인 실전 배포가 부담스러운 오픈소스 모델이다. KittenTTS는 이 둘 사이의 빈틈 — "오프라인·엣지·저비용으로 돌리고 싶은데 품질도 어느 정도는 필요한" 자리를 정확히 겨냥해서 14.3천 개의 스타를 모았다.

기존 방식과 무엇이 다른가

비교 대상클라우드 TTS API (ElevenLabs 등)대형 오픈소스 TTS (VITS/Coqui류)KittenTTS의 접근
실행 위치클라우드 서버 필수보통 GPU 서버로컬 CPU 한 대로 충분
비용 구조글자 수/시간당 과금GPU 서버 운영비모델 다운로드 한 번, 이후 무료
프라이버시텍스트가 외부 서버로 전송됨자체 서버면 안전완전 오프라인 실행 가능
모델 크기비공개(추정 수억+)보통 수백MB~수GB25~80MB
엣지/온디바이스 배포불가능(네트워크 필수)어려움(GPU·VRAM 필요)라즈베리파이·브라우저까지 가능
기존 TTS의 한계
고품질 = 무겁다는 공식이 당연시됐다

자연스러운 억양·감정 표현을 노리는 최신 TTS 모델일수록 파라미터가 커지는 경향이 있다. 결과적으로 오프라인 앱, IoT 기기, 브라우저 내 실행처럼 "서버에 의존할 수 없는" 환경에서는 선택지가 급격히 줄어든다. 클라우드 API를 쓰면 되지만, 이번엔 네트워크 지연·비용·개인정보(사용자가 입력한 텍스트가 외부로 나감) 문제가 따라온다.

KittenTTS의 해결
모델 자체를 작게 설계 + 추론 전용 런타임(ONNX)으로 CPU 최적화

KittenTTS는 사후에 큰 모델을 쥐어짜서 줄인 게 아니라(그런 기법도 int8 양자화로 추가 적용하지만), 애초에 15M~80M라는 작은 파라미터 예산 안에서 학습된 모델이다. 그리고 PyTorch 같은 학습용 프레임워크가 아니라 추론만을 위해 그래프를 최적화한 ONNX Runtime으로 실행하기 때문에, 별도 GPU 드라이버·CUDA 설치 없이 pip install 한 번으로 어떤 OS에서든 바로 소리를 낼 수 있다.

이런 상황을 상상해봐

오프라인 내비게이션 앱에 "다음 신호에서 우회전하세요"라는 음성 안내를 붙이고 싶다고 하자. 클라우드 TTS API를 쓰면 지하 주차장·터널처럼 네트워크가 끊기는 순간 안내가 멈춘다. 대형 오픈소스 TTS를 로컬에 올리면 앱 용량이 수백MB~수GB로 불어나고 배터리도 많이 먹는다. KittenTTS라면 앱 설치 용량에 40~80MB만 더해서 완전히 오프라인으로, 배터리 부담도 적게 음성 안내를 낼 수 있다 — 이게 "엣지 배포(edge deployment)"가 실제로 풀어주는 문제다.

3기술 스택 전체 지도

Python 패키지 하나 + ONNX 추론 엔진 + 음소화 스택 + HuggingFace Hub 배포.

KittenTTS는 "백엔드/프론트엔드가 나뉜 웹서비스"가 아니라 pip로 설치하는 Python 라이브러리 하나다. 그래서 스택을 "추론 엔진 / 전처리 스택 / 모델 배포 인프라" 세 층으로 나눠 보는 게 더 정확하다.

레이어기술 / 라이브러리역할
언어Python (100%, requires-python >= 3.8)전체 코드베이스가 순수 Python. 컴파일 빌드 스텝 없음
추론 엔진onnxruntime (GPU는 onnxruntime-gpu)실제 신경망 순전파(forward pass)를 수행하는 핵심 — ort.InferenceSession
음소화(G2P)phonemizer + espeakng_loader영어 텍스트를 IPA(국제음성기호) 발음 기호로 변환. espeak-NG 엔진을 Python 패키지로 번들
텍스트 정규화자체 구현 kittentts/preprocess.py (외부 의존성 없음)숫자·통화·시간·날짜·URL·약어를 정규식 기반 규칙으로 읽는 말로 확장
수치 연산numpy토큰 ID 배열, 음성 파형 배열 처리
오디오 I/Osoundfile (libsndfile 바인딩)numpy 배열 ↔ WAV 파일 저장/로드
모델 배포huggingface_hub (hf_hub_download)ONNX 모델 파일·보이스 임베딩·설정을 HuggingFace Hub에서 캐싱 다운로드
GPU 가속(선택)nvidia-cublas/cudnn/cufft 등 CUDA 12 계열requirements_gpu.txtbackend="cuda" 지정 시 CUDAExecutionProvider 사용
빌드/패키징setuptools (pyproject.toml + setup.py 이중 지원)*.onnx·*.json·*.txt를 패키지 데이터로 포함(MANIFEST.in)
테스트unittest (표준 라이브러리)tests/test_text_normalization.py — 정규화 규칙의 입출력 예시를 회귀 테스트로 고정
용어
ONNX / ONNX Runtime
ONNX(Open Neural Network Exchange)는 "어떤 프레임워크로 학습했든 하나의 공통 포맷으로 저장하자"는 신경망 교환 표준이다. PyTorch로 학습한 모델을 ONNX로 변환(export)해두면, 학습에 쓰인 PyTorch 없이도 ONNX Runtime이라는 훨씬 가볍고 추론에 최적화된 실행기만으로 그 모델을 돌릴 수 있다. KittenTTS 저장소에는 학습 코드가 아예 없다 — 이미 학습이 끝나 ONNX로 내보내진 모델 파일(.onnx)을 다운로드해서 실행만 하는 구조이기 때문이다.
용어
phonemizer / espeak-NG (음소화 엔진)
사람이 쓰는 글자(grapheme)와 실제 발음(phoneme)은 다르다 — 영어 "through"는 철자에 없는 소리 규칙을 따른다. phonemizer는 이런 글자→발음 변환(G2P, Grapheme-to-Phoneme)을 여러 백엔드로 제공하는 Python 라이브러리이고, KittenTTS는 그중 espeak-NG(오픈소스 음성 합성 엔진의 원조 격, 수십 년 역사)를 백엔드로 쓴다. espeakng_loader는 이 espeak-NG 엔진의 네이티브 라이브러리(.so/.dll)와 발음 데이터를 pip 패키지 안에 미리 넣어 배포하는 역할만 한다 — 사용자가 시스템에 espeak을 별도로 설치할 필요가 없게 해준다.

4아키텍처 심화 분석

텍스트 한 줄이 24kHz 파형이 되기까지 — 6단계 파이프라인.

kittentts/onnx_model.pyKittenTTS_1_Onnx 클래스를 직접 열어보면, README의 "간단한 API 3줄" 뒤에 상당히 구체적인 파이프라인이 숨어 있다. 핵심은 "신경망이 하는 일은 최소화하고, 규칙 기반 전처리로 최대한 다듬어서 넘긴다"는 설계 철학이다.

텍스트 입력 │ ▼ ① 텍스트 정규화 (TextPreprocessor, clean_text=True일 때) "GPT-3 costs $0.002" → "gpt three costs zero dollars point zero zero two cents" │ ▼ ② 문장 청킹 (chunk_text, 최대 400자) 긴 텍스트를 문장 경계로 자르되 "Dr.""p.m." 같은 약어는 안 자름 │ ▼ (청크 하나마다 반복) ③ 음소화 (phonemizer + espeak-NG) "Hello, world." → "h_ə_l_ˈoʊ , w_ˈɜː_l_d ." (IPA 발음 기호) │ ▼ ④ 토큰화 (basic_english_tokenize + TextCleaner) IPA 문자열 → 정수 ID 배열 [0, 12, 5, 33, ..., 10, 0] (0=시작/끝 패딩, 10=종결 토큰) │ ▼ ⑤ ONNX 모델 추론 (ort.InferenceSession.run) 입력: input_ids(음소 토큰) + style(보이스 임베딩) + speed(속도 배수) 출력: 24kHz 오디오 파형 (마지막 5000 샘플 트림) │ ▼ ⑥ 청크별 결과를 이어붙임 (np.concatenate) │ ▼ 24kHz numpy 오디오 배열 → (선택) WAV 파일 저장

① 텍스트 정규화 — 신경망에 "읽기 쉬운 글"만 넘긴다

model.generate(text, clean_text=True)를 켜면 TextPreprocessor가 먼저 돈다. 이 클래스는 25개 넘는 on/off 가능한 규칙(정수 확장, 통화 기호, 퍼센트, 시간, 서수, 로마 숫자, IP 주소, 전화번호, URL, 이메일, 약어 등)을 순서를 지켜 적용한다. 예를 들어 IP 주소(192.168.0.1)는 소수점 확장 규칙보다 먼저 처리해야 "192점168점0점1"처럼 잘못 읽히지 않는다 — 이런 규칙 간 순서 의존성을 주석으로 명시해 둔 게 실전 텍스트 정규화 코드의 특징이다.

# kittentts/preprocess.py — TextPreprocessor.process() 순서 일부
if cfg["expand_ip_addresses"]:      # IP는 소수점 확장보다 먼저
    text = expand_ip_addresses(text)
if cfg["normalize_leading_decimals"]:  # ".5" → "0.5"로 먼저 정리
    text = normalize_leading_decimals(text)
if cfg["expand_currency"]:          # "$12.50" → "twelve dollars and fifty cents"
    text = expand_currency(text)
if cfg["expand_scientific_notation"]:  # "1e-4"가 모델명 확장에 오염되기 전에 처리
    text = expand_scientific_notation(text)

별도 공개 함수인 normalize_text(text, return_spans=True)는 이보다 더 "문서 교정"에 특화되어 있다 — "Dr. Rivera"의 Dr.을 "Doctor"로, "Fig. 2"를 "Figure two"로 바꾸는 식이다. return_spans=True를 주면 원문 글자 위치 → 정규화된 글자 위치를 매핑하는 NormalizedSpan 리스트까지 돌려준다.

설계 패턴
위치 매핑을 유지하는 텍스트 치환 (Span-Preserving Substitution)
_sub_with_spans() 함수는 정규식으로 문자열을 치환하면서도, 원본 텍스트의 몇 번째 글자가 정규화된 텍스트의 몇 번째 글자에 대응하는지를 origins라는 정수 배열로 계속 추적한다. 왜 필요한가? 자막 동기화, 발음 하이라이팅(지금 읽는 단어를 원문에서 강조 표시)처럼 "정규화된 텍스트의 이 부분이 원문의 어디였는지"를 되짚어야 하는 응용에 필수적이기 때문이다. 단순히 str.replace()를 반복하면 이 대응 관계가 사라진다.

② 문장 청킹 — "Dr."을 문장 끝으로 착각하지 않기

chunk_text(text, max_len=400)는 텍스트를 400자 단위로 잘라 모델에 넘긴다(한 번에 너무 긴 시퀀스를 넣으면 품질이 떨어지거나 메모리 부담이 커지기 때문). 그런데 단순히 마침표(.)로만 자르면 "Dr. Rivera", "3:05 p.m.", "et al." 같은 표현이 매번 문장이 끝난 것으로 오인된다. _is_sentence_boundary()약어 사전(_NON_BOUNDARY_ABBREVIATIONS)과 앞뒤 문맥(숫자 사이의 소수점, "a.m./p.m." 패턴)을 함께 검사해 진짜 문장 경계만 골라낸다.

③ 음소화 — 글자가 아니라 "소리"로 신경망에 입력한다

self.phonemizer = phonemizer.backend.EspeakBackend(language="en-us", preserve_punctuation=True, with_stress=True) — 이 한 줄이 KittenTTS가 "문자 그대로"가 아니라 발음 기호(IPA, International Phonetic Alphabet)를 신경망 입력으로 쓴다는 걸 보여준다. with_stress=True는 강세 표시(ˈ, ˌ)까지 살리고, preserve_punctuation=True는 쉼표·마침표 같은 문장부호를 운율(억양이 오르내리는 지점) 신호로 남긴다.

왜 굳이 발음 기호로 바꿀까

영어 철자와 발음은 자주 어긋난다 — "though", "through", "tough"는 "ough"가 전부 다르게 소리 난다. 신경망에 철자 그대로("t-h-r-o-u-g-h")를 넣으면 모델이 이런 불규칙성을 전부 암기해야 한다. 반면 발음 기호("θruː")로 미리 바꿔서 넣으면, 모델은 "이 발음 기호 시퀀스를 소리로 바꾸는" 더 규칙적인 문제만 풀면 된다. 즉 음소화는 신경망의 짐을 덜어주는 전처리 단계다.

④ 토큰화 — 손으로 짠 IPA 심볼 사전

TextCleaner 클래스는 학습된 토크나이저(BPE 등)가 아니라, 패딩 기호($) + 문장부호 + 영어 알파벳 + IPA 발음 기호 전체를 나열한 고정 심볼 테이블이다. 각 문자에 정수 인덱스를 매겨, 음소화된 문자열을 "정수 ID 시퀀스"로 바꾼다. 이 방식은 VITS·StyleTTS2 같은 음소 기반 TTS 모델들이 공통으로 쓰는 "고정 음소 사전" 패턴이다.

// kittentts/onnx_model.py — TextCleaner 심볼 테이블 구성 (일부)
_pad = "$"
_punctuation = ";:,.!?¡¿—…\"«»"" "
_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
_letters_ipa = "ɑɐɒæɓʙβɔɕçɗɖðʤə..."  // IPA 발음기호 전체

symbols = [_pad] + list(_punctuation) + list(_letters) + list(_letters_ipa)
// 문자 하나 = 정수 인덱스 하나. 총 심볼 수가 모델의 "어휘 크기(vocab size)"가 된다

마지막으로 토큰 시퀀스 맨 앞뒤에 [0, ..., 10, 0]을 붙인다 — 0은 시작/끝 패딩, 10(마침표에 해당하는 인덱스)은 문장 종결을 신경망에 명시적으로 알리는 역할로 추정된다.

⑤ ONNX 모델 추론 — 3개 입력, 1개 출력

_prepare_inputs()가 만드는 최종 입력은 정확히 세 가지다.

입력 이름형태(shape)의미
input_idsint64[1, seq_len]음소를 토큰화한 정수 시퀀스 (시작/종결 패딩 포함)
stylefloat32[1, style_dim]"어떤 목소리로 말할지"를 나타내는 보이스 임베딩 벡터 (voices.npz에서 조회)
speedfloat32[1]말하는 속도 배수 (기본 1.0, 보이스별 speed_priors가 곱해짐)

출력은 24kHz 오디오 파형 하나이며, 코드는 outputs[0][..., :-5000]맨 끝 5000 샘플(약 0.2초)을 잘라낸다 — 학습 시 모델이 문장 끝에 약간의 무음/잡음 꼬리를 생성하는 경향을 사후 보정하는 실전적인 트림(trim)이다. README나 공식 문서 어디에도 설명되지 않은, 소스코드를 직접 읽어야만 알 수 있는 디테일이다.

⑥ 8가지 보이스는 어떻게 만들어지나 — "보이스 임베딩"

self.voices = np.load(voices_path)로 불러오는 voices.npz는 보이스 이름을 키로 하는 numpy 배열 모음이다. 실제 내부 보이스 이름은 expr-voice-2-f, expr-voice-3-m 같은 4단계 × 남녀 2종 = 8개 코드이고, 사용자가 쓰는 "Bella", "Jasper" 같은 친숙한 이름은 HuggingFace의 config.json에 담긴 voice_aliases 딕셔너리로 매핑된다.

// HuggingFace config.json (kitten-tts-mini-0.8) 실제 내용
"voice_aliases": {
  "Bella": "expr-voice-2-f",   "Jasper": "expr-voice-2-m",
  "Luna":  "expr-voice-3-f",   "Bruno":  "expr-voice-3-m",
  "Rosie": "expr-voice-4-f",   "Hugo":   "expr-voice-4-m",
  "Kiki":  "expr-voice-5-f",   "Leo":    "expr-voice-5-m"
}

더 흥미로운 지점은 ref_id = min(len(text), self.voices[voice].shape[0] - 1)다. 각 보이스는 단일 벡터가 아니라 여러 행(row)을 가진 배열이고, 입력 텍스트의 길이로 어느 행을 쓸지 인덱싱한다. 즉 짧은 문장과 긴 문장에서 조금씩 다른 스타일 벡터가 선택될 수 있다는 뜻이다 — 문장 길이에 따라 운율이 달라지는 것을 감안한 설계로 추정된다.

용어
보이스 임베딩 / 스타일 벡터 (Voice Embedding / Style Vector)
사람 목소리의 "색깔"(음색·억양·화자 정체성)을 신경망이 이해할 수 있는 고정 길이 숫자 벡터로 압축한 것. VITS나 StyleTTS 계열 모델은 화자 임베딩을 텍스트 인코더의 출력과 결합해, "같은 문장이라도 이 벡터를 바꾸면 다른 목소리로 말하게" 만든다. KittenTTS의 style 입력이 정확히 이 역할이며, 8명의 목소리는 사실 "같은 신경망에 다른 스타일 벡터를 꽂아 넣은 결과"다 — 목소리마다 별도로 학습된 모델이 8개 있는 게 아니다.

모델 크기 3종 + int8 — 무엇이 트레이드오프인가

모델파라미터용량특징
kitten-tts-mini-0.880M80MB최고 품질(README 권장 기본값)
kitten-tts-micro-0.840M41MB속도-품질 균형
kitten-tts-nano-0.8 (fp32)15M56MB가장 작은 파라미터, 그러나 32비트 부동소수점이라 용량은 micro보다 큼
kitten-tts-nano-0.8-int815M25MB"25MB 미만" 헤드라인의 주인공. 일부 환경에서 이슈 보고됨

주목할 점: nano(fp32)가 56MB인데 int8 버전은 25MB로 절반 이하로 줄어든다. 파라미터 수는 똑같이 15M인데 용량만 확 줄어드는 이유가 바로 양자화(quantization)다.

용어
int8 양자화 (Post-Training Quantization)
신경망의 가중치(weight)는 보통 32비트 부동소수점(fp32)으로 저장된다. int8 양자화는 학습이 끝난 모델의 가중치를 8비트 정수로 다시 표현해, 이론상 용량을 1/4로 줄이는 기법이다(fp32 4바이트 → int8 1바이트). 대신 표현 가능한 숫자의 정밀도가 떨어지므로 음질이 미세하게 저하될 수 있다 — README가 "일부 사용자가 int8 nano 모델에서 문제를 겪었다"고 명시한 이유도 이 정밀도 손실과 관련 있을 가능성이 높다. ONNX Runtime은 int8로 양자화된 모델도 그대로 추론할 수 있어, "양자화 → ONNX 배포" 조합이 엣지 배포의 표준 공식처럼 쓰인다.

"스트리밍"의 진짜 의미 — 실시간 토큰 생성이 아니라 청크 파이프라이닝

example_streaming.pygenerate_stream()을 읽어보면, KittenTTS의 스트리밍은 모델이 오디오를 실시간으로 한 샘플씩 뱉어내는 스트리밍(예: LLM의 토큰 스트리밍)이 아니다. 실제로는 chunk_text()로 잘라둔 문장 단위 청크를 순서대로 하나씩 ONNX 세션에 통과시키고, 청크가 끝날 때마다 그 결과를 곧바로 반환(yield)하는 구조다.

// kittentts/onnx_model.py
def generate_stream(self, text, voice="expr-voice-5-m", speed=1.0, clean_text=True):
    if clean_text:
        text = self.preprocessor(text)
    for text_chunk in chunk_text(text):
        yield self.generate_single_chunk(text_chunk, voice, speed)  // 청크 하나 끝날 때마다 즉시 반환

이렇게 설계하면 사용자는 전체 문단이 다 합성되기를 기다리지 않고, 첫 문장이 끝나는 즉시 재생을 시작할 수 있다 — 실시간 대화형 에이전트처럼 "체감 지연(latency)"이 중요한 응용에 실질적인 이득이다. 다만 오디오 품질 관점에서는 한 청크가 다른 청크의 문맥을 전혀 참고하지 못한다는 제약이 있다.

5디렉토리 구조 해부

단일 패키지 + 예시 스크립트 3종 + 최소 테스트.

KittenTTS/ ├── kittentts/ # 배포되는 pip 패키지 본체 (전부 여기 안) │ ├── __init__.py # public API 노출: KittenTTS, normalize_text 등 (지연 import) │ ├── __index__.py # get_model 재노출용 보조 진입점 │ ├── get_model.py # KittenTTS 클래스 + HuggingFace Hub 다운로드 로직 │ ├── onnx_model.py # 실제 추론 파이프라인 — 이 레포의 두뇌 │ │ # TextCleaner(토큰화) + KittenTTS_1_Onnx(ONNX 세션·음소화·생성) │ └── preprocess.py # 텍스트 정규화 엔진 — 1,384줄, 이 레포에서 가장 큰 단일 파일# 숫자/통화/날짜/시간/URL/이메일/약어 확장 25종+ normalize_text() │ ├── tests/ │ ├── __init__.py │ └── test_text_normalization.py # normalize_text·chunk_text 입출력 예시를 회귀 테스트로 고정 │ ├── example.py # 기본 사용 예시 — CPU, mini 모델, generate() → wav 저장 ├── example_cuda.py # backend="cuda"로 GPU 추론 예시 ├── example_streaming.py # generate_stream()으로 청크 단위 재생 + 저장 예시 │ ├── pyproject.toml # PEP 621 빌드 메타데이터 (setuptools 백엔드) ├── setup.py # 레거시 방식 빌드 스크립트 (pyproject.toml과 내용 중복) ├── requirements.txt # CPU 실행에 필요한 의존성 6개 ├── requirements_gpu.txt # + onnxruntime-gpu + CUDA 12 계열 nvidia-* 패키지 8종 ├── MANIFEST.in # *.onnx/*.json도 패키지에 포함시키라는 지시 └── LICENSE # Apache License 2.0
파일/폴더역할
kittentts/onnx_model.py핵심 — 음소화 백엔드 초기화, 토큰화, ONNX 세션 실행, 청크 스트리밍까지 전부 여기 173줄 안에.
kittentts/preprocess.py텍스트 정규화 규칙 전체(1,384줄). TextPreprocessor(내부용 대량 확장)와 normalize_text()(공개 API, 약어·스팬 매핑 포함)로 이원화되어 있다.
kittentts/get_model.py사용자가 보는 KittenTTS(...) 클래스. 실제 무거운 로직은 onnx_model.py에 위임하고, 여긴 HuggingFace Hub 다운로드 + 얇은 퍼사드(facade) 역할.
kittentts/__init__.py__getattr__KittenTTS·get_model을 지연 임포트(lazy import)한다 — 패키지를 불러오는 순간 무거운 onnxruntime/phonemizer가 바로 로드되지 않도록 하는 최적화.
tests/test_text_normalization.py"Dr. Rivera paid $12.50 at 3:05 p.m." 같은 실제 GitHub 이슈에서 나온 사례를 그대로 테스트 케이스로 박아둔 것이 특징 — 회귀 방지 목적이 뚜렷하다.
(레포에 없음) 학습 코드·데이터셋모델 학습 파이프라인은 이 오픈소스 레포에 포함되어 있지 않다 — 여기 있는 건 "이미 학습된 ONNX 모델을 내려받아 추론하는" 클라이언트 라이브러리뿐이다.
주의 — 모델 파일은 이 레포 안에 없다
git clone만 해서는 소리가 나지 않는다

GitHub 저장소 자체에는 .onnx 가중치 파일이나 voices.npz가 들어있지 않다. KittenTTS("KittenML/kitten-tts-mini-0.8")를 처음 실행하는 순간 huggingface_hub가 별도의 HuggingFace 저장소(config.json + *.onnx + voices.npz)에서 파일을 내려받아 로컬 캐시에 저장한다. 즉 "코드 저장소"와 "모델 저장소"가 분리되어 있다 — 요즘 딥러닝 오픈소스 프로젝트의 흔한 배포 패턴이다.

6학습 포인트 (기술별)

이 저장소 하나로 배울 수 있는 것 + 실습 아이디어.

6-1. ONNX Runtime으로 "학습 없이 추론만" 해보기

ort.InferenceSession(model_path, providers=providers) 단 한 줄이 딥러닝 추론 서빙의 핵심을 보여준다 — providers 리스트에 CPUExecutionProvider/CUDAExecutionProvider/ROCMExecutionProvider 중 무엇을 넣느냐로 같은 모델 파일이 CPU·NVIDIA GPU·AMD GPU 어디서든 돌아간다. PyTorch/TensorFlow 없이도 session.run(None, {input_name: value, ...})만으로 신경망을 실행할 수 있다는 걸 직접 체험하기 좋은 코드다.

실습 아이디어

아무 사전학습 ONNX 모델(예: sklearn2onnx나 torchvision 예제 모델)을 하나 준비해 onnxruntime만으로 session.get_inputs()/get_outputs()로 입출력 이름과 shape를 출력해보고, session.run()으로 추론 결과를 얻어보자. KittenTTS의 _prepare_inputs()가 이름이 맞는 딕셔너리를 어떻게 조립하는지와 비교하면 "ONNX 모델을 쓴다"가 실제로 무슨 뜻인지 감이 잡힌다.

6-2. TTS 파이프라인의 각 단계를 손으로 재현해보기

phonemizer.backend.EspeakBackend(...).phonemize(["hello world"])만 따로 실행해서 IPA 출력을 직접 눈으로 확인해보면, "신경망이 실제로 받는 입력이 글자가 아니라 발음 기호"라는 걸 체감할 수 있다. 그다음 TextCleaner의 심볼 사전에 그 IPA 문자열을 통과시켜 정수 배열이 되는 과정까지 재현하면, README의 3줄 예시 뒤에 있는 "진짜 파이프라인"을 전부 손으로 밟아본 셈이 된다.

실습 아이디어

kittentts/onnx_model.py_prepare_inputs()를 참고해 phonemizerbasic_english_tokenizeTextCleaner 세 단계를 별도 스크립트로 분리해서 각 단계의 중간 출력(음소 문자열, 토큰 리스트, 정수 ID 배열)을 print()로 확인해보자. "Hello"와 "Hello!"가 다른 토큰 시퀀스를 만드는지도 비교해보면 좋다.

6-3. 텍스트 정규화 설계 — 규칙 순서와 return_spans

TextPreprocessor가 IP 주소를 소수점 확장보다 먼저 처리하는 것처럼, 규칙 기반 텍스트 처리는 "어떤 순서로 적용하느냐"가 결과를 좌우한다. 또한 normalize_text(text, return_spans=True)가 원문-정규화문 간 글자 위치 매핑을 돌려주는 설계는, 자막·하이라이팅처럼 "가공된 텍스트를 원본과 다시 연결해야 하는" 모든 NLP 응용에 재사용 가능한 패턴이다.

# kittentts/preprocess.py 실제 테스트 케이스 (tests/test_text_normalization.py)
normalize_text("Dr. Rivera paid $12.50 at 3:05 p.m.")
# → "Doctor Rivera paid twelve dollars and fifty cents at three oh five p m."

result = normalize_text("Fig. 2", return_spans=True)
# result.text  == "Figure two"
# result.spans == [(원본 0~4글자, "abbreviation"), (원본 5~6글자, "number")]
실습 아이디어

자신만의 정규화 규칙(예: 한국어 "3시간"을 "three hours"로, 또는 이모지를 말로 풀어쓰는 규칙)을 _sub_with_spans() 패턴을 참고해 하나 추가해보고, tests/test_text_normalization.py 형식으로 회귀 테스트 케이스를 작성해보자.

6-4. int8 양자화로 모델을 가볍게 만들기

KittenTTS 저장소 자체엔 양자화 코드가 없지만(이미 양자화된 .onnx를 HuggingFace에서 받아 쓸 뿐), onnxruntime.quantization 모듈로 임의의 ONNX 모델을 int8로 변환하는 실습은 누구나 해볼 수 있다. nano(fp32) 56MB → nano-int8 25MB로 줄어든 실측 수치를 목표 삼아, "양자화 전후 용량·추론 속도·출력 품질이 어떻게 달라지는지"를 직접 측정해보는 게 이 개념을 체화하는 가장 빠른 길이다.

실습 아이디어

onnxruntime.quantization.quantize_dynamic()으로 아무 작은 ONNX 모델(직접 만든 간단한 분류기 등)을 int8로 변환해보고, 변환 전후 파일 크기를 os.path.getsize()로 비교해보자. 실제로 4배 가까이 줄어드는지 확인.

6-5. HuggingFace Hub 모델 배포·캐싱 구조 이해하기

get_model.pydownload_from_huggingface()는 "코드 저장소(GitHub)와 모델 저장소(HuggingFace Hub)를 분리하고, config.json 하나로 어떤 파일들을 더 받아야 하는지 스스로 알아내는" 패턴을 보여준다. config["type"] not in ["ONNX1", "ONNX2"]이면 에러를 던지는 버전 체크도 실전적인 하위호환성 관리 방식이다.

실습 아이디어

HuggingFace 계정에 아무 파일(작은 텍스트 파일도 무방) + config.json을 올려 저장소를 하나 만들고, huggingface_hub.hf_hub_download()cache_dir을 지정해 다운로드해보자. 같은 파일을 두 번 요청했을 때 실제로 캐시가 재사용되는지(다운로드 로그로) 확인.

6-6. 엣지 배포 관점에서 "무엇을 줄였는가" 되짚어보기

KittenTTS가 "작다"는 인상만 남기지 말고, 구체적으로 ①모델 파라미터 자체를 작게 설계 ②ONNX Runtime으로 무거운 학습 프레임워크 의존성 제거 ③int8 양자화로 가중치 용량 추가 절감 ④espeak-NG 같은 검증된 경량 음소화 엔진 재사용(직접 G2P 모델을 학습하지 않음)이라는 네 가지 결정이 합쳐진 결과라는 걸 이해하는 게 이 레포의 진짜 학습 포인트다.

실습 아이디어

본인이 아는 다른 "무거운" AI 도구(이미지 생성, 음성 인식 등) 하나를 골라, 위 네 가지 축(모델 크기 설계/추론 런타임/양자화/규칙 기반 컴포넌트 재사용) 각각에 대해 "이 도구라면 어떻게 경량화할 수 있을까"를 표로 정리해보자.

7시스템 요구사항

GPU 없이 노트북 한 대로 충분 — 진짜 제약은 디스크 용량이 아니라 첫 실행 시 네트워크(HF 다운로드).

항목요구사항
OSLinux · macOS · Windows (README가 명시적으로 3종 모두 지원 표기)
Python3.8 이상 (pyproject.toml classifiers는 3.8~3.12 명시)
하드웨어CPU만으로 동작 — GPU 불필요. onnxruntime의 기본 CPUExecutionProvider로 충분
디스크 용량모델별 25~80MB (nano-int8 25MB ~ mini 80MB) + 의존성 패키지 용량
네트워크최초 KittenTTS(...) 실행 시 HuggingFace Hub에서 모델 파일 다운로드 필요(이후 cache_dir에 캐시되어 오프라인 재사용 가능)
GPU(선택)requirements_gpu.txt 설치 + backend="cuda" — NVIDIA CUDA 12 계열 필요. AMD는 backend="amd_gpu"(ROCm) 코드 경로도 존재
가상환경README가 conda/venv 등 가상환경 사용을 권장(의존성 충돌 방지)
정리하면

"하드웨어 요구사항"이라는 항목 자체가 무색할 만큼 가볍다 — 일반 사무용 노트북, 심지어 라즈베리파이급 SBC(Single Board Computer)에서도 무리 없이 돌아가는 게 이 프로젝트의 존재 이유다. 진짜 신경 써야 할 부분은 "처음 한 번은 인터넷이 필요하다"는 것과, README가 명시한 대로 "developer preview"라 API가 버전 간 바뀔 수 있다는 안정성 측면이다.

8직접 해볼 수 있는 실습 과제

난이도별 5단계.

실습 1

설치하고 첫 wav 파일 만들어보기 난이도 ★☆☆ 입문

pip install https://github.com/KittenML/KittenTTS/releases/download/0.8.1/kittentts-0.8.1-py3-none-any.whl로 설치 후, README의 3줄 예시(KittenTTS("KittenML/kitten-tts-mini-0.8")model.generate(...)sf.write(...))를 그대로 실행해 output.wav를 만들어보자. 첫 실행 시 콘솔에 뜨는 HuggingFace 다운로드 로그를 눈여겨보고, 두 번째 실행부터는 다운로드 없이 바로 로드되는지 확인. 목표: 전체 파이프라인이 한 번 끝까지 도는 걸 눈과 귀로 확인하기.

실습 2

8개 보이스 · speed 파라미터 비교 청음 난이도 ★☆☆ 입문

같은 문장을 available_voices 8개(Bella, Jasper, Luna, Bruno, Rosie, Hugo, Kiki, Leo) 전부로 생성해 각각 voice_이름.wav로 저장하고 들어보자. 그다음 같은 보이스로 speed=0.7, 1.0, 1.5를 비교 생성해본다. 목표: "같은 신경망 + 다른 스타일 벡터 = 다른 목소리"라는 보이스 임베딩 개념을 귀로 체감.

실습 3

normalize_text로 정규화 과정 직접 관찰 난이도 ★★☆ 중급

from kittentts import normalize_text로 "Meeting on Jan. 5, 2026 at 10:30 AM costs $99.99 (v2.1.0)"처럼 숫자·날짜·시간·통화·버전이 뒤섞인 문장을 넣어 결과를 확인. return_spans=TrueNormalizedTextResult.spans를 출력해, 어떤 원문 구간이 어떤 reason(date/time/currency/number 등)으로 바뀌었는지 표로 정리해보자. 목표: 규칙 기반 텍스트 정규화가 실전 텍스트에서 어떤 순서·범위로 적용되는지 이해.

실습 4

스트리밍 생성으로 첫 청크까지 걸리는 시간 측정 난이도 ★★☆ 중급

example_streaming.py를 참고해 긴 문단(3~5문장)을 generate_stream()으로 생성하면서, time.perf_counter()로 "호출 시작~첫 청크 도착" 시간과 "전체 완료" 시간을 각각 재보자. 같은 텍스트를 generate()(전체 한 번에)로 생성했을 때와 비교. 목표: "청크 파이프라이닝"이 체감 지연을 실제로 얼마나 줄이는지 숫자로 확인.

실습 5

모델 크기별(nano/micro/mini · int8 포함) 품질·속도 벤치마크 난이도 ★★★ 고급

같은 문장을 kitten-tts-nano-0.8-int8, kitten-tts-nano-0.8(fp32), kitten-tts-micro-0.8, kitten-tts-mini-0.8 4개 모델 전부로 생성하며 ①모델 로드 시간 ②generate() 추론 시간 ③다운로드된 .onnx 파일 실제 크기 ④청음 품질(주관 평가)을 표로 정리해보자. int8 nano에서 README가 언급한 "일부 문제"가 실제로 재현되는지도 관찰. 목표: "작을수록 빠르지만 무엇을 희생하는가"라는 트레이드오프를 데이터로 확인.

9관련 기술 심화 학습 로드맵

이 저장소를 출발점으로, 4주 코스.

주차주제학습 자료
1주차디지털 오디오 기초샘플레이트(sample rate)·양자화 비트·파형 개념 · soundfile 문서 · 실습 1~2
2주차TTS 파이프라인 (음소화 → 신경망 → 파형)IPA 표 훑어보기 · phonemizer/espeak-NG 문서 · VITS/StyleTTS2 논문 개요 · onnx_model.py 정독 · 실습 3~4
3주차ONNX · 양자화ONNX 공식 문서(onnx.ai) · ONNX Runtime 실행 프로바이더 문서 · onnxruntime.quantization 튜토리얼 · 실습 5
4주차엣지 배포 · 온디바이스 AI모바일/브라우저 추론(ONNX Runtime Web, WebGPU) 자료 · 라즈베리파이 등 SBC에서 실제 배포 실습 · KittenTTS 로드맵(모바일 SDK·최적화 엔진) 문서 추적

10핵심 키워드 사전

본문에 나온 용어 빠른 참조.

용어의미
TTS (Text-to-Speech)텍스트를 사람 목소리처럼 들리는 음성 파형으로 변환하는 기술.
ONNXOpen Neural Network Exchange. 학습 프레임워크에 무관하게 신경망을 저장·교환하는 공통 포맷.
ONNX RuntimeONNX 모델을 CPU/GPU 등 다양한 환경에서 빠르게 추론(실행)하도록 최적화된 실행 엔진.
phonemizer / G2P글자(grapheme)를 발음 기호(phoneme)로 변환하는 라이브러리·과정. Grapheme-to-Phoneme의 줄임말.
음소 (Phoneme)말소리를 구별하는 최소 단위. IPA(국제음성기호)로 표기하며, 철자와 달리 실제 발음을 직접 나타낸다.
espeak-NG오랜 역사를 가진 오픈소스 음성 합성/음소화 엔진. KittenTTS는 이를 phonemizer의 백엔드로 사용.
보이스 임베딩 (Voice Embedding)화자의 음색·억양을 신경망이 이해하는 고정 길이 숫자 벡터로 압축한 것. "style" 입력에 해당.
텍스트 정규화 (Text Normalization)숫자·통화·날짜·약어 등을 신경망이 읽기 쉬운 "풀어 쓴 말"로 확장하는 전처리 과정.
int8 양자화 (Quantization)32비트 부동소수점 가중치를 8비트 정수로 다시 표현해 모델 용량을 줄이는 사후 압축 기법.
24kHz 샘플레이트1초를 24,000번 측정해 디지털 오디오로 저장하는 표준 해상도 중 하나. 음성 합성에서 흔히 쓰임.
스트리밍 추론 (Streaming Inference)전체 결과가 끝나길 기다리지 않고, 완성된 부분(청크)부터 순차적으로 반환하는 실행 방식.
VITS / StyleTTS2 계열음소 시퀀스 + 화자(스타일) 임베딩을 입력받아 파형을 직접 생성하는 신경망 TTS 아키텍처 계열. KittenTTS의 입출력 구조(음소 토큰 + style 벡터 → 파형)가 이 계열과 유사하다.
엣지 배포 (Edge Deployment)클라우드 서버가 아니라 사용자 기기(노트북·모바일·IoT) 자체에서 AI 모델을 실행하는 배포 방식.

11참고 링크