서론
LLM을 공부할 때 가장 답답한 지점은 구조가 너무 많이 가려져 있다는 점이다. 데이터 전처리, 토크나이저, 모델, 학습 루프, 추론 배포가 각각 거대한 프레임워크 안으로 숨어 버리면서, 처음 접하는 사람들은 전체 흐름을 한번에 보기 어렵다. GuppyLM은 처음 접하는 사람들을 위해 만든 오픈소스 프로젝트이다.
현재 2026년 4월 7일 기준으로 작성했으며, 향후에는 얼마든지 프로젝트의 구성이나 형태가 바뀔수 있을 것이다. 하지만, 지금 작성하는 이유는 지금 시점에서 처음 LLM을 접하는 사람들에게 조금이나마 도움이 되었으면 하는 마음이 있기 때문이다.
우선 먼저 보려고 하는 부분은 README.md, docs/index.html, docs/download.sh, guppylm/model.py, prepare_data.py, train.py, inference.py, tools/export_onnx.py 으로 프로젝트 초기 생성자의 의도가 분명해진다. 목표는 성능 경쟁이 아니라, “내 손으로 작은 언어모델을 끝까지 만들어 보면 LLM이 더 이상 블랙박스처럼 보이지 않는다”는 점을 보여 주는 일이다.
우선은 README와 docs 폴더 내용을 먼저 번역해 핵심을 정리하고, 그다음에 LLM 이해를 위해 중요한 기술 포인트를 코드 기준으로 다시 설명하는 순서로 구성했다. 특히 토크나이저, 데이터 포맷, 디코더형 트랜스포머, 학습 손실, 양자화, 브라우저 추론 같은 기본기가 어떻게 연결되는지에 초점을 맞췄다.

1. README.md
- 프로젝트 목적: 언어모델 훈련을 블랙박스가 아니라 재현 가능한 절차로 보여 주는 실습용 설계
- 모델 정체: 작은 물고기 캐릭터를 흉내내는 8.7M 파라미터 LLM
- 데이터 규모: 60개 주제, 60K 합성 대화, 57K 학습과 3K 평가 분리
- 모델 구조: 6층, 은닉 차원 384, 헤드 6개, FFN 768, BPE 어휘 4,096, 최대 길이 128
- 실행 방식: Colab 학습, 로컬 채팅, 브라우저 ONNX 추론까지 모두 제공
README의 첫 문장은 이 저장소의 목적을 거의 다 설명한다. 이 프로젝트는 “자신의 언어모델을 훈련하는 일이 마술이 아니다”라는 사실을 보여 주기 위해 만들어졌다. 거대한 GPU 클러스터나 복잡한 서비스 스택 대신, 하나의 Colab 노트북과 작은 코드베이스만으로 데이터 생성부터 추론까지 전부 따라가게 하는 구조다.
GuppyLM은 “작은 물고기처럼 말하는 약 9M 파라미터 LLM”이다. 문장은 짧고, 대체로 소문자 스타일이며, 세계를 물, 온도, 빛, 진동, 먹이 중심으로 해석한다. 돈, 정치, 휴대폰 같은 인간 추상 개념은 이해하지 못한다. 이 설정은 농담처럼 보이지만, LLM을 이해하기에는 오히려 장점이 크다. 모델이 다뤄야 할 세계를 의도적으로 좁혀서, 데이터와 성격이 어떻게 연결되는지 더 선명하게 보여 주기 때문이다.

