Chapter 05

어텐션

'배'라는 글자 하나만 보고는 그것이 과일인지, 바다 위의 배인지, 사람의 배인지 알 수 없다. 단어의 뜻은 주변 단어가 정한다. 어텐션(attention)은 문장 속 모든 토큰이 다른 모든 토큰을 둘러보고, 지금 자신에게 중요한 정보를 골라 섞어 오는 연산이다. 2017년 Transformer 이후 거의 모든 언어 모델·이미지 모델·음성 모델의 심장이 된 이 연산을, 이 장에서는 숫자 하나하나까지 손으로 만져 본다. 점수 행렬의 칸을 끌어 올리면 소프트맥스가 다시 계산되고, 쿼리 벡터를 끌어 옮기면 어텐션 패턴이 통째로 바뀐다.

왜 어텐션인가: 뜻은 문맥이 정한다

4장에서 각 토큰을 하나의 벡터(임베딩)로 바꿨다. 그런데 임베딩 테이블은 토큰마다 벡터를 딱 하나만 준다. 다음 세 문장의 '배'는 모두 같은 토큰이니 처음에는 같은 벡터에서 출발한다.

시장에서 산 배가 달다  → 과일
항구에 배가 들어왔다  → 선박
점심을 급하게 먹었더니 배가 아프다  → 신체

사람은 '시장·달다', '항구·들어왔다', '먹었더니·아프다'를 보고 즉시 뜻을 고른다. 모델도 그렇게 하려면 각 토큰의 벡터를 주변 토큰의 정보로 고쳐 쓰는 장치가 필요하다. 영어의 고전적인 예도 있다.

The animal didn't cross the street because it was too tired
The animal didn't cross the street because it was too wide

두 문장은 마지막 단어 하나만 다르다. 그런데 'it'이 가리키는 대상이 'animal'에서 'street'로 바뀐다. 'it'의 의미를 정하려면 문장 끝의 'tired/wide'와 문장 앞의 'animal/street'를 동시에 참고해야 한다. 거리가 먼 두 토큰 사이에 정보가 직접 오가야 한다는 뜻이다.

RNN의 병목: 모든 것을 벡터 하나에

Transformer 이전의 대표 구조인 RNN(Recurrent Neural Network)은 토큰을 왼쪽부터 하나씩 읽으며 은닉 상태 \(h_t = f(h_{t-1}, x_t)\)를 갱신했다. 번역용 인코더-디코더 RNN(2014)은 원문 전체를 읽은 마지막 은닉 상태 하나, 즉 고정 길이 벡터 c에 문장 전체를 압축한 뒤 디코더가 그 벡터만 보고 번역문을 만들었다. 문장이 길어질수록 앞부분 정보가 희미해지고, 번역 품질이 문장 길이에 따라 뚝 떨어졌다.

(a) RNN 인코더-디코더: 문장 전체가 벡터 c 하나로 압축된다 h₁ h₂ h₃ ⋯ h₁₁ Theanimaldidn't⋯tired c 고정 길이 병목 s₁ s₂ ⋯ 그동물은⋯ (b) 어텐션: 매 단계 모든 위치를 직접 들여다보고, 가중치로 섞는다 h₁ h₂ h₃ ⋯ h₁₁ Theanimaldidn't⋯tired s₂ '동물은'을 만들 차례 선 굵기 = 어텐션 가중치 거리와 무관하게 한 단계로 연결
그림 5-1. (a) 2014년식 인코더-디코더 RNN은 원문 전체를 마지막 은닉 상태 c에 욱여넣는다. (b) Bahdanau 등(2014)은 디코더가 매 단계 인코더의 모든 은닉 상태를 다시 보고, 지금 필요한 위치에 큰 가중치를 주어 섞어 오게 했다. 이것이 어텐션의 출발점이다. Transformer(Vaswani 등, 2017)는 RNN을 아예 없애고 어텐션만으로 문장을 처리했다.

어텐션의 장점은 세 가지로 정리된다. 첫째, 거리와 무관한 직접 연결: 1번 토큰과 1000번 토큰 사이도 한 번의 연산으로 정보가 오간다(RNN이라면 1000단계를 거쳐야 한다). 둘째, 병렬성: 모든 토큰 쌍의 유사도는 행렬곱 한 번으로 동시에 계산되므로 GPU에 꼭 맞는다. 셋째, 내용 기반 라우팅: 어디를 볼지가 고정된 규칙이 아니라 토큰의 내용에 따라 매번 달라진다. 대가도 있다. 모든 쌍을 보므로 비용이 길이의 제곱에 비례한다(9절).

직관: 부드러운 사전 검색

파이썬 딕셔너리 d["animal"]는 키가 정확히 일치하는 항목 하나의 값을 돌려준다. 어텐션은 이것을 미분 가능하게 '부드럽게' 만든 것이다. 세 가지 역할이 있다.

쿼리를 모든 키와 비교해 유사도 점수 \(s_j = q\cdot k_j\)를 얻고, 소프트맥스로 합이 1인 가중치 \(a_j\)로 바꾼 뒤, 값을 그 가중치로 평균한다. 정확히 하나만 고르는 대신 '조금씩 다' 고르는 셈이다.

$$ a_j = \frac{\exp(q\cdot k_j / T)}{\sum_{l}\exp(q\cdot k_l / T)}, \qquad \text{출력} = \sum_j a_j\, v_j $$
여기서 \(T\)는 온도. \(T\to 0\)이면 가장 비슷한 키 하나만 고르는 hard lookup(딕셔너리)이 되고, \(T\to\infty\)면 모든 값의 단순 평균이 된다.
하드 룩업 (딕셔너리) q = "animal" "street" "animal" "tired" v₁ v₂ v₃ v₂ 일치 = 1, 불일치 = 0 → 미분 불가, 비슷한 키가 있어도 정확히 같지 않으면 실패 소프트 룩업 (어텐션) q k₁ k₂ k₃ 0.42.1−0.3 점수 q·kⱼ .14.78.08 softmax → aⱼ 출력 = 0.14·v₁ + 0.78·v₂ + 0.08·v₃ 비슷한 정도만큼 섞는다 → 매끄럽고 미분 가능
그림 5-2. 딕셔너리는 일치하는 키 하나만 고른다. 어텐션은 모든 키와의 유사도를 소프트맥스로 확률처럼 바꿔 값을 섞는다. 가중치가 연속적이므로 경사하강(3장)으로 "어떤 키를 얼마나 볼지"를 학습할 수 있다.
SIMULATOR

소프트 룩업: 쿼리를 끌어 키 사이를 돌아다니기

q·키 모두 드래그
유사도
최대 가중치—
엔트로피—
유효 키 수 1/Σa²—
출력 색 (RGB)—
해볼 것: ① 쿼리 q(큰 원)를 한 키 바로 위에 올리고 온도를 낮춰 보자. 가중치가 그 키 하나로 몰리며 출력 색이 그 키의 색이 된다 — 딕셔너리다. ② 온도를 5로 올리면 출력이 모든 색의 평균(탁한 회색 쪽)으로 간다. ③ '내적' 모드에서 q를 원점에서 멀리 끌면 같은 방향이라도 길이가 긴 키가 이긴다(내적은 길이에도 비례). '−거리²' 모드와 비교해 보자. 키를 끌어 원점 근처로 모으면 어떤 쿼리에도 점수가 비슷해진다.

이 시뮬레이터의 값 \(v_j\)는 색(RGB 3차원 벡터)이다. 실제 어텐션에서 값은 수십~수백 차원 벡터이고, 출력은 그 벡터들의 가중 평균이다. 원리는 똑같다. 한 가지 짚어 둘 것은 내적은 방향뿐 아니라 길이에도 반응한다는 점이다. 그래서 학습된 모델은 키·쿼리의 길이(노름)를 이용해 "이 토큰은 웬만하면 주목받게" 같은 효과도 낸다.

Scaled dot-product attention

문장에 토큰이 n개 있고 각각 d차원 벡터라고 하자. 이를 행으로 쌓은 것이 입력 행렬 \(X\in\mathbb{R}^{n\times d}\)다. 모든 토큰이 동시에 쿼리이자 키이자 값이므로(셀프 어텐션), 행렬 세 개 \(W_Q, W_K\in\mathbb{R}^{d\times d_k}\), \(W_V\in\mathbb{R}^{d\times d_v}\)로 각 역할의 벡터를 만든다.

