Skip to content
psymon-ai
Go back

Tiny-Ko-Stories - 한국어다운 데이터가 작은 모델을 바꿀 수 있을까

약 21분 읽기

Table of contents

Open Table of contents

들어가는 글

안녕하세요. 이번 글은 Tiny-Ko-Stories 프로젝트를 정리한 글입니다.

TinyStories를 처음 알게 된 건 2023년 5월 무렵입니다. 마침 LLaMA 유출 이후 로컬 AI에 관심을 갖고 이것저것 만져보던 시기였습니다. 아마 다들 비슷했을 겁니다. 갑자기 손에 잡히는 언어 모델이 생겼고, 누구나 자기 컴퓨터에서 모델을 돌려볼 수 있게 됐으니 말입니다.

그때 눈에 들어온 논문 중 하나가 TinyStories1였습니다. 아이디어는 단순하지만 꽤 강했습니다. GPT-3.5와 GPT-4로 3~4세 아이가 이해할 수 있을 만한 짧은 영어 이야기를 잔뜩 만들고, 그 데이터만으로 아주 작은 언어 모델을 학습했습니다.

결과는 흥미로웠습니다. 10M 파라미터도 안 되는 모델, 심지어 transformer block 하나짜리 모델도 문법이 거의 맞는 짧은 이야기를 이어 썼습니다. 물론 ChatGPT 같은 모델과 비교할 수는 없습니다. 그래도 “작은 모델이 과연 뭘 배울 수 있나?”라는 질문에 꽤 인상적인 답을 내놓은 셈입니다.

작은 모델을 왜 만들까

요즘은 작은 모델을 만드는 방식도 많이 달라졌습니다. 큰 모델을 먼저 만든 뒤 작은 모델이 그 출력을 따라 하게 만드는 distillation을 쓰기도 하고, 큰 모델에서 덜 중요한 부분을 덜어내는 pruning을 쓰기도 합니다. MoE처럼 전체 파라미터는 크지만 실제 계산 때는 일부 expert만 쓰는 방식도 흔해졌습니다. 전부 중요한 방법입니다.

그럼에도 저는 여전히 작은 모델을 처음부터 키워가는 방식에 관심이 있습니다. 작은 모델을 큰 모델로 자연스럽게 확장하려면 결국 지속학습(Continual Learning) 문제를 풀어야 하니, 쉽지 않음은 분명합니다. 그래도 작은 모델을 직접 만들고, 실패를 쌓고, 그다음 크기로 넘어가는 방식에는 분명한 매력이 있습니다.

그런 의미에서 TinyStories는 좋은 출발점처럼 보였습니다. 거대한 웹 코퍼스를 무작정 먹이는 대신, 아주 작고 질 좋은 데이터만으로 작은 모델이 어디까지 배울 수 있는지 확인할 수 있기 때문입니다.

왜 번역만으로는 부족했나

TinyStories가 발표된 뒤 같은 프로젝트를 한국어로 재현하려는 시도가 여럿 있었고, 저도 그중 한 명이었습니다. 그런데 막상 해보니 가장 큰 문제는 모델이 아니라 데이터였습니다.

영어 TinyStories처럼 짧고 쉬우면서 일관된 한국어 이야기 수백만 편을 어떻게 만들 것인가.

처음 떠올릴 방법은 번역입니다. 실제로 2024년 5월 중순에는 Hugging Face에 영어 TinyStories를 한국어로 번역한 데이터셋도 올라왔습니다. 이런 데이터셋이 있다는 건 분명 반가운 일입니다.

하지만 한국어로 번역한 글과 한국어다운 글은 전혀 다릅니다. TinyStories를 기계번역한 데이터는 품질 열화가 눈에 보였습니다. 어쩌면 당연한 일입니다. 실력 있는 번역가가 옮긴 글도 번역체 특유의 질감이 섞이기 마련입니다.

물론 번역 데이터가 쓸모없다는 뜻은 아닙니다. 문제는 TinyStories 프로젝트의 핵심이 데이터 품질에 있다는 점입니다. TinyStories의 메시지는 분명했습니다. 작은 모델도 좋은 데이터만 주면 생각보다 많은 것을 배울 수 있다. 그렇다면 한국어판 TinyStories도 영어 이야기를 번역한 데이터가 아니라, 처음부터 한국어로 쓴 이야기여야 하지 않을까 싶었습니다.

그때는 왜 어려웠나

문제는 두 가지였습니다.

먼저 비용이 컸습니다. TinyStories 논문은 생성 API 비용을 밝히지 않습니다. 그래도 유추하자면, GPT-4와 GPT-3.5로 200만 편이 넘는 데이터를 만들려면 예산을 $15,000 이상은 잡아야 합니다. 개인 프로젝트가 감당하기에는 너무 큰 부담이었습니다.