제공하는 핵심 수치도 교육용으로 적절하다. 파라미터 수는 8.7M, 레이어는 6개, 최대 시퀀스 길이는 128토큰, 어휘 크기는 4,096이다. 최신 상용 LLM과 비교하면 매우 작지만, 기본 원리를 설명하기에는 충분한 크기다. 특히 이 정도 규모는 모델 구조와 추론 흐름을 눈으로 따라가기에 좋고, 브라우저 배포까지 시도할 수 있을 만큼 가볍다.
설계 하는 부분도 상당히 중요하다. 여기서 저장소는 왜 시스템 프롬프트를 두지 않았는지, 왜 멀티턴 대신 싱글턴만 선택했는지, 왜 RoPE나 SwiGLU 없이 바닐라 트랜스포머를 썼는지, 왜 합성 데이터를 택했는지를 직접 설명한다. 이 네 가지는 작은 LLM을 이해할 때 꼭 짚어야 할 축이다.
2. docs 폴더 번역 핵심
- docs/index.html 역할: GitHub Pages에서 돌아가는 브라우저 데모
- docs/download.sh 역할: Hugging Face에서 model.onnx와 tokenizer.json을 내려받는 보조 스크립트
- docs/model.onnx 의미: float32 모델을 uint8로 줄인 브라우저용 추론 자산
- docs/tokenizer.json 의미: 학습용 토크나이저와 웹 추론 토크나이저의 일치 보장
docs 폴더를 번역해 읽으면, 이 저장소가 단순히 훈련으로 끝나지 않는다는 사실이 보인다. index.html은 가벼운 데모 페이지가 아니라, 브라우저 안에서 모델을 직접 불러와 추론까지 수행하는 클라이언트 전체다. 서버 API를 호출하지 않고, 사용자가 여는 웹페이지 안에서 ONNX Runtime Web과 WASM으로 모델을 돌린다.
download.sh는 역할이 단순하다. Hugging Face 모델 저장소에서 model.onnx와 tokenizer.json을 현재 docs 디렉터리로 내려받는다. 그런데 이 단순한 스크립트가 중요한 이유는, “배포용 가중치”와 “토큰화 규약”이 함께 이동해야 한다는 점을 드러내기 때문이다. 모델 파일만 옮기면 끝나는 것이 아니라, 같은 토크나이저를 정확히 맞춰야 학습 때와 동일한 입력이 만들어진다.
docs 폴더가 교육용으로 특히 좋은 이유는, LLM 서비스에서 흔히 숨겨지는 요소를 노출한다는 데 있다. 브라우저 추론을 위해 어떤 런타임이 필요한지, 모델과 토크나이저를 어떤 순서로 불러오는지, 추론 시에 어떤 샘플링 파라미터를 쓰는지까지 코드 한 파일 안에서 확인할 수 있다. 즉, docs는 설명서이면서 동시에 추론 구현 예제다.
3. LLM을 이해하기 위한 핵심 기술 포인트
1. 데이터의 포맷이 모델의 답변 형태를 결정
- 핵심 포맷: <|im_start|>user, <|im_end|>, <|im_start|>assistant로 이어지는 단일 대화 형식
- 설계 의도: 채팅 구조를 런타임 프롬프트가 아니라 학습 데이터 안에 직접 고정하는 방식
- 이해 포인트: 작은 모델일수록 규칙을 외부 지시보다 내부 가중치에 심는 전략
generate_data.py를 보면 학습 샘플은 단순 문장 쌍이 아니다. 사용자 입력과 물고기 답변을 <|im_start|>, <|im_end|> 특수 토큰으로 감싼 뒤 하나의 긴 텍스트로 합친다. 이 포맷은 format_sample 함수에서 명확하게 드러난다. 다시 말해, 모델은 “채팅을 한다”는 사실 자체를 프롬프트로 듣는 것이 아니라 학습 중에 이미 이 패턴을 계속 본다.
이 지점은 LLM 입문에서 매우 중요하다. 초보자는 시스템 프롬프트가 성격을 만든다고 생각하기 쉽다. 하지만 GuppyLM처럼 작은 모델에서는 같은 지시를 모든 샘플에 반복해서 넣으면, 그 성격은 프롬프트보다 가중치에 더 강하게 새겨진다. README가 시스템 프롬프트를 제거한 이유를 설명하는 것도 이 맥락이다. 이 모델은 “물고기처럼 말하라”를 매번 외부에서 듣기보다, 애초에 그렇게 학습된 모델인 셈이다.
2. ByteLevel BPE와 특수 토큰 설계
- 특수 토큰: pad_id 0, bos_id 1, eos_id 2의 고정 규약
- 어휘 크기: 4,096 토큰의 BPE 기반 어휘
- 구현 의미: 학습용 Python 토크나이저와 브라우저용 JavaScript 토크나이저의 동작 일치
prepare_data.py는 토크나이저를 Hugging Face tokenizers 라이브러리로 직접 훈련한다. 방식은 ByteLevel BPE다. 이 선택은 교육용으로 매우 적절하다. 문자 단위보다 효율적이고, 단어 단위보다 유연하며, 원시 텍스트를 바이트 수준에서 안전하게 처리할 수 있기 때문이다. 영어 중심 데이터에서는 특히 구현이 단순하고 재현성이 높다.
토크나이저에서 더 중요한 부분은 특수 토큰 규약이다. , <|im_start|>, **<|im_end|>**를 각각 0, 1, 2로 박아 둔다. 이 값은 config.py, inference.py, docs/index.html의 브라우저 설정에서 모두 다시 맞춰진다. 작은 프로젝트지만 이 일관성이 매우 중요하다. 모델 가중치, 추론 코드, 웹 데모가 모두 같은 토큰 계약을 공유해야 출력이 깨지지 않기 때문이다.

