English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

게스트
1 / ?
수업 목록으로

어텐션 + MLP, 반복

Transformer Block Pre-Norm Structure


두 개의 하위 계층

트랜스포머 블록은 정확히 두 개의 하위 계층으로 구성되며, 각 계층은 [batch, seq_len, d_model] 형태의 토큰 시퀀스를 입력으로 받습니다:


1. 멀티헤드 어텐션 서브레이어. 토큰들이 서로를 바라봅니다. 이 내용은 Activity 68에서 자세히 다루었습니다. 출력 형태는 입력 형태와 동일합니다.

2. 피드포워드 MLP 서브레이어. 각 토큰은 독립적으로 2층 퍼셉트론을 통해 변환됩니다. 토큰 간 정보 흐름은 없습니다. 출력 형태는 입력 형태와 동일합니다.


두 서브레이어 모두 [batch, seq_len, d_model] 형태를 유지합니다. 이 형태 유지는 블록을 쌓을 수 있게 해줍니다. 즉, N번째 레이어의 출력이 N+1번째 레이어의 입력으로 그대로 전달되므로 형태 변환 없이 연결할 수 있습니다.


각 서브레이어가 담당하는 역할

어텐션은 위치 간 정보를 이동시킵니다. 위치 17에 있는 토큰은 위치 1~16의 정보를 가져올 수 있습니다. MLP은 각 위치 내 정보를 변환합니다. 토큰의 표현은 학습된 비선형 함수를 통해 재구성되지만, 주변 토큰을 참조하지는 않습니다.


블록을 쌓으면 이 두 연산이 번갈아 수행됩니다. Layer 1 어텐션은 위치를 섞고, Layer 1 MLP는 위치별로 표현을 재구성합니다. Layer 2 어텐션은 다시 섞되, 이번에는 재구성된 표현을 대상으로 합니다. 이러한 번갈아 수행은 깊이에 따라 표현력을 키웁니다.


ANDREA의 스택


변형n_layern_headd_modelmlp_dim
ANDREA-12M6123841536
ANDREA-120M12127683072
ANDREA-480M162415366144

mlp_dim = 4 × d_model 비율이 이 계열 전체에서 유지됩니다. 이 비율은 거의 모든 현대 트랜스포머에서도 동일하게 나타납니다. Section 3에서 그 이유를 살펴봅니다.

서브레이어 이름 짓기

트랜스포머 블록은 두 개의 서브레이어를 포함합니다. 순서대로 이름을 말하고, 각 서브레이어가 위치 간 정보를 이동시키는지(token-to-token) 아니면 단일 위치 내에서 정보를 변환하는지(independent per token) 설명하세요. 서브레이어당 한 문장으로 답하세요.

Why Skip Connections Matter

The Residual Pattern

각 하위 계층은 계산을 잔차 연결(residual connection)로 감쌉니다. 출력은 입력을 다시 더합니다:


x = x + Attention(LayerNorm(x))     # attention sublayer
x = x + MLP(LayerNorm(x))           # MLP sublayer

각 서브레이어 내부에서 Attention(...) 또는 MLP(...) 함수는 델타를 생성합니다. 블록은 입력을 대체하지 않고 학습된 보정을 더합니다.


왜 중요한가

잔차 연결이 깊은 구조에서 지배적인 세 가지 이유:


1. 그래디언트 흐름. 역전파 과정에서 덧셈은 그래디언트가 서브레이어를 우회하여 출력에서 입력으로 직접 전달되는 경로를 제공합니다. 잔차 연결이 없는 12층 스택은 임베딩까지 그래디언트 신호가 도달하기 전에 크게 소실되지만, 잔차 연결을 사용하면 수백 층까지도 그래디언트 크기가 유효하게 유지됩니다.


2. 항등 초기화. 초기화 시 서브레이어 가중치는 작은 출력을 생성합니다. 잔차 연결 덕분에 N번째 레이어는 처음에 거의 변화 없이 통과합니다. 학습은 동작하는 시작점에서 점진적으로 델타를 학습합니다.


3. 구성적 학습. 각 블록은 대체가 아닌 정제를 학습합니다. Layer 1은 위치 정보를 추가할 수 있고, Layer 2는 구문 구조를 추가할 수 있으며, Layer 7은 의미 관계를 추가할 수 있습니다. 잔차 스트림은 이를 누적합니다.


레이어 정규화

각 서브레이어 앞에서 LayerNorm은 각 토큰의 표현을 평균 0, 분산 1로 재조정한 후, 학습된 특징별 게인과 바이어스를 적용합니다:


y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta

Mean & variance get computed across the d_model dimension, separately for every token. Two learned vectors (gamma, beta, each shape [d_model]) restore expressive scale. Normalization keeps activations in a numerically stable range; without it, small training instabilities snowball into NaN gradients.

