byte-level BPE와 decoder Transformer를 직접 구현해 학습·재개·생성까지 이었다.

역할
팀 과제 · 개인 확장 진행 중
기간
2026-05-14 ~ 2026-09-22

woonyong-choi/lrn-gpt

GPT가 글자를 토큰으로 쪼개고 다음 토큰을 배우는 전 과정을 이해하려고, byte-level BPE와 작은 decoder Transformer를 직접 구현해 학습·재개·생성까지 이어 붙인 프로그램입니다. PyTorch는 텐서 연산과 자동 미분에만 사용합니다.

lrn-gpt 학습 loss 곡선: 검증 loss는 step 100에서 최저(3.09), 이후 과적합으로 상승

버전업된 모습

결과: 테스트 23개 통과 · 검증 loss 5.71 → 3.09(step 100) → 3.29(step 200) · BPE 학습 약 4배 빠름(merge·encode 결과 동일) — 수치와 재현 명령은 검증, 설계 근거는 docs/design.md.

팀 시기(cf42373)에는 실험 러너 9개, 감성 분류 헤드, NSMC 대량 탐색, 학습 예제 스크립트가 흩어져 있었습니다. 이 저장소에서는 이를 정리하고 “BPE 학습 → Transformer 학습 → checkpoint → 재개 → 생성”이 한 CLI(runnable.py)로 끝나는 경로만 남겼습니다. 데이터 다운로드 없이 make demo로 학습 전·후 모델의 생성 결과를 바로 비교할 수 있습니다.

항목 팀 시기 (cf42373) 현재 재현
진입점 scripts/run_*.py 9개 + 학습 스크립트 다수 src/runnable.py 하나 (train·generate·demo) git diff --name-status cf42373..4c066ec -- scripts src
BPE 학습 시간 4.81 s 1.13 s (약 4.3배, merge 340개·encode 결과 동일) make bench
테스트 모듈별 테스트 다수 23개 (BPE property 포함, 커버리지 82%) make test
코드 변화 208 files, +564 / −28,201 git diff --shortstat cf42373..4c066ec

BPE 시간은 78.7 KB 고정 코퍼스(cf42373 리비전의 소스·README, sha256 기록), vocab 600 기준입니다. 두 구현을 번갈아 7회 측정한 최솟값이며, 독립 실행 3회에서 4.29 / 3.77 / 4.27배였습니다. 처음에는 한 구현씩 몰아서 재다가 같은 머신에서 배율이 3.5~6.8배로 흔들렸습니다 — 머신 부하 변화가 나중에 잰 쪽에만 얹혔기 때문이고, 그래서 지금은 교차 측정합니다. 전체 수치는 docs/bench-bpe.json.

어려웠던 점. BPE는 규칙이 단순한 만큼 “같은 코퍼스는 항상 같은 결과”를 보장하는 세부가 어려웠습니다. 빈도가 같은 pair, aaaaa처럼 겹치는 pair를 어떻게 병합할지 정하지 않으면 실행마다 vocabulary가 달라지고, 그러면 checkpoint를 재개할 때 토큰 ID가 어긋납니다. 재개도 마찬가지로, optimizer·난수 상태·설정·tokenizer·corpus hash를 함께 저장해야 dropout이 있어도 학습 궤적이 이어집니다(데이터가 바뀐 재개는 거절).

솔직한 한계도 있습니다. 코퍼스가 842바이트뿐이라 검증 loss는 step 100에서 최저(3.09)를 찍고 그 뒤 올라갑니다 — 과적합 시연이지 쓸 만한 생성 모델이 아닙니다. 팀 시기의 감성 분류·NSMC 실험은 이 경로에 포함하지 않았고 정리 전 이력에만 있습니다.

구동모습

lrn-gpt 데모: 학습 로그의 검증 loss 5.71에서 3.09로 감소, 학습 전·후 모델의 같은 prompt 생성 비교

bash scripts/demo_terminal.sh의 실제 실행입니다(1.3배속 재생). 100 step 학습 로그에서 검증 loss가 5.71 → 3.09로 내려가고, 같은 prompt A small model과 seed 42로 학습 전(step 0)·방금 학습한 모델(step 100)·제공 모델(step 200)의 생성을 비교합니다. 생성은 결과를 한 번에 출력하며 토큰 단위 스트리밍은 지원하지 않습니다.

make demo는 같은 prompt A small model을 학습 전(step 0)·후(step 200) 모델에 넣습니다. 실제 출력입니다(byte-level이라 학습 전 출력에는 제어 문자와 깨진 UTF-8이 섞입니다).

모델 temperature / top-k 생성 결과
학습 전 (step 0) 0.5 / 5 A small model + 임의 바이트열(제어 문자· 포함)
학습 후 (step 200) 0.5 / 5 A small modelearns fromistttttuent ita small model on a small model on a sepataire om se
학습 후 (step 200) 1.0 / 20 A small modelearncan� tut inut token. We ve the ve me the ve sutimalizere m se

