통계 실험실 차트
넘스탯 로고

넘스탯

DATA ANALYTICS & INSIGHTS

확률에서 LLM까지 – 데이터 사이언스 전문 교육 플랫폼

학습 메뉴
도움말
LLM 이론 · 심화 단원

Transformer 수학 완전 정복
— 공식과 파라미터를 손으로 계산한다

어텐션 심화에서 어텐션 한 조각을 열어봤다면, 이제 Transformer 전체의 수학적 골격을 세웁니다. softmax의 성질부터 Multi-Head의 차원 산술, Positional Encoding, 그리고 6층 Transformer의 파라미터 수를 마지막 한 자리까지 직접 계산합니다. 필요한 선수지식은 행렬 곱셈뿐이에요.

유사도 행렬 · softmax와 온도 · Multi-Head 차원 산술 · sin/cos 위치 부호화 · 파라미터 종합 계산 · 25분

3분 요약

  • QKQK^{\top}모든 토큰 쌍의 관련도를 한 번에 잰 유사도 행렬이고, dk\sqrt{d_k} 나누기는 내적의 분산을 1로 되돌리는 안전장치다.
  • softmax는 점수를 합이 1인 비중으로 바꾸며, 나누는 수(온도)가 클수록 완만해진다 — dk\sqrt{d_k} 나누기는 온도 T=dkT=\sqrt{d_k} 를 건 셈. 계산 전에 최댓값을 빼도 결과는 같다(오버플로 방지 트릭).
  • Multi-Head는 dmodeld_{model}hh 개 헤드가 dk=dmodel/hd_k = d_{model}/h 씩 나눠 갖는다 — 그래서 헤드가 몇 개든 어텐션 블록의 파라미터는 4dmodel24\,d_{model}^2 로 같다.
  • Positional Encoding은 sin/cos 파동으로 위치를 새긴다 — 파장이 2π2\pi 부터 100002π10000 \cdot 2\pi 까지 기하급수로 퍼져, 상대 위치를 회전(선형 변환)으로 표현할 수 있다.
  • dmodel=512, h=8, dff=2048d_{model}=512,\ h=8,\ d_{ff}=2048 인 인코더 층 1개는 3,150,336개, 6층이면 18,902,016개의 파라미터 — 이 글에서 전부 손으로 합산한다.
선수 단원 — 이 글은 Self-Attention 심화의 바로 다음 단원입니다. Q·K·V가 무엇인지, 어텐션의 4단계(점수 → 스케일 → softmax → 섞기)가 낯설다면 먼저 보고 오세요. 여기서는 그 지식을 발판으로 Transformer 전체의 수학을 세웁니다.

STEP 1Scaled Dot-Product — QKQK^{\top} 는 유사도 행렬이다

Attention(Q,K,V)=softmax ⁣(QKdk)V\text{Attention}(Q,K,V)=\text{softmax}\!\left(\dfrac{QK^{\top}}{\sqrt{d_k}}\right)V

어텐션 심화에서는 토큰 하나의 관점에서 이 공식을 따라갔죠. 이번에는 행렬 전체로 봅니다. 토큰이 nn 개면 Q,KQ, K 는 각각 n×dkn \times d_k 행렬이고, 곱 QKQK^{\top}n×nn \times n 행렬이 됩니다. 이 행렬의 (i,j)(i, j) 성분은 qikjq_i \cdot k_j — 즉 "i번째 토큰의 질문이 j번째 토큰의 색인과 얼마나 잘 맞는가"예요. 행렬 곱 한 번으로 모든 토큰 쌍의 관련도를 동시에 계산하니, 이것이 유사도 행렬(관련도 점수표)이고 Transformer가 병렬 처리에 강한 이유입니다.

토큰 2개짜리 초미니 예제로 2×22 \times 2 유사도 행렬을 끝까지 계산해봅시다. dk=4d_k = 4 (그래서 dk=2\sqrt{d_k} = 2), Value는 2차원으로 잡았어요.

토큰Query (Q)Key (K)Value (V)
토큰 1[1, 0, 1, 0][1, 0, 1, 0][4, 0]
토큰 2[0, 1, 0, 1][1, 1, 0, 0][0, 4]

① 유사도 행렬 — QKQK^{\top} (모든 쌍의 내적)

