어텐션은 값 벡터의 가중평균을 계산해. 가중치는 query 벡터를 key 벡터 집합과 비교해 구하지. 각 출력 위치는 자신에게 중요한 입력에 주목하고, 가중치는 역전파를 통해 처음부터 끝까지 함께 학습돼.
셀프 어텐션은 query, key, 값이 모두 같은 시퀀스에서 나오는 특수한 경우야. 이제 모든 토큰이 다른 모든 토큰을 한 단계에서 직접 볼 수 있어. 순환 구조도, 고정 크기 은닉 상태도 필요 없어. 덕분에 장거리 의존성을 아주 쉽게 표현할 수 있지.
팁: 이 퀘스트 전체에서 논문을 한 편만 읽는다면 'Attention Is All You Need' (Vaswani et al., 2017)를 읽어. 단 8쪽에 모든 현대 LLM을 구동하는 아키텍처가 설명되어 있어.
어텐션 헤드 하나의 수학
Q, K, V가 주어졌고 각각의 형태가 [B, T, d_k]라면 다음과 같이 계산해: attention(Q, K, V) = softmax(Q K^T / sqrt(d_k)) V. sqrt(d_k)는 차원이 커질수록 내적값이 지나치게 커지는 것을 막아. 소프트맥스는 내적값을 입력 토큰에 대한 확률 분포로 바꿔 주지. 결과의 형태는 [B, T, d_k]로, 입력과 같아.
멀티헤드 어텐션
어텐션 헤드 하나는 한 종류의 관계를 학습해. 멀티헤드 어텐션은 여러 헤드를 병렬로 실행하고, 각 헤드가 저마다 Q, K, V 투영을 사용하게 한 뒤 결과를 이어 붙여. 직관적으로 보면, 서로 다른 헤드가 구문, 상호 참조, 위치, 의미 유사성처럼 서로 다른 관계를 하나의 가중치 집합 안에서 경쟁하지 않고 학습할 수 있는 거야.
어텐션을 병렬화할 수 있는 이유
RNN과 달리 어텐션은 모든 위치의 출력을 동시에 계산할 수 있어. 모든 내적이 서로 독립적이기 때문이야. Transformer가 GPU에서 RNN보다 훨씬 빠르게 학습되는 이유가 바로 여기에 있어.
원칙: 어텐션은 지난 10년 동안 등장한 가장 중요한 아키텍처 구성 요소야. 순전파에서 정확히 무엇을 계산하는지, 즉 Q, K, V에서 시작해 소프트맥스를 거쳐 V의 가중합을 구하는 과정을 이해해야 모든 현대 LLM의 토대를 이해할 수 있어.
Code
밑바닥부터 구현하는 스케일드 점곱 어텐션·python
import torch
import torch.nn.functional as F
import math
def scaled_dot_product_attention(Q, K, V, mask=None):
# Q, K, V: [B, H, T, d_k]
d_k = Q.size(-1)
scores = Q @ K.transpose(-2, -1) / math.sqrt(d_k) # [B, H, T, T]
if mask is not None:
scores = scores.masked_fill(mask == 0, float("-inf"))
weights = F.softmax(scores, dim=-1) # [B, H, T, T]
return weights @ V # [B, H, T, d_k]
# PyTorch has a fused, fast version too
out = F.scaled_dot_product_attention(Q, K, V, attn_mask=None)