학습 후에도 문장은 되지 않지만 코퍼스의 단어 조각(small model, token, learns)이 나타납니다. 이것이 이 모델이 보여 주는 전부이며, 그 이상의 품질은 주장하지 않습니다.

메인 기술

UTF-8 텍스트 → BPE → 입력·정답 토큰 쌍 → causal attention·Transformer → loss/backward → checkpoint → sampling 순서입니다. 스택 나열이 아니라 직접 구현한 기법입니다. 왜 이 구조인지, 어떤 대안을 왜 버렸는지는 docs/design.md에 정리했습니다.

  • byte-level BPE — 설계 결정은 아래에 따로 정리했습니다. bpe.py · test_bpe.py
  • causal self-attention — 미래 토큰을 가리는 mask, 다중 head. head 루프 참조 구현과 PyTorch scaled_dot_product_attention 양쪽에 대조합니다. attention.py · test_attention.py
  • decoder Transformer — token·position embedding, residual, pre/post-normalization(norm_first), GELU feed-forward. model.py · embeddings.py
  • 재개 가능한 학습 루프 — model·optimizer·step·설정·tokenizer·난수 상태·corpus hash를 checkpoint에 저장하고, 저장은 임시 파일 → fsyncrename 으로 원자적입니다. Ctrl-C에서도 현재 checkpoint를 남깁니다. runnable.py
  • temperature·top-k sampling — seed 고정 생성. generation.py
  • 입력 검증 guard — 잘못된 shape·설정은 조기에 실패시킵니다. guards.py

BPE 설계 결정

결정 이유 · 대가
UTF-8 바이트를 기본 토큰으로 (ID 0–3 special, 4–259 byte, 260~ merge) 어떤 문자열도 <unk> 없이 표현되고 한글 같은 멀티바이트도 그대로 처리합니다. 대가: 문자보다 시퀀스가 길고, 생성이 UTF-8이 아닌 바이트 조합을 내면 로 보입니다.
결정적 tie-break — 빈도 → 최초 등장 위치 → token ID 같은 코퍼스는 항상 같은 merge 목록을 만들어, 재개 시 vocabulary가 어긋나지 않습니다.
겹치는 pair는 왼쪽부터 겹치지 않게 (aaaaa → 0·2번째만 병합) 병합 결과를 유일하게 정의합니다. _replace_pair
멈춤 조건 3가지 — 목표 vocab_size 도달 / 더 이상 pair 없음 / 최대 빈도 < min_frequency 작은 코퍼스에서 의미 없는 1회 pair를 병합하지 않도록 합니다. 예제 설정은 vocab_size=300(merge 40개).
decode는 byte를 모아 마지막에 한 번만 UTF-8 해석 문자 중간의 바이트를 하나씩 decode하면 깨집니다.
pair 카운트를 NumPy로 — 두 ID를 한 정수로 packing해 np.unique 팀 시기 구현 대비 학습 약 4배 빠르고 merge·encode 결과는 동일합니다. 대가: 병합마다 전체 시퀀스를 다시 훑어 코퍼스가 2배면 시간은 2배보다 더 듭니다(21 KB 0.41 s → 157 KB 3.20 s). NSMC 규모의 다음 병목입니다.

계획

  • 코퍼스를 늘려 학습하면(make data + make train-nsmc) 검증 loss의 최저점이 100 step보다 뒤로 밀리는지 확인하고, 그 로그와 곡선을 docs/에 추가한다.
  • NSMC 규모에서 BPE 준비 시간이 병목으로 확인되면, 병합마다 전체를 재계산하는 대신 pair 빈도를 증분 갱신하는 방식으로 바꾸고 make bench의 동일성 검사(merge 목록·encode 결과)로 회귀가 없는지 확인한다.
  • byte-level 생성의 깨진 UTF-8이 불편하면, 생성 끝을 유효한 UTF-8 경계에서 자르는 옵션을 추가한다.
  • 분류 헤드가 다시 필요해지면, 정리 전 이력의 감성 분류 구현을 현재 checkpoint 계약에 맞춰 옮긴다.

링크

담당

크래프톤 정글 팀 과제(원본: Jungle-12-303/wk13_6_gpt, 비공개)에서 시작했고, 종료 후 개인 저장소에서 계속 수정·학습·확장하고 있다.

