Paper Review논문·원문 분석

Attention Is All You Need를 읽으며 다시 본 Transformer의 본질

RNN을 없앴다는 한 문장을 넘어, Transformer가 토큰 사이의 정보 이동을 어떻게 다시 설계했는지 계산 흐름으로 읽었다.

논문을 한 번 읽었지만 아직 “이해했다”고 말하기는 어렵다. 그림 속 블록 이름은 익숙해졌는데, 왜 이 구조여야 하는지를 설명하려고 하면 중간중간 막혔다. 특히 Query, Key, Value를 데이터베이스의 조회문이나 사전의 표제어처럼 비유하는 순간에는 이해한 것 같다가도 수식으로 돌아오면 다시 흐려졌다.

그래서 이번에는 용어를 외우는 대신 하나만 추적했다.

한 토큰의 표현에 다른 토큰의 정보를 어떤 규칙으로 섞는가?

지금 내가 이해한 Transformer의 중심은 문맥에 따라 매번 다시 만들어지는 정보 전달 경로다. Attention은 토큰마다 “누구의 정보를 얼마나 가져올지” 계산한다. Feed-forward network는 그렇게 모인 정보를 각 위치에서 변환한다. Residual connection과 layer normalization은 이 과정을 여러 층 쌓을 수 있게 받쳐준다.

논문이 바꾸려 했던 것

Attention Is All You Need는 2017년 기계 번역을 다룬 논문이다. 당시 주류였던 RNN 계열은 이전 시점의 hidden state를 다음 시점 계산에 사용했다. 문장의 순서를 자연스럽게 처리하지만, 10번째 토큰을 계산하려면 앞선 계산을 순서대로 거쳐야 한다. 학습 시퀀스 안의 계산을 한꺼번에 처리하기 어렵다는 뜻이다.

Transformer는 recurrence와 convolution을 제거하고 self-attention으로 토큰 사이의 의존성을 계산했다. 입력 토큰 전체의 Query, Key, Value를 행렬로 만들면 같은 층의 attention을 큰 행렬 연산으로 처리할 수 있다. 논문의 중요한 주장은 단순히 “attention의 성능이 좋다”가 아니다. 시퀀스 안의 정보 교환 방식을 GPU가 병렬로 계산하기 좋은 형태로 바꿨다는 데 가깝다.

여기서 한 가지는 구분해야 한다. 학습 중에는 정답 시퀀스 전체를 알고 있으므로 causal mask를 적용한 상태에서 여러 위치를 병렬 계산할 수 있다. 하지만 autoregressive 생성은 다음 토큰이 나와야 그 토큰을 입력에 붙일 수 있다. Transformer가 학습의 순차성을 크게 줄였다고 해서 생성까지 완전히 병렬이 된 것은 아니다.

원 논문의 전체 구조

원 논문의 Transformer는 번역을 위한 encoder-decoder 구조다.

source tokens
  → token embedding + sinusoidal positional encoding
  → encoder block × 6
      ├─ multi-head self-attention
      └─ position-wise feed-forward network
  → encoder memory

target tokens shifted right
  → token embedding + sinusoidal positional encoding
  → decoder block × 6
      ├─ masked multi-head self-attention
      ├─ encoder-decoder cross-attention
      └─ position-wise feed-forward network
  → linear projection → softmax → next-token probability

각 sub-layer 바깥에는 residual connection이 있고, 원 논문은 그 합을 layer normalization에 넣는 post-LN 구조를 사용했다.

LayerNorm(x + Sublayer(x))

요즘 GPT 구현에서 자주 보는 순서는 다를 수 있다. 예를 들어 minGPT는 layer normalization을 attention과 MLP 앞에 두는 pre-LN 형태다.

x = x + Attention(LayerNorm(x))
x = x + MLP(LayerNorm(x))

둘을 모두 “Transformer block”이라고 부르기 때문에 논문 그림과 GPT 코드를 바로 대조하면 순서가 헷갈린다. 원형과 후속 구현을 분리해서 봐야 한다.