언어 모델의 한국어 품질도 지금과 달랐습니다. 당시만 해도 SOTA급 모델조차 종종 어색한 한국어를 출력했습니다. 문법은 맞는데 번역투가 남고, 이름이 이상하고, 이야기 호흡이 잘 살아나지 않았습니다. 짧은 동화는 쉬워 보이지만 막상 만들어보면 생각보다 어렵습니다.

그렇게 한동안 묵혀 둔 생각이 3년 뒤 다시 돌아왔습니다. 다시 언어 모델 개발에 뛰어들자, 출발점으로 TinyStories만큼 좋은 프로젝트가 없어 보였습니다. 작지만 만만하지 않고, 데이터 품질, 토크나이저, 모델 크기, 평가 방법을 한 번에 건드릴 수 있기 때문입니다.

처음부터 한국어로 만들기

Tiny-Ko-Stories는 TinyStories에서 아이디어를 얻었지만, 영어 TinyStories를 번역한 데이터셋은 아닙니다. 목표는 한국어다운 짧은 이야기 코퍼스를 처음부터 새로 만드는 일이었습니다.

처음에는 “짧은 동화를 많이 만들면 되겠지” 정도로 생각했습니다. 그런데 실제로 해보면 그렇게 간단하지 않습니다.

예를 들어 이런 문제가 계속 나왔습니다.

  • 등장인물 이름이 외국어로 나온다.
  • 동물이나 사물이 하기 어려운 행동을 한다.
  • 아주 친하 상태, 대단하 마음처럼 어미가 깨진다.
  • 교훈은 있지만 사건이 없다.
  • 문장은 맞지만 왜 그런 일이 일어났는지 알기 어렵다.

이 문제를 줄이려면 생성 규칙을 꽤 촘촘하게 잡아야 했습니다.

제가 중요하게 본 요소는 다음과 같습니다.

요소이유
한국어 이름과 호칭번역투 이름이나 무작위 낱말 이름이 나오면 품질이 확 떨어집니다.
작은 문제와 해결사건 구조가 있어야 분량이 짧아도 이야기라고 볼 수 있습니다.
해요체어린이 이야기에 맞는 문체입니다.
색채어한국어는 발그스름하다, 새파랗다 같은 색채어가 풍부합니다.
의성어와 의태어살랑살랑, 보들보들, 아장아장 같은 단어는 글의 리듬감을 살려줍니다.
감정 변화걱정, 놀람, 사과, 안도처럼 감정 변화가 있어야 이야기가 평면적이지 않습니다.

자연스러운 한국어 호칭을 위해 한국인 이름 데이터를 따로 모았습니다. 생성 프롬프트는 출생 신고 기준 이름 순위 1~500위만 쓰도록 했습니다. 외국어 이름이 나오면 남녀 상위 60개 이름으로 바꾸고, 같은 이야기 안에서 이름이 유지되도록 처리했습니다.

색채어, 의성어와 의태어는 한국어다운 글을 위해 꼭 필요한 요소였습니다. 다만 과하면 금방 어색해집니다. 한 이야기 안에 자연스럽게 한두 개만 들어가야 했습니다. 쉬운 말로 쓰되 너무 밋밋하면 안 되고, 교훈은 있되 설교처럼 들리면 안 됐습니다.

그래서 생성 규칙을 다듬는 데 생각보다 오랜 시간이 걸렸습니다.

어떤 모델로 이야기를 만들 것인가

언어 모델로 대량 데이터셋을 생성할 땐 품질만 보면 안 됩니다. 비용, 속도, 사용량 제한, 실패 복구까지 같이 봐야 합니다. 품질이 좋아도 한도가 너무 빨리 닳으면 주력으로 쓰기 어렵습니다. 반대로 빠르고 싸도 어미가 깨지거나 이야기 논리가 흔들리면 검수 비용이 더 커집니다.

처음에는 품질이 좋은 프런티어 모델을 기준으로 삼았습니다. 다만 API 호출이 아닌 구독제 환경에서 200만 편 규모를 맡기기에는 사용량 한도가 문제였습니다.

GPT-5.5는 짧은 한국어 이야기에서 꽤 안정적 결과를 냈습니다. 문장도 자연스럽고 이야기 구조도 빨리 잡혔습니다.

Claude Opus-4.7도 품질은 좋았습니다. 감정 흐름과 문장 연결이 안정적이었습니다. 대신 GPT보다 토큰을 훨씬 많이 썼고, 대량 호출 때는 연결이 자주 끊겨 오래 돌리기 어려웠습니다.