토크나이저는 단순 전처리 도구가 아니다. 채팅 포맷과 특수 토큰 규약을 숫자 시퀀스로 고정해, 모델이 학습과 추론에서 같은 언어를 보게 만드는 계약에 가깝다.
특히 docs/index.html의 자바스크립트는 이 점을 잘 보여 준다. 저장소는 브라우저에서 Hugging Face Python 토크나이저를 그대로 쓰지 않고, ByteLevel BPE를 직접 포팅한 구현을 넣었다. 그리고 특정 프롬프트가 기대한 토큰 ID 배열과 정확히 일치하는지 자체 테스트도 한다. 이 부분은 LLM 입문자에게 좋은 교훈이다. 토크나이저가 어긋나면 모델 품질이 무너지는 이유를 아주 직접적으로 보여 주기 때문이다.
3. 시프트된 라벨로 배우는 next-token prediction
- 데이터 구성: 입력 시퀀스 x와 한 칸 오른쪽으로 민 정답 시퀀스 y
- 손실 계산: 패딩 토큰 0 무시 기반의 cross entropy
- 이해 포인트: 언어모델이 “정답 문장”이 아니라 “다음 토큰”을 맞히는 방식
dataset.py는 언어모델 학습의 가장 핵심적인 부분을 아주 짧게 보여 준다. 토큰 시퀀스가 있으면 x = ids[:-1], **y = ids[1:]**로 나눈다. 즉, 모델은 현재까지 본 토큰을 입력받고 그다음 토큰을 맞히는 문제를 푼다. 이 단순한 시프트가 LLM의 본질이다.
손실 계산은 model.py에서 cross_entropy로 이루어지며, 패딩 토큰 0은 무시한다. 이 구조를 이해하면 “채팅 모델”도 결국은 매우 큰 next-token predictor라는 사실이 더 선명해진다. 말투, 성격, 세계지식, 질문 응답 능력은 전부 이 반복 학습의 결과다.
4. 바닐라 디코더형 트랜스포머의 구조
- 임베딩 구조: 토큰 임베딩과 학습형 위치 임베딩의 합산
- 블록 구조: LayerNorm → Multi-Head Attention → Residual → LayerNorm → FFN → Residual
- 단순화 포인트: RoPE, GQA, SwiGLU, parallel residual 생략
- 이해 포인트: 최신 기법을 빼고도 LLM의 기본 원리는 그대로 남는 구조
model.py는 정말로 “교과서형 디코더”에 가깝다. 토큰 임베딩과 위치 임베딩을 더한 뒤, 6개의 블록을 통과시키고, 마지막에 LayerNorm과 LM head로 logits를 뽑는다. Attention은 하나의 선형층에서 QKV를 동시에 만들고, causal mask를 씌운 뒤 softmax attention을 계산한다.
흥미로운 부분은 Pre-LN 구조라는 점이다. 각 블록은 Attention이나 FFN에 들어가기 전에 먼저 LayerNorm을 통과한다. 요즘 대형 모델에서도 널리 쓰이는 안정화 방식이지만, 이 저장소는 여기에 다른 복잡한 요소를 더하지 않는다. 덕분에 학습 안정성에 필요한 최소 구조와 추가 최적화 기법을 구분해서 볼 수 있다.