Pre-Norm vs Post-Norm

A Subtle But Critical Choice

잔차 하위 계층에 레이어 정규화를 연결하는 두 가지 방법:


Post-norm (2017년 원본 논문):

x = LayerNorm(x + Attention(x))

레이어 정규화는 잔차 덧셈 이후에 위치합니다. 잔차 스트림 자체는 매 레이어에서 정규화됩니다.


Pre-norm (현대 표준, ANDREA에서 사용):

x = x + Attention(LayerNorm(x))

레이어 정규화는 서브레이어 이전에 위치하며, 잔차 경로 내부에 있습니다. 잔차 스트림은 정규화되지 않은 상태로 유지되며, 서브레이어의 입력만 재조정됩니다.


Pre-Norm이 승리한 이유

Post-norm은 학습률 워밍업과 세심한 하이퍼파라미터 튜닝 없이는 학습이 잘 되지 않습니다. 각 레이어 정규화가 잔차 스트림에 누적된 상태를 뒤섞기 때문에 초기 레이어에서 그래디언트가 폭발합니다. 2017년 원본 논문은 광범위한 튜닝을 통해 post-norm을 사용했으나, 이후 GPT-2, LLaMA, ANDREA 등은 pre-norm을 표준으로 채택했습니다.


Pre-norm은 안정적으로 학습합니다. Residual stream은 모든 레이어에서 깨끗하게 누적되며, 오직 sublayer 입력만 수치 안정을 위해 정규화됩니다. 현대 트랜스포머는 기본적으로 pre-norm을 사용하며, ANDREA도 이 방식을 따릅니다.


최종 블록 수식

Residual, pre-norm 위치의 Layer Norm, 그리고 두 개의 sublayer를 결합하면 ANDREA의 전체 블록이 됩니다:


def block_forward(x):
x = x + Attention(LayerNorm(x))   # attention sublayer
x = x + MLP(LayerNorm(x))         # MLP sublayer
return x

두 개의 서브레이어, 두 번의 잔차 연결, 두 번의 레이어 정규화 (각 서브레이어에 자체 레이어 정규화가 있음; ANDREA-120M은 12개 블록에 24개의 레이어 정규화가 있고 출력에 최종 1개가 더해 총 25개). 이를 12번 반복. 이것이 ANDREA-120M의 트렁크입니다.

Pre-Norm이 학습을 안정화하는 이유

ANDREA는 pre-norm을 사용합니다: `x = x + Attention(LayerNorm(x))`. post-norm(`x = LayerNorm(x + Attention(x))`)과 비교하세요. 깊은 스택에서 pre-norm이 post-norm보다 학습이 더 안정적인 이유를 그래디언트 흐름 관점에서 하나 제시하세요. 답변에서 residual stream을 언급하세요.

Two Linear Layers, One Activation

Three Operations

The MLP sublayer is a two-layer perceptron with a non-linear activation between the layers:


def mlp_forward(x):
h = x · W_1 + b_1        # 확장: d_model → mlp_dim
h = GELU(h)              # 비선형 활성화
y = h · W_2 + b_2        # 축소: mlp_dim → d_model
return y

세 가지 연산. 두 개는 선형, 하나는 비선형. 첫 번째 선형 연산은 폭을 확장하고, 두 번째 선형 연산은 다시 축소합니다.


4× 확장 비율

최신 트랜스포머는 mlp_dim = 4 × d_model로 설정합니다. ANDREA-120M:


d_model = 768
mlp_dim = 4 × 768 = 3072
W_1 shape = [768, 3072]      # ~2.36M params
W_2 shape = [3072, 768]      # ~2.36M params
MLP params per block = 4.72M (ignoring biases)

두 개의 MLP가 모든 어텐션 하위 계층 사이에 위치합니다 (각 블록당 하나). 12개 블록 × 4.72M ≈ 56.6M MLP 파라미터가 ANDREA-120M의 전체 파라미터 중 약 절반을 차지합니다.


왜 4×인가

4× 비율은 실험적으로 도출되었습니다. 더 작은 비율은 모델 용량을 줄이고, 더 큰 비율은 파라미터당 수익 체감 현상을 일으킵니다. 수십 년간의 아키텍처 탐색에서 4× 비율이 지속적으로 유지되었으며, GPT, BERT, T5, LLaMA, ANDREA 등에서 사용됩니다.


최근 연구(PaLM, Chinchilla)에서는 게이팅 메커니즘(SwiGLU)을 사용하면 8/3× 확장으로 비슷한 용량을 더 적은 비용으로 달성할 수 있음을 발견했습니다. ANDREA는 단순성을 위해 기존 GELU + 4× 방식을 유지합니다.

GELU: 부드러운 활성화 함수

GELU가 계산하는 것

GELU(Gaussian Error Linear Unit)는 현대 트랜스포머의 MLP 레이어 사이에 사용되는 표준 활성화 함수입니다. 공식은 다음과 같습니다:


GELU(x) = x · Φ(x)

Φ(x)는 표준정규분포의 누적분포함수입니다. 즉, 표준 가우시안 확률변수가 x 이하일 확률을 나타냅니다. 수치적으로 계산됩니다:


Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))

영역별 동작

- 큰 양수 x: Φ(x) ≈ 1이므로 GELU(x) ≈ x. ReLU와 유사.

- 큰 음수 x: Φ(x) ≈ 0이므로 GELU(x) ≈ 0. ReLU와 유사.

- x = 0 근처: Φ(x) ≈ 0.5이므로 GELU(0) = 0. 원점을 통과하는 부드러운 전환.


ReLU와 달리 GELU는 Φ(x)에 의해 가중치가 적용된 일부 음수 입력을 통과시킵니다. 작은 음수 입력은 여전히 작은 음수 출력을 기여하지만, 전체 입력보다는 작습니다.


GELU가 ReLU를 능가한 이유

실험적으로, GELU를 사용해 학습한 트랜스포머는 동일한 파라미터 수에서 ReLU를 사용한 트랜스포머보다 더 낮은 손실에 도달합니다. 0 근처에서의 부드러움이 중요합니다. ReLU는 0에서 급격히 끊어져 그래디언트 불연속성을 발생시키지만, GELU의 부드러운 곡선은 역전파에 더 깨끗한 그래디언트를 제공합니다.


ANDREA의 학습 엔진 microgpt_cuda.cu는 직접 작성한 GELU CUDA 커널을 포함합니다. 이 커널은 위의 tanh 근사를 사용하며, 최신 GPU는 tanh를 단일 명령어 연산으로 지원합니다.

MLP 파라미터 계산하기

ANDREA-120M은 `d_model=768`, `mlp_dim=3072`, `n_layer=12`를 가집니다. 12개 블록 전체에서 MLP 가중치 행렬(`W_1` & `W_2`)의 총 파라미터 수를 계산하세요. 편향은 무시합니다. 계산 과정을 보여주고, ANDREA-120M의 약 120M 전체 파라미터 중 이 값이 차지하는 비율을 소수점 첫째 자리까지 반올림하여 제시하세요.

Twelve Blocks Compose Into ANDREA-120M

From Block to Model

ANDREA-120M의 전체 순전파:


def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer):       # 12 blocks
x = block_forward(x)               # attention + MLP w/ residuals
x = LayerNorm(x)                       # final norm
logits = x · token_embed.T             # tied weights for output projection
return logits

여섯 줄. 대부분의 연산은 block_forward 안에 있으며, 이 함수는 열두 번 호출됩니다. 임베딩이 파이프라인을 시작하고, tied unembedding(입력 조회에 사용된 동일한 행렬을 출력 투영을 위해 전치한 것)이 파이프라인을 마무리합니다.


깊이로서의 합성

각 블록은 residual stream을 읽고, 델타를 계산한 뒤 다시 더합니다. 열두 번의 과정을 거치면 스트림에는 모든 블록의 누적된 기여가 담기게 됩니다. 내부적으로 레이어들은 다음과 같이 전문화되는 경향이 있습니다:


- 초기 레이어 (1-3): 구문 패턴, 위치 구조

- 중간 레이어 (4-8): 단어 간 관계, 구 경계

- 후기 레이어 (9-12): 의미적 내용, 사실 회상


이 전문화는 아키텍처 선택이 아닌 학습 압력에서 비롯됩니다. 동일한 균일 블록 설계도 언어 학습 시 전문화된 레이어를 생성합니다.


총 블록 파라미터


구성 요소블록당12개 블록 전체
어텐션 프로젝션 (4×W)2.36M28.3M
MLP 가중치 (W_1 + W_2)4.72M56.6M
Layer norm (gamma, beta)~3K (무시 가능)~37K
블록당 총합~7.1M~85M

트렁크에 85M개의 파라미터가 있습니다. 토큰 임베딩에 약 13M개(8449 vocab × 768 d_model × 2, tied input/output)를 더하고 마지막 layer norm을 추가하면 ANDREA-120M의 총 파라미터 수는 약 120M이 됩니다. 블록 설계가 3분의 2를 차지하고, 임베딩이 나머지를 차지합니다.

한 블록을 통과하는 토큰 추적

768차원 토큰 벡터가 ANDREA-120M의 7번째 블록에 들어갑니다. pre-norm 구조에서 블록 내부에서 이 벡터에 어떤 일이 일어나는지 단계별로 설명하세요. 두 개의 layer norm, 두 개의 sublayer, 두 번의 residual addition, 그리고 최종 shape를 언급하세요. residual stream이 전혀 수정되지 않는 지점과 수정되는 지점을 각각 하나씩 지적하세요.