Query, Key, Value를 계산으로 다시 보기

입력 표현을 X라고 두자. 시퀀스 길이가 T, 모델 차원이 d_model이면 한 샘플의 입력은 대략 다음 모양이다.

X: [T, d_model]

학습 가능한 세 행렬을 곱해 Query, Key, Value를 만든다.

Q = XW_Q   # [T, d_k]
K = XW_K   # [T, d_k]
V = XW_V   # [T, d_v]

같은 X에서 출발해도 서로 다른 선형 투영을 거치므로 역할이 갈린다.

  • Query: 현재 위치가 어떤 정보를 찾는지 비교에 사용하는 표현
  • Key: 각 위치가 Query와 얼마나 관련 있는지 비교되는 표현
  • Value: 관련도가 정해진 뒤 실제로 섞이는 정보

이 설명은 입문할 때 유용하지만 Q가 실제 질문 문장이고 K가 데이터베이스 키라는 뜻은 아니다. 셋 모두 학습으로 만들어지는 벡터다. 모델은 loss를 줄이는 방향으로 W_Q, W_K, W_V를 조정한다. 역할은 사람이 미리 적어 넣는 것이 아니라 계산 구조가 부여한다.

왜 Q와 K를 비교하고 V를 가져올까

한 위치 i의 Query와 모든 위치 j의 Key를 내적하면 ij를 얼마나 참고할지 결정하기 위한 점수를 얻는다.

score(i, j) = q_i · k_j

점수만으로는 가중치가 되지 않는다. 스케일을 조정하고 softmax를 적용한다.

weight(i, :) = softmax(score(i, :) / sqrt(d_k))

마지막으로 가중치에 Value를 곱해 더한다.

output_i = Σ_j weight(i, j) · v_j

행렬 한 줄로 쓰면 논문의 scaled dot-product attention이 된다.

Attention(Q, K, V) = softmax(QKᵀ / sqrt(d_k))V

이 계산에서 attention score는 답이 아니라 정보를 섞는 비율을 만들기 전의 호환도다. softmax를 지난 값이 각 Query 행에서 합이 1인 attention weight가 되고, 그 weight가 Value를 가중합한다.

왜 sqrt(d_k)로 나눌까

처음에는 “숫자가 커지니 적당히 줄인다” 정도로 이해했다. 논문 설명을 따라가면 이유가 더 구체적이다.

Query와 Key의 각 성분이 평균 0, 분산 1인 독립 변수라고 단순화하자. d_k개의 곱을 더한 내적은 분산이 d_k에 비례해 커진다. 차원이 커질수록 내적의 절댓값도 커지기 쉽다. 큰 값이 softmax에 들어가면 확률이 한쪽으로 지나치게 몰리고, 포화된 영역에서는 gradient가 매우 작아진다.

sqrt(d_k)로 나누면 내적의 표준편차 크기를 다시 비슷한 범위로 맞출 수 있다.

Var(q · k) ≈ d_k
Std(q · k) ≈ sqrt(d_k)

즉 scaling은 장식이 아니라 head dimension이 커져도 softmax가 학습 가능한 범위에서 움직이게 하는 안정화 장치다. minGPT 코드에서도 같은 계산을 그대로 확인할 수 있다.

Multi-head attention은 무엇을 늘리는가

하나의 attention만 사용하면 모든 관계를 하나의 가중치 행렬에 압축해야 한다. Multi-head attention은 모델 차원을 여러 head로 나누고, head마다 별도의 Q, K, V 투영을 학습한다.

head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
MultiHead = Concat(head_1, ..., head_h)W_O

원 논문의 base model은 d_model=512, h=8, d_k=d_v=64를 사용했다. 각 head가 64차원에서 계산하고, 결과를 이어 붙이면 다시 512차원이 된다. head 수가 8이라고 계산량이 단순히 8배가 되는 구조는 아니다. full dimension 하나를 여러 작은 head로 나눴기 때문이다.