이 모델은 최신 기법을 경쟁적으로 쌓지 않는다. 그 대신 임베딩, causal attention, FFN, residual, LM head라는 기본 뼈대를 분명하게 드러낸다.
또 하나 중요한 점은 weight tying이다. lm_head.weight = tok_emb.weight로 입력 임베딩과 출력 투영 가중치를 공유한다. 이 방식은 파라미터를 줄이고, 입력 토큰 공간과 출력 토큰 공간을 더 긴밀하게 연결하는 고전적인 최적화다. 작은 모델에서 특히 효율이 좋다.
5. 학습 안정성을 위한 최소한의 훈련 루프
- 옵티마이저: AdamW, betas 0.9 / 0.95, weight decay 0.1
- 학습률 스케줄: 200 step warmup 뒤 cosine decay
- 안정화 장치: gradient clipping 1.0, CUDA 환경에서 AMP
- 운영 방식: 일정 간격 평가, 최고 성능 체크포인트 저장, 중간 단계 저장
train.py는 작은 프로젝트지만 학습 루프를 꽤 정석적으로 구성했다. 초반에는 warmup으로 학습률을 서서히 올리고, 이후에는 cosine decay로 부드럽게 내린다. 작은 모델에서도 이런 스케줄이 필요한 이유는, 초기 학습을 너무 급하게 시작하면 손실이 출렁이거나 수렴이 불안정해질 수 있기 때문이다.
Gradient clipping과 AMP도 중요한 포인트다. clipping은 폭주를 막고, AMP는 CUDA에서 연산 효율을 높인다. 이 저장소는 “작은 모델이니 대충 학습해도 된다”는 식으로 가지 않는다. 오히려 작은 모델일수록 기본기를 정석대로 넣어서, 무엇이 정말로 필요한 최소 장치인지 보여 준다.
6. 128토큰 한계와 싱글턴 설계의 의미
- 문맥 창 크기: max_seq_len 128의 짧은 제약
- README 판단: 멀티턴은 3회차 이후 품질 저하
- docs 구현: 채팅 UI처럼 보이지만 실제 프롬프트는 현재 사용자 메시지만 포함하는 구조
이 프로젝트를 LLM 입문용으로 좋게 만드는 또 다른 이유는 한계를 숨기지 않는다는 점이다. README는 멀티턴 대화가 3~4턴 이후 품질이 떨어진다고 직접 말한다. 그리고 docs/index.html의 send 함수는 실제로 이전 대화 기록을 계속 쌓지 않는다. 매번 현재 사용자 메시지와 assistant 접두부만 넣어 새 응답을 뽑는다.
이 설계는 단점이 아니라 좋은 교육 장치다. 문맥 창이 짧으면 메모리 환상이 금방 깨진다. 즉, “LLM이 대화를 이해한다”는 말은 실제로는 토큰 창 안에 담긴 범위에서만 성립한다는 사실을 보여 준다. 대형 모델의 장문 문맥, 요약 메모리, 외부 상태 저장이 왜 필요한지도 여기서 자연스럽게 이해된다.

chat UI와 멀티턴 기억은 같은 개념이 아니다. GuppyLM은 UI는 채팅처럼 보이지만, 실제 추론 입력은 단일 턴에 가깝게 유지해 품질 붕괴를 막는다.
7. ONNX 양자화와 브라우저 로컬 추론
- 내보내기 방식: torch.onnx.export로 logits 전용 forward graph 생성
- 경량화 방식: QuantType.QUInt8 기반 동적 양자화
- 배포 효과: 대략 35MB float32 → 9MB uint8 축소
- 추론 방식: onnxruntime-web과 WASM 기반의 브라우저 로컬 실행
- 샘플링 방식: temperature 0.7, top_k 50, 수동 softmax 샘플링
tools/export_onnx.py는 브라우저 배포 쪽에서 특히 교육 가치가 높다. 먼저 PyTorch 모델을 ONNX로 내보내고, 그다음 동적 양자화로 파일 크기를 크게 줄인다. README가 말한 “브라우저에서 돌릴 수 있을 만큼 작다”는 표현은 감상이 아니라 이 파이프라인의 결과다.
브라우저 쪽 index.html은 더 흥미롭다. 모델을 부른 뒤 logits를 받고, 마지막 토큰의 분포만 꺼내 temperature와 top_k를 적용하고, softmax를 계산한 뒤 샘플링한다. 많은 입문자가 “생성”을 블랙박스로 생각하지만, 이 코드는 생성이 결국 다음 토큰 분포에서 하나를 고르는 반복이라는 사실을 매우 직접적으로 보여 준다.

