- Brian Kitano는 TinyShakespeare로 축소판 Llama를 직접 만들며, 논문 구현은 작은 모델에서 출발해 부품을 하나씩 교체하고 매번 학습·평가해야 안전하다고 정리함
- 데이터 분할, 배치 생성, 손실 평가, 생성 함수 같은 검증 보조 함수를 먼저 갖추고, 단순 모델로 컴파일과 학습 가능 여부를 확인한 뒤 Llama 구성요소를 추가함
- RMSNorm, RoPE, SwiGLU를 순서대로 붙이면서 텐서 shape, 수식 성질, 어텐션 맵으로 각 레이어가 기대대로 동작하는지 확인함
- RoPE 어텐션에서 causal mask를 빼면 검증 손실은 0.16까지 내려가지만 생성 품질은 나빠졌고, 미래 토큰을 보는 정보 누수가 원인이었음
- 최종 축소 Llama는 4개 블록, 약 237만 파라미터로 검증 손실을 약 1.0까지 낮췄으며, gradient 흐름과 학습률 스케줄까지 함께 점검해야 함
작게 시작해 반복적으로 확신 쌓기
- 논문 구현은 작은 모델에서 시작해 구성요소를 하나씩 바꾸고, 바꿀 때마다 학습과 평가를 반복하는 방식이 핵심임
- 먼저 모델을 정량적으로 확인할 보조 함수들을 준비함
- 데이터 분할
- 학습 루프
- 손실 시각화
- 검증 손실 평가
- 논문 구성요소를 모두 한 번에 옮기기보다, 이미 구현 경험이 있는 단순하고 빠른 모델로 생성 결과를 보는 정성 평가 함수도 마련함
- 텐서 레이어는
.shape,assert,plt.imshow로 확인하고, 처음부터 행렬곱 최적화에 들어가기보다 손으로 기대 결과를 검산한 뒤torch함수로 효율화함 - 배치 크기, 시퀀스 길이, 임베딩 차원을 바꿔가며 테스트해야 하며, 한 크기에서만 맞는 코드는 추론 시점에 깨질 수 있음
데이터셋과 기본 설정
- 구현 대상은 Meta AI의 Llama를 크게 축소한 버전이며, 학습 데이터는 TinyShakespeare임
- Llama는 1.4T 토큰으로 학습하지만, 여기서는 약 111만 문자 규모의 TinyShakespeare를 사용함
- 원래 Llama는 SentencePiece byte-pair encoding 토크나이저를 쓰지만, 이 구현은 단순 문자 단위 토크나이저를 사용함
- vocabulary size는 65
- 데이터셋이 작아 메모리 저장 방식은 별도로 최적화하지 않음
MASTER_CONFIG딕셔너리로vocab_size,batch_size,context_window,d_model같은 모델 설정을 관리함- 상수와 매직 넘버를 줄이고 코드를 읽기 쉽게 하기 위한 목적임
get_batches함수는 데이터를 train 80%, val 10%, test 10%로 나누고, 임의 시작점에서 입력x와 한 글자 뒤 라벨y를 생성함
기본 모델로 컴파일과 학습 확인
- 첫 모델은 임베딩과 간단한 feed-forward 네트워크로 구성된
SimpleBrokenModel임nn.EmbeddingLinearReLULinear
- 논문 구현에서 모델이 “동작한다”는 말은 두 조건을 모두 만족해야 함
- 컴파일: 텐서 shape이 레이어 사이에서 맞음
- 학습: 손실이 실제로 내려감
evaluate_loss함수는 train과 val split에서 10번 배치를 샘플링해 평균 손실을 계산함SimpleBrokenModel은 1000 epochs 학습 후 검증 손실이 3.94 수준이었고, 초기 cross-entropy 4.17에서 거의 줄지 않음- 원인은
F.cross_entropy에 이미 softmax 처리된 값을 넣은 것이었음- PyTorch의
F.cross_entropy는 정규화되지 않은 logits를 직접 받음 - softmax를 제거한
SimpleModel은 검증 손실을 2.51 수준까지 낮춤
- PyTorch의
- 이후
generate함수를 추가해 모델이 만든 문자를 직접 확인했고, 기본 모델은 완전하지 않지만 검증 손실이 내려가는 상태가 됨
Llama 구성요소 1: RMSNorm
- Llama는 원래 Transformer와 비교해 세 가지 주요 아키텍처 수정을 사용함
- RMSNorm pre-normalization
- Rotary embeddings
- SwiGLU activation function
- 원래 Transformer는 BatchNormalization을 쓰지만, Llama는 벡터를 centering하지 않고 variance로 스케일링하는 RMSNorm을 사용함
- 원래 Transformer가 attention layer 출력에 normalization을 적용하는 post-normalization 방식인 반면, Llama는 입력에 먼저 적용하는 pre-normalization 방식을 사용함
- 구현한
RMSNorm은 입력 shape을(batch, seq_len, d_model)로 가정함 - RMSNorm 결과는 레이어 norm이 레이어 원소 수의 제곱근이 되는 성질로 테스트함
assert- row-wise comparison
torch.allclose
- 기본 모델에 RMSNorm을 추가한
SimpleModel_RMS는 검증 손실을 2.5015 수준으로 소폭 낮춤
Llama 구성요소 2: RoPE와 causal mask
- RoPE는 Transformer용 위치 인코딩 방식이며, 토큰 위치를 embedding 회전으로 표현함
get_rotary_matrix는 context window와 embedding dimension에 대해 위치별 회전 행렬을 생성함- RoPE 구현은 다음 성질로 테스트함
- 위치
m,n에서 회전한 두 벡터의 내적이 상대 위치n-m회전과 일치해야 함
- 위치
RoPEAttentionHead는w_q,w_k,w_v를 만들고, query와 key에 RoPE 회전을 적용한 뒤F.scaled_dot_product_attention을 사용함- 학습 시점과 추론 시점의 텐서 shape 차이를 주의해야 함
- 학습 시에는
(config['batch_size'], config['context_window'], config['d_model'])처럼 설정과 맞는 경우가 많음 - 추론 시에는
(1, 1, config['d_model'])같은 단일 예시를 처리할 수 있음 forward안에서는 모델 설정값이 아니라 입력에서 얻은 shape을 기준으로 인덱싱해야 함
- 학습 시에는
- causal mask 없이 RoPE multi-head attention을 추가한 모델은 검증 손실이 0.1623까지 급격히 낮아졌지만, 생성 결과는
OOOO...,IIII...처럼 좋지 않았음 - attention map을 확인하자 모든 위치가 모든 위치를 참조하고 있었고, 다음 토큰 예측에서 미래 토큰을 보는 정보 누수가 발생함
F.scaled_dot_product_attention에is_causal=True를 적용한RoPEMaskedAttentionHead로 바꾸자 미래에 해당하는 upper triangular attention이 거의 0이 됨- causal mask 적용 후 검증 손실은 2.0815가 되었고, 더 오래 학습하면 1.8985까지 내려감
Llama 구성요소 3: SwiGLU와 블록 쌓기
- Llama는 ReLU 비선형성을 SwiGLU activation function으로 교체함
- 구현한
SwiGLU는 Swish-gated linear unit이며, 두 linear 변환과 learnablebeta파라미터를 사용함 - feed-forward 부분에 SwiGLU를 넣은 RopeModel은 파라미터 수가 592,706개였고 검증 손실은 1.8963 수준임
- 이후
LlamaBlock을 만들어 다음 구성을 하나의 블록으로 묶음- RMSNorm pre-normalization
- masked RoPE multi-head attention
- residual connection
- RMSNorm pre-normalization
- SwiGLU feed-forward
- residual connection
- 최종
Llama모델은n_layers=4로 설정하고,OrderedDict기반nn.Sequential로 4개LlamaBlock을 쌓음 - 최종 모델 파라미터 수는 2,370,246개이며, 학습 결과는 다음과 같음
- 초기 4-layer 학습 후 검증 손실 1.5532
- 10,000 epochs로 더 학습 후 검증 손실 1.1479
- 추가 학습 후 검증 손실 0.9997
- test split의 한 배치 손실은 1.2358
생성 결과와 디버깅 점검
- 최종 모델은 Shakespeare 형식과 유사한 이름, 줄바꿈, 단어 조각을 만들지만 실제 문장 품질은 제한적임
- cross-entropy 손실은 토큰 선택 관점에서 직관화할 수 있음
- 초기 손실 4.17은 vocabulary size 65에서 무작위 선택에 가까움
- 손실 1.08은 무작위로 약 2.9개 토큰 중 고르는 것과 같은 수준으로 해석함
- gradient 흐름은
show_grads함수로 확인함- 각 파라미터에서 절댓값이 작은 gradient 비율을 계산함
- 대부분의 파라미터 gradient가 0에 가깝지 않으면 흐름이 괜찮은 상태임
- 원래 Llama는 Cosine Annealing learning schedule을 쓰지만, 이 구현에서는 실험 결과가 더 나빴음
- Cosine Annealing 실험에서는 매우 낮은 tolerance에서도 attention bias가 신호를 거의 받지 못했고, 이유는 확실하지 않아 실제 구현에서는 단순하게 시작하는 편이 안전함