$$ Q = XW_Q,\quad K = XW_K,\quad V = XW_V $$ $$ \mathrm{Attention}(Q,K,V) = \underbrace{\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}} + M\right)}_{A\ (n\times n)}\,V $$
\(S = QK^\top/\sqrt{d_k}\)의 (i, j) 원소는 "토큰 i의 쿼리와 토큰 j의 키의 유사도". 소프트맥스는 행마다 따로 적용되어 각 행의 합이 1이 된다. \(M\)은 마스크(볼 수 없는 위치는 \(-\infty\), 나머지는 0). 출력 \(O = AV\)의 i번째 행은 값 벡터들의 가중 평균이다.
모양으로 따라가 보기 (n = 4 토큰, d = 6, d_k = d_v = 3) X n×d · W_Q d×d_k = Q n×d_k K n×d_k V XW_V → XW_K → Q · Kᵀ d_k×n = S: n×n ÷√d_k, +M 행별 softmax A: n×n 각 행 합 = 1 · V = O n×d_v · W_O → n×d • 비용의 핵심은 n×n 행렬 S, A • 입력과 출력 모양이 같다(n×d) → 층을 계속 쌓을 수 있다 • i번째 출력 행 = Σⱼ Aᵢⱼ·vⱼ
그림 5-3. 셀프 어텐션의 행렬 모양. 입력 X(n×d)에서 Q·K·V를 만들고, n×n 점수 행렬 S를 행별 소프트맥스로 A로 바꾼 뒤, V와 곱해 다시 n×d_v를 얻는다. 멀티헤드(7절)에서는 출력 투영 \(W_O\)가 헤드들을 섞어 다시 n×d로 돌려놓는다.

왜 \(\sqrt{d_k}\)로 나누는가

쿼리와 키의 각 성분이 평균 0, 분산 1인 독립 확률변수라고 하자. 내적 \(q\cdot k = \sum_{i=1}^{d_k} q_i k_i\)는 분산 1짜리 항 \(d_k\)개의 합이므로 분산이 \(d_k\), 표준편차가 \(\sqrt{d_k}\)다. \(d_k = 128\)이면 점수가 대략 ±11 범위로 퍼진다. 소프트맥스는 입력 차이에 지수적으로 반응하므로, 이렇게 큰 점수가 들어가면 가장 큰 항 하나에 가중치가 거의 다 몰린다(포화). 포화된 소프트맥스는 기울기 \(\partial a/\partial s = \mathrm{diag}(a) - aa^\top\)가 거의 0이라 학습 신호가 사라진다. \(\sqrt{d_k}\)로 나누면 분산이 다시 1이 되어 차원이 몇이든 소프트맥스가 '적당히 부드러운' 영역에서 시작한다.

$$ \mathrm{Var}(q\cdot k) = \sum_{i=1}^{d_k}\mathrm{Var}(q_i k_i) = d_k \quad\Longrightarrow\quad \mathrm{Var}\!\left(\frac{q\cdot k}{\sqrt{d_k}}\right) = 1 $$
SIMULATOR

스케일링의 필요성: 차원이 커지면 소프트맥스가 굳는다

Var(q·k) 측정—
Var(q·k/√d) 측정—
평균 최대 가중치 (스케일 X / O)—
평균 엔트로피 (X / O)—
기울기 크기 ‖J‖F (X / O)—
해볼 것: ① d_k를 2에서 1024까지 올리며 'Var(q·k)'가 d_k를 그대로 따라가는지 보자(400회 샘플 평균). ② d_k = 128(Llama·GPT-2급 헤드 차원은 64~128)에서 스케일 없는 소프트맥스(분홍)는 거의 한 칸만 남고, 엔트로피가 0 근처로 떨어진다. ③ 아래 곡선은 d_k별 평균 최대 가중치다. 스케일 O(보라)는 d_k와 무관하게 평평하다. ④ 기울기 크기를 비교해 보자. 포화되면 학습 신호가 수십 배 작아진다. 모든 값은 정규분포 q, k를 실제로 뽑아 계산한다.

어텐션 행렬을 직접 편집하기

이제 이 장의 핵심 실험이다. 실제 모델에서 점수 행렬 S는 \(QK^\top/\sqrt{d_k}\)로 계산되지만, 여기서는 그 앞단을 잠시 잊고 S의 칸을 손으로 직접 정한다. 칸을 위로 끌면 점수가 올라가고, 아래로 끌면 내려간다. 오른쪽(모바일에서는 아래)의 A는 각 행에 소프트맥스를 적용한 실제 결과이고, 맨 아래 아크 그림은 선택한 행(쿼리 토큰)이 어느 토큰에서 정보를 가져오는지를 선 굵기로 보여 준다.

먼저 관찰해 둘 성질 세 가지가 있다. ① 소프트맥스는 행 안에서의 상대 차이에만 반응한다. 한 행 전체에 같은 값을 더해도 A는 그대로다. ② 점수 차이가 1 늘 때마다 가중치 비율은 \(e \approx 2.7\)배가 된다. 차이가 3이면 약 20배, 5면 약 150배다. ③ 다른 행은 서로 독립이다. 행 i를 고쳐도 다른 행의 A는 변하지 않는다. 각 토큰이 '자기만의 질문'을 따로 던지기 때문이다.

SIMULATOR

어텐션 행렬 직접 편집기

마스크
선택 행
점수 패턴 (S 전체를 바꾼다)
선택 행 엔트로피—
최대 가중치—
유효 토큰 수 1/Σp²—
행 합 Σp—
해볼 것: ① 기본 문장에서 'it' 행(선택됨)의 'animal' 칸을 위로 끌어 5 이상으로 올려 보자. A의 그 칸이 짙어지고 아크가 굵어진다. 이제 'street' 칸을 같은 높이로 올리면 가중치가 반반으로 갈린다 — 모델이 "it = animal인지 street인지" 망설이는 상태다. ② 온도를 0.1로 내리면 행마다 최댓값 하나만 남고(엔트로피 → 0, 유효 토큰 수 → 1), 5로 올리면 모든 칸이 비슷해진다. ③ '인과 마스크'를 켜면 대각선 위가 회색(−∞)이 되고, 첫 행은 자기 자신만 볼 수 있어 가중치가 1.00이 된다. '배를 먹었더니…' 문장에서 마스크를 켜면 첫 '배를'은 뒤의 '먹었더니'를 볼 수 없다 — 왼쪽에서 오른쪽으로 생성하는 언어 모델의 한계다. ④ '첫 토큰(sink)' 패턴을 누르고 몇 행만 다른 칸을 올려 보자. 실제 LLM의 많은 헤드가 이런 모양이다(8절). 셀 위에서 마우스 휠로도 ±0.25씩 조정된다.

편집기의 숫자는 교육용으로 손으로 넣은 값이지만, 실제로 학습된 Transformer를 시각화하면 비슷한 장면이 보인다. BERT의 일부 헤드는 대명사에서 선행 명사로 강한 가중치를 보내고(Clark 등, 2019), GPT 계열의 많은 헤드는 첫 토큰이나 문장부호에 가중치 대부분을 몰아 둔다. 단, 어텐션 가중치가 크다고 해서 그 토큰이 출력에 '원인'이 된다고 단정할 수는 없다는 점도 기억해 두자. 값 벡터의 크기와 그 뒤의 \(W_O\)가 실제 기여를 함께 정한다.

유효 토큰 수 \(1/\sum p_j^2\)

가중치가 k개 토큰에 균등하게 퍼져 있으면 정확히 k가 된다. 하나에 몰리면 1, n개 모두에 고르게 퍼지면 n이다. 엔트로피 \(H\)(비트)로 쓰면 \(2^H\)도 비슷한 '몇 개를 보고 있나'의 척도다. 어텐션 헤드가 얼마나 '날카로운지' 비교할 때 자주 쓴다.

Q, K, V는 어디서 오나: 같은 X, 다른 W

앞 절에서는 S를 손으로 넣었다. 실제 모델에서 S를 정하는 것은 학습되는 행렬 \(W_Q\)와 \(W_K\)다. 핵심은 \(S_{ij} = (x_i W_Q)\cdot(x_j W_K)/\sqrt{d_k} = x_i\,(W_Q W_K^\top)\,x_j^\top/\sqrt{d_k}\)라는 점이다. 즉 어텐션 점수는 임베딩 공간에 정의된 학습 가능한 쌍선형 유사도다. 그냥 \(x_i\cdot x_j\)를 쓰면 "비슷한 단어끼리" 서로 보게 될 뿐이지만, \(W_Q W_K^\top\)를 거치면 "대명사는 명사를 찾는다", "각 토큰은 바로 앞 토큰을 찾는다"처럼 비대칭적이고 목적이 있는 관계를 표현할 수 있다. 같은 문장에 서로 다른 W를 가진 헤드가 여러 개 있는 이유도 여기에 있다.

값 쪽도 같다. \(V = XW_V\)는 "선택되면 무엇을 넘겨줄지"를 정한다. 키는 '광고'이고 값은 '내용물'이다. 둘을 분리해 두었기 때문에, 어떤 토큰이 "나는 명사다"라는 키로 주목을 받으면서 정작 넘겨주는 값은 "나는 살아 있는 존재이고 피곤할 수 있다" 같은 다른 성질일 수 있다.

SIMULATOR

Q·K 공간에서 조작하기: W를 바꾸면 패턴이 바뀐다