로컬 모델도 여러 번 시도했습니다. 가능하면 로컬에서 대량 생성까지 해결하고 싶었기 때문입니다. 기대는 컸지만 결과는 아쉬웠습니다. 로컬 환경(RTX 5080 16GB)에서 시도할 수 있는 모델은 품질 기준에 못 미쳤습니다.

EXAONE 3.5 7.8B는 실행 자체는 가능했지만, 이야기 자연스러움과 의미 연결이 자주 흔들렸습니다.

민준이는 아빠 민준이아와 함께 어느새 깊은 숲 속으로 여행을 떠났어요.
숲 속은 평온하게 보였지만, 민준이는 아버지가 짜다고 느껴진다는 이상한 느낌을 받았어요.

Gemma 4 26B A4B는 더 큰 로컬 모델이라는 점에서 기대했지만, 실제 출력은 조사가 빠지거나 활용이 깨지는 문제가 보였습니다.

아주 친하 상태로, 정말 크 보물, 대단하 마음을, 기분이 더하 좋아질

HyperCLOVAX-SEED-Think-14B도 시도했습니다. 한국어 모델이라는 점에서 흥미로웠지만, 당시 로컬 실행과 생성 파이프라인 안정성 문제 때문에 대량 생성에 넣기 어려웠습니다.

출력 품질 문제는 처음부터 신경을 써야 합니다. 코퍼스는 한두 편의 샘플이 아니라 수백만 편의 누적입니다. 작은 오류도 규모가 커지면 꽤 큰 오염이 됩니다.

결국 대량 생성은 여러 모델을 나누어 쓰는 방향으로 갔습니다. 프런티어 모델은 품질 기준을 잡는 데 도움을 주었고, 로컬에서 실행하기 어려운 더 큰 모델은 OpenRouter에서 돌렸습니다. 여러 모델을 테스트한 결과 Gemma 4 31B가 품질, 비용, 처리량의 균형이 괜찮았습니다. 최종 200만 편 공개본도 하나의 모델이 한 번에 만든 결과가 아닙니다. 여러 모델, 여러 번의 프롬프트 조정, 여러 단계의 검수 끝에 나온 결과입니다.

비용도 기록해 둘 만합니다. 이번 데이터셋 생성과 검수 과정에서 OpenRouter는 약 $375, OpenAI Pro 플랜은 $100, Claude Max 플랜은 $100을 썼습니다. 합치면 $575입니다. 지금 환율로 보면 결코 가벼운 돈은 아닙니다. 그래도 수백만 편 규모의 한국어 합성 코퍼스를 개인 프로젝트에서 시도해 볼 수 있게 됐습니다. 몇 년 전만 해도 상상하기 어려웠던 일입니다.

최종 공개본 구성은 다음과 같습니다.

생성 모델기록 수
gemma-4-31b1,710,866
gpt-5.5283,582
opus-4-89,003
opus-4-791
합계2,003,542

생성보다 어려웠던 품질 관리

데이터셋을 만들며 가장 많이 배운 점은 이겁니다.

생성보다 검수가 더 어렵다.

생성은 어떻게든 됩니다. 어려운 건 무엇을 남기고 무엇을 버릴지 정하는 일입니다. 문법이 맞는 이야기와 학습에 좋은 이야기는 다릅니다.

Tiny-Ko-Stories는 생성 직후부터 여러 번 걸러냈습니다. 먼저 어떤 이야기를 통과시킬지 기준을 세웠습니다. 길이, 문체, 사건 구조, 한국어 표현, 이름, 안전성 같은 항목을 나누고, 규칙으로 잡을 수 있는 문제와 모델 판단이 필요한 문제를 분리했습니다.

방식은 크게 다음과 같습니다.

  • 길이와 형식을 봤습니다. 너무 짧거나 긴 이야기는 제외하고, 문장과 문단이 지나치게 늘어지는 경우도 걸렀습니다.
  • 문체를 확인했습니다. 어린이 이야기로 쓰기 위해 기본 문체는 해요체로 맞추고, 문장 끝이 자주 흔들리는 샘플은 다시 생성하거나 제외했습니다.
  • 한국어 표현을 점검했습니다. 아주 친하 상태, 대단하 마음, 둥글 둥글한처럼 활용이나 띄어쓰기가 깨진 패턴을 찾아 고쳤습니다.
  • 이름과 고유명사를 정리했습니다. 외국어식 이름, 이야기 분위기와 맞지 않는 고유명사는 제거하거나 교체했습니다.
  • 내용 안전성을 봤습니다. 어린이 이야기 코퍼스에 맞지 않는 폭력, 성적 표현, 약물 관련 표현은 금지어 목록으로 걸렀습니다.
  • 같은 문장이 반복되거나, 사실상 하나의 문장을 여러 변주로 넣은 이야기는 제외했습니다.
  • 규칙으로 잡기 어려운 품질도 확인했습니다. 사건이 자연스럽게 이어지는지, 교훈만 있고 실제 이야기는 없는지, 문장은 맞지만 한국어 이야기로 어색하지 않은지 GPT-5.5를 이용해 한 번 더 검사했습니다.

