Wooseok Song Study

Study Notes

GPT Study

Period2025.12.23 - 12.26
FormatLecture Notes · Code
TopicDecoder-only Transformer

강의 목표

Andrej Karpathy의 강의는 Tiny Shakespeare 데이터와 character-level tokenizer에서 출발해, next-token prediction을 수행하는 작은 GPT를 PyTorch로 직접 구현한다. Bigram language model을 기준선으로 만든 뒤 causal self-attention과 Transformer block을 차례로 추가하면서 각 구성 요소가 필요한 이유를 확인한다.

01Text corpus
02Character tokens
03Context batches
04Causal Transformer
05Next-token sampling

GPT와 확률적 생성

GPT는 Generative Pre-trained Transformer의 약자이며, Transformer를 기반으로 다음 token의 확률분포를 예측한다. 이 강의에서 구현하는 작은 언어 모델도 같은 next-token prediction 원리를 따른다.

같은 prompt에서도 출력이 달라지는 이유모델은 예측한 확률분포에서 다음 token을 sampling한다. 따라서 입력이 같더라도 선택된 token sequence가 달라질 수 있다.

1. 텍스트를 학습 데이터로 바꾸기

강의에서는 데이터에 등장하는 고유 문자를 vocabulary로 만들고, 각 문자를 정수 ID와 양방향으로 매핑한다. 전체 텍스트를 token ID tensor로 변환한 뒤 train/validation split을 구성한다.

Character-level tokenization

요소역할
stoi문자를 정수 token ID로 변환한다.
itostoken ID를 다시 문자로 복원한다.
vocab_size데이터에 등장한 고유 문자의 개수다.

Context와 batch

block_size는 모델이 한 번에 볼 수 있는 최대 context 길이다. 입력 \(x\)와 정답 \(y\)는 한 token만큼 어긋나 있으며, 각 위치에서 지금까지의 context로 바로 다음 token을 예측하도록 학습한다. batch_size는 이런 독립적인 sequence를 병렬로 몇 개 처리할지 정한다.

TensorShape의미
\(x, y\)\((B, T)\)Token ID로 이루어진 입력과 다음-token 정답
Embedding\((B, T, C)\)각 token을 \(C\)차원 벡터로 표현한 결과
데이터 준비 핵심 코드 보기
stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for i, ch in enumerate(chars)}
encode = lambda s: [stoi[c] for c in s]
decode = lambda ids: ''.join(itos[i] for i in ids)

def get_batch(split):
    source = train_data if split == 'train' else val_data
    ix = torch.randint(len(source) - block_size, (batch_size,))
    x = torch.stack([source[i:i + block_size] for i in ix])
    y = torch.stack([source[i + 1:i + block_size + 1] for i in ix])
    return x, y

2. Bigram model로 기준선 만들기

가장 단순한 모델은 현재 token 하나만 보고 다음 token을 예측한다. nn.Embedding(vocab_size, vocab_size)의 각 행은 현재 token에 대응하며, 각 열의 값은 다음 token 후보에 대한 logits다. Softmax를 적용하기 전이므로 이 값 자체가 확률은 아니다.

학습할 때는 logits의 \((B, T, C)\)를 \((B \times T, C)\)로, target의 \((B, T)\)를 \((B \times T)\)로 펼친 뒤 cross-entropy loss를 계산한다. 생성 단계에서는 마지막 timestep의 logits를 확률로 바꾸고 다음 token을 sampling해 기존 sequence 뒤에 붙인다.

한계Bigram model은 현재 token 외의 문맥을 사용하지 않는다. 이후 Self-Attention을 추가하는 이유는 이전 context 전체에서 다음 token에 필요한 정보를 선택하기 위해서다.
Bigram 기준선 코드 보기
class BigramLanguageModel(nn.Module):
    def __init__(self, vocab_size):
        super().__init__()
        self.token_embedding_table = nn.Embedding(vocab_size, vocab_size)

    def forward(self, idx, targets=None):
        logits = self.token_embedding_table(idx)  # (B, T, C)
        if targets is None:
            return logits, None
        B, T, C = logits.shape
        loss = F.cross_entropy(logits.view(B * T, C), targets.view(B * T))
        return logits, loss

3. Self-Attention으로 context를 읽기

Self-Attention은 각 token이 같은 sequence 안의 다른 token을 얼마나 참고할지 계산한다. Query와 Key의 내적으로 관계 점수를 만들고, causal mask로 미래 token을 가린 뒤 softmax weight로 Value를 가중 합산한다.