q1·k1 = [1,0,1,0]·[1,0,1,0] = 1+0+1+0 = 2   q1·k2 = [1,0,1,0]·[1,1,0,0] = 1
q2·k1 = [0,1,0,1]·[1,0,1,0] = 0   q2·k2 = [0,1,0,1]·[1,1,0,0] = 1
QK=[2101]QK^{\top}=\begin{bmatrix} 2 & 1 \\ 0 & 1 \end{bmatrix}

② 스케일링 — ÷dk=÷2\div\sqrt{d_k} = \div 2

QK2=[1.00.50.00.5]\frac{QK^{\top}}{2}=\begin{bmatrix} 1.0 & 0.5 \\ 0.0 & 0.5 \end{bmatrix}

③ softmax — 행마다 따로

1행: e1.0=2.7183, e0.5=1.6487 (합 4.3670) → [0.6225, 0.3775]
2행: e0.0=1.0000, e0.5=1.6487 (합 2.6487) → [0.3775, 0.6225]

④ 값 섞기 — 가중치 · V

1행: 0.6225·[4,0] + 0.3775·[0,4] ≈ [2.49, 1.51]
2행: 0.3775·[4,0] + 0.6225·[0,4] ≈ [1.51, 2.49]

토큰 1은 자기 자신(점수 2)에, 토큰 2는 상대적으로 토큰 2 쪽(0 대 1)에 비중을 실었죠. 어텐션의 출력 각 행은 Value들의 가중 평균이라는 것 — 그리고 그 가중치가 유사도 행렬에서 나온다는 것이 전부입니다.

그런데 왜 하필 dk\sqrt{d_k} 로 나눌까요? 원논문(Vaswani et al. 2017, §3.2.1 각주 4)의 논증은 확률 이야기예요. Query와 Key의 각 성분이 서로 독립이고 평균 0, 분산 1이라면, 내적 qk=i=1dkqikiq \cdot k = \sum_{i=1}^{d_k} q_i k_i 는 분산이 1인 항 dkd_k 개의 합이므로 분산이 dkd_k 가 됩니다(독립 확률변수 합의 분산 = 분산의 합). 표준편차 dk\sqrt{d_k} 로 나누면,

Var ⁣(qkdk)=dkdk=1\operatorname{Var}\!\left(\frac{q\cdot k}{\sqrt{d_k}}\right)=\frac{d_k}{d_k}=1

점수의 분산이 차원과 무관하게 1 근처로 유지됩니다. 나누지 않으면 dk=512d_k = 512 같은 큰 차원에서 점수가 ±512±23\pm\sqrt{512} \approx \pm 23 규모로 널을 뛰고, 다음 단계의 softmax가 포화되어 학습이 멈춥니다. 그 이유는 STEP 2에서 softmax를 뜯어보면 바로 보여요.

STEP 2softmax의 수학 — 온도, 그리고 max 빼기 트릭

softmax의 정의부터 정확히 적어봅시다.

softmax(z)i=ezij=1nezj모두 양수, 합이 1\text{softmax}(z)_i=\dfrac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}} \quad\Rightarrow\quad \text{모두 양수, 합이 } 1

지수함수 eze^{z}zz 가 조금만 커져도 폭발적으로 커지므로, softmax는 큰 점수를 더 크게 벌리는 성질이 있어요. 점수를 상수 TT (온도, temperature)로 나눈 뒤 softmax를 걸면 이 벌어짐의 정도를 조절할 수 있습니다. 점수 z=[2,0,1]z = [2, 0, 1] 로 직접 계산해보면,

온도 Tsoftmax(z / T)성격
0.5[0.867, 0.016, 0.117]뾰족 — 1등에 몰아줌
1[0.665, 0.090, 0.245]기본
2[0.506, 0.186, 0.307]완만 — 고르게 나눔
스케일링 = 온도 — STEP 1의 ÷dk\div\sqrt{d_k} 는 정확히 온도 T=dkT = \sqrt{d_k} 를 건 softmax와 같습니다 (dk=4d_k = 4 였으니 T=2T = 2 — 위 표의 셋째 줄). 점수의 분산이 커질수록 softmax가 "T = 0.5" 줄처럼 한쪽에 1을 몰아주는 포화 상태가 되는데, 이때 입력을 조금 바꿔도 출력이 거의 안 변하니 기울기가 0에 가까워져 학습이 멈춥니다. dk\sqrt{d_k} 나누기는 온도를 올려 이 포화를 막는 장치예요. (LLM 생성 옵션의 temperature도 같은 수식입니다 — 출력 확률을 뾰족하게/완만하게 조절하죠.)