브라우저 추론의 핵심은 세 단계다. 같은 토크나이저로 숫자 시퀀스를 만들고, ONNX 모델에서 logits를 받고, 샘플링으로 다음 토큰을 고른다. 서버가 없어도 이 구조는 유지된다.
4. 큰 LLM과 비교하면 무엇이 보이나
- GuppyLM 역할: 기본 원리를 드러내는 현미경
- 상용 LLM 역할: 범용성, 장문 문맥, 안전성, 도구 사용을 겹겹이 얹은 완성형 제품
- 핵심 차이: 데이터 다양성, 문맥 길이, 아키텍처 최적화, 후처리 정렬, 운영 인프라
이 저장소를 잘 읽는 방법은 “왜 최신 대형 모델처럼 만들지 않았나”를 따지는 것이 아니다. 오히려 “최신 대형 모델에서 무엇을 일부러 뺐나”를 보는 편이 맞다. RoPE, GQA, SwiGLU, 긴 컨텍스트, 시스템 프롬프트 제어, 툴 호출, 멀티턴 메모리, 안전 정렬 같은 층을 벗겨 내면 결국 남는 것은 토크나이저, next-token 학습, causal decoder, 샘플링이라는 기본 구조다.
그 점에서 GuppyLM은 생산용 모델의 축소판이라기보다, LLM의 뼈대를 보여 주는 투명 모형에 가깝다. 상용 모델을 이해하려면 복잡한 기능을 외우기 전에, 이런 기본 골격을 먼저 보는 편이 훨씬 낫다.

GuppyLM은 범용 성능을 겨루는 모델이 아니다. 대신 대형 LLM에서 복잡한 기능 아래 숨겨지는 기본 원리를 전면에 드러낸다.
5. 이 저장소를 읽을 때 특히 눈여겨볼 지점
- 데이터 우선 관점: 모델 성격이 데이터 분포에서 나온다는 사실
- 토크나이저 우선 관점: 같은 텍스트라도 토큰 규약이 달라지면 모델 입력이 달라진다는 사실
- 문맥 우선 관점: 기억처럼 보이는 성질이 실제로는 문맥 창 크기와 포맷의 결과라는 사실
- 추론 우선 관점: 생성이 거대한 사고가 아니라 확률 분포 위의 반복 선택이라는 사실
- 배포 우선 관점: 모델을 만든 뒤 어디서 어떻게 돌릴지까지 설계에 포함된다는 사실
LLM을 처음 공부할 때 많은 사람이 모델 구조만 붙잡고 시작한다. 하지만 GuppyLM을 보면 구조만으로는 설명이 끝나지 않는다. 데이터 포맷, 특수 토큰, 시프트 학습, 샘플링, 배포 형식, 양자화가 모두 하나의 체계로 묶여 있다. 이런 저장소를 제대로 읽으면 “모델”보다 “시스템”이라는 관점이 먼저 생긴다.
결론
GuppyLM의 README와 docs 폴더를 번역해 따라가 보면, 언어모델은 생각보다 더 구체적인 부품들의 조합이라는 점이 드러난다. 합성 대화 데이터가 있고, 그 데이터를 ByteLevel BPE로 잘라 숫자 시퀀스로 바꾸고, 디코더형 트랜스포머가 다음 토큰을 예측하며, 학습된 가중치를 ONNX로 내보내 양자화한 뒤 브라우저에서 다시 실행한다. 거대한 LLM도 결국 이 골격 위에 세워진다.
이 저장소의 진짜 가치는 성능 수치보다 투명성에 있다. 토크나이저가 왜 중요한지, 짧은 문맥 창이 왜 기억을 제한하는지, 시스템 프롬프트가 언제 필요한지, 양자화가 왜 브라우저 배포를 가능하게 하는지 같은 질문에 코드로 답한다는 점이다. LLM을 제대로 이해하고 싶다면, 복잡한 상용 제품을 보기 전에 이런 작은 저장소 하나를 끝까지 읽는 편이 훨씬 빠를 수 있다.
참고 자료
- GuppyLM README - GitHub, 2026년 4월 7일 확인
- docs/index.html - GitHub, 2026년 4월 7일 확인
- docs/download.sh - GitHub, 2026년 4월 7일 확인
- guppylm/config.py - GitHub, 2026년 4월 7일 확인
- guppylm/model.py - GitHub, 2026년 4월 7일 확인
- guppylm/prepare_data.py - GitHub, 2026년 4월 7일 확인
- guppylm/train.py - GitHub, 2026년 4월 7일 확인
- guppylm/inference.py - GitHub, 2026년 4월 7일 확인
- tools/export_onnx.py - GitHub, 2026년 4월 7일 확인
'최신IT 정보 > IT 개발정보' 카테고리의 다른 글
| 코딩 에이전트 구성하기 위한 6개의 요소 (0) | 2026.04.07 |
|---|---|
| 안드레이 카파시 LLM Wiki 공개: 문서 업로드보다 오래 남는 지식 베이스 (0) | 2026.04.06 |
| 개발팀이 느린 진짜 이유: 사람보다 코드베이스 드래그가 문제다 (0) | 2026.04.06 |