W_Q (2×2)
W_K (2×2)
숫자 칸을 위아래로 끌면 값이 바뀐다(휠도 가능). W_V = I(항등)로 고정해 V = X.
W 프리셋
옵션
선택 토큰—
가장 많이 보는 토큰—
it → animal 가중치—
출력 o 와 x_animal 거리—
해볼 것: ① '명사→대명사 헤드'를 누르면 'it' 행이 거의 전부 'animal'로 간다. 왼쪽 X 평면에서 'it'의 출력 o(◆)가 xanimal 바로 옆으로 끌려간 것을 확인하자 — 문맥이 반영된 'it'이다. ② 임베딩은 그대로 두고 '이전 토큰 헤드'를 누르면, 같은 X에서 A가 한 칸 아래 대각선(i → i−1)으로 완전히 바뀐다. ③ 가운데 q·k 평면에서 qit(●)를 직접 끌어 ktired(■) 쪽으로 옮겨 보자. 'it' 행만 바뀐다. 키 ■를 끌면 그 토큰의 열 전체가 바뀐다. ④ 왼쪽 평면의 임베딩 점 x를 끌면 그 토큰의 q와 k가 W를 통해 함께 움직인다. ⑤ W 칸을 끌어 W_Q의 크기를 키우면(전체 배율↑) 패턴은 같고 더 날카로워진다 — 온도를 낮춘 것과 같다.

이 시뮬레이터의 2차원 임베딩은 교육용으로 직접 설계한 것이다. 7개 토큰을 원 둘레에 문장 순서대로 배치해 두었는데, 이는 실제 모델에서 토큰 벡터에 위치 정보가 섞여 들어가는 것(6장의 위치 인코딩·RoPE)을 아주 거칠게 흉내 낸 것이다. 실제 모델은 \(d = 768\)(GPT-2 small)~\(4096\)(Llama 3 8B) 차원이고 헤드당 \(d_k = 64\)~\(128\)이라, 의미 축과 위치 축이 서로 다른 방향을 차지하며 공존한다. 2차원에서는 둘을 동시에 담기 어렵기 때문에 '이전 토큰 헤드'는 원 위의 회전으로, '명사→대명사 헤드'는 특정 방향으로의 사영(rank-1 W)으로 구현했다.

\(W_Q W_K^\top\)는 한 덩어리다

점수는 \(W_Q W_K^\top\)라는 곱에만 의존하므로, \(W_Q \to W_Q R\), \(W_K \to W_K R^{-\top}\)로 바꿔도(R은 가역 행렬) 어텐션 패턴은 똑같다. 그래서 해석 가능성 연구에서는 \(W_Q\)와 \(W_K\)를 따로 보지 않고 \(W_{QK} = W_Q W_K^\top\)(d×d, 랭크 ≤ d_k)라는 'QK 회로'로 묶어 분석한다. 같은 이유로 \(W_V W_O\)도 'OV 회로'로 묶는다(Elhage 등, 2021).

마스크: 인과 마스크와 패딩 마스크

어텐션은 기본적으로 모든 토큰이 모든 토큰을 본다. 하지만 상황에 따라 보면 안 되는 위치가 있다. 마스크 \(M\)은 그런 위치의 점수에 \(-\infty\)를 더해, 소프트맥스 뒤 가중치가 정확히 0이 되게 한다(\(e^{-\infty} = 0\)). 구현에서는 \(-\infty\) 대신 \(-10^4\)이나 dtype의 최솟값을 쓰기도 한다.

(a) 인과 마스크 — 하삼각 고양이가고양이가쥐를쥐를쫓았다쫓았다그것은그것은빨랐다빨랐다−∞−∞−∞−∞−∞−∞−∞−∞−∞−∞키 위치 j → (행 = 쿼리 i) (b) 패딩 마스크 — 배치의 두 번째 문장 고양이가고양이가잤다잤다[PAD][PAD][PAD][PAD]−∞−∞−∞−∞−∞−∞−∞−∞[PAD] 열 차단, [PAD] 행은 버림
그림 5-4. (a) 인과 마스크: 행 = 쿼리 위치, 열 = 키 위치. 회색(−∞) 칸은 소프트맥스 후 가중치 0. 첫 토큰은 자기 자신만, 마지막 토큰은 모두를 본다. (b) 패딩 마스크: "고양이가 잤다 [PAD] [PAD]"에서 [PAD] 열을 막는다. 실제로는 두 마스크를 더해(합집합) 함께 쓴다.

위의 편집기(4절)에서 인과 마스크를 켜고 끄며 차이를 다시 확인해 보자. 마스크를 켜면 행마다 소프트맥스의 분모에 들어가는 항이 줄어들기 때문에, 남은 칸의 가중치가 커진다. 첫 행은 항이 하나뿐이라 무조건 1이다. 이 '첫 토큰은 항상 보인다'는 성질은 8절의 어텐션 싱크와 연결된다.

마스크는 소프트맥스 전에

소프트맥스를 먼저 하고 가중치를 0으로 지우면 행 합이 1보다 작아진다. 다시 정규화하면 수학적으로는 같지만, 미래 토큰의 점수가 분모의 최댓값 계산에 섞여 수치가 불안정해질 수 있다. 표준 구현은 항상 점수 단계에서 \(-\infty\)를 더한다.

멀티헤드 어텐션: 여러 관점을 병렬로

한 헤드는 행마다 하나의 확률분포만 만든다. 그런데 한 토큰은 동시에 여러 종류의 정보가 필요하다. 'it'은 지시 대상(animal)도, 바로 앞 단어(because)도, 문장 주어도 알아야 한다. 하나의 소프트맥스로 이 모든 것을 섞으면 서로 경쟁해 흐려진다. 그래서 Transformer는 어텐션을 h개의 헤드로 나눠 병렬로 계산한다.

$$ \mathrm{head}_i = \mathrm{Attention}(XW_Q^{(i)}, XW_K^{(i)}, XW_V^{(i)}), \qquad \mathrm{MHA}(X) = \mathrm{Concat}(\mathrm{head}_1,\dots,\mathrm{head}_h)\,W_O $$
각 헤드의 차원은 \(d_{head} = d_{model}/h\). GPT-2 small은 768 = 12 × 64, Llama 3 8B는 4096 = 32 × 128. 헤드 수를 늘려도 전체 파라미터·연산량은 거의 같다(차원을 나눠 쓰므로). \(W_O\in\mathbb{R}^{d\times d}\)가 헤드 출력들을 다시 섞는다.
Xn×768 head₁: Q,K,V n×64 → 이전 토큰 head₂: 구문 관계 ⋯ head₁₂: 첫 토큰(sink) Concatn×(12·64) · W_O 출력n×768
그림 5-5. GPT-2 small 기준 멀티헤드 어텐션. 768차원을 64차원 헤드 12개로 나눠 각자 다른 패턴으로 정보를 모은 뒤, 이어 붙이고(Concat) \(W_O\)로 섞는다. 구현에서는 12개 헤드를 텐서의 한 축으로 두고 한 번의 배치 행렬곱으로 계산한다.
SIMULATOR

멀티헤드: 네 가지 관점과 그 혼합

헤드 켜기/끄기 (혼합에 포함)
선택 헤드 / 토큰—
선택 헤드 최대 가중치—
혼합 결과 유효 토큰 수—
해볼 것: ① 위의 작은 히트맵 4개 중 하나를 누르면 그 헤드가 선택되고, 오른쪽 아크가 그 헤드의 가중치를 보여 준다. 혼합 행렬의 행이나 아크의 토큰을 누르면 쿼리 토큰이 바뀐다. ② 기본 선택인 'ran'(9번)에서 헤드를 하나씩 바꿔 보자. H1은 바로 앞 'quickly', H2는 주어 'dog'(7번), H3은 같은 단어가 앞에 없어 자기 자신, H4는 문장 첫 'the'로 간다 — 한 토큰이 네 곳에서 동시에 정보를 받는다. ③ 두 번째 'dog'(7번)를 고르면 H3이 첫 'dog'(4번)를 찾는다. ④ 헤드를 끄면 혼합 결과에서 그 패턴이 사라진다. β를 낮추면 모든 헤드가 흐려진다.