여기서 중요한 건 완벽한 필터 하나를 찾는 일이 아니었습니다. 여러 기준을 겹쳐 써야 했습니다. 구조 검증은 형식 오류를 잡고, 길이 검사는 이상한 생성물을 줄이고, 표현 검사는 한국어다운 질감을 지켜줍니다. 그렇게 걸러내고 다듬은 뒤, 최종 공개본은 2,003,542편으로 정리했습니다. 한 줄에 text 하나만 들어가는 JSONL 형식이고, 평균 길이는 공백과 줄 바꿈을 포함해 약 346.41자입니다.

토크나이저는 작지만 중요했다

데이터셋을 만든 뒤 토크나이저를 골라야 했습니다.

처음에는 SentencePiece도 생각했습니다. LLaMA 계열 모델에서 많이 쓰이고, 공백을 문자로 다루기 때문에 한국어 조각도 꽤 깔끔합니다. 다만 이번 프로젝트는 공개와 재현 편의성도 중요했습니다. 모델을 받은 사람이 Hugging Face의 tokenizer.json 하나로 바로 로드하고, 예제 코드에서도 같은 방식으로 실행할 수 있어야 했습니다.

토크나이저 구현은 Hugging Face tokenizersRust BPE로 통일했습니다. vocab size는 초기 124M 실험과 최종 35M 모델 모두 32K로 맞췄습니다. 작은 모델일수록 embedding이 차지하는 비율이 높아지기 때문에, vocab을 무작정 크게 잡고 싶지는 않았습니다.

토크나이저 후보는 크게 세 가지였습니다.

방식평균 tokens/storyTPWCFert
Metaspace BPE87.241.05250.3343
cl100k-style BPE99.681.19850.3809
ByteLevel BPE105.671.27470.4048

수치만 보면 Metaspace BPE가 가장 좋았습니다. 평균 tokens/story도 낮고, TPW와 CFert도 가장 낮았습니다. 한국어 조각도 사람이 보기 편했습니다. 예를 들어 ByteLevel 계열은 vocab 안에 보리처럼 byte escape된 조각이 많지만, Metaspace는 ▁보리는처럼 훨씬 읽기 쉽습니다.

그렇다고 TPW만 보고 Metaspace를 고르기는 어려웠습니다. smoke test에서 낯선 기호가 <unk>로 떨어져 원문 복원이 깨지는 경우가 있었습니다. 평가 코퍼스 기준 <unk> 수는 0이었지만, 공개 모델의 토크나이저라면 학습 데이터 밖 문자도 안정적으로 처리해야 합니다.

또 이전 토크나이저 실험에서 확인한 사실도 마음에 걸렸습니다. TPW가 낮다고 항상 LM의 BPB가 좋아지지는 않았습니다. 특히 소형 모델은 토큰을 적게 쓰는 일보다, 모델이 배우기 쉬운 토큰 단위를 남기는 일이 더 중요할 때가 많았습니다. 때문에 최종 기준은 단순 압축률이 아니라 가역성, 배포 안정성, LM 학습 가능성까지 함께 보는 쪽으로 잡았습니다.

이 기준으로 보면 ByteLevel BPE가 더 안정적이었습니다. 사람이 vocab을 읽기는 불편하지만, 모든 UTF-8 byte를 다룰 수 있고 낯선 문자도 원문으로 되돌릴 수 있습니다. 대신 기본 ByteLevel BPE는 GPT-2 계열 regex를 사용합니다. 오래 쓰인 기준이지만, 지금 기준으로는 다소 낡았습니다. 숫자, 기호, 영문, 한글이 섞이는 표현을 다루는 방식도 아쉬웠습니다.

직접 커스텀한 regex를 쓸 수도 있었습니다. 실제로 이전 토크나이저 실험에서 여러 regex를 비교했고, 한국어 TPW 기준으로 더 좋은 조합도 있었습니다. 다만 이번 프로젝트 목적은 regex 비교가 아니므로 새로운 토크나이저를 도입하기보단 보편적 기준을 사용하는 게 맞다고 판단했습니다.

최종적으로는 Hugging Face BPE 32K + cl100k-style pre-tokenization을 썼습니다. ByteLevel 기반의 가역성을 가져가면서도, GPT-2 기본 regex보다 요즘 더 널리 쓰이는 cl100k 계열 기준을 쓰는 절충안이었습니다.

언어 모델 학습

