제1부 기반: 언어모델과 트랜스포머
1.4 어텐션과 트랜스포머
이 책에서 되풀이해 마주칠 식이 하나 있다.
$$\text{Attention}(Q,K,V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V$$
ChatGPT든 Claude든 Gemini든, 그 핵심에는 트랜스포머가 있고 트랜스포머의 핵심에는 이 한 줄이 있다. 사전학습도, 정렬도, 서빙의 KV 캐시도, 롱컨텍스트의 비용도, 전부 이 식이 무슨 일을 하고 그 대가로 무엇을 요구하는지에서 갈라져 나온다. 그래서 이 장은 다른 장보다 천천히 간다 — 식을 눈으로 훑는 대신 직접 여러 번 끝까지 실행해 보고, 차원이 어떻게 흐르는지, 파라미터가 몇 개이고 FLOP이 몇 번인지, 대규모에서 무엇이 학습을 중단시키는지까지 다룬다. 위치 인코딩·정규화·활성함수의 세부는 1.5로 미루지만, 그것들이 왜 필요한지와 어텐션이 왜 지금 형태인지는 여기서 근본까지 살펴본다.
1.4.1 순차 모델은 어디서 막혔나
트랜스포머가 나오기 전, 언어를 다루는 표준은 순환신경망(RNN)과 그 개량형 LSTM이었다. 이들은 문장을 한 토큰씩 순서대로 읽으며 은닉 상태 $h_t$ 라는 벡터에 지금까지의 문맥을 압축해 담는다.
$$h_t = f(h_{t-1},\, x_t), \qquad y_t = g(h_t)$$
이 한 줄이 우아해 보이지만, 여기에 세 개의 장벽이 동시에 들어 있다.
첫째는 속도다. $h_t$ 를 구하려면 $h_{t-1}$ 이 먼저 있어야 하고, 그것은 다시 $h_{t-2}$ 를 기다린다. 길이 $n$ 문장은 원리상 $n$ 단계를 직렬로 밟아야 하며, GPU가 수천 개 있어도 이 순서를 건너뛸 수 없다. 딥러닝이 강한 이유는 연산을 병렬로 투입하는 데 있는데, RNN은 바로 그 병렬성을 시퀀스 축에서 원천 봉쇄한다. 뒤에서 보겠지만 트랜스포머의 가장 실질적인 승리는 더 똑똑해서가 아니라 문장 전체를 한 번의 행렬곱으로 처리해 이 장벽을 허물었기 때문이다.
둘째는 거리다. "그 소설을 다 읽고 나서, 나는 그것이 지루하다고 말했다"에서 "말했다"가 무엇을 가리키는지 알려면 문장 맨 앞의 "소설"까지 거슬러야 한다. RNN에서 이 신호는 $h_t \to h_{t+1} \to \cdots$ 로 모든 중간 상태를 통과해야 도달한다 — 두 토큰이 $k$ 만큼 떨어져 있으면 경로 길이가 $O(k)$ 다. 게다가 과거 전부가 고정 크기 벡터 하나에 압축되어 담기니, 문장이 길어질수록 앞쪽 정보가 뒤쪽에 덮여 사라진다. 은닉 상태가 아무리 커도 그것은 상수 크기이고, 담아야 할 과거는 문장 길이에 비례해 늘어나므로 이 병목은 원리적이다.
셋째는 학습이다. 이것은 조금 더 구체적으로 볼 가치가 있다. 역전파에서 시각 $t$ 의 손실이 먼 과거 $h_{t-k}$ 로 전달되는 기울기는 연쇄법칙에 따라 야코비안의 곱으로 표현된다.
$$\frac{\partial h_t}{\partial h_{t-k}} = \prod_{i=t-k+1}^{t} \frac{\partial h_i}{\partial h_{i-1}}$$
이 곱에 든 야코비안들의 스펙트럼 반경(가장 큰 특이값)이 평균적으로 1보다 작으면 곱이 $k$ 에 대해 지수적으로 0으로 사라지고(기울기 소실), 1보다 크면 지수적으로 폭주한다. 둘 다 학습을 망친다 — 소실이면 먼 과거로부터 배우지 못하고, 폭주면 갱신이 발산한다. LSTM의 셀 상태와 게이트는 이 곱셈 사슬에 덧셈 경로를 삽입해 소실을 완화하는 장치이지 없애는 장치가 아니다. 수백에서 수천 토큰 규모의 실제 장거리 의존에서는 여전히 학습이 어렵다.
토큰을 섞는 방법이 순환만 있는 것은 아니다. 합성곱(CNN)도 후보였고 실제로 언어에 쓰였다. 합성곱은 병렬화가 되고 국소 패턴을 잘 포착하지만, 한 층의 커널이 보는 범위가 고정돼 있어 멀리 떨어진 두 토큰을 잇으려면 층을 많이 쌓아야 한다 — 거리 $k$ 를 잇는 경로가 커널 크기에 따라 $O(k/w)$ 층으로, RNN보다는 낫지만 여전히 거리에 비례한다. 어텐션은 이 셋을 한꺼번에 겨냥한다. 핵심은 위치 $i$ 가 위치 $j$ 를 볼 때 중간을 거치지 않고 직접 연결한다는 것이다. 두 토큰 사이의 경로가 $O(1)$ 로 상수가 되니 거리 문제가 사라지고, 위의 야코비안 곱이 상수 길이가 되니 기울기가 먼 곳까지 곧장 흐르고, 모든 위치를 동시에 계산하니 병렬화가 열린다.
무비용은 아니다. 순서를 버려 병렬성을 얻은 대가로 어텐션은 위치 정보를 잃고(뒤에서 증명한다), 모든 쌍을 직접 잇는 대가로 시퀀스 길이의 제곱에 비례하는 비용을 부담한다. 이 두 비용이 이 장 후반과 이후 여러 장의 주제다.
1.4.2 어텐션에 들어가기 전 — 토큰이 벡터가 되기까지
어텐션 식의 입력 $X$ 가 어디서 오는지부터 살펴보자. 텍스트는 먼저 토크나이저를 거쳐 정수 토큰 ID 시퀀스가 된다(1.2). 각 ID는 임베딩 행렬 $E \in \mathbb{R}^{V\times d_{model}}$ 에서 한 행을 꺼내 오는 조회(lookup)로 $d_{model}$ 차원 벡터가 된다 — 어휘 $V$ 개 각각에 학습되는 벡터가 하나씩 있는 셈이다. 토큰 ID 시퀀스가 길이 $n$ 이면 이 조회로 $X \in \mathbb{R}^{n\times d_{model}}$ 가 만들어지고, 이것이 첫 블록의 입력이자 잔차 스트림의 출발점이다.
여기서 두 가지가 더 추가된다. 하나는 위치 정보다. 곧 증명하겠지만 어텐션은 순서를 모르므로, 각 위치가 몇 번째인지를 어떤 식으로든 주입해야 한다 — 절대 위치 방식에서는 위치 임베딩을 $X$ 에 더하고, RoPE 같은 방식에서는 어텐션 계산 시점에 회전으로 주입한다(1.5). 다른 하나는 스케일이다. 일부 구현은 임베딩에 $\sqrt{d_{model}}$ 을 곱해 위치 인코딩과 크기를 맞춘다. 세부는 구현마다 다르지만, 요점은 어텐션이 보는 $X$ 가 "토큰의 의미 + 위치"를 담은 벡터들의 묶음이라는 것이다.
한 가지 자주 놓치는 대칭이 있다. 입력 임베딩 $E$ 와, 스택 맨 끝에서 최종 벡터를 어휘 크기 로짓으로 되돌리는 언임베딩 행렬은 종종 같은 행렬을 공유한다(weight tying). 개념적으로 "토큰을 벡터로 넣는 사전"과 "벡터를 토큰으로 읽는 사전"이 같아야 자연스럽고, 실용적으로는 $V\times d_{model}$ 짜리 큰 행렬을 두 벌 대신 한 벌만 저장해 파라미터를 아낀다. 어휘가 크고 모델이 작을수록 이 절약이 크다.
1.4.3 질의·키·값 — 부드러운 조회
수식에 들어가기 전에 그림 하나를 정리하자. 어텐션은 파이썬 딕셔너리 조회를 미분 가능하게 만든 것이다.
memory = {"소설": v1, "지루하다": v2, "말했다": v3}
result = memory["소설"] # 키가 정확히 일치하는 값 하나를 꺼낸다
딕셔너리는 키가 정확히 맞아야 값을 준다. 맞거나 안 맞거나, 둘 중 하나다. 이 하드한 조회는 미분이 안 된다 — 키를 아주 조금 바꿔도 결과가 튀거나 그대로이지, 부드럽게 변하지 않는다. 경사하강으로 학습하려면 모든 것이 매끄럽게 미분 가능해야 하므로(1.3), 어텐션은 이 조회를 부드럽게 푼다. 질의가 각 키와 얼마나 닮았는지를 연속적인 점수로 매기고, 그 점수를 가중치 삼아 모든 값을 섞어 돌려준다. 정확히 일치하는 하나를 꺼내는 대신, 닮은 정도에 비례해 여럿을 조금씩 섞는 것이다.
여기서 세 역할이 등장한다. 질의(Query) 는 지금 무엇을 찾고 있는가 — 정보를 가져오려는 쪽의 표현이다. 키(Key) 는 나는 무엇에 관한 것인가 — 각 토큰이 내거는 광고판이고, 질의와 매칭된다. 값(Value) 은 매칭됐을 때 실제로 넘겨줄 내용이다. 키와 값을 분리하는 것이 중요하다. "무엇으로 검색되는가"(키)와 "검색되면 무엇을 내놓는가"(값)는 다른 것일 수 있기 때문이다 — 이를테면 "동사"라는 문법적 성질로 검색되지만 정작 넘겨줄 내용은 그 동사의 시제나 의미일 수 있다.
한 토큰이 이 세 역할을 동시에 하도록, 입력 벡터에서 서로 다른 세 개의 선형 투영으로 Q·K·V를 만든다. "질의로서의 나"와 "키로서의 나"와 "값으로서의 나"가 각각 다른 부분공간에 놓이는 셈이다. 결국 어텐션의 출력은 한 문장으로 요약된다 — 질의와 키의 유사도로 가중한 값들의 평균. 나머지는 이 문장을 행렬로 정확히 옮기는 일이다.
1.4.4 스케일드 닷프로덕트 어텐션
이제 단계별로 쌓는다. 입력은 토큰 임베딩 행렬 $X \in \mathbb{R}^{n \times d_{model}}$ — $n$ 개 토큰, 각 $d_{model}$ 차원이다. 모든 단계에서 텐서의 모양을 함께 따라가면 혼동하지 않는다.
투영. 학습되는 세 가중치 $W^Q, W^K \in \mathbb{R}^{d_{model}\times d_k}$, $W^V \in \mathbb{R}^{d_{model}\times d_v}$ 로 입력을 세 부분공간에 사영한다.
$$Q = X W^Q\ (n\times d_k), \qquad K = X W^K\ (n\times d_k), \qquad V = X W^V\ (n\times d_v)$$
$Q$ 의 $i$ 번째 행은 토큰 $i$ 가 제시하는 질의, $K$ 의 $j$ 번째 행은 토큰 $j$ 가 내건 키, $V$ 의 $j$ 번째 행은 토큰 $j$ 가 넘겨줄 값이다. 이 세 행렬이 어텐션이 학습으로 얻는 지식의 대부분을 담는다 — "무엇을 무엇으로 찾을지"가 여기 저장된다. 질의와 키는 서로 내적을 해야 하므로 차원이 같아야 하지만($d_k$), 값의 차원 $d_v$ 는 원리상 달라도 된다(보통은 같게 둔다).
점수. 질의와 키의 내적으로 유사도를 잰다. 내적은 두 벡터가 같은 방향을 가리킬수록 크고, 직교하면 0, 반대면 음수다.
$$S = QK^\top\ (n\times n), \qquad S_{ij} = q_i \cdot k_j$$
$S_{ij}$ 는 토큰 $i$ 가 토큰 $j$ 에 얼마나 주목할지의 원점수다. 그리고 바로 이 $n\times n$ 행렬이 나타나는 순간이 제곱 비용의 근원이다 — 토큰 수가 두 배면 이 행렬은 네 배가 된다.
스케일. 점수를 $\sqrt{d_k}$ 로 나눈다. 왜 하필 이 값인지는 1.4.6에서 분산 논증으로 따진다.
$$S_{\text{scaled}} = \frac{QK^\top}{\sqrt{d_k}}$$
마스크(선택). 디코더처럼 미래를 봐선 안 되는 경우, 금지된 자리에 $-\infty$ 를 더한다(1.4.10). 전부 봐도 되는 인코더라면 건너뛴다.
softmax. 각 행에 softmax를 취해 합이 1인 가중치로 바꾼다.
$$A_{ij} = \frac{\exp(S_{\text{masked},\,ij})}{\sum_{j'} \exp(S_{\text{masked},\,ij'})}$$
$A$ 의 각 행은 확률분포다 — 토큰 $i$ 가 주의를 어디에 어떻게 배분할지. 흔히 시각화되는 "어텐션 맵"이 이것이다. 행별로 정규화한다는 점이 중요하다 — 각 질의가 독립적으로 자기 주의를 100% 배분한다.
가중 평균. 마지막으로 그 가중치로 값들을 섞는다.
$$\text{Attention}(Q,K,V) = A V\ (n\times d_v)$$
출력의 $i$ 번째 행 $o_i = \sum_j A_{ij}\, v_j$ 는 토큰 $i$ 가 주목한 곳들의 값을 가중 평균한 것 — 주변을 반영해 갱신된, 그 위치의 문맥화된 표현이다. 이것이 전부다. 처음의 그 식이 이 여섯 단계의 압축이다.
1.4.5 수계산 — 완전한 어텐션 한 층
식만 보면 미끄러진다. 아주 작지만 완전한 예제를 끝까지 실행해 보자(모두 설명용 개략값이다). $d_k = d_v = 2$, 토큰 3개, 투영은 이미 끝났다고 하자.
$$Q = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \end{bmatrix}, \qquad K = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \end{bmatrix}, \qquad V = \begin{bmatrix} 10 & 0 \\ 0 & 10 \\ 5 & 5 \end{bmatrix}$$
먼저 점수 행렬 $S = QK^\top$ 를 전부 계산한다. 각 원소는 질의 행과 키 행의 내적이다.
$$S = \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 1 & 1 & 2 \end{bmatrix}$$
$\sqrt 2 \approx 1.414$ 로 나눈 뒤 각 행에 softmax를 취한다. 첫 행 $[0.707, 0, 0.707]$ 의 지수는 $[2.028, 1, 2.028]$, 합 $5.056$ 이므로 가중치는 $[0.401, 0.198, 0.401]$ 이다. 나머지 행도 같은 방식으로 계산하면
$$A \approx \begin{bmatrix} 0.401 & 0.198 & 0.401 \\ 0.198 & 0.401 & 0.401 \\ 0.248 & 0.248 & 0.503 \end{bmatrix}$$
각 행의 합이 1임을 확인하라. 이제 $O = AV$ 를 계산한다. 첫 행은 $0.401[10,0] + 0.198[0,10] + 0.401[5,5] \approx [6.0, 4.0]$ 이다. 세 행을 모두 계산하면
$$O \approx \begin{bmatrix} 6.0 & 4.0 \\ 4.0 & 6.0 \\ 5.0 & 5.0 \end{bmatrix}$$
토큰 1은 $k_1$ 과 $k_3$ 에 쏠려 값이 $[10,0]$ 쪽으로 당겨졌고, 토큰 2는 대칭으로 $[0,10]$ 쪽, 토큰 3은 셋을 고루 섞어 $[5,5]$ 로 나왔다. 세 토큰이 각자 다른 방식으로 주변을 종합한 새 표현을 얻은 것이다.
이제 여기에 인과 마스크를 씌우면 무엇이 달라지는지 보자. 마스크는 $q_i$ 가 $j>i$ 인 키를 못 보게 하므로, 첫 행은 $k_1$ 만, 둘째 행은 $k_1,k_2$ 만 살아남는다. 첫 행은 후보가 하나뿐이라 softmax가 $[1.0]$ 이 되어 $o_1 = v_1 = [10, 0]$ 이다. 둘째 행은 점수 $[0, 0.707]$ 의 softmax $[0.330, 0.670]$ 으로 $o_2 = 0.330[10,0] + 0.670[0,10] = [3.3, 6.7]$ 이다. 셋째 행은 3-토큰 시퀀스에서 이미 전부를 보므로 그대로 $[5.0, 5.0]$ 이다.
$$O_{\text{masked}} \approx \begin{bmatrix} 10.0 & 0.0 \\ 3.3 & 6.7 \\ 5.0 & 5.0 \end{bmatrix}$$
마스크 전 $o_1$ 은 $[6.0, 4.0]$ 이었는데 마스크 후엔 $[10, 0]$ 이다 — 첫 토큰이 미래(토큰 2, 3)를 못 보게 되니 오직 자기 자신만 반영한다. 이것이 "다음 토큰 예측"에서 각 위치가 과거만으로 미래를 맞히도록 강제하는 정확한 메커니즘이다.
1.4.6 왜 하필 √d_k 로 나누나
이 나눗셈은 장식이 아니다. 빼면 학습이 잘 안 된다. 논거는 분산에 있다.
$q$ 와 $k$ 의 각 성분이 독립이고 평균 0, 분산 1이라 하자(학습 초기의 근사다). 그러면 내적 $q\cdot k = \sum_{i=1}^{d_k} q_i k_i$ 의 기댓값은 각 항의 기댓값이 $\mathbb{E}[q_i]\mathbb{E}[k_i]=0$ 이라 0이고, 분산은 독립 항들의 분산이 더해지므로
$$\text{Var}(q\cdot k) = \sum_{i=1}^{d_k}\text{Var}(q_i k_i) = \sum_{i=1}^{d_k} \mathbb{E}[q_i^2]\mathbb{E}[k_i^2] = d_k$$
즉 내적의 표준편차가 $\sqrt{d_k}$ 다. $d_k$ 가 64면 점수의 전형적 크기가 $\pm 8$, 128이면 $\pm 11$ 수준으로, 차원이 커질수록 점수 전체가 커진다.
왜 이것이 문제인가는 softmax의 두 성질에서 나온다. 첫째, softmax는 입력 로짓의 절대 크기에 민감하다. $\text{softmax}([1, 2])$ 와 $\text{softmax}([10, 20])$ 는 차이가 같은 1과 10이라도 전혀 다르다 — 후자는 거의 $[0, 1]$ 에 가깝게 포화한다. 점수 전체가 커지면 softmax가 가장 큰 항으로 쏠려 거의 원-핫이 된다. 그러면 부드러운 가중 평균이어야 할 어텐션이 사실상 "하나만 고르는" 딱딱한 조회로 붕괴한다 — 애써 미분 가능하게 만든 것을 도로 계단 함수로 되돌리는 셈이다.
둘째가 더 치명적이다. softmax의 기울기가 포화 지점에서 사라진다. softmax 출력 $p$ 의 야코비안은
$$\frac{\partial p_i}{\partial z_j} = p_i(\delta_{ij} - p_j)$$
이다($\delta_{ij}$ 는 $i=j$ 일 때 1, 아니면 0). 여기서 어떤 $p_i$ 가 1이나 0에 가까워지면 $p_i(1-p_i)$ 형태의 항이 전부 0으로 수렴한다. 즉 포화한 softmax를 통과하는 역전파 기울기가 거의 없어져, 어텐션 가중치를 어떻게 바꿔야 손실이 줄어드는지에 대한 신호가 끊긴다. 1.3에서 본 기울기 소실이 여기서 재발하는 것이다.
해법은 점수를 다시 분산 1로 되돌리는 것이다. $\sqrt{d_k}$ 로 나누면 $\text{Var}(q\cdot k/\sqrt{d_k}) = d_k/d_k = 1$ 이 되어, 차원이 아무리 커도 점수가 $O(1)$ 로 유지된다. 이 논증은 "성분이 독립·평균0·분산1"이라는 이상화에 기댄 근사이고 학습이 진행되면 정확히 성립하지 않지만, 스케일이 없으면 큰 $d_k$ 에서 학습이 불안정하다는 실무적 사실은 확고하다. 참고로 이 스케일은 샘플링의 온도(1.7)와 형제 관계다 — 온도가 로짓을 나눠 분포의 뾰족함을 조절하듯, $\sqrt{d_k}$ 는 점수의 전형적 크기를 조절해 어텐션이 적당히 부드럽게 유지되도록 한다.
softmax를 실제로 계산할 때의 수치 안정도 정리해 둘 가치가 있다. $\exp$ 는 입력이 조금만 커도 오버플로한다 — $\exp(1000)$ 은 부동소수 범위를 넘는다. 그래서 모든 구현은 softmax 전에 각 행의 최댓값을 빼고 계산한다: $\text{softmax}(z)_i = \exp(z_i - \max z)/\sum_j \exp(z_j - \max z)$. 최댓값을 빼도 분자·분모에서 상쇄되어 결과는 똑같지만, 지수의 인자가 0 이하가 되어 오버플로가 사라진다. 이 "max 빼기" 트릭은 사소해 보이지만 FlashAttention이 어텐션을 타일 단위로 계산할 때 최댓값을 온라인으로 갱신하는 온라인 softmax의 토대가 된다(1.4.17).
1.4.7 어텐션은 순서를 모른다
앞에서 어텐션이 "집합 연산"이라 위치 정보를 잃는다고 했는데, 이것은 수사적 표현이 아니라 증명할 수 있는 성질이고, 위치 인코딩(1.5)이 왜 반드시 필요한지의 근거다. 입력 토큰의 순서를 바꾸는 것을 치환 행렬 $P$ 를 곱하는 것으로 표현하자($PX$ 는 $X$ 의 행들을 재배열한 것이다). 그러면 셀프어텐션은 다음을 만족한다.
$$\text{Attention}(PX) = P\,\text{Attention}(X)$$
즉 입력을 섞으면 출력도 정확히 같은 방식으로 섞일 뿐, 각 토큰이 얻는 표현의 값 자체는 달라지지 않는다. 왜 그런지는 식을 따라가면 보인다. $Q, K, V$ 는 $X$ 의 각 행에 같은 $W$ 를 곱한 것이라 행을 섞으면 그대로 섞이고($PQ, PK, PV$), 점수 행렬은 $(PQ)(PK)^\top = PQK^\top P^\top$ 로 행과 열이 동시에 재배열되며, softmax는 행별 연산이라 이 재배열을 보존하고, 마지막 $PV$ 와의 곱에서 $P^\top P = I$ 가 상쇄되어 결국 $P\cdot\text{Attention}(X)$ 가 남는다.
무슨 뜻인가. "고양이가 개를 쫓았다"와 "개가 고양이를 쫓았다"는 같은 토큰 집합의 다른 순서인데, 위치 정보가 없으면 어텐션은 두 문장에서 각 단어에 똑같은 문맥 표현을 준다. 주어와 목적어를 구별하지 못한다는 뜻이고, 이것은 언어모델로서 치명적이다. RNN은 토큰을 순서대로 처리하니 순서가 구조에 내장돼 있었지만, 어텐션은 병렬성을 얻으려 그 순서를 버렸다. 그래서 트랜스포머는 순서 정보를 따로 주입해야 한다 — 토큰 임베딩에 위치 정보를 더하거나(절대 위치), 어텐션 점수를 계산할 때 상대 위치에 따라 편향을 주는(RoPE·ALiBi) 방식으로. 이 부품이 없으면 위의 대칭성 때문에 트랜스포머는 단어 가방(bag of words)과 다를 바 없어진다. 구체적인 방식은 1.5에서 다룬다.
한 가지 예외를 정리해 두자. 인과 마스크는 순서 정보를 일부 되돌리는 것처럼 보인다 — $q_i$ 가 $j\le i$ 만 보게 하니 "앞뒤" 개념이 생긴다. 실제로 인과 마스크만으로도 위치를 어느 정도 추론할 수 있다는 연구가 있지만(각 토큰이 볼 수 있는 과거의 개수가 위치에 따라 다르므로), 그것만으로는 부족하고 명시적 위치 인코딩이 여전히 필요하다. 마스크는 미래를 차단하는 장치이지 위치를 온전히 인코딩하는 장치가 아니다.
1.4.8 멀티헤드 — 하나로는 부족하다
지금까지 본 것은 어텐션 하나다. 실제 트랜스포머는 이것을 여러 벌 병렬로 실행한다. 이유는 softmax에 있다. 어텐션 한 벌은 softmax 때문에 하나의 가중 평균만 만든다 — 각 질의는 자기 주의 100%를 하나의 분포로 배분한다. 그런데 한 토큰은 동시에 여러 관계를 봐야 할 때가 있다. 예컨대 "그녀가 어제 산 책"에서 "책"은 수식하는 관계로 "산"을, 시점 관계로 "어제"를, 주어 관계로 "그녀"를 동시에 봐야 한다. 이것을 하나의 어텐션 분포에 담으려 하면 세 곳에 주의를 나눠야 하는데, 그러면 각각이 약해지고 서로 평균되어 구분되지 않는다. 그래서 어텐션을 여러 개 두고 각자 다른 부분공간에서 다른 종류의 관계를 보게 한다.
방법은 $d_{model}$ 차원을 $h$ 개 헤드로 분할하는 것이다. 각 헤드는 $d_k = d_v = d_{model}/h$ 차원에서 자기만의 투영으로 작동한다.
$$\text{head}_i = \text{Attention}(X W_i^Q,\ X W_i^K,\ X W_i^V), \qquad \text{MultiHead}(X) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)\, W^O$$
파라미터를 세어 보면 왜 이것이 사실상 무비용인지 보인다. 단일 헤드로 폭 $d_{model}$ 전체에 어텐션을 하면 $W^Q, W^K, W^V$ 각각이 $d_{model}\times d_{model}$ 이다. 멀티헤드에서는 각 헤드의 투영이 $d_{model}\times (d_{model}/h)$ 이고 헤드가 $h$ 개이므로, 모든 헤드의 $W^Q$ 를 합치면 다시 $d_{model}\times d_{model}$ 이다. $W^K, W^V$ 도 마찬가지고, 출력 투영 $W^O$ 가 하나 더 붙어, 어텐션 한 층의 파라미터는 대략 $4\,d_{model}^2$ 이다. 헤드를 몇 개로 분할하든 이 총량은 그대로다 — 멀티헤드는 하나의 큰 어텐션 위에 뭔가를 추가한 것이 아니라 그것을 여러 작은 어텐션으로 분할한 것이다. 출력 투영 $W^O$ 의 역할도 정리해 두면, 각 헤드가 자기 부분공간에서 따로 계산한 결과를 concat만 하면 부분공간별로 분리된 채로 남는데, $W^O$ 가 이들을 다시 섞어 하나의 $d_{model}$ 차원 출력으로 통합한다 — 헤드 간 정보를 결합하는 층이다.
멀티헤드를 설명할 때 흔히 "한 헤드는 문법, 한 헤드는 지시대명사를 맡아 배운다"고 말하는데, 이것은 절반만 맞다. 다음 절에서 실제 관찰되는 패턴을 보겠지만, 상당수 헤드는 잘라내도 성능이 크게 떨어지지 않을 만큼 중복이다. head pruning 연구들은 학습이 끝난 뒤 많은 헤드를 제거해도 품질이 거의 유지됨을 보였다. "헤드마다 뚜렷한 역할이 있다"는 그림은 대체로 사후 해석이지 설계로 강제한 것이 아니다.
정작 실무에서 이 중복이 문제가 되는 지점은 서빙이다. 위의 파라미터 회계는 학습·연산 비용의 이야기였지만, 추론에서는 각 헤드가 자기 키·값을 캐시에 저장해야 한다는 것이 별도의 비용이다. KV 캐시 크기는 헤드 수에 비례하는데(5.1에서 정확히 계산한다), 헤드가 많으면 이 캐시가 배치 크기와 컨텍스트 길이를 옥죈다. 그래서 여러 질의 헤드가 키·값을 공유하는 변형이 나왔다 — MQA는 모든 질의 헤드가 키·값 한 벌을 공유하고, GQA는 몇 개씩 묶어 공유하고, MLA는 키·값을 저차원으로 압축해 저장한다. 이들은 품질을 거의 잃지 않으면서 KV 캐시를 몇 배로 줄이는데, 헤드가 어차피 상당히 중복이라는 관찰이 이것이 통하는 이유의 일부다. 상세는 1.6이다.
1.4.9 어텐션이 실제로 배우는 패턴
어텐션 맵 $A$ 를 학습된 모델에서 실제로 들여다보면 몇 가지 반복되는 패턴이 나타난다. 이것들은 설계로 넣은 것이 아니라 학습이 스스로 찾아낸 것이라 흥미롭고, 몇몇은 실무 문제로 직결되니 알아 둘 가치가 있다.
가장 흔한 것은 위치 패턴이다. 어떤 헤드는 거의 항상 바로 앞 토큰(또는 앞에서 두세 번째)에 주목한다 — 대각선 바로 아래가 밝은 어텐션 맵이다. 이런 헤드는 사실상 국소 합성곱처럼 동작하며, 이웃 정보를 옮기는 배관 역할을 한다. 구문 패턴을 보이는 헤드도 있어서, 동사가 자기 주어에, 대명사가 자기 선행사에, 전치사가 목적어에 주목하는 식으로 문법적 의존을 따라간다. 가장 유명한 것은 induction head로, "A B … A"처럼 앞서 나온 패턴을 감지해 두 번째 A 다음에 B가 올 것을 예측하도록 돕는다 — 이것은 두 개 이상의 층에 걸친 헤드가 협력해 만드는 회로로 역설계됐고, in-context 학습(6.1)의 핵심 메커니즘 중 하나로 지목된다.
그리고 실무에서 반드시 알아야 할 현상이 하나 있다. 어텐션 싱크(attention sink)다. 학습된 모델에서 상당수 헤드가 실제로 관련이 있든 없든 첫 번째 토큰에 큰 주의를 집중하는 경향을 보인다. 왜 그런가. softmax는 합이 반드시 1이라, 어떤 질의가 "지금 특별히 볼 게 없다"고 판단해도 주의를 어딘가에는 100% 배분해야 한다 — 안 보고 싶어도 안 볼 수가 없다. 모델은 이 잉여 주의를 버릴 곳으로 첫 토큰(보통 문장 시작 특수 토큰)을 학습한다. 일종의 "주의 쓰레기통"인 셈이다. 이것이 왜 실무 문제인가. 스트리밍이나 슬라이딩 윈도우로 오래된 토큰을 캐시에서 버릴 때, 만약 이 싱크 토큰까지 버리면 잉여 주의가 갈 곳을 잃어 어텐션 분포가 망가지고 생성 품질이 급락한다. 그래서 롱컨텍스트 서빙(5.6)에서는 첫 몇 개의 싱크 토큰을 항상 캐시에 남겨 두는 기법을 쓴다. 순수한 이론(softmax는 0을 못 만든다)이 구체적 서빙 설계로 이어지는 좋은 예다.
1.4.10 인과 마스킹 — 미래를 가리는 법
언어모델은 다음 토큰을 예측한다(1.1). 학습할 때 위치 $i$ 의 예측은 오직 $1$ 부터 $i$ 까지의 토큰만 봐야 한다. 만약 $i$ 가 미래를 훔쳐보면 정답을 미리 보는 것과 같아 학습이 무의미해진다 — 극단적으로, 위치 $i$ 가 위치 $i+1$ 을 볼 수 있으면 "다음 토큰"을 그대로 복사하면 되니 손실이 0이 되지만 아무것도 배우지 못한다. 그런데 셀프어텐션은 기본적으로 모든 위치를 다 본다. 이 전부-보기를 위치 $i$ 에서 미래로 자르는 것이 인과 마스킹이다.
방법이 명료하다. softmax 전에 점수 행렬의 미래 자리($j>i$)에 $-\infty$ 를 더한다. $e^{-\infty}=0$ 이니 그 자리의 가중치가 정확히 0이 된다.
$$M_{ij} = \begin{cases} 0 & j \le i \quad(\text{과거·현재})\\[2pt] -\infty & j > i \quad(\text{미래}) \end{cases}$$
이 작은 삼각형에서 챙길 것이 세 가지인데, 특히 두 번째가 트랜스포머가 이긴 핵심이다.
첫째, 이 마스크가 있으면 디코더, 없으면 인코더다 — GPT류·ChatGPT류는 인과 마스크를 쓴 디코더다. 둘째, 이 마스크 덕분에 학습이 대규모로 병렬화된다. 마스크가 없다면 "위치 $i$ 는 과거만 보고 다음을 맞혀야 한다"는 조건을 지키려고 시퀀스를 한 토큰씩 잘라 $n$ 번 순전파해야 할 것이다. 하지만 인과 마스크를 쓰면 길이 $n$ 시퀀스 전체를 한 번의 순전파에 넣고도 각 위치가 자기 미래를 못 보게 되므로, $n$ 개의 다음-토큰 예측을 단 한 번의 순전파로 병렬 학습한다 — 한 문장에서 $n$ 개의 학습 신호가 추가 비용 없이 나온다. 이것이 1.4.1에서 말한 RNN의 순차 장벽을 트랜스포머가 넘은 정확한 지점이고, 인과 LM이 데이터 효율이 좋은 이유이기도 하다. 셋째, 이 마스크는 추론의 KV 캐시로 이어진다. 생성할 때는 토큰을 하나씩 만들며 과거만 참조하는데, 인과성 덕분에 과거 토큰의 키·값이 미래에 바뀌지 않으므로 캐시해 두고 재사용할 수 있다. 인과 마스크가 없으면(양방향이면) 새 토큰이 추가될 때마다 과거의 표현도 바뀌어 캐시가 성립하지 않는다. 이 캐시가 서빙의 핵심 병목이 되는 이야기가 5.1이다.
한 가지 흔한 실수를 명시해 두자. 마스크는 반드시 softmax 전에 더해야 한다. softmax 후에 0을 곱해 미래를 지우면 남은 가중치의 합이 1이 아니게 되어 분포가 깨진다. "$-\infty$ 를 로짓에 더하고 나서 정규화"가 정답이다. 실제 구현에서는 $-\infty$ 대신 아주 큰 음수(예: $-10^9$)를 쓰는데, 지수를 취하면 0에 충분히 가까워 같은 효과를 내면서 수치 예외를 피한다.
1.4.11 크로스어텐션 — 두 시퀀스를 잇기
지금까지 본 셀프어텐션은 Q·K·V가 모두 한 시퀀스에서 나왔다. 어텐션의 일반형은 두 시퀀스를 잇는 크로스어텐션이다 — 질의는 한 시퀀스(예: 지금 생성 중인 번역문)에서, 키와 값은 다른 시퀀스(예: 원문)에서 나온다.
$$\text{CrossAttn} = \text{softmax}\!\left(\frac{Q_{\text{디코더}} K_{\text{인코더}}^\top}{\sqrt{d_k}}\right) V_{\text{인코더}}$$
번역을 예로 들면 직관이 산다. 디코더가 다음 단어를 생성하려 할 때, 그 위치의 질의가 원문의 모든 토큰(인코더의 키)과 매칭해 "지금 옮길 부분이 원문 어디인가"를 찾고, 매칭된 원문 토큰의 값을 가져와 생성에 반영한다. 인코더-디코더 구조(1.4.19)의 핵심 배관이 바로 이 크로스어텐션이다. 셀프어텐션과 기계적으로 완전히 같은 식이고 마스크만 다르다 — 크로스어텐션에는 보통 인과 마스크가 없다(디코더가 원문 전체를 봐도 컨닝이 아니다, 원문은 답이 아니니까). 다만 디코더의 셀프어텐션은 여전히 인과적이다.
디코더-온리 모델(GPT류)에는 별도의 크로스어텐션이 없다. 대신 프롬프트와 생성을 하나의 시퀀스에 이어 붙여, 인과 셀프어텐션만으로 "생성 중인 부분이 프롬프트의 어디를 참조할지"를 처리한다 — 크로스어텐션이 할 일을 셀프어텐션이 겸하는 셈이다. 이 통합이 디코더-온리가 단순하면서도 범용인 이유의 일부다. 크로스어텐션은 멀티모달에서 다시 등장한다 — 이미지 인코더가 낸 표현을 언어 디코더가 크로스어텐션으로 참조하는 구조가 비전-언어 모델의 한 갈래다(6.8).
1.4.12 블록으로 조립하기
어텐션은 트랜스포머의 한 부품일 뿐이다. 실제 층(블록)은 네 요소로 조립된다. 멀티헤드 셀프어텐션이 토큰들 사이에서 정보를 섞고, 위치별 FFN이 각 토큰 안에서 정보를 가공하고, 잔차 연결이 각 부품의 출력을 입력에 더하고, 정규화가 그 흐름을 안정시킨다(정규화의 세부는 1.5).
관용구는 "각 서브층을 잔차로 감싸고 정규화한다"이다. 정규화를 어디에 두느냐로 두 가지가 갈리는데, 이 차이가 깊은 모델의 학습 가능 여부를 가른다. 원조인 Post-LN은 잔차를 더한 뒤 정규화한다($x \leftarrow \text{Norm}(x + \text{Sublayer}(x))$). 이 경우 정규화가 잔차 경로 위에 올라앉아, 층이 깊어질수록 순전파에서 신호의 크기가, 역전파에서 기울기의 크기가 불안정하게 커지거나 작아진다. 그래서 Post-LN 깊은 모델은 학습률 웜업을 길게 주고 조심스럽게 다뤄야 발산하지 않는다. 요즘 대형 모델의 사실상 표준인 Pre-LN은 정규화를 서브층 안으로 넣고 잔차는 순수한 덧셈 경로로 남긴다.
$$x' = x + \text{MHA}\big(\text{Norm}(x)\big), \qquad x'' = x' + \text{FFN}\big(\text{Norm}(x')\big)$$
이렇게 하면 입력에서 출력까지 아무 변형 없는 덧셈 경로가 열려 있어, 기울기가 이 고속도로를 통해 어느 층으로든 곧장 흐른다. 수십에서 수백 층을 안정적으로 쌓을 수 있는 것은 대부분 이 구조 덕이다. 대가로 Pre-LN은 마지막에 잔차 스트림이 커진 상태로 나오므로 스택 맨 끝에 정규화를 한 번 더 넣는다.
잔차 연결이 왜 그렇게 중요한가는 미분 한 줄에 있다. 서브층을 $f$ 라 하면 $\frac{\partial (x + f(x))}{\partial x} = I + \frac{\partial f}{\partial x}$ 이다. 항등항 $I$ 덕에, $\frac{\partial f}{\partial x}$ 가 소실하더라도 기울기가 최소한 $I$ 를 통해 그대로 전달된다. 여러 층을 지나면 곱 $\prod (I + J_\ell)$ 이 되는데, 이것은 1.4.1에서 RNN을 괴롭히던 순수한 야코비안 곱 $\prod J_\ell$ 과 달리 항등항이 살아 있어 지수적으로 사라지지 않는다. 잔차가 없는 깊은 트랜스포머는 사실상 학습이 안 된다.
여기에 초기화 얘기를 하나 덧붙이면 그림이 완성된다. 각 층이 잔차 스트림에 무언가를 더하는데, 층이 $L$ 개면 이 더해짐이 $L$ 번 누적되어 스트림의 크기가 층 수에 따라 커진다. 이를 통제하려고 잔차로 더해지는 서브층 출력을 $1/\sqrt{2L}$ 같은 인자로 줄여 초기화하는 관례가 있다 — 그래야 학습 초기에 스트림 크기가 폭주하지 않는다. 이런 초기화·스케일링 디테일은 작아 보이지만 대규모에서 학습이 발산하느냐 완주하느냐를 가르는 요인이고, 2.6에서 다시 만난다.
블록 하나를 이해하면 전체는 단순하다. 같은 구조의 블록을 $L$ 개 쌓는다(각 블록은 자기 가중치를 가진다). 토큰 임베딩과 위치 정보에서 시작해 $L$ 개 블록을 통과하고, 마지막 정규화 후 언임베딩으로 어휘 크기의 로짓을 뽑아 다음 토큰 분포를 만든다.
여기서 주목할 것은 텐서의 모양이 블록을 지나도 $n \times d_{model}$ 로 변하지 않는다는 점이다. 각 블록은 같은 모양의 표현을 조금씩 다듬는 함수다. 이 불변성이 뒤에 나올 잔차 스트림 관점을 자연스럽게 만든다.
1.4.13 위치별 FFN — 토큰마다 생각하는 곳
어텐션이 토큰들 사이의 통신이라면, 위치별 FFN은 각 토큰 안의 계산이다. 각 위치의 벡터에 독립적으로, 그러나 모두 같은 가중치로 적용되는 2층 MLP다. "위치별"이라고 부르지만 위치마다 다른 것이 아니라 모든 위치가 하나의 FFN을 공유한다.
$$\text{FFN}(x) = \sigma(x W_1 + b_1)\,W_2 + b_2, \qquad W_1 \in \mathbb{R}^{d_{model}\times d_{ff}},\ W_2 \in \mathbb{R}^{d_{ff}\times d_{model}}$$
전형적으로 $d_{ff} = 4\,d_{model}$ 이다 — 벡터를 네 배 넓은 공간으로 펼치고, 비선형 $\sigma$ 를 통과시킨 뒤, 다시 $d_{model}$ 로 접는다. 왜 넓혔다 좁히는가. 넓은 중간 공간에서 비선형을 적용해야 표현력이 생기기 때문이다. 만약 비선형 없이 두 선형 변환만 곱하면 $x W_1 W_2$ 로 결국 하나의 선형 변환과 같아져 층을 쌓는 의미가 없어진다. FFN의 비선형이 트랜스포머의 비선형 계산 용량을 대부분 담당한다 — 어텐션은 softmax를 빼면 값에 대해 선형인 가중 평균에 가까워서, FFN이 없으면 여러 층을 쌓아도 표현력이 크게 늘지 않는다.
활성함수 $\sigma$ 는 역사적으로 진화했다. 원조는 ReLU였고, 이후 음수 구간도 약간 통과시키는 매끄러운 GELU를 거쳐, 요즘 대형 모델은 SwiGLU 같은 게이팅 계열이 흔하다. 게이팅은 FFN을 약간 바꾼다 — 확장을 두 갈래로 하고 한 갈래가 다른 갈래를 원소별로 곱해(게이트) 통과시킨다: $\text{SwiGLU}(x) = (\text{Swish}(xW_1) \odot xW_3)\,W_2$. 세 개의 가중치 행렬을 쓰는 대신 각 폭을 $\frac{2}{3}$ 로 줄여 파라미터 수를 맞추는 것이 관례다. 왜 게이팅이 더 나은지는 완전히 이론적으로 규명되진 않았지만(원 제안자조차 반농담으로 "신의 은총"이라 했다) 경험적으로 같은 파라미터에서 조금 더 나은 손실을 준다. 세부는 1.5이다.
역할 분담을 분명히 하면 트랜스포머를 훨씬 정확히 보게 된다. 어텐션은 위치들을 가로질러 정보를 혼합하고(누가 누구를 보나), FFN은 위치마다 독립적으로 그 정보를 변환한다(가져온 정보로 무엇을 계산하나). 한 가지 흥미로운 해석은 FFN을 일종의 키-값 메모리로 보는 것이다 — $W_1$ 의 각 행이 "패턴"(키)이고 $W_2$ 의 각 열이 "그 패턴이 감지되면 스트림에 쓸 내용"(값)이라는 관점인데, 이 렌즈에서 FFN은 학습으로 축적한 사실·연상을 저장하는 곳처럼 동작한다. 지식이 주로 FFN에 저장된다는 이 해석은 모델 편집 연구의 출발점이기도 하다(연구 관점이며 완전히 확립된 이론은 아니다).
1.4.14 한 번의 순전파를 차원으로 따라가기
지금까지의 부품을 하나로 이어, 토큰 하나가 모델을 통과하며 어떤 모양을 거치는지 끝까지 따라가 보자. 앞으로 쓸 예시 설정은 $d_{model}=4096$, 층 $L=32$, 헤드 $h=32$($d_{head}=128$), $d_{ff}=16384$, 어휘 $V=128{,}000$ 이다.
시작은 토큰 ID 시퀀스다. 길이 $n=1000$ 이라 하자. 임베딩 조회로 $X$ 는 $1000\times 4096$ 이 된다. 여기에 위치 정보가 추가되어도 모양은 그대로다. 첫 블록에서 정규화를 거친 뒤 어텐션에 들어가면, $W^Q, W^K, W^V$ 를 곱해 $Q, K, V$ 가 각각 $1000\times 4096$ 이 되고, 이것이 헤드별로 $32$ 개의 $1000\times 128$ 조각으로 분할된다. 각 헤드에서 $QK^\top$ 는 $1000\times 1000$ 점수 행렬이 되고 — 헤드가 32개이니 이런 행렬이 32장 — 스케일·마스크·softmax를 거쳐 $AV$ 로 다시 $1000\times 128$ 이 나온다. 32개 헤드의 출력을 concat하면 $1000\times 4096$, $W^O$ 를 곱해도 $1000\times 4096$ 이고, 이것을 잔차 스트림에 더한다.
이어 FFN에서 정규화 후 $W_1$ 을 곱해 $1000\times 16384$ 로 커지고, 활성함수를 통과한 뒤 $W_2$ 로 다시 $1000\times 4096$ 으로 접어 잔차에 더한다. 여기까지가 한 블록이다. 나온 $1000\times 4096$ 이 다음 블록의 입력이 되어 이 과정이 32번 반복된다 — 모양은 시종 $1000\times 4096$ 으로 불변이고, 값만 계속 다듬어진다. 마지막 블록 뒤 정규화를 거쳐, 언임베딩 $4096\times 128000$ 을 곱하면 각 위치가 $128000$ 차원 로짓이 된다. 생성에서는 이 중 마지막 위치의 로짓만 써서 다음 토큰을 샘플링하고(1.7), 학습에서는 1000개 위치 전부의 로짓으로 1000개의 다음-토큰 예측 손실을 낸다. 이 차원 여행을 한 번 직접 따라가 두면, 나중에 KV 캐시가 왜 층과 헤드에 비례하는지(5.1), MoE가 어느 행렬을 분할하는지(1.6)가 훨씬 선명해진다.
1.4.15 파라미터와 FLOP을 실제로 세어 보기
"이 모델은 7B다" 할 때 그 70억이 어디서 오는지 세어 보면 트랜스포머의 골격이 명확해진다. 위 설정을 그대로 쓰자.
한 층의 파라미터는 어텐션과 FFN으로 나뉜다. 어텐션은 $W^Q, W^K, W^V, W^O$ 네 개가 각각 $d_{model}^2$ 이므로 $4 d_{model}^2 = 4\times 4096^2 \approx 6710$ 만이다. FFN은 $W_1, W_2$ 가 각각 $d_{model}\times d_{ff}$ 이므로 $2\times d_{model}\times 4d_{model} = 8 d_{model}^2 = 8\times 4096^2 \approx 1$ 억 3400만이다. 한 층 합이 약 $12 d_{model}^2 \approx 2$ 억이고, 여기서 FFN이 어텐션의 두 배라는 점을 주목하라. 이것을 $L=32$ 층 쌓으면 $32\times 2$ 억 $\approx 64$ 억이다. 여기에 임베딩 행렬 $V\times d_{model} = 128000\times 4096 \approx 5$ 억 2천만이 붙는다(언임베딩과 공유하면 한 벌). 다 합치면 대략 70억 — 그래서 7B다. 이 계산에서 두 가지가 드러난다. 파라미터의 대부분이 층에 쌓인 FFN에 있다는 것(그래서 MoE가 바로 이 FFN을 분할해 조건부로 켜서 파라미터를 키우되 연산은 유지한다, 1.6), 그리고 작은 모델일수록 임베딩 행렬이 상대적으로 큰 몫을 차지한다는 것.
FLOP도 세어 보자. 순전파에서 $m\times k$ 와 $k\times p$ 를 곱하면 곱셈·덧셈 각 하나로 대략 $2mkp$ FLOP이다. 토큰 $n$ 개를 한 층에 통과시킬 때 네 투영은 $8 n d_{model}^2$, FFN의 두 행렬곱은 $16 n d_{model}^2$ 이라, 이 "선형" 비용이 $24 n d_{model}^2$ 이고, 방금 센 층당 파라미터 $12 d_{model}^2$ 의 정확히 두 배다 — 즉 순전파는 토큰당 대략 파라미터 수의 두 배 FLOP을 쓴다($2P$). 이 규칙이 2.2의 학습 컴퓨트 $6ND$(순전파 2 + 역전파 4)와 5.1의 추론 산술강도 계산의 근원이다.
어텐션의 점수·가중합은 성격이 다르다. $QK^\top$ 와 $AV$ 가 각각 $2 n^2 d_{model}$ 이라 합이 $4 n^2 d_{model}$ 로, $n$ 에 제곱이다. 이제 이 둘이 언제 뒤집히는지 정확히 계산할 수 있다. 선형 항 $24 n d_{model}^2$ 과 어텐션 항 $4 n^2 d_{model}$ 이 같아지는 지점은 $24 d_{model} = 4 n$, 즉 $n = 6 d_{model}$ 근처다. $d_{model}=4096$ 이면 $n \approx 24000$ 토큰이다. 다시 말해 컨텍스트가 수천 토큰까지는 FFN·투영이 비용을 지배하고 어텐션은 상대적으로 저렴하다가, 컨텍스트가 수만 토큰으로 길어지면 어텐션의 $n^2$ 이 전체를 흡수한다. "트랜스포머는 $O(n^2)$ 이라 항상 느리다"는 반쪽 진실인 이유가 이 계산에 있다.
1.4.16 잔차 스트림이라는 렌즈
블록을 "입력 → 어텐션 → FFN → 출력"의 파이프라인으로 봐도 되지만, 더 밝은 렌즈가 하나 있다. 잔차 스트림 관점이다(트랜스포머 회로 해석 연구에서 정식화됐다). Pre-LN에서 각 서브층은 $x \leftarrow x + f(\text{Norm}(x))$ 였다. 여기서 $x$ 를 모든 층을 관통하는 하나의 통신 버스로 볼 수 있다. 이 버스가 잔차 스트림이다. 각 어텐션 헤드와 각 FFN은 스트림에서 현재 상태를 읽고, 무언가를 계산해, 그 결과를 스트림에 더해 되돌린다. 스트림 자체는 어느 층에서도 전부 덮어써지지 않는다 — 오직 더해질 뿐이다(잔차의 항등항 덕이다). 그래서 스트림은 여러 부품이 각자 기여분을 누적하는 공유 작업 공간처럼 동작한다.
이 렌즈가 왜 유용한가. 초기 층이 스트림에 써 둔 특성 — 이를테면 "이 토큰은 사람 이름이다" — 을 뒤 층이 읽어 이어 쓴다. 층들이 스트림을 매개로 통신하는 회로를 이룬다는 것이 해석가능성 연구의 출발점이고, 실제로 이 언어로 몇몇 구체적 회로가 역설계됐다 — 대표적으로 1.4.9에서 본 induction head가 그렇다. 이 프레임이 디버깅·안전·정렬 분석(3.1·4.3)에 실제로 쓸모가 있다. 다만 분명히 해 둘 것은, 이것은 강력한 분석 도구이자 연구 관점이지 "트랜스포머가 실제로 이렇게 생각한다"는 검증된 인지 이론이 아니라는 점이다. 특정 헤드나 회로에 대한 해석은 특정 모델·조건에서의 발견이며, 다른 모델로 일반화할 때는 조심해야 한다.
1.4.17 제곱 비용을 어떻게 다루나
앞에서 어텐션 비용이 $4n^2 d_{model}$ 로 $n$ 에 제곱임을 셌다. 이 제곱이 부담이 되는 방식은 학습과 추론에서 다르고, 그것을 구분하는 것이 서빙을 이해하는 핵심이다.
학습과 프리필에서는 전체 시퀀스를 한 번에 처리하므로 $n\times n$ 점수 행렬 전체를 다뤄야 해서 $O(n^2)$ 메모리가 특히 문제가 된다. $n=8192$ 면 한 헤드의 점수 행렬만 $8192^2$ 원소이고, 헤드와 배치까지 곱하면 활성값 메모리가 급증한다. 이 메모리 장벽을 깨는 것이 FlashAttention이다. 핵심 통찰은 $n\times n$ 행렬 전체를 만들어 메모리에 저장할 필요가 없다는 것이다 — 어텐션 출력은 결국 값들의 가중 평균이므로, 점수 행렬을 타일(블록) 단위로 계산하며 softmax를 온라인으로 누적하면 전체 행렬을 재구성하지 않고도 정확한 출력을 얻는다. 온라인 softmax는 1.4.6에서 본 "max 빼기"를 확장한 것으로, 지금까지 본 최댓값과 지수합을 들고 다니다 새 블록이 올 때마다 갱신하며, 이전까지 누적한 출력을 새 최댓값에 맞춰 재조정한다. 이 덕에 어텐션을 $O(n)$ 메모리로 계산한다. 흔한 오해와 달리 이것은 근사가 아니라 정확히 같은 결과를 메모리 이동만 줄여 내는 것이고, 그래서 트랜스포머의 복잡도를 바꾸지 않으면서도(여전히 $O(n^2)$ 연산) 실측 속도와 메모리를 크게 개선한다.
반면 자기회귀 디코딩에서는 토큰을 하나씩 생성하며 새 질의 하나가 과거 $n$ 개 키·값을 본다. 과거의 키·값을 캐시하면 매 스텝이 $O(nd)$ 로 떨어져 재계산을 피하지만, 그 대가로 캐시가 층 수와 헤드 수에 비례해 메모리에 쌓인다. 이 KV 캐시 메모리가 서빙에서 배치 크기와 컨텍스트 길이를 옥죄는 병목이고, 그 정확한 산식과 규모는 5.1에서 계산한다. 헤드 간 키·값 공유(MQA·GQA·MLA)와 PagedAttention이 정확히 이 병목을 겨냥한다(1.6·5.3).
$n^2$ 을 근본적으로 낮추려는 연구는 방대하지만 크게 세 갈래다. 정확하되 IO를 효율화하는 FlashAttention 계열은 복잡도 자체는 $O(n^2)$ 이지만 실측을 크게 개선한다. 각 토큰이 전부가 아니라 이웃 일부(슬라이딩 윈도우)나 블록 단위 부분집합만 보게 하는 희소·국소 어텐션은 $O(n\cdot w)$ 로 줄이는 대신 표현력을 맞바꾼다 — 멀리 떨어진 의존을 직접 포착하지 못하니 여러 층을 거쳐 간접적으로만 연결된다. softmax를 커널로 근사하거나 순환 형태로 바꾸는 선형 어텐션과 상태공간 모델(SSM/Mamba)은 $O(n)$ 을 노리며, 어텐션의 "모든 쌍 직접 연결"을 포기하는 대신 고정 크기 상태로 과거를 압축한다 — RNN의 아이디어를 병렬 학습 가능한 형태로 되살린 셈이다(1.6). 이들 대부분은 특정 조건에서 유리한 트레이드오프이지 무비용 승리가 아니다 — "차세대 구조가 트랜스포머를 완전히 대체했다"는 서사는 아직 과장에 가깝다.
1.4.18 대규모에서 어텐션은 어떻게 학습을 중단시키나
작은 모델에서는 잘 실행되는 어텐션이 수십억 파라미터 규모에서는 학습을 불안정하게 만드는 원흉이 되곤 한다. 이 절은 2.6(학습 안정화)의 예고이지만, 그 불안정의 상당수가 어텐션에서 나오므로 여기서 단초를 마련해 둔다.
가장 잘 알려진 것이 어텐션 로짓 폭주다. 학습이 진행되며 $q$ 와 $k$ 의 크기가 커지면 $QK^\top$ 점수 전체가 커지고 — 1.4.6에서 본 바로 그 포화가 대규모에서 실제로 발생한다 — softmax가 거의 원-핫으로 굳어 버린다. 이것을 어텐션 엔트로피 붕괴라 부른다. 어텐션이 한 곳만 보게 되면 그 헤드는 사실상 비활성화되고, 심하면 손실이 갑자기 급등하는 스파이크로 나타나 학습을 발산시킨다. 대응책 몇 가지가 표준이 됐다. QK-norm은 $q$ 와 $k$ 를 내적하기 전에 정규화해 점수의 크기가 통제 불능으로 커지지 않게 막는다. 로짓 소프트 캡(soft-capping)은 어텐션 점수(또는 최종 로짓)를 $\tanh$ 같은 함수로 부드럽게 상한을 씌워 폭주를 원천 차단한다. 이런 장치는 작은 모델에서는 없어도 되지만 대규모에서는 학습을 완주시키느냐 마느냐를 가른다.
또 하나는 어텐션의 수치 정밀도다. 대규모 학습은 메모리와 속도를 위해 fp16이나 bf16 같은 저정밀도를 쓰는데(2.6), softmax와 그 안의 지수는 정밀도에 민감하다. 그래서 어텐션의 softmax 부분만은 fp32로 계산하는 것이 흔한 관례다 — 점수 누적에서 정밀도가 부족하면 분포가 미묘하게 틀어져 학습이 불안정해진다. 이런 "어디를 어느 정밀도로 계산하나"의 결정은 2.6의 주제이지만, 어텐션이 그 결정이 가장 예민한 지점 중 하나임을 기억해 두면 좋다.
이 절의 요점은 하나다. 어텐션은 우아한 수식이지만, 그것을 수십억 파라미터·수조 토큰 규모로 확장하면 1.4.6에서 이론적으로 본 포화가 실제 발산으로 나타나고, 그것을 다스리는 실무적 장치들이 현대 대형 모델 레시피의 일부가 된다. 원리와 실무가 만나는 지점이다.
1.4.19 왜 생성형은 디코더-온리로 수렴했나
같은 어텐션 부품이라도 어떻게 연결하느냐에 따라 세 계열이 나온다. 차이의 핵심은 마스킹, 즉 누가 무엇을 보느냐다.
인코더-온리는 마스크 없이 양방향으로 전부 보므로 각 토큰이 양쪽 문맥을 다 반영한 풍부한 표현을 얻는다 — 이해·분류·문장 임베딩·검색(6.2)에 강하다. 대신 양방향이라 자기회귀 생성을 못 한다(다음 토큰을 맞히려는데 그 토큰을 이미 보고 있으니 학습이 성립하지 않는다). 학습은 문장의 일부를 가리고 맞히는 마스크드 LM(빈칸 채우기)으로 한다. 인코더-디코더는 양방향 인코더로 입력을 이해하고, 인과 디코더가 크로스어텐션(1.4.11)으로 그 인코딩을 참조하며 출력을 생성하는 구조다 — 원문과 결과의 경계가 뚜렷한 번역·요약 같은 변환에 잘 맞는다. 디코더-온리는 인과 마스크로 미래를 차단한 단일 스택으로, 프롬프트든 답이든 전부 하나의 이어 쓰기로 다룬다.
번역 같은 seq2seq에서 출발한 원조 트랜스포머는 인코더-디코더였는데, ChatGPT·Claude류 대화형 모델은 대부분 디코더-온리로 수렴했다. 왜 그랬는지는 몇 가지 힘이 겹친 결과다. 가장 크게는 모든 과제를 이어 쓰기 하나로 통일할 수 있기 때문이다. 질의응답도, 번역도, 요약도, 코딩도, 대화도 전부 "프롬프트에 이어 다음 토큰을 생성"으로 표현되니, 입력과 출력을 나누는 경계가 필요 없어지고 한 스택이 범용이 된다. 학습 효율도 크다. 앞서 봤듯 인과 LM은 시퀀스의 모든 위치가 예측 대상이라 한 문장에서 $n$ 개의 학습 신호가 나오는데, 빈칸 채우기는 가린 일부(흔히 15% 정도)만 학습에 쓰여 같은 데이터에서 신호 밀도가 낮다. 단일 스택은 분산학습과 서빙 파이프라인이 단순해 스케일링에 유리했고, 예시를 프롬프트에 넣으면 파라미터를 안 바꾸고도 새 과제를 푸는 in-context 학습이 이 구조의 대규모 사전학습에서 두드러지게 나타났다(6.1). 마지막으로 인과 마스크 덕에 생성 시 KV 캐시를 쓸 수 있어 실시간 스트리밍에 잘 맞는다(5.1).
이것은 역사적·경험적 수렴이지 인코더-디코더가 열등하다는 결론이 아니다. 입력-출력이 뚜렷한 변환 과제나 임베딩·검색에서는 다른 계열이 여전히 유효하고, 인코더처럼 양방향으로 프롬프트를 보되 디코더처럼 생성하는 프리픽스 LM 같은 절충 구조도 있다. 다만 이 책이 이후 대화형 LLM을 다룰 때 기본으로 가정하는 것은 디코더-온리다 — 그것이 현재 대화형 시스템의 주류이기 때문이다.
이 장에서 미뤄 둔 세 부품이 있다. 어텐션에 순서를 되돌려 주는 위치 인코딩, 학습을 안정시키는 정규화, FFN의 비선형을 담당하는 활성함수다. 위치 인코딩은 1.4.7에서 왜 필요한지 증명했으니 이제 그것을 어떻게 하는지 — 사인·코사인, RoPE, ALiBi — 가 남았고, 정규화와 활성함수도 여기서는 블록의 어디에 위치하는지만 봤다. 1.5에서 이 셋을 왜·어떻게까지 판 다음, 1.6에서 이 블록을 대규모와 롱컨텍스트로 변형하는 현대 아키텍처(MoE·GQA·SSM)로 넘어간다.