실전에서는 한 가지 문제가 더 있습니다. 점수가 크면 eze^{z} 자체가 컴퓨터로 계산 불가능해져요. e1000e^{1000} 은 오버플로(무한대 처리)됩니다. 해법은 모든 점수에서 최댓값을 빼고 시작하는 것: 임의의 상수 cc 에 대해,

ezicjezjc=eziececjezj=ezijezj(결과 불변)\dfrac{e^{z_i-c}}{\sum_j e^{z_j-c}}=\dfrac{e^{z_i}\,e^{-c}}{e^{-c}\sum_j e^{z_j}}=\dfrac{e^{z_i}}{\sum_j e^{z_j}} \quad \text{(결과 불변)}

분자·분모에 같은 수 ece^{-c} 가 곱해지니 결과는 그대로예요. c=max(z)c = \max(z) 로 잡으면 지수가 전부 0 이하가 되어 절대 오버플로되지 않습니다. 예를 들어 z=[1000,1001,1002]z = [1000, 1001, 1002] 는 그대로는 계산 불가지만, 최댓값 1002를 빼면 [2,1,0][-2, -1, 0] 이 되고,

e−2=0.1353, e−1=0.3679, e0=1.0000 (합 = 1.5032)
→ softmax = [0.0900, 0.2447, 0.6652] — 원래 [1000, 1001, 1002]의 softmax와 정확히 같은 값

이 "max 빼기 트릭"은 numpy·PyTorch 등 모든 라이브러리의 softmax 구현에 들어 있는 표준 기법입니다. 아래 실습의 코드에도 넣어뒀으니 확인해보세요.

실습 1 — 손계산을 numpy로 검증

STEP 1의 2×22 \times 2 손계산을 코드로 재현합니다. 실행 버튼을 누르면 브라우저 안에서 파이썬이 돌아가고, 가중치 [0.6225, 0.3775][0.6225,\ 0.3775] 와 출력 [2.49, 1.51][2.49,\ 1.51] 이 그대로 나와야 합니다.

첫 실행 시 파이썬 런타임을 내려받습니다 (수 초 소요)

직접 해보기 — V를 바꾸면 출력이 어떻게 변하나요? d_k를 64로 늘리고 Q·K에 큰 값을 넣으면(예: 전부 3) 스케일링 전후의 softmax가 얼마나 달라지는지 scaled 대신 scores로 softmax를 걸어 비교해보세요.

STEP 3Multi-Head의 차원 산술 — 512를 8명이 나눠 갖기

언어에는 문법·의미·시제 같은 여러 관계가 동시에 작동합니다. 그래서 Transformer는 어텐션을 hh 개 병렬로 돌리는데, 이때 차원을 늘리는 게 아니라 나눕니다. 원논문의 기본 설정은 dmodel=512, h=8d_{model} = 512,\ h = 8 이고, 각 헤드는

dk=dv=dmodelh=5128=64d_k = d_v = \frac{d_{model}}{h} = \frac{512}{8} = 64

차원씩 나눠 갖습니다. 헤드 ii 는 자기만의 투영 행렬 WiQ,WiK,WiVW_i^{Q}, W_i^{K}, W_i^{V} (각 512×64512 \times 64)로 입력을 64차원 세계로 내려보내 어텐션을 계산하고, 8개 헤드의 출력(각 64차원)을 이어 붙인 512차원을 WOW^{O} (512×512512 \times 512)로 한 번 더 섞습니다(Vaswani et al. 2017, §3.2.2).

MultiHead(Q,K,V)=Concat(head1,,headh)WO,headi=Attention(QWiQ,KWiK,VWiV)\text{MultiHead}(Q,K,V)=\text{Concat}(\text{head}_1,\ldots,\text{head}_h)\,W^{O}, \quad \text{head}_i=\text{Attention}(QW_i^{Q},\,KW_i^{K},\,VW_i^{V})