확인하고 싶었던 질문은 단순합니다. 같은 크기 모델을 학습했을 때, 처음부터 한국어로 만든 코퍼스와 영어 TinyStories를 번역한 코퍼스로 학습한 모델은 차이가 날까. loss뿐 아니라 실제 생성문에서도 차이가 날까. 이 질문이 가장 중요했습니다.

124M 모델

첫 실험은 124M 파라미터 모델이었습니다. 구조는 단순하게 잡았습니다.

항목
파라미터 수123,685,632
구조Decoder-only Transformer
Context length512
Layer12
Hidden size768
Heads / KV heads12 / 4
FFN size2,880
TokenizerHF BPE 32K

먼저 1B token까지 학습했습니다. 이 지점은 번역 TinyStories 모델과 비교하기 위한 기준점이었습니다.

항목1B 기준 결과
학습 토큰1,000,079,360
Effective epochs5.022
Train loss / PPL1.7125 / 5.54
Validation loss / PPL2.0082 / 7.45
학습 환경RTX 5080
학습 시간약 21시간 46분

비교를 위해 g0ster/TinyStories-Korean 번역 코퍼스도 같은 124M 구조로 학습했습니다. 다만 토크나이저는 각 코퍼스에서 따로 학습했습니다. 실제 모델을 배포할 때 토크나이저와 코퍼스는 함께 가기 때문입니다.

모델학습 토큰final val lossval PPL
Tiny-Ko 124M1,000,079,3602.00827.45
g0ster 번역 TinyStories 124M1,108,344,8322.03847.68

정량 지표만 보면 차이가 크지 않지만, 모델이 생성한 문장은 차이를 보였습니다. Tiny-Ko 모델은 한국어 이름, 조사, 짧은 문장 호흡, 작은 문제와 해결 구조를 더 안정적으로 유지했습니다. 반대로 번역 모델은 이름이 흔들리거나, 그녀, 그것, 말했습니다 같은 번역투가 더 자주 남았습니다.

예를 들어 같은 시작 문장을 넣었을 때 Tiny-Ko 모델은 작은 일상 문제와 해결로 이어졌습니다.

민지는 아침에 작은 노란 우산을 들고 마당으로 나갔어요. 비가 톡톡 내려 물웅덩이가 생겼어요.
친구 지우가 우산 없이 서 있었어요. 민지는 우산을 혼자 쓰고 싶었지만, 지우의 어깨가 젖는 것을 보았어요.
민지는 우산을 반쯤 내밀었어요. "같이 쓰자. 우리 둘 다 젖지 않게 천천히 가자." 지우는 "고마워." 하고 웃었어요.

번역 데이터 모델은 이름이나 지시 대상이 흔들리는 경우가 있었습니다.

"자, 여기 있어, 민트,"라고 엄마가 말했어요.
...
민트는 젖지 않고 젖지 않았죠.

여기까지만 보면 Tiny-Ko 코퍼스는 꽤 잘 작동했습니다. 그런데 124M 모델을 계속 살펴보면서 눈에 걸리는 문제가 생겼습니다. 특정 이야기 구조와 문장이 너무 자주 나왔습니다. 시작 문장을 바꿔도 바닥에 떨어짐, 속상함, 누군가 도와줌, 문제가 해결됨 같은 흐름으로 흘러갔습니다. 어떤 샘플은 -라고 적힌 작은 표지 같은 문장을 반복했습니다.

원인을 분석해 본 결과. 여러 epoch를 학습한 게 문제였습니다. 실제 출력 문장을 놓고 보면 가장 자연스러웠던 지점은 1.1B보다 훨씬 이른 0.65B tokens, 약 3.29epoch 체크포인트였습니다.

이 판단은 Muennighoff et al.의 data-constrained scaling 연구와도 맞닿아 있습니다.2 이 연구는 제한된 데이터에서 반복 학습할 때, 약 4epoch까지는 새 데이터를 쓴 경우와 비교해 loss 차이가 거의 없고 downstream 성능 차이도 대체로 크지 않다고 봅니다. 그 이상으로 반복하면 반복 토큰의 가치는 점점 줄어듭니다. 흥미로운 점은 평가 기준에 따라 답이 갈린다는 사실입니다. held-out loss만 보면 16epoch 근처까지도 이득이 남을 수 있지만, downstream 성능은 4epoch 이후부터 떨어지기 시작합니다. 우리 실험도 비슷했습니다. validation loss만 보면 더 갈 이유가 있었지만, 실제 생성문 품질을 보면 더 이른 체크포인트가 나았습니다. 124M을 계속 밀어붙이기보다, 4epoch 전후에서 충분히 학습되는 더 작은 모델을 찾는 편이 맞다고 봤습니다.

35M 모델