흔히 “한 head는 문법, 다른 head는 대명사 관계를 본다”고 설명한다. 실제로 특정 head에서 그런 패턴을 관찰할 수 있지만, 모든 head에 사람이 읽을 수 있는 역할이 하나씩 배정된다고 단정하면 안 된다. 내가 받아들인 핵심은 조금 다르다.

하나의 유사도 기준으로 모든 관계를 평균내지 말고, 서로 다른 투영 공간에서 여러 정보 전달 경로를 동시에 학습한다.

Attention 뒤에 FFN이 필요한 이유

Attention만 보면 Transformer가 전부 토큰 사이의 관계 계산처럼 보인다. 하지만 각 block에는 position-wise feed-forward network가 따라온다.

원 논문의 FFN은 각 위치에 같은 두 개의 선형 변환과 ReLU를 독립적으로 적용한다.

FFN(x) = max(0, xW_1 + b_1)W_2 + b_2

base model에서는 512차원을 2048차원으로 넓혔다가 다시 512차원으로 줄인다. 이 층은 위치끼리 정보를 직접 섞지 않는다. 모든 토큰에 같은 가중치를 쓰되 토큰별로 독립 실행한다.

내 머릿속에서는 두 역할을 이렇게 나누는 편이 이해하기 쉬웠다.

  • Attention: 다른 위치에서 어떤 정보를 가져올지 결정한다.
  • FFN: 가져온 정보를 현재 위치 안에서 비선형 변환한다.

둘 중 하나만으로는 부족하다. Attention은 동적인 routing 또는 mixing에 가깝고, FFN은 각 위치의 feature transformation에 가깝다.

Residual connection과 LayerNorm은 왜 붙어 있을까

Residual connection은 sub-layer가 만든 변화량을 기존 표현에 더한다.

y = x + Sublayer(x)

이 우회 경로가 있으면 깊은 층에서도 이전 표현과 gradient가 지나갈 길이 남는다. 모든 층이 표현 전체를 처음부터 다시 만들기보다 필요한 수정값을 학습할 수 있다.

Layer normalization은 한 토큰 표현의 feature 차원을 기준으로 평균과 분산을 정규화하고, 학습 가능한 scale과 bias를 적용한다. 배치 전체 통계에 의존하는 batch normalization과 기준 축이 다르다. 시퀀스 길이나 배치 구성의 변화가 있는 언어 모델에서 다루기 편한 이유 중 하나다.

다만 “Residual은 정보 보존, LayerNorm은 값 안정화”라는 한 줄만 외우고 끝내면 실제 코드를 읽을 때 다시 막힌다. 정규화가 합산 전인지 후인지, 마지막 layer norm이 별도로 있는지를 함께 봐야 한다. 이 차이가 original Transformer, GPT-1, GPT-2 계열 코드를 대조할 때 중요하다.

위치 정보: 원 논문의 Positional Encoding과 RoPE는 다르다

Self-attention 자체는 토큰의 순서를 모른다. 입력 행을 같은 방식으로 섞으면 순서가 바뀌었다는 사실을 계산에 넣을 근거가 없다. 원 논문은 embedding과 같은 차원의 sinusoidal positional encoding을 더했다.

input = token_embedding + positional_encoding

짝수·홀수 차원에 서로 다른 주기의 sine과 cosine 값을 사용한다. 논문은 고정된 offset의 위치 표현을 다른 위치 표현의 선형 함수로 나타낼 수 있다는 점, 학습 때보다 긴 시퀀스로 외삽할 가능성을 선택 이유로 들었다. 학습형 position embedding도 실험했으며 번역 성능은 거의 비슷했다고 기록했다.

