Attention is all you need
transformer는 기존의 시퀀스 데이터 처리를 독점하던 순환 신경만(RNN)과 합성곱 신경망(CNN)의 구조적 한계를 극복하기 위해 제안되었다. 이전의 순환 신경만은 단어를 한 단계씩 순차적으로 처리해야 했기 때문에 병렬 연산이 불가능했고, 입력 시퀀스가 길어질수록 이전 정보가 손실되는 문제가 발생했다. transformer는 순환 연산을 완전히 제거하고 attention 메커니즘만을 사용하여 시퀀스 전체를 한 번에 병렬로 처리하도록 설계되었다. 이를 통해 단어 간의 거리에 상관없이 직접적인 연산을 수행할 수 있게 되었으며, 현재 GPT, Claude, Gemini와 같은 LLM의 아키텍처로 자리 잡았다.
토큰화, 임베딩, 위치 인코딩
토큰화와 임베딩
transformer에 입력되는 문장은 가장 먼저 서브워드 단위의 토큰으로 분활된 뒤 고유한 정수 식별자(ID)로 변환된다. 여기서 서브워드 단위는 단어를 더 이상 쪼갤 수 없는 완전한 단어(Word) 전체로 다루는 대신, 그보다 더 작은 부분 단어 단위로 나눈 텍스트 분할 방식이다. 쉽게 말해 접두사, 접미사, 어근 등이라 생각하면 된다. 아무튼 이 토큰 id는 학습 가능한 임베딩 테이블을 거쳐 고차원 연속 벡터로 매핑된다. 원본 논문의 베이스 모델 기준으로 이 벡터의 차원 수는 512차원이다.
즉, 한마디로 임베딩을 한다라고 보면 된다.
위치 인코딩
transformer는 시퀀스 전체를 동시에 입력받기 때문에 순환 신경망과 달리 단어의 위치 정보를 자체적으로 인식하지 못한다. 어텐션 연산만으로는 단어의 순서가 바뀐 문장 간의 차이를 구분할 수 없기 때문에, 각 토큰의 임베딩 벡터에 위치 인코딩 벡터(positional encoding) 벡터를 직접 더해준다. 논문에서는 주기가 서로 다른 사인과 코사인 삼각함수를 사용해 위치 인코딩을 생성했다.
위치 인코딩이 필요한 이유
기존의 RNN(순환 신경망)은 문장을 읽을 때 첫 번째 단어부터 마지막 단어까지 순차적으로 한 단어씩 입력받았다. 따라서 신경망 자체가 단어로 들어온 순서를 자연스럽게 기억할 수 있었다.
하지만 transformer는 문장 전체를 한번에 병렬로 입력받아 연산한다.
예를 들어
- I love cats
- cats love I 가 있다고 가정하면, transformer에서 셀프 어텐션(self-attention)은 단어 벡터 사이의 행렬 곱(내적)을 기반으로 유사도를 계산한다. 순서에 대한 별도의 정보가 없다면 I와 cats 사이의 연산 결과나 cats와 I 사이의 연산 결과가 동일하게 처리된다. 즉, 단어의 위치가 달라져도 모델이 인식하는 수학적 표현이 똑같아지는 문제가 발생한다.
이 문제를 해결하기 위해 각 단어의 의미를 담은 임베딩 벡터에 단어의 위치 정보를 나타내는 고유한 위치 벡터를 1:1로 더해주는 작업이 필요하며, 이를 위치 인코딩이라고 한다.
위치 인코딩 벡터를 만드는 방법은 주기가 다른 sine과 cosine 함수를 사용하면 된다. 수식은 차원이 짝수인지, 홀수인지 여부에 따라 다르다.
삼각함수 기반 위치 인코딩 수식
-
짝수일때는 sine함수를 사용한다. \(\text{PE}_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)\)
-
홀수일때는 cosine을 적용한다. \(\text{PE}_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)\)
$pos$: 문장 내에서 단어의 위치 (0번째, 1번째, 2번째 …)
$i$: 벡터 내부의 차원 인덱스 (0번째 차원, 1번째 차원, 2번째 차원 …)
$d_{\text{model}}$: 단어 임베딩 벡터의 전체 차원 수
이 수식을 사용하면 위치 값(pos)이 커짐에 따라 각 차원마다 일정한 패턴을 가지는 수치가 생성된다. 모델은 이 수치 변화를 통해 단어의 absolute position(절대적 위치)과 relative distance(상대적 거리)를 모두 파악할 수 있게 된다.
토큰화, 임베딩, 위치 인코딩 수치 계산 시뮬레이션
한번 시뮬레이션 해보자.
문장은 “I love cats”이다. 단어 벡터 차원 수는 4차원으로 줄여서 설정해보자. 위치 인덱스(pos)는 다음과 같을 것이다.
- “I” -> pos = 0
- “love” -> pos = 1
- “cats” -> pos = 2
이제 임베딩을 한다. 학습을 통해 각 단어가 가지는 무작위임의의 4차원 의미 벡터이다.
- “I” (pos=0) = $[0.10, \ 0.20, \ 0.30, \ 0.40]
- “love” (pos=1) = $[0.50, \ 0.60, \ 0.70, \ 0.80]
- “cats” (pos=2) = $[0.90, \ 0.10, \ 0.20, \ 0.30]
이제 위 삼각함수 공식으로 위치 인코딩 벡터를 계산한다.
- 차원 인덱스 $i=0$ 일 때: 0번째 차원(짝수, $\sin$), 1번째 차원(홀수, $\cos$)
- 차원 인덱스 $i=1$ 일 때: 2번째 차원(짝수, $\sin$), 3번째 차원(홀수, $\cos$)
계산된 위치 인코딩 값(소수점 셋째 자리 반올림)은 다음과 같다.
- 0번째 단어 I ($pos=0$)의 위치 벡터:
- 0번째 차원: $\sin(0 / 10000^0) = \sin(0) = 0.00$
- 1번째 차원: $\cos(0 / 10000^0) = \cos(0) = 1.00$
- 2번째 차원: $\sin(0 / 10000^{0.5}) = \sin(0) = 0.00$
- 3번째 차원: $\cos(0 / 10000^{0.5}) = \cos(0) = 1.00$
- $\text{PE}(pos=0) = [0.00, \ 1.00, \ 0.00, \ 1.00]$
- 1번째 단어 love ($pos=1$)의 위치 벡터:
- 0번째 차원: $\sin(1 / 1) = \sin(1) \approx 0.84$
- 1번째 차원: $\cos(1 / 1) = \cos(1) \approx 0.54$
- 2번째 차원: $\sin(1 / 100) = \sin(0.01) \approx 0.01$
- 3번째 차원: $\cos(1 / 100) = \cos(0.01) \approx 1.00$
- $\text{PE}(pos=1) = [0.84, \ 0.54, \ 0.01, \ 1.00]$
- 2번째 단어 cats ($pos=2$)의 위치 벡터:
- 0번째 차원: $\sin(2 / 1) = \sin(2) \approx 0.91$
- 1번째 차원: $\cos(2 / 1) = \cos(2) \approx -0.42$
- 2번째 차원: $\sin(2 / 100) = \sin(0.02) \approx 0.02$
- 3번째 차원: $\cos(2 / 100) = \cos(0.02) \approx 1.00$
- $\text{PE}(pos=2) = [0.91, \ -0.42, \ 0.02, \ 1.00]$
이제 최종적으로 transformer의 입력으로 들어가는 벡터는 [단어 임베딩] + [위치 인코딩]이다. 각 차원끼리 같은 위치의 숫자를 더한다.
- I 의 최종 입력 벡터:
\([0.10, \ 0.20, \ 0.30, \ 0.40] + [0.00, \ 1.00, \ 0.00, \ 1.00] = [0.10, \ 1.20, \ 0.30, \ 1.40]\) - love 의 최종 입력 벡터:
\([0.50, \ 0.60, \ 0.70, \ 0.80] + [0.84, \ 0.54, \ 0.01, \ 1.00] = [1.34, \ 1.14, \ 0.71, \ 1.80]\) - cats 의 최종 입력 벡터:
\([0.90, \ 0.10, \ 0.20, \ 0.30] + [0.91, \ -0.42, \ 0.02, \ 1.00] = [1.81, \ -0.32, \ 0.22, \ 1.30]\)
이렇게 단어 임베딩과 위치 인코딩이 만들어지며 입력된다.
이를 통해서 모델은 각 토큰의 절대적인 위치뿐만 아니라 토큰 간의 상대적인 거리 정보까지 원활하게 파악할 수 있다. 이후 아키텍처의 발전 과정에서 이 방식은 학습형 위치 임베딩, 상대 위치 임베딩, 그리고 LLaMA 모델 등에서 활용하는 회전 위치 임베딩(RoPE)으로 확장되었다.
인코더 구조와 Query, Key, Value 셀프 어텐션
인코더 및 셀프 어텐션
토큰화, 임베딩, 위치 인코딩을 통해서 전처리된 데이터가 모델 내부에 입력되면, 모델 내부에서 그 입력 값을 받아 본격적으로 계산하는 단계이다.
인코더는 입력받은 토큰 시퀀스를 문맥이 깊게 반영된 벡터 표현으로 변환하는 역할을 수행한다. 인코더 내부의 셀프 어텐션(Self Attention)은 하나의 시퀀스 내에 있는 모든 토큰이 서로를 참조하여 관계 가중치를 계산하는 연산이다.
셀프 어텐션을 수행하기 위해 각 토큰의 512차원 벡터는 학습 가능한 세 개의 가중치 행렬과 곱해져 쿼리(Query, Q), 키(Key, K), 밸류(Value, V)라는 세 가지 벡터로 투영된다. 쿼리는 현재 토큰이 다른 토큰에 대해 얻고자 하는 정보의 기준이며, 키는 각 토큰이 보유하고 있는 고유한 식별 정보이다. 밸류는 실제로 다른 토큰에게 전달할 정보 내용에 해당한다.
어텐션 스코어의 계산 공식은 $\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$ 이다. 먼저 쿼리 행렬(Q)과 키 행렬의 전치 행렬($K^T$)을 행렬곱하여 모든 토큰 간의 유사도 점수를 구한다. 이 값을 키벡터의 차원 수인 $d_k$의 제곱근($\sqrt{d_k}$)으로 나누어 주는 스케일링 과정을 거친다. 스케일링을 적용하는 이유는 차원 수가 커질수록 점수의 내적 값이 지나치게 커져 소프트맥스 함수의 기울기가 소실되는 현상을 방지하기 위함이다. 스케일링된 점수는 소프트맥스 함수를 거치면서 합이 1이 되는 확률 형태의 가중치로 변환되며, 이 가중치를 밸류 행렬($V$)에 곱해 가중합을 구함으로써 각 토큰의 문맥이 반영된 최종 어텐션 표현을 생성한다.
셀프 어텐션의 5단계 연산 과정
다시 쉽게 설명을 하자면,
셀프 어텐션의 목적은 단어 단위의 개별 벡터를 입력받아, 시퀀스 내 다른 모든 단어들과의 연관성을 가중치로 계산한 뒤 이를 반영한 문맥 반영 벡터를 만들어 내는 것이다.
- 먼저 입력 행렬 ($X$)을 구성한다.
입력 시퀀스의 토큰 개수를 $N$, 각 토큰의 임베딩 차원을 $d_{\text{model}}$이라 한다.
입력 행렬 $X$의 크기는 $(N \times d_{\text{model}})$이다.
- $Q, K, V$ 투영 변환한다.
입력 $X$에 학습 가능한 가중치 행렬 $W^Q, W^K, W^V$를 각각 곱하여 $Q, K, V$ 행렬을 만든다. $W^Q, W^K$의 크기는 $(d_{\text{model}} \times d_k)$ 이고, $W^V$의 크기는 $(d_{\text{model}} \times d_v)$이다.
투영된 결과는 다음과 같다.
- $Q = X W^Q$ (크기: $N \times d_k$): 각 토큰이 연산을 일으킬 기준값
- $K = X W^K$ (크기: $N \times d_k$): 각 토큰이 비교당할 기준값
- $V = X W^V$ (크기: $N \times d_v$): 실제로 전달할 정보값
- 위에서 구한 Q, K, V를 활용하여 유사도 점수를 계산한다. ($Q K^T$)
$Q$와 $K$의 전치 행렬 $K^T$를 행렬곱 하고, 연산 결과 행렬의 크기는 $(N \times N)$이 되며, $i$번째 행 $j$번째 열의 값은 $i$번째 토큰과 $j$번째 토큰 간의 내적 값이다. 두 벡터의 내적이 클수록 두 토큰 간의 유사도가 높음을 나타낸다
연산 차원: $(N \times d_k) \times (d_k \times N) = (N \times N)$
$(i, j)$ 번째 원소는 $i$번째 토큰의 Query와 $j$번째 토큰의 Key 벡터 간의 내적값이다. 내적값이 클수록 두 벡터의 방향이 유사함을 나타낸다.
- 스케일링 및 소프트맥스 ($\text{softmax}(Q K^T / \sqrt{d_k})$)
이후 내적 값을 $\sqrt{d_k}$로 나눈다. $d_k$ 차원이 커질수록 내적 결과의 절댓값이 매우 커져 소프트맥스 계산 시 기울기 소실(Gradient Vanishing)이 발생하는 현상을 방지한다.
이후 스케일링된 $(N \times N)$ 행렬의 각 행에 소프트맥스(Softmax) 함수를 적용한다. 이를 통해 각 행의 모든 값은 0 이상 1 이하가 되며, 행 전체의 합은 정확히 1이 된다. 이 값이 어텐션 가중치(Attention Weight) 행렬이 된다.
- 가중합 계산 (최종 출력 $Z$) 어텐션 가중치 행렬 $(N \times N)$과 $V$ 행렬 $(N \times d_v)$을 행렬곱 한다. 결과 행렬 $Z = \text{Attention}(Q, K, V)$의 크기는 $(N \times d_v)$가 되며, 각 토큰 위치에는 시퀀스 전체의 $V$ 벡터들이 어텐션 가중치 비율대로 섞인 문맥 표현이 저장된다.
셀프 어텐션 수치 계산 시뮬레이션
한번 시뮬레이션 해보자.
입력 조건을
- 토큰 개수 $N = 2$ (토큰 1, 토큰 2)
- 임베딩 차원 $d_{\text{model}} = 2$
- $K, Q$ 차원 $d_k = 2$
- $V$ 차원 $d_v = 2$
으로 가정한다.
1단계: 투영된 $Q, K, V$ 행렬
가중치 행렬곱을 마친 후 계산된 $Q, K, V$ 행렬이 아래와 같다고 가정한다.
\[Q = \begin{bmatrix} 1.0 & 0.0 \\ 0.0 & 2.0 \end{bmatrix} \quad (\text{행 1: 토큰 1의 } Q, \text{ 행 2: 토큰 2의 } Q)\] \[K = \begin{bmatrix} 1.0 & 2.0 \\ 0.0 & 1.0 \end{bmatrix} \quad (\text{행 1: 토큰 1의 } K, \text{ 행 2: 토큰 2의 } K)\] \[V = \begin{bmatrix} 0.5 & 1.5 \\ 2.0 & 0.0 \end{bmatrix} \quad (\text{행 1: 토큰 1의 } V, \text{ 행 2: 토큰 2의 } V)\]2단계: 유사도 내적 점수 계산 ($Q K^T$)
$K$의 전치 행렬 $K^T = \begin{bmatrix} 1.0 & 0.0 \ 2.0 & 1.0 \end{bmatrix}$을 구한 뒤 $Q$와 곱한다.
\[Q K^T = \begin{bmatrix} 1.0 & 0.0 \\ 0.0 & 2.0 \end{bmatrix} \begin{bmatrix} 1.0 & 0.0 \\ 2.0 & 1.0 \end{bmatrix} = \begin{bmatrix} 1.0 \cdot 1.0 + 0.0 \cdot 2.0 & 1.0 \cdot 0.0 + 0.0 \cdot 1.0 \\ 0.0 \cdot 1.0 + 2.0 \cdot 2.0 & 0.0 \cdot 0.0 + 2.0 \cdot 1.0 \end{bmatrix} = \begin{bmatrix} 1.0 & 0.0 \\ 4.0 & 2.0 \end{bmatrix}\]- 결과 해석:
- 1행 $[1.0, 0.0]$: 토큰 1의 $Q$와 (토큰 1의 $K$, 토큰 2의 $K$) 간의 내적 값
- 2행 $[4.0, 2.0]$: 토큰 2의 $Q$와 (토큰 1의 $K$, 토큰 2의 $K$) 간의 내적 값
두 토큰 사이의 원시 수치 연관성을 도출한다. 토큰 2의 Query는 토큰 1의 Key와 내적값 4.0을 기록하여, 토큰 2가 자기 자신(2.0)보다 토큰 1에 더 강하게 반응하고 있음을 알 수 있다.
3단계: 스케일링 ($\sqrt{d_k}$로 나누기)
$d_k = 2$ 이므로 $\sqrt{d_k} = \sqrt{2} \approx 1.414$ 이다.
\[\frac{QK^T}{\sqrt{d_k}} = \begin{bmatrix} \frac{1.0}{1.414} & \frac{0.0}{1.414} \\ \frac{4.0}{1.414} & \frac{2.0}{1.414} \end{bmatrix} \approx \begin{bmatrix} 0.707 & 0.000 \\ 2.828 & 1.414 \end{bmatrix}\]내적 값이 지나치게 커지지 않도록 보정함으로써, 이후 소프트맥스 단계에서 확률값이 특정 한 요소로 쏠려 기울기가 소실되는 현상을 예방한다.
4단계: 소프트맥스 적용 (가중치 확률화)
각 행별로 소프트맥스 함수 $\text{softmax}(x_i) = \frac{e^{x_i}}{\sum e^{x}}$를 적용한다.
- 1행 계산 (토큰 1 기준):
- $e^{0.707} \approx 2.028$, $e^{0.000} = 1.000$
- 분모 합: $2.028 + 1.000 = 3.028$
-
소프트맥스 결과: $\left[\frac{2.028}{3.028}, \frac{1.000}{3.028}\right] \approx [0.67, 0.33]$
- 2행 계산 (토큰 2 기준):
- $e^{2.828} \approx 16.911$, $e^{1.414} \approx 4.112$
- 분모 합: $16.911 + 4.112 = 21.023$
- 소프트맥스 결과: $\left[\frac{16.911}{21.023}, \frac{4.112}{21.023}\right] \approx [0.80, 0.20]$
소프트맥스 후 완성된 어텐션 가중치 행렬:
\[A = \begin{bmatrix} 0.67 & 0.33 \\ 0.80 & 0.20 \end{bmatrix}\]- 결과 해석:
- 토큰 1을 인코딩할 때: 자기 자신(토큰 1)의 정보 67%, 상대방(토큰 2)의 정보 33%를 반영한다.
- 토큰 2를 인코딩할 때: 상대방(토큰 1)의 정보 80%, 자기 자신(토큰 2)의 정보 20%를 반영한다.
수치 스코어를 합이 1인 가중치 비중으로 변환하여, 각 토큰이 문맥 정보를 합성할 때 주변 단어들의 정보를 어떤 비율로 참조해야 할지 명확한 확률 분포를 확립한다.
5단계: $V$ 행렬과의 행렬곱 (최종 출력 $Z$)
계산된 어텐션 가중치 행렬 $A$에 실제 정보가 담긴 $V$ 행렬을 곱한다.
\[Z = A V = \begin{bmatrix} 0.67 & 0.33 \\ 0.80 & 0.20 \end{bmatrix} \begin{bmatrix} 0.5 & 1.5 \\ 2.0 & 0.0 \end{bmatrix}\]- 토큰 1의 최종 벡터 ($Z$의 1행):
- 토큰 2의 최종 벡터 ($Z$의 2행):
최종 출력 행렬 $Z$:
\[Z = \begin{bmatrix} 0.995 & 1.005 \\ 0.800 & 1.200 \end{bmatrix}\]단순히 개별 임베딩 위치에 고정되어 있던 초기 벡터가, 셀프 어텐션 연산을 거치면서 다른 토큰과의 관계 확률(가중치)만큼 정보가 섞여 들어간 문맥 반영 벡터 $Z$로 전환된다.
단순 독립 위치에 머물러 있던 초기 벡터가 주변 토큰의 실제 정보(V)와 결합되어, 시퀀스 전체의 문맥 의미가 가중치만큼 포함된 고차원 표현 벡터 Z로 전환되었음을 알 수 있다.
멀티헤드 어텐션 (Multi-Head Attention)
단일 셀프 어텐션(Single-Head Attention)만 사용하는 경우, 문자 내 모든 단어 간의 연관성이 하나의 가중치 평균으로 뭉개지는 문제가 발생할 수 있다. 예륻들어 특정 단어가 문법적 관계, 위치적 관계, 의미적 유사도 등 여러 측면에서 주변 단어들과 형성하는 복합적인 관계를 한 번의 어텐션 스코어만으로 모두 담아내기 어렵다.
이를 해결하기 위해 transformer는 Query, Key, Value를 단 한 번만 연산하는 대신, h개의 서로 다른 가중치 행렬로 선형 투영(Linear Projection)하여 여러 개의 어텐션 헤드로 나누어 병렬로 연산하는 멀티헤드 어텐션 구조를 사용한다.
\[Q_i = Q W_i^Q, \quad K_i = K W_i^K, \quad V_i = V W_i^V \quad (i = 1, \dots, h)\] \[\text{head}_i = \text{Attention}(Q_i, K_i, V_i) = \text{softmax}\left(\frac{Q_i K_i^T}{\sqrt{d_k}}\right) V_i\]각 헤드는 서로 다른 표현 공간(Subspace)에서 독자적으로 어텐션을 수행한다. 예를들어 1번 헤드는 문법적 주어-동사 관계에 집중하고, 2번 헤드는 대명사가 가리키는 대상(참조 관계)에 집중하며, 3번 헤드는 멀리 떨어진 문맥적 연관성에 집중하도록 각자 다른 관점을 학습하게 된다.
계산된 h개의 어텐션 헤드 결과물($\text{head}1, \dots, \text{head}_h$)은 하나로 이어 붙인 뒤, 최종 출력 가중치 행렬 $W^O$와 곱해져 원래의 차원 수($d{\text{model}}$)로 병합된다.
\[\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \text{head}_2, \dots, \text{head}_h) W^O\]원본 논문의 베이스 모델 기준으로는 전체 차원 $d_{\text{model}} = 512$를 $h = 8$개의 헤드로 나누어, 각 헤드당 $d_k = d_v = 512 / 8 = 64$차원에서 어텐션을 병렬로 수행한다. 각 헤드의 차원을 줄여서 병렬 처리하기 때문에, 전체 연산 비용은 단일 어텐션을 512차원에서 한 번 수행하는 것과 비슷하게 유지된다.
피드포워드 네트워크(FFN), 잔차 연결 및 레이어 정규화
셀프 어텐션을 거친 표현 벡터는 각 위치(토간)별로 독립적으로 적용되는 Position-wise Feed-Forward Network(FFN)를 지나게 된다.
어텐션 메커니즘이 단어와 단어 사이의 정보(토큰 간 혼합)를 섞어주는 역할을 한다면, FFN은 각 토큰별로 섞인 정보를 개별적으로 비선형 변환하여 더 풍부한 특징 표현으로 재구성하는 역할을 담당한다.
\[\text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2\]FFN은 두 개의 선형 변환(Linear Transformation)과 그 사이에 위치한 ReLU 활성화 함수로 구성된다.
- 첫 번째 선형 레이어($W_1$)가 512차원 벡터를 2048차원($d_{ff} = 2048$)으로 확장
- ReLU 비선형 활성화 함수를 통과한다.
- 두 번째 선형 레이어($W_2$)가 다시 원래 차원인 512차원으로 투영하여 축소한다.
또한, 인코더 내의 모든 서브레이어(Multi-Head Attention 및 FFN)의 출력에는 잔차 연결(Residual Connection)과 레이어 정규화(Layer Normalization)가 적용된다.
\[\text{LayerNorm}(x + \text{SubLayer}(x))\]입력 값 $x$를 서브레이어 출력 값 $\text{SubLayer}(x)$에 직접 더해주는 잔차 연결은 경사 소실(Gradient Vanishing) 문제를 방지하여 신경망을 수십 레이어 이상으로 깊게 쌓을 수 있도록 해주며, 레이어 정규화는 학습 과정을 안정화한다.
디코더 구조와 크로스 어텐션
인코더가 입력 시퀀스 전체를 해석하여 문맥 표현을 만드는 역할이라면, 디코더는 이 표현을 바탕으로 순차적으로 다음 토큰을 생성하는 역할을 담당한다.
디코더의 하나의 레이어는 크게 세 가지 서브레이어로 구성된다.
타겟 시퀀스 입력(Shifted Right)
–>
디코더 레이어
1. Masked Multi-Head Self-Attention
- 미래 토큰 참조 방지 (Causal Masking: Score -> -∞)
-(Add & LayerNorm)->
2. Encoder-Decoder Attention (Cross-Attention)
- Query(Q): 디코더 내부 이전 상태
- Key(K) & Value(V): 인코더의 최종 출력 벡터
-(Add & LayerNorm)->
3. Position-wise Feed-Forward Network (FFN)
–>
최종 출력 확률 분포 (Softmax & Linear)
1. Masked Self-Attention
디코더의 셀프 어텐션은 인코더와 기본 동작은 같지만, 미래의 토큰을 참조하지 못하도록 마스킹(Causal Masking)조치가 추가된다.
예를 들어 세 번째 단어를 생성할 때는 첫 번째와 두 번째 단어만 참조할 수 있어야 하며, 네 번째 이후 단어를 미리 훔쳐봐서는 안 된다. 이를 위해 어텐션 스코어 행렬에서 현재 위치보다 뒤에 있는 미래 위치의 점수를 $-\infty$(음의 무한대)로 치환한다. 이 점수들이 softmax 함수를 통과하면 가중치가 정확히 0이 되어 미래 정보가 반영되지 않는다. 배치 내 문장 길이를 맞추기 위한 패딩 마스크 역시 함께 처리된다.
2. Encoder-Decoder Cross-Attention
디코더의 핵심 연결 고리로, 생성할 문장의 각 위치에서 원문(인코더 출력)의 어느 부분에 집중해야 하는지 결정한다.
- Query(Q): 디코더의 이전 masked 어텐션 레이어 출력에서 생성된다. (즉, “지금 내가 생성할 단어에 필요한 정보가 무엇인가?”를 뜻한다.)
- Key(K) & Value(V): 인코더가 최종적으로 출력한 최상휘 벡터에서 가져온다. (즉, “원문 문장이 가지고 있는 식별 정보와 내용이 무엇인가?”를 뜻한다.)
프랑스어 “Je vais à l’école”를 영어 “I am going to school”로 번역할 때, 디코더가 “school”을 생성할 시점이 되면 디코더의 Query(Q)는 인코더가 “école”을 표현한 Key/Value 벡터에 강한 어텐션 가중치를 부여한다. 기존 RNN 기반 seq2seq가 전체 원문을 단 하나의 고정 길이 벡터(Thought Vector)로 압축해야 했던 고질적인 병목 현상을 해결하였다.
3. 피드포워드 네트워크 및 최종 언어 모델 헤드 (Linear & Softmax)
크로스 어텐션 후 인코더와 동일한 FFN을 통과한 출력 벡터는 선형 레이어를 거쳐 전체 단어 사전(Vocabulary)의 크기만큼 차원이 확장된다. 이후 Softmax 함수를 적용하여 각 단어가 다음에 올 확률 분포로 변환하고, 가장 확률이 노픈 토큰을 선택하여 출력에 추가한 뒤 이과정을 반복한다.
transformer의 실증적 성과 (Empirical Results)
transformer는 발표 당시 기계 번역 벤치마크에서 기존의 모든 신경망 아키텍처를 압도하는 실증 성과를 기록했다.
기존 SOTA 모델들을 더 적은 학습시간 및 컴퓨팅 자원으로 더 높은 BLEU 점수를 냈다.
또한 RNN은 문장 길이가 N일 때 순차적으로 N번의 단계를 거쳐야 하므로 계산 경로 길이가 $O(N)$이었지만, transformer의 self attention은 임의의 두 단어 간 계산 경로 길이가 $O(1)$이다. 이에 따라 어떤 단어와 단어 사이든 단 한 번의 어텐션으로 직접 연결되어 장기 의존성(Long-range dependencies)을 손쉽게 학습하고 학습 속도를 더욱 단축 시켰다.
이 뿐만 아니라, 기계 번역 및 영문 구문 분석 과제에서 아키텍처 변경 없이 적용했을 때도 높은 F1 점수를 달성하며, 순환 구제에 의존하던 기존의 귀속적 편향(Inductive Bias)이 시퀀스 처리에 절대적인 필수 요소가 아님을 입증했다.
Universal Transformer
transformer 발표 당시 커뮤니티 일각에서는 순환 구조가 완전히 배제된 것에 대해 “단어 순서를 명시적으로 다루지 않고 위치 인코딩이라는 임시방편(Hack)에 의존한다”는 회의론이 존재했다.
이러한 우려를 반영해 2018년 Google 연구진은 transformer에 순환 구조의 이점을 재도입한 Universal Transformer(UT)를 제안했다.
- UT는 레이어를 위로 다르게 쌓는 대신, 하나의 동일한 어텐션+FFN 블록(Recurrent Block)을 모든 토큰에 대해 여러 타임스텝(T)동안 반복하여 순환 재적용하는 구조를 취한다.(깊이 방향 재귀: Depth-wise Recurrence)
- 위 깊이 방향 순환 구조 덕분에 UT는 이론적으로 충분한 시간과 메모리가 주어지면 전통적인 컴퓨터가 수행할 수 있는 모든 알고리즘 연산을 복제할 수 있는 ‘튜링 완결성’을 확보했다.
다만 이론적 완전성과 튜링 완결성을 갖춘 UT였으나, 실제 적용에서는 표준 transformer보다 연산 속도가 느렸고, 실질적인 성능 이점이 뚜렸하지 않았다. 결국 딥러닝 세계에서는 복잡한 이론적 편향보다, 구조가 단순하고 대규모 데이터와 컴퓨팅을 투입했을 때 가시적으로 확장(Scaling)되는 단순한 표준 transformer가 승리한다는 대전제를 상징적으로 보여준 사례가 되었다.
The Annotated Transformer와 구현의 대중화
transformer 논문의 이론적 개념은 명확했으나, Google이 최초에 공개했던 오픈소스 코드베이스(Tensor2Tensor)는 내부 인프라 프레임워크와 깊게 결합되어 있어 외부에 있는 연구자나 개발자들이 구조를 한눈에 파악하고 직접 구현하기가 매우 까다로웠다.
이 장벽을 허문 것이 하버드 대학교의 Alexander “Sasha” Rush 교수가 작성한 “The Annotated Transformer”포스트이다.
- 논문의 수식 및 section과 1대1로 대응되는 줄 단위의 PyTorch 구현체 및 주석을 제공했다.
- 전체 인코더-디코더 스택, 멀티헤드 어텐션, 위치 인코딩, 마스킹 및 마이크로 트레이닝 루틴까지 단 400여 줄의 직관적인 PyTorch 코드로 완전하게 작동하는 모델을 재현해 냈다.
- 이 교육용 프로젝트는 transformer의 기술적 문턱을 결정적으로 낮추었고, 이후 The Illustrated Transformer, The Annotated GPT-2, The Annotated Diffusion Model 등 수많은 오픈소스 커뮤니티의 해설서와 실습 생태계가 탄생하는 모태가 되었다.
여담
세상을 바꾼 Attention is All You Need 논문은 정작 NeurIPS 2017 심사 당시 구글이라는 거대 기업의 작품이었음에도 구두 발표(Oral) 기회를 얻지 못하고 Poster 및 짧은 Spotlight 세션으로 밀려났다.
구글은 트랜스포머 발표 불과 1년 뒤 순환성(Recurrence)을 버린 것에 대한 이론적 불안감 때문에 튜링 완결성을 갖춘 Universal Transformer(UT)를 만드는 등 복잡한 인덕티브 바이어스에 집착하며 머뭇거렸다. 반면 OpenAI의 일리야 수츠케버(Ilya Sutskever)는 논문을 보자마자 “우리는 이제 트랜스포머에 올인한다”고 선언한 뒤 단순한 구조를 무식하게 스케일업하는 길을 택했다.
구글이 초기에 공개한 Tensor2Tensor 라이브러리는 내부 도구 결합도가 너무 높아 외부 개발자가 해석하기 극도로 난해했다. 이를 Alexander “Sasha” Rush가 소설 The Annotated Alice를 오마주하여 단 400줄의 깔끔한 PyTorch 코드로 재구현한 The Annotated Transformer를 발표하면서 비로소 전 세계 연구자들에게 대중화되었다.
“X is All You Need” 밈의 시작: 이 논문 이후 AI 학계에 “… is All You Need” 제목의 논문이 무수히 쏟아져 나오는 현상이 생겼다.
Leave a comment