최종 공개 모델은 35M급으로 다시 잡았습니다. 124M은 내부 실험으로 남기고, 공개 모델은 Tiny-Ko-Stories-35M으로 정했습니다.

구조는 다음과 같습니다.

항목
파라미터 수34,217,856
구조Decoder-only Transformer
Context length512
Layer10
Hidden size384
Heads / KV heads6 / 2
FFN size1,536
TokenizerHF BPE 32K, cl100k-style pre-tokenization
Embeddingtied input/output embeddings

학습은 약 4epoch에 맞췄습니다.

항목
이야기 수2,003,542
학습 토큰794,820,608
Effective epochs4.000
Train loss / PPL2.0052 / 7.43
Validation loss / PPL2.1168 / 8.30
학습 환경RTX 5080
학습 시간약 1시간 23분

35M은 124M보다 loss가 높습니다. 대신 124M처럼 몇몇 구조로 강하게 빨려 들어가는 현상이 줄었고, 작은 continuation LM으로 공개하기에는 이쪽이 더 균형이 좋았습니다.

예시입니다.

작은 마을에 조용한 아침이 찾아왔어요. 서준이는 오늘 친구들과 함께 놀기로 했어요. 그런데 아침에 신은 양말 색깔이 서로 달라서 조금 부끄러웠어요.
서준이는 양말을 숨기려고 발을 높이 들어 올렸어요. 하지만 친구들이 다가오자 가슴이 콩닥콩닥 뛰었어요. 서준이는 용기를 내어 양말이 다르다고 솔직하게 말했어요.
친구들은 오히려 알록달록한 양말이 멋지다며 칭찬해 주었어요. 서준이는 기분이 좋아져서 친구들에게 맛있는 간식을 나누어 주었어요. 모두 함께 웃으며 즐거운 아침을 보냈어요.

다른 시작 문장에서도 짧은 문제와 해결 구조는 유지됐습니다.

민지는 작은 노란 우산을 들고 마당으로 나갔어요. 비가 톡톡 내려 길이 젖었어요.
친구 지우는 우산이 없어 발을 동동 굴렀어요. 민지는 우산을 혼자 쓰고 싶었지만 지우의 어깨가 젖는 걸 보았어요.
둘은 우산 아래 어깨를 꼭 붙였어요. 빗방울이 우산 위에서 톡톡 뛰는 것 같았어요.
민지는 우산을 낮게 기울여 지우가 젖지 않게 했어요. 지우도 웃으며 고맙다고 했고, 둘은 빗길을 천천히 걸었어요.

물론 35M도 완벽하지 않습니다. 사건이 단순해질 때가 있고, 인물이나 사물에 맞지 않는 수식어가 붙는 경우도 있습니다. 그래도 모델 크기를 생각할 때 한국어 문장 리듬과 아동 이야기 형식을 안정적으로 유지한다는 점은 고무적입니다.

35M에서 다시 번역 코퍼스와 비교

35M에서도 번역 코퍼스 모델을 다시 학습했습니다. 이번에도 모델 구조는 같게 두고, 토크나이저는 각 코퍼스에서 따로 학습했습니다.

비교 기준은 둘로 나눴습니다. 먼저 동일 학습 토큰량입니다. Tiny-Ko 35M이 4epoch를 마친 794,820,608 tokens에 맞춰, 번역 모델도 ckpt_tokens_794820608.pt를 사용했습니다. 번역 코퍼스 기준으로는 약 2.70epoch입니다. 생성 예시는 이 기준으로 뽑았습니다.

다음은 동일 epoch입니다. 각 코퍼스를 4epoch씩 본 지점도 따로 확인했습니다. 번역 코퍼스는 평균 길이가 더 길기 때문에, 4epoch가 1,179,779,072 tokens까지 갑니다.

비교축모델사용 지점학습 토큰해당 코퍼스 epochval lossval PPL
동일 학습 토큰량Tiny-Ko 35M4epoch / final794,820,6084.002.11688.30
동일 학습 토큰량g0ster 번역 TinyStories 35M생성: ckpt_tokens_794820608.pt / 평가는 가장 가까운 정기 eval794,820,608 / 786,432,0002.70 / 2.672.27539.73
동일 epochTiny-Ko 35M4epoch / final794,820,6084.002.11688.30
동일 epochg0ster 번역 TinyStories 35M4epoch / final1,179,779,0724.002.21589.17

두 기준 모두에서 val loss와 PPL은 Tiny-Ko 35M이 낮았습니다. 문장도 Tiny-Ko 쪽이 더 한국어답습니다. 이름과 조사, 말투, 짧은 문장의 호흡이 더 안정적입니다.

생성문에서도 차이가 분명했습니다. 아래는 동일 학습 토큰량 비교를 위해 번역 모델의 ckpt_tokens_794820608.pt에서 뽑은 예시입니다.