RoPE(Rotary Position Embedding)는 이 논문에 들어 있지 않다. RoFormer 논문이 2021년에 제안한 후속 방식이다. 위치 벡터를 token embedding에 단순히 더하는 대신 Query와 Key의 2차원 성분 쌍을 위치별 각도로 회전시킨다. 그러면 회전된 Query와 Key의 내적에 상대적 위치 차이가 반영된다.

정리하면 다음과 같다.

방식위치가 들어가는 지점핵심 모양
Sinusoidal PEattention 전에 embedding에 더함절대 위치마다 고정 sine/cosine 벡터
Learned position embeddingattention 전에 embedding에 더함위치별 학습 가능한 벡터
RoPE주로 attention의 Q와 K에 적용위치에 따라 벡터를 회전해 상대 거리 반영

RoPE까지 한꺼번에 “Transformer의 positional encoding”으로 묶어 외우면 원 논문과 현대 LLM 구현이 섞인다. 지금은 계보를 분리해 두는 것이 맞다.

Encoder-only, Decoder-only, Encoder-decoder

Transformer라는 이름 아래 세 구조가 자주 섞인다. 차이는 단순히 block을 어느 쪽에서 잘라냈느냐보다 어떤 정보 접근을 허용하고 어떤 학습 목표를 두느냐에 있다.

Encoder-only

Encoder self-attention은 각 토큰이 입력의 왼쪽과 오른쪽을 모두 볼 수 있다. 문장 전체를 읽고 표현을 만드는 데 적합하다. BERT 계열의 masked language modeling, 분류, 토큰 분류, 임베딩 같은 작업이 대표적이다.

attention visibility: 모든 입력 위치 ↔ 모든 입력 위치

Decoder-only

Decoder-only 모델은 causal mask로 미래 위치를 가린다. 각 위치는 자기 자신과 앞선 토큰만 본다. 학습 목표는 보통 다음 토큰 예측이다.

P(x) = Π_t P(x_t | x_<t)

GPT 계열이 여기에 해당한다. 원 Transformer decoder에 있던 encoder-decoder cross-attention은 제거되고, masked self-attention과 FFN을 반복한다.

Encoder-decoder

Encoder는 source 전체를 양방향으로 읽어 memory를 만든다. Decoder는 이미 생성된 target만 보면서 cross-attention으로 encoder memory를 참고한다. 번역, 요약처럼 입력과 출력의 역할이 명확히 나뉘는 sequence-to-sequence 문제와 잘 맞는다.

GPT는 왜 Decoder-only일까

처음에는 “생성 모델이니까 decoder를 쓴다”라고만 생각했다. 틀린 말은 아니지만 이유를 설명하기에는 부족하다.

GPT가 학습하는 기본 문제는 지금까지의 토큰으로 다음 토큰을 예측하는 autoregressive language modeling이다. 이 목표에는 별도 encoder memory가 없어도 된다. prompt와 지금까지 생성한 토큰을 하나의 시퀀스로 놓고, causal self-attention으로 같은 방식으로 처리할 수 있다.

Decoder-only 구조의 장점은 다음처럼 정리할 수 있다.

  1. 대규모 비라벨 텍스트에서 (이전 토큰 → 다음 토큰) 학습 쌍을 바로 만들 수 있다.
  2. 학습 목표와 생성 시 추론 방식이 같은 왼쪽에서 오른쪽 방향을 따른다.
  3. 입력과 출력의 경계를 별도 encoder/decoder로 나누지 않아도 prompt 안에서 다양한 작업을 표현할 수 있다.
  4. 구조가 한 종류의 block 반복으로 단순해 scale을 키우기 쉽다.

대신 양방향 문맥을 한 번에 읽어 표현을 만드는 목적에는 encoder-only가 더 자연스러울 수 있고, 입력과 출력의 역할을 분리해야 하는 문제에서는 encoder-decoder가 효율적일 수 있다. Decoder-only가 모든 문제에서 구조적으로 우월해서 선택된 것은 아니다. 다음 토큰 예측이라는 범용 학습 목표와 잘 맞고, 하나의 반복 구조로 확장하기 좋았기 때문이라고 이해하고 있다.

