MiniMind — 작은 언어 모델을 처음부터 끝까지 직접 학습한다
MiniMind 한 층은 여섯 가지 결정으로 되어 있다
한 줄 요약
MiniMind-3 은 Qwen3 와 같은 뼈대의 디코더다. 한 층(block)은 Pre-Norm RMSNorm → 어텐션(GQA·RoPE·QK-norm) → 잔차 → RMSNorm → SwiGLU FFN → 잔차 이고, 임베딩과 출력층은 가중치를 공유한다. 이 모듈에서는 그 설정을 줄여(768차원·8층 → 128차원·4층) 약 100만 파라미터짜리 모델을 만들고, 각 결정이 숫자로 무엇을 하는지 확인한다.
왜 이게 필요했나
처음 나온 트랜스포머(2017)의 결정 가운데 상당수가 바뀌었다. 학습이 불안정했고(층 뒤의 LayerNorm), 추론 때 메모리를 너무 먹었고(헤드마다의 K·V), 길이를 늘리기 어려웠다(더하는 위치 임베딩). 요즘 공개 모델은 대부분 같은 처방을 쓰고, MiniMind 는 그 처방을 수백 줄 파이썬으로 보여 준다. 어텐션 자체를 손으로 계산하는 일은 트랜스포머 코스에서 했으니, 여기서는 MiniMind 의 실제 코드가 그 처방을 어떻게 구현했는지와 그 숫자를 본다.
어떻게 동작하나
MiniMindConfig 의 기본값과 그 뜻이다.
| 설정 | MiniMind-3 | 이 코스 | 뜻 |
|---|---|---|---|
| hidden_size | 768 | 128 | 토큰 하나를 나타내는 벡터 길이 |
| num_hidden_layers | 8 | 4 | 층 수 |
| num_attention_heads / num_key_value_heads | 8 / 4 | 4 / 2 | Q 헤드 / K·V 헤드 (GQA) |
| vocab_size | 6,400 | 1,024 | 어휘 |
| intermediate_size | ceil(768·π/64)·64 = 2,432 | 448 | FFN 가운데 폭 |
| rope_theta | 1e6 | 1e6 | RoPE 회전 주기의 밑 |
RMSNorm. LayerNorm 은 평균을 빼고 표준편차로 나눈다. RMSNorm 논문은 평균을 빼는 재중심화가 없어도 된다고 보고 제곱평균제곱근(RMS)으로만 나눈다. MiniMind 코드도 x * rsqrt(mean(x²) + eps) 에 가중치를 곱할 뿐이다. 그래서 출력의 RMS 는 1 로 돌아오지만 평균은 0 이 되지 않는다. 층 앞에서 정규화하는 Pre-Norm 이라 잔차 길은 정규화를 거치지 않고 곧게 이어져, 깊어져도 기울기가 잘 흐른다.
GQA. Q 헤드는 4개, K·V 헤드는 2개다. k_proj 의 출력이 2 × head_dim 으로 Q 의 절반이고, 계산할 때 repeat_kv 로 K·V 를 두 번씩 복사해 Q 헤드 수에 맞춘다. 추론 때 캐시에 남는 것은 복사 전의 K·V 라서 토큰당 KV 캐시가 절반이 된다. GQA 논문은 K·V 헤드를 1개로 줄인 MQA 가 품질을 잃는 것을 보고, 그 사이 값을 쓰면 품질은 MHA 에 가깝고 속도는 MQA 에 가깝다고 보고했다.
RoPE 와 QK-norm. 위치를 벡터에 더하지 않고, q 와 k 를 위치만큼 회전시킨다. 두 벡터를 각자의 위치로 돌린 뒤 내적하면 결과에는 두 위치의 차이만 남는다 — (3, 7) 과 (103, 107) 의 내적이 같다. MiniMind 는 회전 전에 q·k 를 각각 RMSNorm 으로 한 번 더 정규화한다(q_norm·k_norm). Qwen3 가 쓰는 방식으로, 어텐션 점수가 지나치게 커지는 것을 막는다.
SwiGLU. FFN 은 down(silu(gate(x)) * up(x)) 이다. 게이트가 있는 GLU 계열은 행렬이 셋이라, MiniMind 는 가운데 폭을 hidden 의 약 π배(64의 배수로 올림)로 잡는다. GLU 변형 논문은 이런 게이트 FFN 이 ReLU·GELU FFN 보다 품질이 좋다고 보고했다.
임베딩 공유. tie_word_embeddings=True 라 lm_head.weight 가 곧 embed_tokens.weight 다. 작은 모델에서 어휘 몫을 절반으로 줄이는 장치다.
MoE 변형. use_moe=True 면 FFN 이 전문가 4개가 되고 토큰마다 1개만 쓴다. MiniMind-3-MoE 가 '198M-A64M' 인 이유다 — 파라미터는 셋 넘게 늘지만 한 토큰이 쓰는 계산은 밀집 모델과 거의 같다.
현장에서 만나는 모습
모델 카드의 설정 파일(config.json) 한 장으로 파라미터 수·KV 캐시 크기·서빙 메모리를 계산할 수 있어야 한다. 'KV 헤드 8, head_dim 128, 32층' 이면 토큰 하나에 몇 KB 인지 바로 나오고, 그 숫자가 동시 사용자 수를 정한다. 반대로 식으로 센 파라미터가 모델과 다르면 공유 가중치나 정규화 가중치를 빠뜨린 것이다. 학습을 시작하기 전 손실이 ln(어휘) 근처인지 보는 것도 흔한 확인이다 — 초기화가 망가졌다면 첫 걸음부터 알 수 있다.
MiniMind README 는 작은 모델에서 너비와 깊이를 어떻게 나눌지에 대해 MobileLLM 논문을 인용한다 — 같은 파라미터라면 얕고 넓은 것보다 좁고 깊은 쪽이 대체로 낫지만, 차원이 512 아래로 내려가면 표현 병목이 뚜렷해진다고 적는다. 이 코스의 128차원은 그 경계보다 한참 아래다. CPU 두 개로 몇 분 안에 학습하려고 일부러 고른 크기이고, 그 대가는 마지막 모듈에서 잰다.
다음 실습에서 할 것
작은 설정으로 MiniMind 모델을 만들어 파라미터를 식과 모델 양쪽에서 센다. GQA 의 투영 크기와 토큰당 KV 캐시, RMSNorm 의 출력, RoPE 의 상대 위치 성질, 학습 전 손실, MoE 로 바꿨을 때의 전체·활성 파라미터를 차례로 확인한다.