LayerNorm과 Residual Connection
지난 장에서 우리는 Transformer Block 안의 두 번째 핵심 부품인 Feed Forward Network를 살펴보았습니다. Attention이 문맥 속 토큰 관계를 섞어 주고, Feed Forward Network가 각 토큰 표현을 더 풍부하게 변환한다는 것도 이해했습니다.
그런데 여기서 한 가지 중요한 질문이 남습니다.
모델을 깊게 쌓으면 정말 계속 좋아질까요?
직관적으로는 그럴 것 같습니다. 한 층보다 열 층이 더 많이 계산하고, 열 층보다 백 층이 더 복잡한 패턴을 배울 수 있을 테니까요. 실제로 LLM은 아주 많은 Transformer Block을 층층이 쌓아 만듭니다. 하지만 단순히 층을 많이 쌓는다고 모델이 저절로 좋아지지는 않습니다. 오히려 깊어진 네트워크는 학습이 불안정해지고, 값이 너무 커지거나 작아지고, 앞쪽 층까지 학습 신호가 제대로 전달되지 않는 문제를 겪습니다.
그래서 Transformer에는 겉으로 보기에는 조용하지만 매우 중요한 두 장치가 들어갑니다. 바로 Residual Connection, 즉 잔차 연결과 LayerNorm, 즉 층 정규화입니다.
이 둘은 화려한 주인공처럼 보이지 않을 수 있습니다. Attention처럼 "무엇을 볼 것인가"를 결정하지도 않고, Feed Forward Network처럼 표현을 적극적으로 바꾸지도 않습니다. 하지만 깊은 Transformer가 실제로 학습되도록 만드는 데 결정적인 역할을 합니다.
좋은 아키텍처는 똑똑한 계산만으로 이루어지지 않습니다. 안정적으로 학습되는 구조까지 포함해야 합니다.