민지는 작은 노란 우산을 들고 마당으로 나갔어요. 해가 쨍쨍 비추고 새들이 지저귀고 있었죠. 그녀는 매우 행복했어요. 갑자기, 그녀는 나무 위에 앉아 있는 작은 새를 봤어요. 그 새는 슬프고 무서워 보였죠. "안녕, 새야,"라고 대시가 말했어요.

프롬프트의 주인공은 민지인데, 중간에 그녀가 나오고 곧바로 대시가 말합니다. 우산을 들고 나간 장면인데 날씨도 금방 해가 쨍쨍한 장면으로 바뀝니다. 영어 이야기 구조에 한국어 문장만 그대로 얹은 느낌입니다.

더 심한 경우도 있었습니다.

서준이는 울긋불긋한 잎사귀 하나를 찾았어요. 그는 그것을 집어 들고 주변을 둘러보았어요. 갑자기, 그는 목소리를 들었어요. "야, 그게 내 잎사귀란다!"라고 목소리가 말했어요. 그건 그의 아빠였어요. 아빠는 말했죠, "그건 네 잎사귀가 아니야, 내 잎사귀야." 쯅바아는 기분이 상했어요.

서준이로 바뀌고, 갑자기 쯅바아 같은 깨진 이름이 나옵니다. 이런 출력은 번역 코퍼스의 약점을 잘 보여줍니다. 사건은 많지만, 한국어 이름과 지시 대상이 안정적으로 유지되지 않습니다.

동일 epoch 기준으로도 확인했습니다. 번역 모델을 4epoch까지 학습한 ckpt_epoch_4.pt로 같은 프롬프트를 넣으면, loss는 조금 좋아졌지만 문체의 성격은 크게 달라지지 않았습니다.

민지는 작은 노란 우산을 들고 마당으로 나갔어요. 그녀는 하늘을 올려다보며 한숨을 쉬었어요. "오늘 비가 올 것 같아요,"라고 그녀는 말했죠. 그때, 그녀의 엄마가 집 밖으로 나와 말했어요. "자, 내 사랑, 비가 올 거야." 하지만 비는 오지 않았어요. 다음 날, 해가 떠서 하늘은 파랗게 변했어요. 작은 노란 우산은 여전히 거기 있었어요. "엄마, 봐요!"라고 그녀는 소리쳤어요. "비가 왔어요!" 엄마는 웃으며 말했어요. "그래, 비가 오고 있어. 이제 안으로 들어가서 젖은 몸을 말리자." 그래서 그들은 안으로 들어가 비를 맞으며 즐거운 시간을 보냈어요.
서준이는 울긋불긋한 잎사귀 하나를 찾았어요. 그는 그것을 엄마에게 보여주고 싶어서 집으로 뛰어갔어요. 가는 길에 그는 한 번도 보지 못한 무언가를 봤어요. 바로 크고 빨간 공이었죠! 그는 그 공을 정말 가지고 싶어 했어요. 그래서 그는 계속 달렸어요, 점점 더 빠르게요. 집에 도착해서 그는 엄마에게 물었어요, "엄마, 이 공을 가질 수 있을까요?" 엄마는 웃으며 말했어요, "그래, 자기야. 네가 가질 수 있어." 그래서 그 작은 소년은 정말 행복했어요. 그는 종일 공으로 놀았죠. 그런데 어느 날, 공이 너무 멀리 갔어요. 그는 그것을 쫓아 달려갔지만, 공은 돌아오지 않았어요. 그는 여기저기 찾아봤지만 찾을 수가 없었죠. 결국, 그는 매우 슬퍼졌어요. 그는 다시는 그 공을 보지 못했답니다.

그녀, 그는, ~했죠, 내 사랑 같은 번역투가 그대로 남았습니다. ‘하늘은 파랗게 변했’는데 비가 온다고 하거나. 엄마가 아들을 부르는 호칭이 잘못된 경우도 보입니다. 세 번째 예시는 어린이 이야기라기엔 너무 어둡게 마무리됩니다.

그럼에도 번역 코퍼스에서 배울 점은 있었습니다. 전개가 더 길고, 대화가 많고, 장소 이동이나 시간 전환이 더 자주 나왔습니다. Tiny-Ko는 한국어다운 문장이 더 좋았지만, 이야기 구조는 조금 더 단순했습니다.

다음 버전의 방향도 꽤 분명해졌습니다. 한국어다운 문장은 유지하되, 이야기 길이, 대화, 장소 이동, 시간 전환, 작은 오해와 해결처럼 이야기 구조의 다양성을 더 넣어야 합니다.

아직 완벽하지 않다