파라미터가 몇 개인지 하나하나 세봅시다.

구성 요소크기개수파라미터 수
WiQW_i^{Q}512 × 64 = 32,768헤드 8개262,144
WiKW_i^{K}512 × 64 = 32,768헤드 8개262,144
WiVW_i^{V}512 × 64 = 32,768헤드 8개262,144
WOW^{O}512 × 5121개262,144
합계1,048,576
깔끔한 결론 — 8개 헤드의 Q·K·V 투영을 다 합치면 8×(512×64)=512×5128 \times (512 \times 64) = 512 \times 512 이므로, 어텐션 블록 전체는 정확히 4dmodel2=4×5122=1,048,5764\,d_{model}^2 = 4 \times 512^2 = 1{,}048{,}576 개. 헤드 수를 1개로 하든 8개로 하든 총 파라미터는 같습니다. dk=dmodel/hd_k = d_{model}/h 로 나눠 갖기 때문이죠. 원논문도 "차원을 줄인 덕에 총 계산 비용이 (전체 차원의) 단일 헤드 어텐션과 비슷하다"고 밝힙니다. 여러 헤드의 이득은 파라미터가 아니라 서로 다른 표현 부분공간을 동시에 보는 능력에서 옵니다.

STEP 4Positional Encoding — 위치를 파동으로 새기다

어텐션은 모든 토큰을 동시에 바라보기 때문에, 그 자체로는 순서를 모릅니다. "개가 사람을 물었다"와 "사람이 개를 물었다"의 유사도 행렬이 (조사를 무시하면) 같아질 수 있어요. 그래서 입력 임베딩에 위치 신호를 더해주는데, 원논문(§3.5)의 정의는 이렇습니다. 위치 pospos2i2i, 2i+12i{+}1 번째 성분에 대해,

PE(pos,2i)=sin ⁣(pos100002i/dmodel),PE(pos,2i+1)=cos ⁣(pos100002i/dmodel)PE_{(pos,\,2i)}=\sin\!\left(\frac{pos}{10000^{2i/d_{model}}}\right), \qquad PE_{(pos,\,2i+1)}=\cos\!\left(\frac{pos}{10000^{2i/d_{model}}}\right)

dmodel=4d_{model} = 4 로 줄여 직접 계산해봅시다. 분모는 성분 쌍마다 100000/4=110000^{0/4} = 1100002/4=10010000^{2/4} = 100 — 즉 첫 쌍은 각도가 pospos 라디안씩, 둘째 쌍은 pos/100pos/100 라디안씩 도는 두 개의 시계입니다.

possin(pos)cos(pos)sin(pos/100)cos(pos/100)
00.00001.00000.00001.0000
10.84150.54030.01001.0000
20.9093−0.41610.02000.9998
30.1411−0.99000.03000.9996

※ 소수 4자리 반올림 (pos=1의 cos(0.01)은 정확히는 0.99995).

왜 이런 모양일까요? 두 가지 성질이 핵심이에요. 첫째, 파장의 스펙트럼. 성분 쌍마다 파장(한 바퀴 도는 데 걸리는 위치 수)이 2π2\pi 에서 100002π10000 \cdot 2\pi 까지 기하급수로 길어집니다. 빨리 도는 시계(첫 쌍)는 이웃 위치를 촘촘히 구분하고, 느리게 도는 시계(뒤쪽 쌍)는 멀리 떨어진 위치를 구분해요 — 초침·분침·시침이 함께 시각을 표현하는 것과 같은 원리입니다. 그리고 sin/cos 값은 늘 [1,1][-1, 1] 안이라 위치가 아무리 커져도 신호가 폭주하지 않죠.

둘째, 상대 위치의 선형 표현(소개 수준). 삼각함수 덧셈정리에 따라 고정 간격 kk 에 대해,

[sin(ω(p+k))cos(ω(p+k))]=[cos(ωk)sin(ωk)sin(ωk)cos(ωk)][sin(ωp)cos(ωp)]\begin{bmatrix}\sin(\omega(p+k))\\ \cos(\omega(p+k))\end{bmatrix}=\begin{bmatrix}\cos(\omega k) & \sin(\omega k)\\ -\sin(\omega k) & \cos(\omega k)\end{bmatrix}\begin{bmatrix}\sin(\omega p)\\ \cos(\omega p)\end{bmatrix}