$$ \operatorname{Attention}(Q,K,V) = \operatorname{softmax}\!\left(\frac{QK^{\mathsf T}}{\sqrt{d_k}}\right)V $$
Scaled Dot-Product Attention. 출처: Attention Is All You Need.
요소질문기능
Query현재 token이 무엇을 찾는가?참고할 context의 조건을 표현한다.
Key각 token이 무엇을 제공할 수 있는가?Query와 비교되어 attention score를 만든다.
Value선택된 token에서 어떤 정보를 가져오는가?Attention weight에 따라 합산되는 실제 내용이다.

왜 \(\sqrt{d_k}\)로 나누는가?

Query와 Key의 차원이 커질수록 내적의 분산도 커진다. 큰 score를 그대로 softmax에 넣으면 분포가 한 위치에 지나치게 몰려 gradient가 불안정해질 수 있으므로, \(\sqrt{d_k}\)로 나눠 scale을 맞춘다.

Attention score scale에 따른 softmax 분포 비교
Softmax scale experiment. Score의 크기가 커질수록 분포가 더 뾰족해지는 현상을 확인한다.
Causal Self-Attention 핵심 코드 보기
k = self.key(x)
q = self.query(x)
v = self.value(x)

wei = q @ k.transpose(-2, -1) * (k.size(-1) ** -0.5)
wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf'))
wei = F.softmax(wei, dim=-1)
wei = self.dropout(wei)
out = wei @ v

4. Transformer Block 조립하기

Karpathy는 Transformer block을 token 사이의 communication을 담당하는 Multi-Head Self-Attention과, 각 token이 얻은 정보를 개별적으로 처리하는 computation 단계인 Feed-Forward Network의 조합으로 설명한다.

Transformer encoder와 decoder block 구조
Transformer blocks. Attention과 Feed-Forward가 residual path 및 normalization과 결합된다.
Residual connection 구조
Residual connection. 입력을 변환 경로의 출력에 직접 더해 gradient의 통로를 보존한다.
구성 요소핵심 역할
Multi-Head Attention여러 attention subspace에서 서로 다른 관계를 병렬로 학습하고 결과를 이어 붙인다.
Feed-Forward Network각 token 벡터를 독립적으로 변환한다. 내부 차원을 보통 4배 확장한 뒤 다시 줄인다.
Residual Connection\(x + F(x)\) 경로로 원본 정보를 보존하고 깊은 network의 gradient 흐름을 안정화한다.
Pre-NormAttention과 Feed-Forward 전에 LayerNorm을 적용해 학습 안정성을 높인다.
DropoutAttention weight와 projection 경로 일부를 무작위로 끄며 과적합을 완화한다.
$$ y = \frac{x-\operatorname{E}[x]} {\sqrt{\operatorname{Var}[x]+\epsilon}} \odot \gamma + \beta $$
Layer Normalization. Feature dimension의 평균과 분산으로 정규화한 뒤 학습 가능한 scale과 bias를 적용한다.
Pre-Norm Transformer Block 코드 보기
class Block(nn.Module):
    def __init__(self, n_embd, n_head):
        super().__init__()
        head_size = n_embd // n_head
        self.sa = MultiHeadAttention(n_head, head_size)
        self.ffwd = FeedForward(n_embd)
        self.ln1 = nn.LayerNorm(n_embd)
        self.ln2 = nn.LayerNorm(n_embd)

    def forward(self, x):
        x = x + self.sa(self.ln1(x))
        x = x + self.ffwd(self.ln2(x))
        return x

5. 구현 결과는 Decoder-only Transformer

원 논문의 Transformer는 encoder와 decoder로 구성된다. Encoder는 입력 전체를 양방향으로 읽고, decoder는 causal mask를 사용하며 encoder output을 cross-attention으로 참고한다. 반면 이 강의에서 만든 언어 모델은 encoder와 cross-attention 없이 masked self-attention만 쌓은 decoder-only 구조다.

Attention Is All You Need의 전체 Transformer 구조
Original Transformer architecture. 강의 구현은 그림의 decoder를 단순히 복제한 것이 아니라, cross-attention을 제거한 causal decoder-only 변형이다.
Token + Position EmbeddingN × Transformer BlockFinal LayerNormLanguage Model HeadNext-token Sampling

6. GPT 학습 단계와 연결하기

여기서 구현한 next-token predictor는 대규모 corpus로 base language model을 만드는 pretraining의 축소판으로 볼 수 있다.

Pretraining

대량의 text corpus에서 다음 token을 예측하며 언어의 통계적 구조를 학습한다. Karpathy의 nanoGPT는 이 단계의 학습을 재현하는 데 초점을 둔다.

Post-training

Instruction data와 선호도 데이터를 사용해 사용자 의도에 더 잘 맞도록 모델을 조정한다. 원문 노트의 labeling → reward model → PPO 흐름은 일반적인 fine-tuning 전체라기보다 RLHF 기반 post-training을 설명한 것이다.

Comments