Tiny-Ko-Stories-35M은 완성품이라기보다 기준선입니다.

좋은 샘플도 있지만, 아직 약점은 많습니다. 가끔 어색한 이름이 나오고, 논리가 약해질 때가 있습니다. 짧은 문체를 유지하다 보니 반복이 생기기도 합니다. 어떤 이야기는 사건이 너무 단순하고, 어떤 이야기는 교훈 문장이 조금 뻔합니다.

생성 예시를 보면 이런 한계도 남아 있습니다.

토리는 울고 있는 다람쥐를 발견했어요. 다람쥐는 토리의 도토리를 가지고 있었어요.
다람쥐는 토리에게 도토리를 돌려주었어요.
토리는 다람쥐에게 고맙다고 말했어요. 다람쥐는 토리에게 도토리를 나누어 주었어요.

구조는 맞지만 반복이 많습니다. 등장인물 구분도 조금 흐릿합니다. 35M이라는 작은 모델과 단순한 continuation 학습만으로는 당연한 한계입니다.

그래도 이 정도면 첫 기준선으로는 의미가 있다고 봅니다.

작은 모델이 한국어 이야기의 기본 형식을 배웠습니다. 번역 데이터셋과 비교했을 때 한국어다운 문장 리듬과 인물 유지가 더 안정적이었습니다. 동시에 이야기 전개와 다양성은 앞으로 더 보강해야 한다는 점도 분명히 보였습니다.

여기서부터 다시 시작

Tiny-Ko-Stories로 확인하고 싶었던 것은 단순했습니다.

작은 모델도 좋은 데이터를 만나면 생각보다 많은 것을 배웁니다. 모델 크기만 키우는 것과는 다른 길입니다. 특히 한국어처럼 공개 코퍼스 품질 편차가 큰 언어는 데이터의 성격이 더 크게 드러납니다.

예전부터 마음에 걸리던 문제도 있었습니다. 한국어 데이터셋 안에 번역문이 너무 많이 들어가는 게 아닌가. 한국어를 잘하는 모델을 만들려면, 한국어답게 쓴 글을 충분히 읽어야 하지 않을까.

말로는 쉽지만 실제로는 어려운 일입니다. 몇 년 전만 해도 개인이 수백만 건 단위의 합성 데이터를 만들고, 검수하고, 모델 학습까지 이어 가기는 쉽지 않았습니다. 비용도 컸고, 쓸 만한 한국어 생성 모델도 많지 않았습니다.

지금은 상황이 조금 달라졌습니다. 로컬 모델은 훨씬 좋아졌고, OpenRouter 같은 서비스를 쓰면 여러 모델을 비교해 가며 합리적인 비용으로 데이터를 만들 수 있습니다. 공짜는 아니고, 품질 관리도 여전히 손이 많이 갑니다. 그래도 개인이 꽤 큰 규모의 한국어 코퍼스를 직접 만들고, 그 코퍼스로 모델을 학습해 공개할 수 있는 지점까지 왔습니다.

Tiny-Ko-Stories는 그 첫 기준선입니다. 앞으로는 더 다양한 고품질 한국어 데이터셋을 만들고, 더 큰 모델에서도 같은 방향이 통하는지 확인해 보고 싶습니다. 짧은 동화뿐 아니라 교과서 지문, 대화, 추론, 영어 혼합 문서처럼 한국어 로컬 AI에 필요한 데이터는 아직 많습니다.

로컬 AI 분야에서 한국은 확실히 후발 주자입니다. 사업성이 크지 않은 분야로 보이기 때문에 몇몇 대기업을 빼면 적극 뛰어들기 어렵습니다. 바로 그래서 개인과 작은 팀이 해볼 수 있는 접근도 있습니다. 대기업이 만들기 어려운 데이터, 너무 작아서 우선순위에서 밀리는 실험, 특정 언어와 사용자 감각에 더 가까운 모델 같은 것들입니다.

아직 모델은 작고, 데이터도 완벽하지 않습니다. 그래도 이번에는 적어도 한 가지를 확인했습니다. 한국어다운 데이터를 직접 만들고, 그 데이터로 작은 모델을 학습하는 일은 이제 개인 프로젝트의 범위 안으로 들어왔습니다.

저는 이 가능성을 조금 더 밀어붙여 보려고 합니다.

Footnotes

  1. Ronen Eldan, Yuanzhi Li, TinyStories: How Small Can Language Models Be and Still Speak Coherent English?. arXiv 제출 이력 기준 v1은 2023년 5월 12일 공개됐습니다.

  2. Niklas Muennighoff et al., Scaling Data-Constrained Language Models, NeurIPS 2023 oral. arXiv HTML 버전은 arXiv:2305.16264v5에서 볼 수 있습니다.