지금 내가 보는 Transformer의 본질

아직 결론이라기보다 다음 구현을 읽기 위한 가설이다.

1. 고정된 순서 대신 내용 기반으로 경로를 만든다

RNN에서는 정보가 시간축을 따라 전달된다. Self-attention에서는 Query와 Key의 현재 내용이 연결 강도를 정한다. 같은 토큰이라도 문맥이 달라지면 attention weight가 달라진다. 계산 그래프의 큰 틀은 고정돼 있지만 실제 정보 흐름은 입력마다 달라진다.

2. Mixing과 transformation을 분리한다

Attention은 토큰 사이를 섞고, FFN은 토큰 내부 feature를 바꾼다. 이 두 단계가 residual stream 위에서 반복된다. Transformer를 attention 하나로만 설명하면 FFN과 residual stream이 맡는 절반을 놓친다.

3. 장거리 관계의 경로를 짧게 만든다

한 self-attention layer 안에서는 멀리 떨어진 두 위치도 직접 연결될 수 있다. RNN처럼 거리에 비례한 여러 hidden-state 전달을 거치지 않는다. 다만 모든 위치 쌍을 비교하므로 기본 attention의 시간·메모리 비용은 시퀀스 길이 T에 대해 O(T²)로 커진다. 긴 context에서 attention 최적화가 별도 연구 주제가 된 이유다.

4. 계산을 행렬 곱으로 모은다

QKV projection, score matrix, Value 가중합, FFN이 모두 큰 행렬 연산으로 표현된다. 모델 아이디어와 하드웨어 효율이 만나는 지점이다. Transformer의 성공을 아키텍처만 보고 이해하면 병렬화와 accelerator 친화성을 놓치게 된다.

실무와 다음 공부에 어떻게 연결할까

Transformer 내부를 아는 것이 곧바로 서비스를 잘 만든다는 뜻은 아니다. 그래도 LLM 시스템을 볼 때 질문의 수준이 달라진다.

  • context가 길어질 때 attention score matrix가 어떤 비용을 만드는지 추적할 수 있다.
  • autoregressive decoding에서 과거 Key와 Value를 왜 KV cache에 저장하는지 이해할 수 있다.
  • embedding model과 generative model의 구조·학습 목표가 왜 다른지 구분할 수 있다.
  • RAG가 모델의 attention을 외부 검색으로 대체하는 것이 아니라, 모델이 참고할 입력 context를 구성하는 시스템이라는 점을 분리해서 볼 수 있다.
  • head 수, hidden size, context length가 tensor shape과 메모리에 어떤 영향을 주는지 코드에서 확인할 수 있다.

지금 단계에서 가장 중요한 다음 행동은 구현을 읽는 것이다. 수식은 이해한 것처럼 착각하기 쉽다. B, T, C, n_head, head_size가 실제 코드에서 어떻게 바뀌는지 한 줄씩 추적해야 한다.

아직 남아 있는 질문

  • 학습이 진행되면서 각 head의 QKV projection은 어떤 패턴으로 분화하는가?
  • pre-LN과 post-LN의 gradient 흐름 차이를 작은 모델에서 어떻게 확인할 수 있을까?
  • RoPE의 회전이 내적에서 상대 위치 차이로 바뀌는 과정을 숫자로 직접 계산할 수 있는가?
  • 같은 파라미터 수에서 head 수와 head dimension을 바꾸면 표현력과 학습 안정성은 어떻게 달라지는가?
  • attention weight를 모델의 설명이나 근거로 어디까지 믿어도 되는가?

이 질문은 글로 답을 꾸며 넣지 않고 minGPT와 작은 tensor 실험에서 하나씩 확인할 생각이다.

확인한 원문

다음 글에서는 같은 구조를 그림이 아니라 실제 tensor 모양으로 다시 조립한다.

대화

댓글

0
댓글을 불러오는 중입니다.