구분 내용
원본 저장소 Jungle-12-303/wk13_6_gpt (비공개). 과제 제공물은 krafton-jungle/gpt-lab(현재 접근 불가), 중간 개인 저장소는 woonyong-choi/SW_AI-W13-gpt(비공개). 파생 기준 revision cf42373
기간 2026-05-14(과제 제공 커밋) ~ 2026-06-04(팀 저장소 마지막 커밋)
팀 구성 GitHub contributors 기준 4개 계정(본인 + 팀원 2명 + 과제 제공 계정 1)
팀 시기 본인 담당 Transformer 블록·attention, loss·checkpoint·생성 유틸, 사전학습 루프, 감성 분류 헤드와 학습 루프, 실험 러너·NSMC 실험, BPE 구현 통합 — 커밋 기준 본인 65 / 팀원 17 / 과제 제공 1. 팀원은 BPETokenizer의 encode·decode와 train·save·load, 실험 보고서를 작성
개인 확장 b283d41(2026-09-08) ~ 최근, 개인 커밋 — 학습·재개·생성 경로 완성(runnable.py), BPE 처리 개선(약 4배), 실험 코드·보고서 정리, NSMC 준비 비용과 검증 범위 기록, CI·README 정리

개인 확장 범위 확인: git log --author='choi woo-nyong' --oneline cf42373..HEAD. 과제 제공물·팀 구현·이후 확장은 Git author와 diff로 구분하며 기존 저작권 표시는 유지합니다.

구동방법

Python 3.12와 uv가 필요합니다. 의존성은 requirements.lock으로 고정합니다.

make setup      # .venv 생성 + 고정 의존성 설치
make demo       # 포함된 학습 전·후 모델로 생성 비교 (데이터 다운로드·새 학습 불필요)
make test       # pytest (23개, 약 6 s)
make bench      # BPE 속도·결과 동일성 비교 -> docs/bench-bpe.json (git 이력 필요)

직접 학습하고 재개하려면 (--steps는 추가 횟수가 아니라 최종 step입니다):

.venv/bin/python -m src.runnable train --steps 100 --checkpoint .artifacts/run.pt
.venv/bin/python -m src.runnable train --resume .artifacts/run.pt --steps 200 --checkpoint .artifacts/run.pt
.venv/bin/python -m src.runnable generate --model .artifacts/run.pt --prompt 'A small model' --temperature 0.8 --top-k 20 --length 60
# NSMC 데이터 준비와 학습은 별도 실행 (다운로드·BPE 준비 비용이 큼)
make data && make train-nsmc

학습 중 Ctrl-C는 현재 checkpoint를 저장하고, 강제 종료는 마지막 저장 지점까지 복구합니다. 초기 BPE 준비 중에는 checkpoint가 아직 없습니다.

스펙

항목 내용
언어·런타임 Python 3.12 (측정 환경 3.12.12)
주요 라이브러리 PyTorch 2.14.0(CPU, 텐서 연산·autograd 전용), NumPy · pytest
모델 파라미터 86,688개 — vocab 300, context 32, emb 48, head 4, layer 2, dropout 0.1, pre-norm
학습 설정 batch 8, lr 0.002, seed 42, CPU. 코퍼스 examples/train.txt 842 B / validation.txt 323 B
측정 환경 Apple M4 · macOS · 2026-09-08 기록 (manifest)
범위 밖 범용 챗봇·분산 학습·RLHF·RAG. 같은 CPU 환경의 재개를 검증했으며 버전·하드웨어가 다른 실행의 bitwise 일치는 보장하지 않음. checkpoint는 weights_only=True로 읽으며 이전 노트북 형식과 자동 호환하지 않음

검증

CI

항목 결과 재현
테스트 23 passed (BPE 7 · attention 8 · 학습·재개 8, 약 6 s). src/ 커버리지 82% make test
검증 loss (step 0 / 100 / 200) 5.7103 / 3.0901 / 3.2903 — 재학습한 값이 manifest와 소수점까지 일치 구동방법의 학습·재개 명령, 로그 train-log.jsonl
BPE 학습 시간 4.81 s → 1.13 s (4.27배), merge 340개·encode 동일 make bench (git 이력 필요) → docs/bench-bpe.json
직접 구현 attention head 루프 참조 구현·PyTorch SDPA 와 동일 출력 make testtest_attention.py
재개 정확성 3 step 연속 학습 == 1 step + 저장·로드 + 2 step, rtol=0 atol=0 make testtest_runnable.py

학습 로그(docs/train-log.jsonl)에서 뽑은 곡선 수치입니다. “train batch”는 해당 step 한 mini-batch의 loss라 흔들릴 수 있고, 검증 loss는 192토큰 기준입니다.

step 0 25 50 75 100 125 150 175 200
검증 loss 5.71 4.23 3.63 3.27 3.09 3.14 3.19 3.17 3.29
train batch loss 4.11 3.27 2.67 2.14 1.77 1.46 1.32 1.24

곡선 이미지 재생성: python scripts/plot_loss.py (plot_loss.py, 표준 라이브러리만 사용).

참고

  • 원본: Jungle-12-303/wk13_6_gpt(비공개), 과제 제공 krafton-jungle/gpt-lab(접근 불가)
  • Transformer Wiki
  • NSMC 원본 데이터: e9t/nsmc
  • 정리 전 이력: 7875b64

이 페이지의 본문은 woonyong-choi/lrn-gpt 의 README(커밋 efe8050)를 그대로 옮긴 것입니다. 프로젝트 목록으로