nanoGPT 스타일 100줄¶
이 챕터에서 배우는 것
- Ch 8 attention + Ch 9 모던 블록을 한 파일로 모은 GPT-mini 100 줄
- block → layer → model 의 결합 패턴 — 이후 모든 Part 4 학습 코드의 베이스
- Karpathy 의 nanoGPT 정신을 따라 "의존성 최소 · 한 화면 안에 모델 전체"
전제
Ch 8 Attention 의 SDPA, Ch 9 의 RoPE/RMSNorm 개념. PyTorch nn.Module 한 번 만들어본 적.
1. 개념 — 한 파일에 모델 전체¶
큰 라이브러리 (transformers, fairseq) 는 추상이 깊어 처음 배울 때 흐름이 안 보인다. nanoGPT 는 정반대 — 단일 파일, 의존성 PyTorch 만, 한 화면에 모델 전체. 학습용으로 최적.
본 책의 GPT-mini 도 같은 정신:
| 구성 | 줄 수 | 역할 |
|---|---|---|
RMSNorm |
8 | Ch 9 그대로 |
apply_rope |
6 | Ch 9 그대로 |
CausalSelfAttention |
22 | Ch 8 + RoPE |
FFN (SwiGLU 옵션) |
10 | Ch 9 그대로 |
Block (Norm → Attn → Norm → FFN) |
14 | residual 두 번 |
GPTMini (embedding + N×Block + lm_head) |
25 | 전체 |
| 합계 | 약 85 |
학습 루프는 Part 4 에서. 이 챕터는 모델 클래스 자체.
2. 왜 이 구조인가 — Block 의 두 residual¶
표준 트랜스포머 디코더 블록:
Pre-norm + residual 두 번. 핵심 두 사실:
- residual 이 있어 깊어져도 gradient 가 살아 흐른다.
- pre-norm (norm 을 sublayer 앞에) 이 학습 안정. post-norm 은 100 layer 부터 깨짐.
이걸 N 번 쌓으면 그게 모델.
3. 어디에 쓰이나¶
- 이 책 Part 4 의 학습 베이스 — 다음 4 챕터가 이 모델 클래스를 그대로 학습.
- Part 5 의 평가 대상 — 이 모델로 perplexity / 샘플 검토.
- Part 6 의 양자화·GGUF 대상 — 학습된 가중치를 변환.
- 캡스톤 — 도메인 SLM 의 시작점.
4. 최소 예제 — 100줄 코드 전체¶
| nano_gpt.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | |
- RoPE 적용은 attention 직전, head 차원 분리 후. Ch 9 식 그대로.
- multi-head 분리 → RoPE → SDPA. 한 줄에 FlashAttention 까지.
- pre-norm. norm → sublayer → residual 두 번. Ch 9 의 권장.
- weight tying — 입력/출력 임베딩 공유. 파라미터 수 절감 + 학습 안정 (Press & Wolf, 2017).
- context window 초과 방지 — 가장 최근 max_len 만 사용.
5. 실전 — 한 번 돌려보기¶
전형적 출력:
확인 포인트:
- 파라미터 수가 약 10M — 본 책 기준선과 일치.
- 학습 전 loss 가 uniform 분포의 cross-entropy ≈ ln(vocab) = 8.99 — sanity check 통과.
- 무작위 가중치라 생성은 의미 없음. 학습이 시작되면 loss 가 8.99 → 4 부근으로 떨어진다 (Part 4).
6. 자주 깨지는 포인트¶
1. RoPE 테이블을 매 forward 마다 다시 만든다 — register_buffer 로 한 번만. CPU↔GPU 이동도 자동.
2. weight tying 안 함 — 파라미터 수가 vocab_size × d_model 만큼 더 듦. 8K × 256 = 2M, 10M 모델의 20%. 큰 손실.
3. RMSNorm 의 gamma 초기값 0 — 학습 안 됨. 1.0 으로 초기화 (torch.ones).
4. attention dropout 을 학습 시 항상 0.0 — 작은 모델 (10M) 은 dropout 빼는 게 나음. 큰 모델에서만 0.1 정도.
5. nn.Linear(bias=True) 기본 — 트랜스포머 표준은 bias 없음. bias=False 로.
6. cos[:T] 가 batch 차원 broadcast 못 함 — apply_rope 에서 (T, head_dim/2) 를 (B, H, T, head_dim/2) 에 broadcast. PyTorch 가 알아서 해주지만 shape mismatch 시 .unsqueeze(0).unsqueeze(0) 필요.
7. 생성 시 KV cache 미사용 — 매 토큰마다 처음부터 forward. 본 책 학습 단계에선 OK, Part 6 양자화·서빙 단계에서 추가.
7. 운영 시 체크할 점¶
- 파라미터 수 출력 — config 바꿀 때마다 sanity check
- 학습 전 loss ≈ ln(vocab) — 모델 정상 초기화 확인
- 작은 입력 (B=2, T=8) 으로 forward 한 번 — shape 검증
-
model.eval()모드에서 dropout 0 확인 -
register_buffer로 RoPE 테이블 — 모델 저장 시 자동 포함 (persistent=False면 제외) - config 를 dataclass 로 — 실험 추적 시 dict 변환 쉽고 reproducibility ↑
8. 연습문제¶
- 위 코드를 그대로 돌려 본인 환경에서 파라미터 수와 학습 전 loss 를 확인하라. ln(8000) ≈ 8.99 와 일치하는가?
n_layer를 6 → 12,d_model을 256 → 384 로 늘리면 파라미터가 몇 M 이 되는가?training_memory_gb(Ch 3) 로 노트북에서 학습 가능한지 확인.- SwiGLU 를 GeLU FFN 으로 교체하고 (
hidden = 4 × d_model) 파라미터 수와 학습 전 loss 를 비교. weight_tying을 끄고 (self.lm_head.weight = self.tok_emb.weight줄 제거) 파라미터 수가 얼마나 늘어나나?- (생각해볼 것) nanoGPT 가 의도적으로 의존성을 PyTorch 만으로 한정한 이유는? 학습 자료 vs 프로덕션 코드의 트레이드오프 관점에서 한 단락.
원전¶
- Karpathy. nanoGPT — https://github.com/karpathy/nanoGPT
- Karpathy. minGPT — https://github.com/karpathy/minGPT
- Press & Wolf (2017). Using the Output Embedding to Improve Language Models. arXiv:1608.05859 — weight tying
- Touvron et al. (2023). Llama — pre-norm + RMSNorm + RoPE + SwiGLU 표준화