깊은 네트워크가 어려운 이유
간단한 신경망을 생각해 봅시다. 입력이 들어오고, 여러 층을 지나며 조금씩 변환되고, 마지막에 예측값이 나옵니다. 학습할 때는 예측이 얼마나 틀렸는지 계산한 뒤, 그 오차를 거꾸로 흘려보내며 각 층의 가중치를 조정합니다. 이것이 역전파입니다.
층이 몇 개 없을 때는 큰 문제가 없습니다. 하지만 층이 깊어질수록 신호는 긴 길을 지나야 합니다. 입력은 수십, 수백 번 변환되어야 하고, 학습 신호도 다시 수십, 수백 층을 거슬러 올라가야 합니다.
이때 두 가지 문제가 자주 생깁니다.
첫째, 값의 크기가 점점 불안정해질 수 있습니다. 어떤 층을 지날 때마다 값이 조금씩 커진다면, 깊은 층에서는 숫자가 지나치게 커집니다. 반대로 값이 조금씩 작아진다면, 뒤쪽으로 갈수록 거의 0에 가까워질 수 있습니다.
둘째, 원래 입력 정보가 흐려질 수 있습니다. 각 층은 입력을 변환합니다. 그런데 변환을 너무 많이 거치다 보면 초기에 있던 정보가 사라지거나 왜곡될 수 있습니다. 예를 들어 문장 초반의 중요한 단서가 여러 층을 지나며 희미해진다면, 모델은 긴 문맥을 제대로 활용하기 어렵습니다.
Transformer는 이런 문제를 피하기 위해 각 Block 안에 안전장치를 넣습니다. Residual Connection은 정보가 사라지지 않도록 우회 도로를 만들어 주고, LayerNorm은 각 층의 값 분포가 너무 흔들리지 않도록 정리해 줍니다.
Residual Connection: 원래 정보를 잃지 않는 우회 도로
Residual Connection은 이름만 들으면 어렵지만, 아이디어는 매우 단순합니다.
어떤 층이 입력 x를 받아 변환 결과 F(x)를 만든다고 합시다. 일반적인 신경망에서는 다음 층으로 F(x)만 넘깁니다.
x → F(x) → 다음 층Residual Connection은 여기에 원래 입력 x를 더합니다.
x → F(x) + x → 다음 층즉, 층이 무언가를 새로 계산하더라도 원래 입력을 완전히 버리지 않습니다. 변환 결과에 원래 정보를 다시 얹어 다음으로 보냅니다.
수식으로 쓰면 다음과 같습니다.
출력 = x + F(x)여기서 F(x)는 Attention일 수도 있고 Feed Forward Network일 수도 있습니다. Transformer Block에서는 보통 Attention 뒤에 한 번, Feed Forward Network 뒤에 한 번 Residual Connection을 사용합니다.
x = x + Attention(x)
x = x + FeedForward(x)이 구조를 도로에 비유해 봅시다. 일반적인 층은 입력이 반드시 복잡한 변환 도로를 지나야 하는 구조입니다. 길이 험하거나 막히면 정보가 제대로 전달되지 않습니다. Residual Connection은 옆에 고속도로를 하나 더 내는 것과 같습니다. 변환 도로를 지나온 정보와 원래 고속도로를 타고 온 정보가 다시 합쳐집니다.
이 덕분에 모델은 두 가지 이점을 얻습니다.
원래 입력 정보를 더 오래 보존할 수 있습니다.
학습 신호가 앞쪽 층까지 더 쉽게 흘러갈 수 있습니다.
각 층은 전체 표현을 새로 만들기보다 "필요한 만큼만 수정"하는 방식으로 학습할 수 있습니다.
마지막 항목이 특히 중요합니다. Residual Connection이 없으면 한 층은 입력을 완전히 다른 표현으로 바꿔야 합니다. 하지만 Residual Connection이 있으면 층은 이렇게 생각할 수 있습니다.
"기존 표현은 일단 유지하고, 내가 보탤 변화만 계산하자."
그래서 F(x)는 전체 답이 아니라, 기존 표현에 더할 수정량처럼 작동합니다. Residual이라는 말도 여기서 나옵니다. 잔차, 즉 남은 차이만 더해 주는 구조입니다.
LLM에서 각 토큰 표현은 여러 층을 지나며 계속 갱신됩니다. 이때 Residual Connection이 없으면 초반에 담긴 토큰의 기본 의미나 위치 정보가 깊은 층에서 손상되기 쉽습니다. Residual Connection은 모델이 새 정보를 추가하면서도 기존 정보를 잃지 않게 해 줍니다.
Attention에도, Feed Forward에도 잔차를 더한다
Transformer Block에는 크게 두 개의 하위 계산이 들어갑니다. 하나는 Multi-Head Self-Attention이고, 다른 하나는 Feed Forward Network입니다.
Attention은 토큰들이 서로를 참고하며 문맥 정보를 섞는 역할을 합니다. Feed Forward Network는 각 토큰의 표현을 더 복잡하게 변환합니다. Residual Connection은 이 두 계산 뒤에 각각 붙습니다.
조금 단순화하면 다음과 같은 흐름입니다.
입력 x
↓
Self-Attention
↓
x + Attention 결과
↓
Feed Forward Network
↓
이전 값 + Feed Forward 결과여기서 중요한 점은 각 단계가 "덮어쓰기"가 아니라 "더하기"라는 것입니다.
예를 들어 어떤 토큰 표현이 "고양이"라는 의미를 담고 있다고 합시다. Attention은 주변 문맥을 보고 이것이 동물로서의 고양이인지, 캐릭터 이름인지, 비유적 표현인지를 보강합니다. Feed Forward Network는 그 표현을 더 정교한 특징 공간으로 바꿉니다. 하지만 Residual Connection 덕분에 원래 표현의 핵심은 계속 유지됩니다.
LLM이 깊은 층을 통과하면서도 문장의 기본 의미를 놓치지 않는 이유 중 하나가 여기에 있습니다.
LayerNorm: 토큰마다 값의 스케일을 정리한다
Residual Connection이 정보의 우회 도로라면, LayerNorm은 도로 위의 속도를 일정하게 맞춰 주는 장치에 가깝습니다.
신경망 안에서 토큰 표현은 벡터입니다. 예를 들어 임베딩 차원이 768이라면, 한 토큰은 768개의 숫자로 표현됩니다.
토큰 하나 = [0.12, -1.31, 0.48, ..., 2.05]모델이 여러 층을 지나며 이 숫자들을 계속 바꾸다 보면 어떤 토큰의 벡터 값은 너무 커지고, 어떤 토큰의 벡터 값은 너무 작아질 수 있습니다. 값의 분포가 층마다 크게 흔들리면 학습이 어려워집니다. 같은 학습률을 적용해도 어떤 층에서는 변화가 너무 크고, 어떤 층에서는 변화가 너무 작아질 수 있기 때문입니다.
LayerNorm은 각 토큰 벡터 내부의 값들을 정리합니다.
입력 텐서의 모양을 B × T × C라고 해 봅시다.
B: 배치 크기
T: 토큰 길이
C: 임베딩 차원LayerNorm은 보통 각 토큰의 C차원 값들을 기준으로 평균과 분산을 계산합니다. 다시 말해, 배치 전체나 문장 전체를 한꺼번에 정규화하는 것이 아니라, 각 토큰의 벡터 하나하나를 따로 정규화합니다.
각 위치의 토큰 벡터 x에 대해
평균을 빼고, 표준편차로 나눈다.수식으로 간단히 쓰면 다음과 같습니다.
LayerNorm(x) = γ × (x - 평균) / √(분산 + ε) + β여기서 γ와 β는 모델이 학습하는 값입니다. 정규화만 하면 모든 표현이 지나치게 비슷한 형태로 고정될 수 있으므로, 모델이 필요에 따라 다시 스케일을 조절하고 이동할 수 있게 해 줍니다. ε는 아주 작은 숫자로, 분산이 0에 가까울 때 나눗셈이 불안정해지는 것을 막습니다.
핵심은 이것입니다.
LayerNorm은 각 토큰 표현의 숫자들이 너무 한쪽으로 치우치지 않도록 정리해 줍니다. 덕분에 다음 층은 비교적 안정적인 입력을 받습니다.
BatchNorm이 아니라 LayerNorm을 쓰는 이유
딥러닝을 공부해 본 적이 있다면 BatchNorm이라는 말을 들어 보았을 수 있습니다. BatchNorm은 여러 데이터 샘플을 묶은 배치 단위로 평균과 분산을 계산해 정규화합니다. 이미지 모델에서 자주 쓰였습니다.
그런데 언어 모델에서는 LayerNorm이 더 자연스럽습니다. 문장은 길이가 제각각이고, 생성 과정에서는 토큰을 하나씩 이어 붙이며 예측합니다. 배치 안의 다른 문장들에 의존해 정규화하면 상황이 복잡해질 수 있습니다.
LayerNorm은 배치의 다른 샘플에 크게 의존하지 않습니다. 각 토큰의 벡터 내부에서 정규화가 이루어지기 때문입니다. 그래서 문장 길이가 달라도, 배치 구성이 달라도 비교적 일관된 방식으로 작동합니다.
Mini GPT를 만들 때도 우리는 nn.LayerNorm(n_embd)처럼 임베딩 차원에 대해 LayerNorm을 적용하게 됩니다. 이 한 줄이 깊은 Transformer Block을 안정적으로 쌓는 데 큰 역할을 합니다.
Post-LN: 원래 Transformer의 흐름
이제 Residual Connection과 LayerNorm을 어디에 배치할지 생각해 봅시다. 둘 다 필요하다는 것은 알았습니다. 그런데 순서는 어떻게 해야 할까요?
먼저 Post-LN 구조를 보겠습니다. Post-LN은 하위 계산을 수행하고 Residual Connection으로 더한 뒤, 그 결과에 LayerNorm을 적용하는 방식입니다.
x = LayerNorm(x + Attention(x))
x = LayerNorm(x + FeedForward(x))이 구조를 그림처럼 풀면 다음과 같습니다.
입력 x
↓
Attention
↓
x와 더함
↓
LayerNorm
↓
Feed Forward
↓
이전 값과 더함
↓
LayerNormPost-LN은 직관적으로 깔끔합니다. 어떤 계산을 하고, 원래 입력을 더하고, 마지막에 결과를 정리합니다. "계산 후 정돈"이라는 흐름이라 이해하기 쉽습니다.
초기의 Transformer 설명에서는 이런 형태가 자주 등장했습니다. 하지만 모델이 매우 깊어질수록 Post-LN은 학습 안정성 측면에서 까다로워질 수 있습니다. 모든 층에서 정규화가 Residual Connection 뒤에 오기 때문에, 깊은 모델에서는 기울기 흐름이 상대적으로 불안정해질 수 있습니다.
물론 Post-LN이 항상 나쁘다는 뜻은 아닙니다. 모델 규모, 초기화, 학습률, 최적화 기법에 따라 잘 작동할 수도 있습니다. 다만 현대 GPT 계열 구현에서는 더 안정적인 학습을 위해 다른 배치를 자주 사용합니다.
Pre-LN: 현대 GPT 계열에서 자주 쓰이는 흐름
Pre-LN은 이름 그대로 LayerNorm을 먼저 적용합니다. 하위 계산에 들어가기 전에 입력을 정규화하고, 그 결과를 Attention이나 Feed Forward Network에 넣습니다. 그리고 나온 결과를 원래 입력에 더합니다.
x = x + Attention(LayerNorm(x))
x = x + FeedForward(LayerNorm(x))흐름을 풀어 쓰면 다음과 같습니다.
입력 x
↓
LayerNorm
↓
Attention
↓
원래 x와 더함
↓
LayerNorm
↓
Feed Forward
↓
이전 값과 더함Pre-LN의 중요한 장점은 Residual 경로가 더 깨끗하게 유지된다는 점입니다. 원래 입력 x가 정규화 이후 결과로 덮이는 것이 아니라, 비교적 직접적인 경로로 다음 층까지 이어집니다. Attention과 Feed Forward Network는 정규화된 입력을 받아 안정적으로 계산하고, 그 결과만 Residual 경로에 더합니다.
Mini GPT를 구현할 때는 보통 Pre-LN 구조를 사용하면 이해하기도 쉽고 학습도 안정적입니다. 코드로 보면 매우 간단합니다.
x = x + self.sa(self.ln1(x))
x = x + self.ffwd(self.ln2(x))이 두 줄은 Transformer Block의 핵심을 거의 그대로 담고 있습니다.
첫 번째 줄에서 self.ln1(x)는 Attention에 들어가기 전 입력을 정리합니다. self.sa(...)는 Multi-Head Self-Attention입니다. 그 결과를 원래 x에 더합니다.
두 번째 줄에서 self.ln2(x)는 Feed Forward Network에 들어가기 전 입력을 다시 정리합니다. self.ffwd(...)는 각 토큰 표현을 비선형적으로 변환합니다. 그 결과를 다시 원래 흐름에 더합니다.
처음에는 이 두 줄이 너무 짧아서 허무해 보일 수 있습니다. 하지만 이 안에는 깊은 Transformer를 안정적으로 쌓기 위한 핵심 설계가 들어 있습니다.
Pre-LN과 Post-LN을 비교해 보기
두 구조를 나란히 놓고 보면 차이가 더 선명합니다.
Post-LN:
x = LayerNorm(x + Attention(x))
x = LayerNorm(x + FeedForward(x))
Pre-LN:
x = x + Attention(LayerNorm(x))
x = x + FeedForward(LayerNorm(x))Post-LN은 각 하위 층의 출력과 Residual을 더한 뒤 정규화합니다. Pre-LN은 각 하위 층에 넣기 전에 정규화합니다.
직관적으로 말하면 Post-LN은 "결과를 만든 다음 정리"하는 방식이고, Pre-LN은 "정리된 입력으로 계산하고, 원래 흐름에 더하기" 방식입니다.
현대 GPT 계열 모델에서는 Pre-LN 또는 그 변형이 자주 사용됩니다. 깊은 모델에서 학습이 더 안정적인 경우가 많기 때문입니다. 특히 우리가 앞으로 만들 Mini GPT에서는 Pre-LN 구조를 사용하면 코드가 단순하면서도 학습 흐름을 이해하기 좋습니다.
다만 여기서 한 가지를 조심해야 합니다. Pre-LN이 모든 상황에서 무조건 정답이라는 뜻은 아닙니다. 실제 대규모 모델은 LayerNorm의 위치뿐 아니라 초기화 방식, 학습률 스케줄, 옵티마이저, 데이터 구성, 정규화 변형까지 함께 고려합니다. 이 책에서는 원리 이해와 작은 모델 구현을 목표로 하므로, 가장 이해하기 쉽고 안정적인 Pre-LN 흐름을 중심으로 보겠습니다.
Dropout: 일부 연결을 잠시 꺼서 과신을 막는다
Transformer Block에서 자주 함께 등장하는 또 다른 장치가 Dropout입니다. Dropout은 학습 중에 일부 값을 무작위로 0으로 만드는 기법입니다.
처음 들으면 이상하게 느껴질 수 있습니다. 열심히 계산한 값을 왜 일부러 버릴까요?
이유는 모델이 특정 경로나 특정 특징에 지나치게 의존하지 않도록 하기 위해서입니다. 예를 들어 학생이 문제를 풀 때 항상 같은 힌트만 보고 답을 맞힌다면, 힌트가 사라졌을 때 실력이 드러납니다. Dropout은 학습 중에 일부 힌트를 일부러 가립니다. 그러면 모델은 더 다양한 신호를 활용하려고 배웁니다.
Transformer에서는 Dropout이 여러 위치에 들어갈 수 있습니다. 예를 들어 Attention 가중치에 적용할 수도 있고, Attention 출력에 적용할 수도 있으며, Feed Forward Network 중간이나 출력에 적용할 수도 있습니다.
Mini GPT에서는 보통 다음과 같은 곳에 Dropout을 넣습니다.
self.dropout = nn.Dropout(dropout)그리고 Attention 결과나 Feed Forward 결과에 적용합니다.
out = self.dropout(out)Dropout은 학습할 때만 활성화되고, 추론할 때는 꺼집니다. PyTorch에서는 model.train() 상태에서는 Dropout이 작동하고, model.eval() 상태에서는 작동하지 않습니다.
Dropout의 목적은 LayerNorm과 조금 다릅니다. LayerNorm은 값의 분포를 안정화해 학습을 부드럽게 만드는 장치입니다. Dropout은 모델이 훈련 데이터에 지나치게 딱 맞춰지는 것을 줄이는 정규화 기법입니다. 둘 다 학습 안정성에 도움을 주지만, 작동 방식은 다릅니다.
작은 데이터셋으로 Mini GPT를 학습할 때 Dropout은 특히 중요할 수 있습니다. 데이터가 적으면 모델이 예문을 거의 외워 버리기 쉽습니다. Dropout은 이런 과한 암기를 어느 정도 줄이는 역할을 합니다. 물론 Dropout 값을 너무 크게 잡으면 학습 자체가 어려워질 수 있으므로, 작은 모델에서는 0.1이나 0.2 같은 값을 먼저 실험해 보는 것이 좋습니다.
Mini GPT의 Block은 어떻게 생겼을까
이제 지금까지 배운 내용을 코드 구조로 연결해 봅시다. 앞으로 구현할 Mini GPT에서 하나의 Transformer Block은 대략 다음 부품으로 이루어집니다.
Transformer Block
├─ LayerNorm
├─ Multi-Head Self-Attention
├─ Residual Connection
├─ LayerNorm
├─ Feed Forward Network
└─ Residual ConnectionPre-LN 구조를 사용하면 코드의 흐름은 다음처럼 됩니다.
class Block(nn.Module):
def __init__(self, n_embd, n_head, dropout):
super().__init__()
self.sa = MultiHeadAttention(n_head, n_embd, dropout)
self.ffwd = FeedForward(n_embd, dropout)
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아직 MultiHeadAttention과 FeedForward의 전체 구현을 다 적지는 않았지만, Block 내부의 순서는 충분히 볼 수 있습니다.
먼저 ln1으로 입력을 정리한 뒤 Self-Attention에 넣습니다. Attention 결과를 원래 입력 x에 더합니다. 그다음 ln2로 다시 정리한 뒤 Feed Forward Network에 넣습니다. Feed Forward 결과를 다시 현재 x에 더합니다.
이 코드에서 가장 중요한 두 줄은 다음입니다.
x = x + self.sa(self.ln1(x))
x = x + self.ffwd(self.ln2(x))왼쪽의 x +가 Residual Connection입니다. 괄호 안의 self.ln1(x)와 self.ln2(x)가 LayerNorm입니다. 이 짧은 코드가 깊은 Transformer를 가능하게 하는 핵심 구조입니다.
Feed Forward Network 안의 Dropout 예시
지난 장에서 본 Feed Forward Network에 Dropout을 넣으면 다음과 같은 형태가 됩니다.
class FeedForward(nn.Module):
def __init__(self, n_embd, dropout):
super().__init__()
self.net = nn.Sequential(
nn.Linear(n_embd, 4 * n_embd),
nn.GELU(),
nn.Linear(4 * n_embd, n_embd),
nn.Dropout(dropout),
)
def forward(self, x):
return self.net(x)여기서 첫 번째 선형층은 차원을 넓히고, GELU는 비선형성을 넣고, 두 번째 선형층은 다시 원래 임베딩 차원으로 줄입니다. 마지막 Dropout은 Feed Forward 결과 일부를 학습 중에 무작위로 꺼 줍니다.
왜 마지막 차원이 다시 n_embd여야 할까요? Residual Connection 때문입니다.
x = x + self.ffwd(self.ln2(x))덧셈을 하려면 두 텐서의 모양이 같아야 합니다. x의 마지막 차원이 n_embd라면, self.ffwd(...)의 출력도 n_embd여야 합니다. 그래서 Feed Forward Network는 중간에서 차원을 넓히더라도 마지막에는 다시 원래 크기로 돌아옵니다.
이런 관점으로 보면 Transformer의 여러 부품이 서로 얼마나 촘촘히 맞물려 있는지 알 수 있습니다. Residual Connection은 단순히 더하기 하나가 아니라, 각 하위 모듈의 입출력 차원 설계까지 결정합니다.
Attention 출력에도 Dropout을 넣을 수 있다
Multi-Head Attention에서도 Dropout을 사용할 수 있습니다. 예를 들어 각 head가 만든 출력을 합친 뒤 마지막 선형층을 통과시키고 Dropout을 적용할 수 있습니다.
class MultiHeadAttention(nn.Module):
def __init__(self, num_heads, n_embd, dropout):
super().__init__()
self.heads = nn.ModuleList([
Head(n_embd // num_heads, n_embd, dropout)
for _ in range(num_heads)
])
self.proj = nn.Linear(n_embd, n_embd)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
out = torch.cat([h(x) for h in self.heads], dim=-1)
out = self.proj(out)
out = self.dropout(out)
return out여기서도 마지막 출력 차원은 n_embd입니다. 그래야 Block에서 다음 덧셈이 가능합니다.
x = x + self.sa(self.ln1(x))Attention도, Feed Forward도 결국 같은 규칙을 따릅니다. 내부에서 아무리 복잡한 계산을 하더라도, Residual Connection으로 돌아오기 위해서는 입력과 같은 모양의 출력을 만들어야 합니다.
하나의 토큰이 Block을 통과하는 과정
이번에는 한 토큰의 입장에서 Block을 지나가는 과정을 상상해 봅시다.
문장에 "고양이가 창밖을 보았다"라는 토큰들이 들어왔다고 합시다. 각 토큰은 임베딩과 위치 정보가 더해진 벡터로 시작합니다.
먼저 "보았다" 토큰의 벡터가 LayerNorm을 통과합니다. 값의 평균과 분산이 정리되어 Attention에 들어가기 좋은 상태가 됩니다. 그런 다음 Self-Attention은 "보았다"가 어떤 토큰들을 참고해야 하는지 계산합니다. 아마 "고양이가"와 "창밖을" 같은 토큰에서 중요한 정보를 가져올 것입니다.
Attention 결과는 원래 "보았다" 벡터에 더해집니다. 이제 이 토큰은 자기 자신이 원래 갖고 있던 정보에 문맥 정보가 덧붙은 상태가 됩니다.
다음으로 이 벡터는 다시 LayerNorm을 통과합니다. 그리고 Feed Forward Network가 각 차원의 조합을 비선형적으로 바꾸며 더 풍부한 특징을 만듭니다. 그 결과가 다시 원래 흐름에 더해집니다.
하나의 Block이 끝나면 "보았다"라는 토큰 표현은 조금 더 문맥적인 표현이 됩니다. 다음 Block에서는 이 표현이 또 정규화되고, Attention을 거치고, Residual로 보존되고, Feed Forward로 변환됩니다.
이 과정이 여러 번 반복되면서 각 토큰은 점점 더 깊은 문맥 정보를 담게 됩니다.
왜 이 장치들이 LLM의 '실력'과 연결될까
LLM의 성능을 이야기할 때 우리는 보통 파라미터 수, 데이터 크기, 컨텍스트 길이, Attention 구조 같은 화려한 요소를 떠올립니다. 물론 모두 중요합니다. 하지만 모델이 실제로 제대로 학습되려면 숫자의 흐름이 안정적이어야 합니다.
Residual Connection이 없으면 깊은 층을 쌓을수록 원래 정보와 학습 신호가 약해질 수 있습니다. LayerNorm이 없으면 층마다 값의 분포가 흔들려 학습이 불안정해질 수 있습니다. Dropout이 없으면 작은 데이터에서는 모델이 예문을 지나치게 외워 버릴 수 있습니다.
이 장치들은 각각 다른 방식으로 모델을 돕습니다.
Residual Connection은 정보와 기울기가 지나갈 우회 경로를 만듭니다.
LayerNorm은 각 토큰 표현의 값 분포를 안정화합니다.
Dropout은 학습 중 일부 신호를 꺼서 과한 의존과 과적합을 줄입니다.
이 셋은 Transformer의 계산을 더 똑똑하게 만든다기보다, 똑똑한 계산이 안정적으로 학습되도록 바닥을 다져 줍니다.
건물을 높게 올리려면 멋진 외관보다 먼저 튼튼한 구조가 필요합니다. Transformer에서 LayerNorm과 Residual Connection은 바로 그 구조에 해당합니다.
Mini GPT 구현에서 기억해야 할 순서
앞으로 Mini GPT를 직접 만들 때, Block 내부 순서를 헷갈릴 수 있습니다. 그때는 다음 흐름을 기억하면 됩니다.
Pre-LN Transformer Block
1. Attention 전에 LayerNorm
2. Attention 결과를 원래 x에 더함
3. Feed Forward 전에 LayerNorm
4. Feed Forward 결과를 원래 x에 더함코드로는 다시 이 두 줄입니다.
x = x + self.sa(self.ln1(x))
x = x + self.ffwd(self.ln2(x))그리고 여러 Block을 쌓은 뒤에는 마지막에 한 번 더 LayerNorm을 적용하는 경우가 많습니다.
self.blocks = nn.Sequential(*[Block(n_embd, n_head, dropout) for _ in range(n_layer)])
self.ln_f = nn.LayerNorm(n_embd)
self.lm_head = nn.Linear(n_embd, vocab_size)전체 forward 흐름은 대략 이렇게 됩니다.
x = token_embedding + position_embedding
x = self.blocks(x)
x = self.ln_f(x)
logits = self.lm_head(x)ln_f는 여러 Transformer Block을 모두 통과한 최종 표현을 한 번 더 정리해 주는 역할을 합니다. 그다음 lm_head는 각 위치에서 다음 토큰 후보들의 점수, 즉 logits를 만듭니다.
이제 우리는 Transformer Block을 구성하는 거의 모든 핵심 부품을 만났습니다. 토큰 임베딩, 위치 임베딩, Self-Attention, Multi-Head Attention, Feed Forward Network, LayerNorm, Residual Connection까지 왔습니다.
다음 장에서는 이 부품들을 하나의 그림으로 합쳐 보겠습니다. 지금까지 따로 배운 조각들이 어떻게 GPT라는 구조 안에서 연결되는지 한눈에 정리할 차례입니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.