— 즉 "kk 칸 뒤의 위치 신호"는 현재 위치 신호에 kk 에만 의존하는 회전 행렬(선형 변환)을 곱한 것입니다. 원논문은 이 성질 덕분에 모델이 "3칸 앞의 단어" 같은 상대 위치 참조를 쉽게 배울 것이라는 가설로 이 형태를 골랐다고 밝힙니다.

실습 2 — Positional Encoding 값을 직접 계산

위 표의 값을 코드로 재현하고, 각 (sin, cos) 쌍이 항상 반지름 1인 원 위의 점이라는 것(sin2+cos2=1\sin^2 + \cos^2 = 1)도 확인합니다.

첫 실행 시 파이썬 런타임을 내려받습니다 (수 초 소요)

직접 해보기 — d_model을 8로, n_pos를 16으로 늘려 뒤쪽 성분일수록 값이 천천히 변하는(파장이 길어지는) 모습을 관찰해보세요. pos=100, 1000처럼 큰 위치를 넣어도 값이 [-1, 1]을 벗어나지 않는 것도 확인해보세요.

STEP 5잔차 연결 + LayerNorm, 그리고 FFN

어텐션과 함께 Transformer 블록을 이루는 나머지 부품을 수식으로 정리합니다(개요 수준). 각 부속층(어텐션이든 FFN이든)은 다음 형태로 감싸입니다(Vaswani et al. 2017, §3.1).

output=LayerNorm(x+Sublayer(x))\text{output}=\text{LayerNorm}\big(x+\text{Sublayer}(x)\big)

x+Sublayer(x)x + \text{Sublayer}(x)잔차 연결(residual connection)입니다. 부속층은 xx 를 통째로 새로 만드는 게 아니라 "고칠 부분(잔차)"만 계산해 원본에 더합니다. 입력이 그대로 통과하는 지름길이 있으니 기울기도 이 길로 잘 흘러, 층을 수십 개 쌓아도 학습이 됩니다(He et al. 2016의 아이디어). LayerNorm은 각 토큰 벡터의 성분들을 평균 0·분산 1로 맞춘 뒤 배율과 이동을 학습하는 정규화입니다(Ba et al. 2016).

LayerNorm(x)=γxμσ2+ϵ+β(μ, σ2는 그 토큰 벡터 dmodel개 성분의 평균\cdotp분산)\text{LayerNorm}(x)=\gamma\odot\frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta \quad \text{(} \mu,\ \sigma^2 \text{는 그 토큰 벡터 } d_{model} \text{개 성분의 평균·분산)}

학습되는 파라미터는 γ,β\gamma, \beta dmodeld_{model} 개씩 — LayerNorm 하나에 2×512=1,0242 \times 512 = 1{,}024 개입니다. 블록에는 어텐션 뒤·FFN 뒤로 2개가 있으니 2,048개.

마지막 부품인 FFN(피드포워드 신경망)은 각 위치(토큰)마다 독립적으로 적용되는 2층 신경망입니다(§3.3). 512차원을 2048차원으로 넓혔다가(ReLU) 다시 512차원으로 좁힙니다.

FFN(x)=max(0, xW1+b1)W2+b2,W1R512×2048, W2R2048×512\text{FFN}(x)=\max(0,\ xW_1+b_1)\,W_2+b_2, \qquad W_1\in\mathbb{R}^{512\times 2048},\ W_2\in\mathbb{R}^{2048\times 512}
구성 요소크기파라미터 수
W1W_1512 × 20481,048,576
b1b_120482,048
W2W_22048 × 5121,048,576
b2b_2512512
합계2,099,712

STEP 6종합 — 6층 Transformer의 파라미터를 전부 세기

이제 부품별 계산을 모읍니다. 원논문 기본 설정(dmodel=512d_{model}=512, h=8h=8, dff=2048d_{ff}=2048)의 인코더 층 1개는,