시뮬레이터의 네 헤드는 실제 모델에서 자주 관찰되는 패턴을 규칙으로 손설계한 것이다(교육용). 아래 '혼합'은 모든 헤드가 같은 V를 쓰고 \(W_O\)가 헤드 출력을 켜진 헤드 수로 나눠 더하는 특별한 경우의 유효 어텐션 \(\tfrac{1}{h'}\sum_h A_h\)다. 이 경우 \(\sum_h g_h A_h V = (\sum_h g_h A_h)V\)이므로 정확히 성립한다. 일반적인 \(W_O\)에서는 헤드마다 다른 값 부분공간을 쓰므로, 행렬 하나로 요약되지 않는다. 그 '요약되지 않음'이 바로 멀티헤드의 표현력이다.

헤드는 정말 다 필요할까?

Michel 등(2019)은 학습된 Transformer에서 상당수 헤드를 지워도 성능이 거의 떨어지지 않음을 보였다. 반면 일부 헤드(예: 인덕션 헤드)는 지우면 특정 능력이 무너진다. 헤드는 역할이 균일하지 않고, 소수의 핵심 헤드와 많은 '보조·중복' 헤드로 이루어진다. 이 관찰은 9·10절의 효율화(MQA, GQA, 헤드 가지치기)로 이어진다.

어텐션 패턴 동물원

학습된 언어 모델의 어텐션 행렬을 열어 보면, 몇 가지 '종'이 반복해서 나타난다. 해석 가능성(mechanistic interpretability) 연구는 이런 패턴이 무슨 계산을 하는지 역설계한다. 아래 그림은 대표 패턴을 교육용으로 단순화해 그린 것이다(실제 헤드는 훨씬 지저분하고 여러 패턴이 섞여 있다).

어텐션 싱크 이전 토큰 인덕션 헤드 슬라이딩 윈도우 첫 토큰 열이 짙다 대각선 한 칸 아래 "ABCD ABCD": 반복 구간에서 '이전 등장 +1' 위치 최근 w개만 (w = 3)
그림 5-6. 인과 마스크가 있는 8토큰 예시. 색이 짙을수록 큰 가중치, 회색은 마스크. 인덕션 헤드 그림의 입력은 "A B C D A B C D"로, 두 번째 A(4번)는 첫 A 다음에 왔던 B(1번)를 본다.

인덕션 헤드는 어떻게 작동하나

두 층의 협업이다. 1층의 이전 토큰 헤드가 각 위치 j에 "내 바로 앞 토큰은 \(t_{j-1}\)이다"라는 정보를 써넣는다. 2층의 인덕션 헤드는 현재 위치 i의 토큰 \(t_i\)를 쿼리로, 각 위치에 적힌 '앞 토큰' 정보를 키로 쓴다. 그러면 \(t_{j-1} = t_i\)인 위치 j, 즉 "예전에 지금과 같은 토큰 바로 다음에 왔던 자리"의 점수가 높아진다. 그 위치의 토큰 \(t_j\)를 값으로 복사하면 다음 토큰 예측이 된다.

$$ S^{(2)}_{ij} = \beta\,\big[\,\mathrm{onehot}(t_i)\cdot \underbrace{\textstyle\sum_l A^{(1)}_{jl}\,\mathrm{onehot}(t_l)}_{\text{1층이 쓴 '앞 토큰'}}\big], \qquad p(\text{다음}) = \sum_j A^{(2)}_{ij}\,\mathrm{onehot}(t_j) $$
SIMULATOR

인덕션 헤드 시연: 2층 회로를 실제로 계산하기

시퀀스
선택 위치의 예측—
앞 절반 정확도—
뒤 절반 정확도—
해볼 것: ① 토큰 줄에서 뒤 절반(반복 구간)의 아무 토큰이나 누르자. 위쪽 보라 아크(2층)가 앞 절반에서 '같은 토큰 다음 자리'를 가리키고, 그 아래 분홍 화살표(1층)가 "그 자리의 앞 토큰이 지금과 같다"는 근거를 보여 준다. 아래 막대가 예측 분포다. ② 앞 절반의 토큰은 아직 반복이 없어 예측이 거의 균등에 가깝다(정확도 ≈ 우연). 뒤 절반은 거의 다 맞힌다. '선택 토큰 바꾸기'로 앞 절반에 같은 토큰을 두 번 만들면 인덕션 헤드가 두 후보 사이에서 갈라진다. ③ '반복' 체크를 끄면 뒤 절반 정확도도 우연 수준으로 떨어진다. ④ 오른쪽 A(2) 히트맵에서 반복 구간에 생기는 '오프셋 대각선'이 인덕션 헤드의 지문이다. β = 0이면 모든 칸이 균등해져 기억이 사라진다.

이 시뮬레이터의 두 층은 학습된 가중치가 아니라 원-핫 벡터와 위치 규칙으로 손설계한 것이다. 그러나 계산 구조(1층 어텐션의 출력이 2층의 키가 되는 'K-합성')는 Elhage 등(2021)과 Olsson 등(2022)이 실제 모델에서 찾아낸 회로와 같다. 해석 가능성 연구는 이처럼 어텐션 헤드, MLP 뉴런, 그리고 최근에는 희소 오토인코더로 찾은 '특징'들을 회로로 엮어 모델 내부를 설명하려 한다. 아직은 작은 모델과 특정 행동에 대한 부분적 설명이 대부분이라는 점을 함께 기억하자.

계산량과 메모리: O(n²)와 FlashAttention

어텐션의 비용은 두 부분으로 나뉜다. Q·K·V·O 투영은 토큰마다 독립이라 길이에 선형이다(\(4\cdot 2nd^2\) FLOPs). 반면 점수 \(QK^\top\)와 \(AV\)는 모든 쌍을 다루므로 길이의 제곱이다(\(2\cdot 2n^2 d\) FLOPs). 둘이 같아지는 지점은 \(n = 2d\)로, Llama 3 8B(d = 4096)라면 약 8K 토큰부터 어텐션 쌍 계산이 투영보다 비싸진다. 메모리는 더 아프다. 점수 행렬 S를 그대로 만들면 헤드마다 \(n^2\)개 원소가 필요하다.

SIMULATOR

시퀀스 길이 vs 비용 (한 층, 배치 1, BF16)

d_model
S 행렬 메모리 (h·n²·2B)—
FlashAttention 추가 메모리—
어텐션 FLOPs (4n²d)—
투영 FLOPs (8nd²)—
H100 이상적 시간 (989 TFLOPS)—
해볼 것: ① d = 4096, h = 32에서 n을 32K(215)로 올리면 S 행렬 하나가 한 층에 64 GiB다. H100 한 장(80 GB)이 층 하나의 점수 행렬로 거의 찬다. ② n을 두 배로 할 때 S 메모리와 어텐션 FLOPs는 4배, 투영 FLOPs는 2배가 되는 것을 로그 축의 기울기(2 대 1)로 확인하자. ③ 헤드 수를 바꾸면 S 메모리는 h에 비례하지만 FLOPs는 그대로다(헤드 차원이 d/h로 줄어들기 때문). ④ FlashAttention 곡선은 n에 선형이다.

FlashAttention: 큰 행렬을 만들지 않는다

GPU에서 병목은 연산이 아니라 메모리 이동인 경우가 많다. H100의 HBM3는 약 3.35 TB/s지만, 연산은 BF16 dense 약 989 TFLOPS라 바이트당 수백 번 연산할 수 있다. 순진한 구현은 S(n×n)를 HBM에 쓰고, 다시 읽어 소프트맥스하고, A를 쓰고, 다시 읽어 V와 곱한다. Dao 등(2022)의 FlashAttention은 Q, K, V를 작은 타일로 잘라 칩 위의 빠른 SRAM(공유 메모리)에 올리고, 한 타일 안에서 점수·소프트맥스·V 곱을 모두 끝낸 뒤 출력만 HBM에 쓴다. n×n 행렬은 어디에도 통째로 존재하지 않는다. 연산량은 같지만(오히려 역전파 때 재계산으로 약간 늘지만) 메모리 읽기·쓰기가 크게 줄어 수 배 빨라지고, 추가 메모리는 O(n)이다.

타일 단위로 K·V 블록을 훑으며 출력 블록을 누적 Q Qᵢ Kᵀ Kⱼ S는 타일(Bq×Bk)만 잠시 SRAM에 V Vⱼ SRAM (SM당 ~228 KB, 매우 빠름) Sᵢⱼ = QᵢKⱼᵀ, m ← max, ℓ ← 재조정 합 Oᵢ ← Oᵢ·e^(m_old−m_new) + e^(Sᵢⱼ−m)Vⱼ HBM (80 GB, ~3.35 TB/s) Q, K, V, O만 저장 — n×n 행렬 없음 추가 저장: 행마다 m, ℓ (O(n))
그림 5-7. FlashAttention의 타일링. Q의 블록 하나(Qᵢ)를 SRAM에 올려 두고 K, V 블록을 차례로 가져와 점수 타일을 계산한다. 소프트맥스의 분모를 아직 모르는 상태에서 블록을 하나씩 처리하려면 아래의 '온라인 소프트맥스'가 필요하다. SRAM 용량은 H100 기준 SM당 약 228 KB(2023).

문제는 소프트맥스다. \(a_j = e^{s_j}/\sum_l e^{s_l}\)는 분모에 행 전체의 합이 들어가고, 수치 안정성을 위해 보통 행 최댓값 \(m\)을 빼서 \(e^{s_j - m}\)으로 계산한다. 블록을 하나씩 보면 최댓값도 합도 아직 모른다. 온라인 소프트맥스(online softmax, Milakov & Gimelshein, 2018)는 지금까지의 최댓값 \(m\)과 합 \(\ell\)을 들고 다니다가, 새 블록에서 최댓값이 커지면 기존 합과 누적 출력에 \(e^{m_{old}-m_{new}}\)를 곱해 소급 보정한다.

$$ m' = \max(m, \max_{j\in B} s_j),\quad \ell' = \ell\, e^{m-m'} + \sum_{j\in B} e^{s_j - m'},\quad \mathbf{o}' = \mathbf{o}\, e^{m-m'} + \sum_{j\in B} e^{s_j-m'}\,v_j $$
모든 블록을 처리한 뒤 \(\mathbf{o}/\ell\)이 정확히 \(\sum_j \mathrm{softmax}(s)_j\, v_j\)와 같다(근사가 아니다). 처음에는 \(m = -\infty,\ \ell = 0,\ \mathbf{o} = 0\).
SIMULATOR

온라인 소프트맥스: 블록 단위로 최댓값·합 갱신

막대를 위아래로 드래그
블록 크기 B
현재 m / ℓ—
온라인 o/ℓ—
일반 softmax·v—
차이 |Δ|—
해볼 것: ① '다음 블록'을 눌러 한 블록씩 처리하자. 위쪽 점선이 지금까지의 최댓값 m이고, 아래 막대는 '지금까지 본 것만으로 정규화한' 가중치다. 새 블록에서 더 큰 점수가 나오면 앞 블록들의 가중치가 일제히 작아지는(재조정 계수 em_old−m_new < 1) 것을 확인하자. ② 끝까지 처리하면 온라인 결과와 일반 소프트맥스 결과의 차이가 10−16 수준(부동소수점 반올림)이다. ③ 마지막 블록의 막대 하나를 가장 높게 끌어 올린 뒤 처음부터 다시 진행해 보자. 마지막 단계에서 큰 보정이 일어난다. ④ 블록 크기를 바꿔도 최종 결과는 같다.

FlashAttention은 2022년 이후 사실상 표준이 되었고, FlashAttention-2(2023)는 병렬화를 개선했으며, FlashAttention-3(2024)는 H100의 비동기 텐서 코어와 FP8을 활용한다. PyTorch의 scaled_dot_product_attention도 조건이 맞으면 이런 커널을 자동으로 쓴다. 중요한 점은 이것이 정확한 어텐션이라는 것이다. 근사로 비용을 줄이는 선형 어텐션·희소 어텐션과 달리, 결과는 순진한 구현과 같고 메모리 접근 순서만 바꿨다.

셀프·크로스 어텐션, MHA·MQA·GQA, 슬라이딩 윈도우

셀프 어텐션 vs 크로스 어텐션

지금까지는 Q, K, V가 모두 같은 X에서 나왔다. 이것이 셀프 어텐션이다. 크로스 어텐션(cross-attention)에서는 쿼리는 한 시퀀스에서, 키와 값은 다른 시퀀스에서 온다. 원래 Transformer의 번역 디코더는 번역문 토큰의 쿼리로 원문 인코더 출력의 키·값을 조회했다(그림 5-1의 b가 바로 그것). 멀티모달 모델에서는 텍스트 쿼리가 이미지 패치 임베딩을 키·값으로 조회하고, Stable Diffusion 같은 이미지 생성 모델은 이미지 특징이 쿼리, 프롬프트 텍스트 임베딩이 키·값이다. 크로스 어텐션의 점수 행렬은 \(n_q\times n_{kv}\)로 정사각형이 아니며, 인과 마스크도 보통 필요 없다.

셀프 어텐션: Q, K, V ← 같은 X X (n×d) Q K V → A: n×n 언어 모델의 모든 층, BERT, ViT 크로스 어텐션: Q ← Y, (K, V) ← Z Y: 번역문 / 텍스트 Z: 원문 / 이미지 Q K V → A: n_q×n_kv 번역 디코더, 멀티모달, 확산 모델의 텍스트 조건
그림 5-8. 셀프 어텐션은 한 시퀀스 안에서, 크로스 어텐션은 두 시퀀스 사이에서 정보를 옮긴다. 수식은 똑같고 Q와 K·V의 출처만 다르다.

MHA, MQA, GQA: KV 헤드를 공유하기

생성 단계에서는 이전 토큰들의 K, V를 저장해 두는 KV 캐시(8장)가 메모리의 주인공이 된다. 표준 멀티헤드(MHA)는 쿼리 헤드마다 자기 K, V 헤드가 있다. MQA(Multi-Query Attention, Shazeer 2019)는 모든 쿼리 헤드가 K, V 헤드 하나를 공유해 캐시를 h배 줄이지만 품질이 약간 떨어질 수 있다. GQA(Grouped-Query Attention, Ainslie 등 2023)는 그 중간으로, 쿼리 헤드를 g개 그룹으로 묶어 그룹마다 K, V 헤드 하나를 둔다. Llama 3 8B는 쿼리 헤드 32개, KV 헤드 8개(그룹당 쿼리 4개)를 쓴다.

MHA · KV 헤드 8개qqqqqqqqkvkvkvkvkvkvkvkvGQA · KV 헤드 2개qqqqqqqqkvkvMQA · KV 헤드 1개qqqqqqqqkv
그림 5-9. 쿼리 헤드 8개 기준. MHA는 KV 헤드 8개, GQA(그룹 4)는 2개, MQA는 1개. 쿼리 헤드 수(어텐션 패턴의 다양성)는 그대로 두고, 캐시에 저장할 K·V만 줄인다.
Llama 3 8B 토큰당 KV 캐시 (GQA, 8 KV헤드)128 KiB2 × 32층 × 8 × 128 × 2 B
같은 모델을 MHA(32 KV헤드)로 만들면512 KiB4배
8K 토큰 문맥 하나 (GQA)1 GiBMHA면 4 GiB
MQA(1 KV헤드)라면16 KiB캐시 1/32, 품질 손실 위험

슬라이딩 윈도우와 그 밖의 변형

슬라이딩 윈도우 어텐션은 각 토큰이 최근 w개 토큰만 보게 해 비용을 \(O(nw)\)로 줄인다(그림 5-6의 넷째 패턴). Mistral 7B(2023)는 w = 4096을 썼고, 층을 쌓으면 정보가 층마다 w씩 더 멀리 전파되므로 L층이면 이론적으로 약 L·w 거리까지 닿는다. Gemma 2·3처럼 지역(윈도우) 층과 전역 층을 번갈아 쓰는 모델도 많다. 키·값을 저차원 잠재 벡터로 압축해 캐시하는 MLA와, 소프트맥스 어텐션 층 일부를 고정 크기 상태의 선형 어텐션으로 바꾸는 하이브리드 구조는 바로 다음 두 절에서 따로 다룬다. 위치 정보를 Q, K의 회전으로 넣는 RoPE는 6장에서, KV 캐시와 디코딩 속도는 8장에서 자세히 다룬다.

방식점수 계산 범위시간KV 캐시 / 토큰·층예
MHA전체 (인과)O(n²d)2·h·d_headGPT-2, GPT-3, Llama 2 7B
MQA전체O(n²d)2·1·d_headPaLM, Falcon
GQA전체O(n²d)2·g·d_headLlama 3 (g = 8), Mistral 7B
슬라이딩 윈도우최근 w개O(nwd)최대 w개 토큰만Mistral 7B, Gemma 2·3 지역 층
MLA전체O(n²d)잠재 c + RoPE 키 (512 + 64)DeepSeek-V2·V3
희소 (DSA, CSA)인덱서가 고른 top-kO(nkd) + 인덱서전체 저장(CSA는 4배 압축)DeepSeek-V3.2, V4
선형 / Gated DeltaNet(점수 없음) 상태 행렬O(nd²)고정 크기 상태, 길이 무관Qwen3.5 선형 층(3:1 하이브리드)
FlashAttention(구현 기법) 전체, 정확O(n²d)변화 없음거의 모든 현대 학습·추론

희소 어텐션: DSA에서 CSA·HCA로

슬라이딩 윈도우는 "어디를 볼지"를 위치로 미리 정한다. 희소 어텐션(sparse attention)은 그 선택을 내용으로 한다. DeepSeek-V3.2(2025)의 DSA(DeepSeek Sparse Attention)는 헤드 수가 적고 저정밀(FP8)로 도는 가벼운 lightning indexer가 쿼리마다 모든 이전 토큰에 값싼 점수를 매기고, 그중 상위 k개(2,048개)만 골라 본 어텐션을 계산한다. 본 어텐션 비용은 \(O(n\cdot k)\)로 줄지만, 인덱서 자체는 여전히 모든 쌍을 훑고(대신 매우 싸다) 고를 후보가 있어야 하므로 KV 캐시는 그대로 남는다.

DeepSeek-V4(2026)는 여기에 압축을 겹쳤다(Hugging Face 블로그, 2026). CSA(Compressed Sparse Attention) 층은 KV를 시퀀스 방향으로 4배 압축(학습된 가중 풀링)한 뒤 FP4 인덱서로 상위 압축 블록만 고르고, HCA(Heavily Compressed Attention) 층은 KV를 128배 압축해 선택 없이 전부 본다. 두 종류 모두 최근 토큰을 압축 없이 보는 슬라이딩 윈도우(128) 가지가 있고, 층마다 번갈아 쌓는다. 그 결과 V4-Pro는 1M 토큰 문맥에서 V3.2 대비 KV 캐시 약 10%, 단일 토큰 추론 FLOPs 약 27%만 쓴다고 보고됐다(8헤드 GQA BF16 기준선 대비로는 약 2%).

희소 어텐션: 인덱서 점수 top-k + 최근 윈도우 인덱서 점수 (값싼 근사) q 파란 칸: top-k로 선택된 키(여기서 k = 4), 보조색: 항상 보는 최근 윈도우 어텐션 싱크 (gpt-oss) aⱼ = e^sⱼ / (e^b + Σₗ e^sₗ) 실제 토큰 가중치 (합 < 1) 싱크 몫 버려짐
그림 5-10. 왼쪽: 희소 어텐션은 값싼 인덱서 점수로 상위 k개 키만 골라(파랑) 최근 윈도우(보조색)와 함께 본다. 오른쪽: gpt-oss의 어텐션 싱크는 헤드마다 학습된 로짓 b를 소프트맥스 분모에만 더해, "볼 것이 없을 때" 가중치를 어디에도 주지 않을 수 있게 한다. 교육용 도식이며 막대 높이는 임의 값이다.

어텐션 싱크를 구조에 넣다

8절(어텐션 패턴 동물원)에서 실제 모델의 많은 헤드가 첫 토큰에 큰 가중치를 몰아 준다는 것, 즉 어텐션 싱크를 보았다. 소프트맥스 가중치의 합은 반드시 1이라, 지금 가져올 정보가 없는 헤드도 어딘가에는 가중치를 줘야 하기 때문이다. 슬라이딩 윈도우에서는 첫 토큰이 곧 창 밖으로 밀려나 싱크가 사라지고 품질이 무너지는데, StreamingLLM(Xiao 등, 2023)은 처음 몇 토큰의 KV를 항상 남겨 이 문제를 풀었다. gpt-oss(OpenAI, 2025)는 이것을 아예 구조에 넣었다. 헤드마다 학습되는 스칼라 \(b_h\)를 소프트맥스 분모에만 더해, 가중치 합이 1보다 작아질 수 있게 한 것이다.

$$ a_{ij} = \frac{e^{s_{ij}}}{e^{b_h} + \sum_{l} e^{s_{il}}},\qquad \sum_j a_{ij} = 1 - \frac{e^{b_h}}{e^{b_h} + \sum_l e^{s_{il}}} < 1 $$
\(b_h\)는 값이 없는 '가상의 키' 하나의 점수라고 생각하면 된다. 그 키로 간 몫은 출력에 아무것도 더하지 않는다. gpt-oss는 128토큰 슬라이딩 윈도우 층과 전체 어텐션 층을 1:1로 번갈아 쓰므로, 창 밖으로 밀려나지 않는 싱크가 특히 중요하다.
2026년의 기본값

대형 공개 모델에서 순수 MHA는 거의 사라졌다. 출발점은 RoPE + GQA(또는 MLA) + FlashAttention 커널이고, 긴 문맥을 위해 여기에 하나를 더 얹는다. ① 지역/전역 층 교대(gpt-oss는 윈도우 128과 전체 층 1:1, Gemma 계열은 지역 층 위주), ② 내용 기반 희소 선택(DeepSeek-V3.2 DSA, V4 CSA/HCA), ③ 일부 층을 고정 크기 상태의 선형 어텐션으로 교체한 하이브리드(Qwen3.5의 3:1). 어느 쪽이든 목표는 같다. KV 캐시와 디코딩 비용이 문맥 길이에 비례해 늘어나는 것을 막는 것이다. 다음 두 절에서 MLA와 선형 어텐션을 직접 계산해 본다.

MLA: 키와 값을 잠재 벡터 하나로 압축한다

GQA는 K·V 헤드 수를 줄여 캐시를 줄인다. 쿼리 헤드 여럿이 같은 키를 쓰니 표현력이 조금 줄어드는 대가가 있다. DeepSeek-V2(2024)가 도입하고 V3(2024)·V3.2가 이어 쓴 MLA(Multi-head Latent Attention)는 다른 길을 택한다. 토큰마다 \(d_c\)차원(DeepSeek-V3: 512)의 잠재 벡터 \(c_t\) 하나만 캐시해 두고, 헤드별 키와 값은 쓸 때마다 업프로젝션으로 만들어 낸다.

$$ c_t = W_{DKV}\,h_t \in \mathbb{R}^{d_c},\qquad k^{C}_{t,i} = W_{UK,i}\,c_t,\qquad v_{t,i} = W_{UV,i}\,c_t \quad (i = 1..H) $$
\(h_t\): 토큰 t의 은닉 벡터(d = 7,168), \(W_{DKV}\): 다운프로젝션(d → 512), \(W_{UK,i}, W_{UV,i}\): 헤드 i의 업프로젝션(512 → 128). 캐시에는 \(c_t\)만 남는다. 쿼리도 비슷하게 1,536차원으로 압축하지만, 이는 학습 중 활성값 메모리를 줄이기 위한 것이고 캐시와는 무관하다.

그런데 디코딩 때마다 512 → 128 업프로젝션을 128개 헤드, 수천 개 과거 토큰에 대해 다시 계산하면 오히려 느려진다. 여기서 흡수 트릭(absorb trick)이 나온다. 점수는 행렬곱의 결합 법칙으로 다시 쓸 수 있다.

$$ q_{t,i}^{\top} k^{C}_{s,i} = q_{t,i}^{\top} W_{UK,i}\, c_s = \big(\underbrace{W_{UK,i}^{\top} q_{t,i}}_{\tilde q_{t,i}\,\in\,\mathbb{R}^{512}}\big)^{\top} c_s,\qquad \sum_s a_{ts}\, v_{s,i} = W_{UV,i} \sum_s a_{ts}\, c_s $$
\(W_{UK,i}\)는 쿼리 쪽으로, \(W_{UV,i}\)는 출력 투영 \(W_O\) 쪽으로 '흡수'된다. 그러면 과거 토큰마다 K, V를 복원할 필요 없이 캐시된 \(c_s\)와 바로 내적한다. 디코딩 때의 MLA는 모든 헤드가 512차원 'KV 헤드' 하나를 공유하는 MQA처럼 돌지만, 학습된 헤드별 업프로젝션 덕분에 표현력은 MHA에 가깝다.

한 가지 걸림돌은 RoPE다(6장). RoPE는 위치 s에 따라 달라지는 회전 \(R_s\)를 키에 곱하므로 \(q^\top R_t^\top R_s W_{UK} c_s\)에서 \(W_{UK}\)를 쿼리 쪽으로 옮길 수 없다(회전이 중간에 끼어 있다). DeepSeek은 분리 RoPE(decoupled RoPE)로 이를 피했다. 위치 정보만 담는 작은 키 \(k^R_t = \mathrm{RoPE}(W_{KR} h_t) \in \mathbb{R}^{64}\)를 따로 만들어 모든 헤드가 공유하게 하고, 이것도 캐시한다. 그래서 토큰·층당 캐시는 \(512 + 64 = 576\)개 원소다.

hₜd = 7168 W_DKVW_KR 잠재 cₜ (512)캐시 RoPE 키 kᴿ (64)캐시 · 헤드 공유 W_UKW_UV k₁ᶜ … k₁₂₈ᶜ (128씩) v₁ … v₁₂₈ (128씩) kᵢ = [kᵢᶜ ; kᴿ] (192) 점선 상자 안의 K·V는 흡수 트릭 덕분에 만들지 않는다 토큰·층당 캐시 원소 MHA 128×(192+128) = 40,960 GQA8 8×(192+128) = 2,560 MLA 512+64 = 576 MQA 1×(192+128) = 320 DeepSeek-V3 차원 기준
그림 5-11. MLA(DeepSeek-V3 차원). 캐시에는 512차원 잠재 벡터 cₜ와 헤드가 공유하는 64차원 RoPE 키만 들어간다. 헤드별 키·값(점선 상자)은 수식상 존재하지만, 흡수 트릭 덕분에 디코딩 때 실제로 만들 필요가 없다.
DeepSeek-V3 토큰당 KV (MLA, 61층)68.6 KiB576 × 61 × 2 B
같은 차원을 MHA로 캐시하면4.77 MiB약 71배
128K 토큰 문맥 하나 (MLA)8.6 GiBMHA라면 약 610 GiB
참고: Llama 3 8B (GQA, 32층)128 KiB훨씬 작은 모델인데도 더 크다
SIMULATOR

KV 캐시 방식 비교: 문맥 길이에 따른 메모리

그래프를 좌우로 드래그해 문맥 길이 n 선택
기준 모델
풀 어텐션 층 비율 (윈도우·하이브리드)
이 모델의 실제 방식—
그 방식의 전체 KV @ n—
MHA 대비—
80 GB GPU 대비—
한 시퀀스(배치 1)의 전체 KV 메모리를 로그-로그 축으로 그린다. KV는 BF16(2 B), 선형 어텐션 상태는 FP32(4 B)로 셌다. 윈도우·하이브리드 방식에서 '풀 어텐션 층'은 g개 KV 헤드의 GQA다. 기준 모델의 층 수·헤드 수·head_dim은 각 모델의 Hugging Face config.json 값이고, 선형 상태는 Qwen3.5의 Gated DeltaNet(V 헤드 64 × 128 × 128)을, 나머지 모델에는 같은 꼴(H × d_head × d_head)을 가정했다. 해볼 것: ① Llama 3 8B에서 n을 128K로 두면 GQA 16 GiB, MHA 64 GiB다. 표의 기울기가 모두 1(길이에 정비례)인데 윈도우·하이브리드만 꺾이는 것을 보자. ② gpt-oss-120b 프리셋(실제: 윈도우 128 + 풀 1:1)에서 비율을 0으로 바꾸면 메모리는 상수가 되지만, 그것은 먼 토큰을 직접 볼 길이 사라진다는 뜻이다. ③ DeepSeek-V3에서 d_c를 512 → 256으로 줄이면 캐시는 거의 절반이 된다. 실제 모델이 512를 고른 것은 품질과의 타협이다. ④ Qwen3.5 프리셋에서 n = 1K로 내리면 고정 상태(약 180 MiB) 때문에 하이브리드가 오히려 순수 GQA보다 크다. 두 곡선이 만나는 교차점(약 2K 토큰)을 찾고, 262K에서 얼마나 벌어지는지 보자.

MLA의 비용은 연산 쪽에서 나타난다. 흡수 후의 점수 계산은 헤드마다 512 + 64 = 576차원 내적이라 GQA의 128차원보다 무겁고, 디코딩 때 바이트당 연산량이 높아 메모리 대역폭 대신 연산 유닛을 더 쓰게 된다. KV 캐시를 읽는 것이 병목인 긴 문맥 디코딩(8장)에서는 이것이 오히려 장점이다. DeepSeek-V4는 MLA 대신 KV 헤드 하나(head_dim 512)를 쓰는 어텐션에 CSA/HCA 압축을 결합했는데(공개 config 기준), "토큰당 캐시를 극단적으로 줄인다"는 방향은 같다.

선형 어텐션과 Gated DeltaNet: 고정 크기 기억

지금까지의 모든 변형은 결국 과거 토큰의 K, V를 (줄여서라도) 전부 들고 있다. 그래서 KV 캐시는 문맥 길이 n에 비례하고, 토큰 하나를 생성할 때마다 n개 키를 훑어야 한다. 정반대 방향의 해법은 과거를 고정 크기 행렬 하나에 요약해 두는 것이다. 선형 어텐션(linear attention, Katharopoulos 등 2020)은 소프트맥스의 \(e^{q\cdot k}\)를 특징 사상의 내적 \(\phi(q)^\top\phi(k)\)로 바꾸면 결합 법칙으로 순서를 바꿀 수 있다는 데서 출발한다(아래에서는 \(\phi\)를 생략하고 정규화된 \(q, k\)를 쓴다).

$$ o_t = \sum_{s\le t} v_s\,(k_s^\top q_t) = \Big(\underbrace{\sum_{s\le t} v_s k_s^\top}_{S_t\ \in\ \mathbb{R}^{d_v\times d_k}}\Big)\, q_t,\qquad S_t = S_{t-1} + v_t k_t^\top $$
\(S_t\)는 키 → 값 연상 기억(associative memory) 행렬이다. 쓰기는 외적 \(v_t k_t^\top\)를 더하는 것, 읽기는 \(S_t q_t\)다. 크기가 \(d_v\times d_k\)로 고정이라 문맥이 1M 토큰이어도 메모리는 같고, 토큰당 계산도 \(O(d^2)\)로 일정한 RNN이 된다. 학습 때는 청크 단위 병렬 알고리즘으로 GPU를 꽉 채운다.

문제는 덧셈만으로는 지울 수 없다는 것이다. 키들이 서로 직교하면 \(S k_i = v_i\)로 정확히 읽히지만, \(d_k\)차원에는 직교 벡터가 \(d_k\)개뿐이다. 그보다 많이 쓰거나, 같은 키에 새 값을 쓰면 옛 값과 겹쳐(superposition) 읽기가 흐려진다. 델타 규칙(delta rule; DeltaNet, Schlag 등 2021 · Yang 등 2024)은 쓰기 전에 지금 기억이 그 키에 대해 무엇을 내놓는지 읽고, 오차만큼만 고쳐 쓴다.

$$ S_t = S_{t-1} + \beta_t\,(v_t - S_{t-1}k_t)\,k_t^\top = S_{t-1}\,(I - \beta_t k_t k_t^\top) + \beta_t\, v_t k_t^\top $$
\(\beta_t\in[0,1]\): 쓰기 강도(토큰마다 학습된 값). 이는 손실 \(\tfrac12\lVert S k_t - v_t\rVert^2\)에 대한 학습률 \(\beta_t\)의 경사하강 한 걸음과 같다(3장). \(\lVert k_t\rVert = 1,\ \beta_t = 1\)이면 쓰고 난 뒤 \(S_t k_t = v_t\)가 정확히 성립한다. 즉 같은 키의 옛 값은 덮어쓰기된다.

Gated DeltaNet(Yang, Kautz, Hatamizadeh, 2024)은 여기에 Mamba-2식 감쇠 게이트 \(\alpha_t\in(0,1)\)를 곱해 오래된 기억을 서서히 잊게 한다. \(S_t = \alpha_t S_{t-1}(I - \beta_t k_t k_t^\top) + \beta_t v_t k_t^\top\), 출력은 \(o_t = S_t q_t\)다. Kimi Linear(2025)의 KDA(Kimi Delta Attention)는 이 게이트를 채널별로 세분한 개량판이다.

소프트맥스 어텐션: KV 캐시가 자란다 … n개 (k, v) 메모리 O(n), 토큰당 읽기 O(n) 선형 어텐션 / DeltaNet: 상태 S 하나 S: d_v × d_k 쓰기: S ← αS(I−βkkᵀ)+βvkᵀ 읽기: o = S q 메모리 O(1) — 문맥 길이와 무관 Qwen3.5: 3 : 1 하이브리드 (× 15 = 60층) Gated DeltaNet → MoE Gated DeltaNet → MoE Gated DeltaNet → MoE Gated Attention → MoE × 15 위 3층: 고정 상태(64헤드 × 128 × 128) 아래 1층: KV 캐시를 갖는 정밀 검색 담당
그림 5-12. 왼쪽: 소프트맥스 어텐션은 과거 (k, v)를 모두 저장하고, 선형 어텐션 계열은 고정 크기 상태 행렬 하나로 요약한다. 오른쪽: Qwen3.5-397B-A17B(2026)의 층 배치. 선형 층 3개마다 풀 어텐션 층 1개를 둬, KV 캐시는 전체 층의 1/4에서만 생긴다(Qwen 모델 카드·config.json).
SIMULATOR

연상 기억 상태 행렬: 덧셈 vs 델타 규칙 vs 소프트맥스

질의 q 막대를 위아래로 드래그 · 아래 표의 행을 누르면 q = 그 키
기록한 쌍 · 키 수 / d—
q에 가장 가까운 키—
복원 오차: 덧셈 / 델타 / softmax—
상태 크기 vs KV 캐시—
d = 4인 교육용 예시다. 키는 단위 벡터이고, 쌍 1~4의 키는 서로 직교, 쌍 5는 쌍 1과 같은 키에 새 값(사실의 갱신), 쌍 6은 키 2·3과 비스듬한 키, 쌍 7·8은 무작위 키다. 위 그림의 두 히트맵이 덧셈만 하는 순수 선형 어텐션의 S와 델타 규칙의 S이고, 오른쪽 막대는 질의 q로 읽어 낸 값(목표 = 그 키에 마지막으로 쓴 값)이다. softmax는 같은 쌍들을 KV 캐시에 모두 저장하고 \(\mathrm{softmax}(\tau\,q\cdot k)\)로 읽는다. 해볼 것: ① 쌍 1~4만 쓰면 세 방식 모두 오차 0이다(직교 키 d개까지는 완벽). ② 쌍 5를 쓰고 키 1을 질의하면 덧셈은 v₁ + v₅가 섞여 나오고, 델타(β = 1)는 v₅로 정확히 덮어쓴다. softmax도 같은 키 두 개의 평균을 내 틀린다. ③ 쌍 6~8까지 쓰면 d = 4를 넘는 순간 델타도 예전 쌍의 오차가 커진다. 이것이 고정 크기 상태의 용량 한계이고, 하이브리드 모델이 풀 어텐션 층을 일부 남기는 이유다. ④ α를 0.8로 낮추면 오래된 쌍부터 희미해진다. ⑤ β를 0.5로 낮추면 한 번에 반만 고쳐 써서 덮어쓰기가 불완전해진다.

그래서 실제 모델은 섞는다. 선형 층은 문맥을 값싸게 요약하고 국소 패턴을 잡으며, 소수의 풀 어텐션 층이 "정확히 그 토큰을 다시 찾아오는" 검색을 맡는다. Qwen3-Next-80B-A3B(2025)가 시작한 3:1 배치를 Qwen3.5(2026)가 플래그십까지 확장했고, 풀 어텐션 층에도 출력에 시그모이드 게이트를 곱하는 'Gated Attention'을 쓴다. NVIDIA Nemotron-H 계열처럼 Mamba-2 층 위주에 어텐션 층을 조금 섞는 하이브리드도 있다. 블록 수준의 배치는 6장에서 다시 본다.

선형 어텐션이 소프트맥스를 완전히 대체하지 못하는 이유

상태 \(S\)의 원소 수는 \(d_v\times d_k\)로 정해져 있다. 그 안에 n개 토큰의 정보를 모두 손실 없이 담을 수는 없다. 긴 문서 속 특정 문장 그대로 인용하기(needle-in-a-haystack), 긴 목록에서 정확한 항목 찾기 같은 정밀 검색은 KV를 다 들고 있는 소프트맥스 어텐션이 여전히 강하다. 하이브리드의 3:1 같은 비율은 이 정밀도와 메모리 사이의 경험적 타협이다.

핵심 정리

  1. 어텐션은 부드러운 사전 검색이다. 쿼리와 키의 내적 → 소프트맥스 가중치 → 값의 가중 평균. 온도가 0에 가까우면 hard lookup, 크면 단순 평균이다.
  2. \(\mathrm{Attention}(Q,K,V) = \mathrm{softmax}(QK^\top/\sqrt{d_k} + M)V\). S와 A는 n×n이고, 소프트맥스는 행별로 적용되며 행 안의 상대 차이에만 반응한다.
  3. 내적의 분산은 \(d_k\)에 비례하므로 \(\sqrt{d_k}\)로 나눠 분산을 1로 맞춘다. 나누지 않으면 소프트맥스가 포화되어 기울기가 사라진다.
  4. \(Q = XW_Q,\ K = XW_K,\ V = XW_V\). 점수는 \(x_i W_Q W_K^\top x_j^\top\)라는 학습된 쌍선형 유사도라, 같은 임베딩에서도 W에 따라 '명사→대명사', '이전 토큰' 같은 전혀 다른 패턴이 나온다.
  5. 인과 마스크(j > i를 −∞)는 디코더 언어 모델의 병렬 학습과 KV 캐시를 가능하게 한다. 패딩 마스크는 [PAD] 열을 막는다. 마스크는 소프트맥스 전에 더한다.
  6. 멀티헤드는 d_model을 h개 헤드(d_head = d/h)로 나눠 여러 관계를 병렬로 본 뒤 Concat·W_O로 섞는다. 실제 헤드에는 어텐션 싱크, 이전 토큰 헤드, 인덕션 헤드 같은 반복 패턴이 있다.
  7. 어텐션 쌍 계산은 O(n²d), 점수 행렬 메모리는 O(h·n²)이다. FlashAttention은 타일링과 온라인 소프트맥스로 n×n 행렬을 만들지 않고 정확히 같은 결과를 O(n) 추가 메모리로 낸다.
  8. 크로스 어텐션은 Q와 K·V의 출처가 다르다. MQA·GQA는 KV 헤드를 공유해 캐시를 줄인다(Llama 3 8B: 32 Q헤드, 8 KV헤드 → 토큰당 128 KiB). 슬라이딩 윈도우는 범위를 최근 w개로 제한한다. 희소 어텐션(DeepSeek DSA, V4의 CSA/HCA)은 인덱서로 top-k를 고르거나 KV를 압축하고, gpt-oss의 어텐션 싱크는 소프트맥스 분모에 학습된 로짓을 더해 가중치를 '버릴 곳'을 준다.
  9. MLA는 K·V 대신 저차원 잠재 벡터(DeepSeek-V3: 512 + RoPE 64)만 캐시하고, 흡수 트릭으로 업프로젝션을 쿼리·출력 쪽에 합친다. 토큰당 KV는 68.6 KiB로 같은 차원 MHA의 약 1/71이다.
  10. 선형 어텐션은 과거를 고정 크기 상태 \(S = \sum v k^\top\)에 요약한다. 델타 규칙 \(S \leftarrow \alpha S(I-\beta kk^\top)+\beta vk^\top\)는 오차만큼 고쳐 써서 덮어쓰기가 가능하지만, 용량(약 d개 키)은 한정된다. 그래서 Qwen3.5처럼 선형 층 3개당 풀 어텐션 1개를 두는 하이브리드가 쓰인다.

확인 퀴즈

1. 점수 행렬 S의 한 행 전체에 같은 상수 5를 더하면 그 행의 어텐션 가중치 A는?

소프트맥스는 \(e^{s_j+c}/\sum_l e^{s_l+c} = e^{s_j}/\sum_l e^{s_l}\)이라 행 안의 상수 이동에 불변이다. 그래서 수치 안정화를 위해 최댓값을 빼도 결과가 같고, 온라인 소프트맥스도 이 성질을 이용한다. 반면 상수를 곱하면(온도 변화) 분포의 날카로움이 바뀐다.

2. q, k의 성분이 독립이고 평균 0, 분산 1일 때 d_k = 256이면 q·k의 표준편차는 대략?

분산이 \(d_k = 256\)이므로 표준편차는 \(\sqrt{256} = 16\). 점수가 ±16 수준으로 퍼지면 소프트맥스가 거의 원-핫으로 포화된다. \(\sqrt{d_k} = 16\)으로 나누면 표준편차가 1로 돌아온다.

3. 인과 마스크가 적용된 길이 6 시퀀스에서, 첫 번째 토큰(위치 0)의 어텐션 가중치 행은 항상 어떻게 되는가?

위치 0은 j > 0을 모두 볼 수 없으므로 소프트맥스에 남는 항이 자기 자신 하나뿐이고, 가중치는 무조건 1이다. 모든 위치에서 항상 보이는 첫 토큰은 '볼 게 없을 때 가중치를 버리는 곳', 즉 어텐션 싱크가 되기 쉽다.

4. d_model = 4096, 헤드 32개인 층에서 헤드 수를 64개로 늘리고 d_head를 64로 줄였다. 변하는 것은?

h·d_head = 4096이 유지되므로 투영 파라미터와 쌍 계산 FLOPs(4n²d)는 그대로다. 그러나 점수 행렬은 헤드마다 n×n 하나씩이라 h에 비례해 늘어난다. FlashAttention은 이 행렬을 만들지 않으므로 이 문제를 피한다.

5. 시퀀스 "x y z … x"의 마지막 위치에서 인덕션 헤드가 높은 가중치를 주는 위치는?

1층 이전 토큰 헤드가 y 위치에 "내 앞은 x"라는 정보를 써 둔다. 2층 인덕션 헤드는 현재 토큰 x를 쿼리로 "앞이 x인 위치"를 찾으므로 y를 보고, 그 값(y)을 복사해 다음 토큰으로 y를 예측한다. 첫 번째 x 자체를 보는 것은 '동일 단어' 헤드다.

6. Llama 3 8B(32층, KV 헤드 8개, d_head = 128, BF16)의 KV 캐시는 토큰당 얼마이고, GQA가 줄이는 것은 무엇인가?

2(K, V) × 32층 × 8헤드 × 128 × 2 B = 131,072 B = 128 KiB. MHA(32 KV 헤드)였다면 512 KiB다. GQA는 쿼리 헤드 4개가 K·V 헤드 하나를 공유하게 해 캐시와 메모리 대역폭을 1/4로 줄이고, 쿼리 쪽 패턴 다양성은 유지한다.

7. MLA에서 '흡수 트릭'이 가능한 이유와, RoPE용 키를 따로(64차원) 두는 이유를 옳게 짝지은 것은?

점수는 결합 법칙으로 \((W_{UK}^\top q)^\top c_s\)가 되어 키를 복원하지 않고 캐시된 c와 바로 내적한다. 그러나 RoPE는 위치 s마다 다른 회전 \(R_s\)를 키에 곱하므로 \(q^\top R_t^\top R_s W_{UK} c_s\)에서 \(W_{UK}\)를 쿼리 쪽으로 옮길 수 없다. 그래서 위치 정보는 헤드가 공유하는 별도 64차원 키 \(k^R\)에 담아 함께 캐시한다(토큰·층당 576원소).

8. dk = 4인 연상 기억 상태 S에, 단위 키 k₁에 값 v₁을 쓴 뒤 같은 k₁에 새 값 v₅를 썼다. k₁로 읽으면(α = 1)?

덧셈은 \(S = v_1k_1^\top + v_5k_1^\top\)라 \(Sk_1 = v_1+v_5\)다. 델타 규칙은 두 번째 쓰기에서 \(S \leftarrow S + (v_5 - Sk_1)k_1^\top\)로 오차만 더하므로 \(Sk_1 = v_5\)가 된다. KV를 모두 저장하는 소프트맥스 어텐션은 같은 키 두 개에 같은 점수를 줘 두 값의 평균을 낸다(실제 모델에서는 위치 정보가 이를 구별한다).