부품계산파라미터 수비중
Multi-Head Attention4 × 512²1,048,57633.3%
FFN512·2048 + 2048 + 2048·512 + 5122,099,71266.7%
LayerNorm × 22 × (2 × 512)2,0480.1%
층 1개 합계3,150,336100%
× 6층3,150,336 × 618,902,016
읽는 법 — 층 파라미터의 약 2/3가 FFN에 있습니다. "어텐션은 정보를 어디서 가져올지 정하는 라우팅, 지식은 주로 FFN에 저장된다"는 통찰이 숫자로도 보이죠. 그리고 18,902,016개는 인코더 스택의 가중치만 센 것입니다. 실제 원논문의 base 모델은 여기에 디코더 스택(어텐션이 층마다 2개라 더 큼)과 토큰 임베딩·출력층까지 더해 약 6,500만(65 × 10⁶)개로 보고됩니다(Vaswani et al. 2017, Table 3).

같은 셈법을 함수로 만들어두면 어떤 모델이든 즉시 셀 수 있습니다. 아래 실습에서 직접 확인해보세요.

실습 3 — Transformer 파라미터 계산기

첫 실행 시 파이썬 런타임을 내려받습니다 (수 초 소요)

직접 해보기 — GPT-2 small 급 설정(d_model=768, h=12, d_ff=3072, 12층)을 넣으면 몇 개가 나오나요? h를 8에서 16으로 바꿔도 총합이 변하지 않는 것(STEP 3의 결론)도 확인해보세요.

스스로 점검 — 5문항

보기를 누르면 즉시 정답과 해설이 나옵니다. 헷갈리면 해당 STEP으로 돌아가 보세요.

Q1. Attention 공식의 QKᵀ 가 만드는 행렬의 (i, j) 성분이 뜻하는 것은?

i번째 토큰의 최종 출력 벡터
i번째 토큰의 Query와 j번째 토큰의 Key의 내적 — 두 토큰의 관련도(유사도) 점수
i번째 토큰이 문장에서 등장한 횟수
i번째와 j번째 토큰의 위치 차이

Q2. √d_k 로 나누는 이유로 가장 정확한 것은?

행렬 크기를 줄여 메모리를 아끼려고
평균 0·분산 1인 독립 성분끼리의 내적은 분산이 d_k가 되므로, 표준편차 √d_k로 나눠 분산을 1 근처로 되돌려 softmax 포화(기울기 소실)를 막는다
출력을 항상 0~1 사이로 만들려고
토큰 개수와 차원 수를 맞추려고

Q3. softmax를 계산할 때 모든 점수에서 최댓값을 빼는(max 빼기) 이유는?

결과 확률을 더 뾰족하게 만들려고
가장 큰 점수를 무조건 1로 만들려고
e^z가 오버플로되는 것을 막기 위해 — softmax(z − c) = softmax(z)이므로 결과는 전혀 변하지 않는다
계산 횟수를 절반으로 줄이려고

Q4. d_model = 512, h = 8 인 Multi-Head Attention에서 각 헤드의 차원 d_k와, 어텐션 블록 전체(W_Q·W_K·W_V·W_O)의 파라미터 수는?

d_k = 512, 파라미터는 헤드 수에 비례해 8배로 늘어난다
d_k = 64, 파라미터는 4 × 512² = 1,048,576개 — 헤드를 나눠도 총량은 단일 헤드(같은 총차원)와 같다
d_k = 8, 파라미터는 512 × 8 = 4,096개
d_k = 64, 파라미터는 W_O 하나뿐이라 262,144개

Q5. Positional Encoding에 sin/cos 함수를 쓴 이유로 원논문이 제시한 가설은?

sin/cos이 계산이 가장 빠른 함수라서
고정 간격 k에 대해 PE(pos+k)를 PE(pos)의 선형 함수(회전)로 표현할 수 있어, 모델이 상대 위치에 주목하는 법을 쉽게 배울 것이라 기대했기 때문
값이 항상 양수가 되기 때문
위치가 커질수록 값이 계속 커져 순서를 구분할 수 있기 때문

참고 자료 — 1차 출처

※ 워크드 예제의 Q·K·V 숫자는 개념 이해를 위해 저자가 구성한 예시이며, softmax 가중치·PE 값은 소수 4자리 반올림으로 표기했습니다. 공식·차원·파라미터 구성과 분산 논거는 위 원논문을 그대로 따르며, 파라미터 수는 본문의 계산기로 재검증했습니다.

선수 단원 — Self-Attention 심화다음 — 어텐션 실습실에서 직접 조작