

확률부터 LLM까지, 원리부터 제대로 배우는 자기주도 학습 플랫폼
어텐션 심화에서 어텐션 한 조각을 열어봤다면, 이제 Transformer 전체의 수학적 골격을 세웁니다. softmax의 성질부터 Multi-Head의 차원 산술, Positional Encoding, 그리고 6층 Transformer의 파라미터 수를 마지막 한 자리까지 직접 계산합니다. 필요한 선수지식은 행렬 곱셈뿐이에요.
유사도 행렬 · softmax와 온도 · Multi-Head 차원 산술 · sin/cos 위치 부호화 · 파라미터 종합 계산 · 25분
3분 요약
어텐션 심화에서는 토큰 하나의 관점에서 이 공식을 따라갔죠. 이번에는 행렬 전체로 봅니다. 토큰이 개면 는 각각 행렬이고, 곱 는 행렬이 됩니다. 이 행렬의 성분은 — 즉 "i번째 토큰의 질문이 j번째 토큰의 색인과 얼마나 잘 맞는가"예요. 행렬 곱 한 번으로 모든 토큰 쌍의 관련도를 동시에 계산하니, 이것이 유사도 행렬(관련도 점수표)이고 Transformer가 병렬 처리에 강한 이유입니다.
토큰 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] |
① 유사도 행렬 — (모든 쌍의 내적)
② 스케일링 —
③ softmax — 행마다 따로
④ 값 섞기 — 가중치 · V
토큰 1은 자기 자신(점수 2)에, 토큰 2는 상대적으로 토큰 2 쪽(0 대 1)에 비중을 실었죠. 어텐션의 출력 각 행은 Value들의 가중 평균이라는 것 — 그리고 그 가중치가 유사도 행렬에서 나온다는 것이 전부입니다.
그런데 왜 하필 로 나눌까요? 원논문(Vaswani et al. 2017, §3.2.1 각주 4)의 논증은 확률 이야기예요. Query와 Key의 각 성분이 서로 독립이고 평균 0, 분산 1이라면, 내적 는 분산이 1인 항 개의 합이므로 분산이 가 됩니다(독립 확률변수 합의 분산 = 분산의 합). 표준편차 로 나누면,
점수의 분산이 차원과 무관하게 1 근처로 유지됩니다. 나누지 않으면 같은 큰 차원에서 점수가 규모로 널을 뛰고, 다음 단계의 softmax가 포화되어 학습이 멈춥니다. 그 이유는 STEP 2에서 softmax를 뜯어보면 바로 보여요.
softmax의 정의부터 정확히 적어봅시다.
지수함수 는 가 조금만 커져도 폭발적으로 커지므로, softmax는 큰 점수를 더 크게 벌리는 성질이 있어요. 점수를 상수 (온도, temperature)로 나눈 뒤 softmax를 걸면 이 벌어짐의 정도를 조절할 수 있습니다. 점수 로 직접 계산해보면,
| 온도 T | softmax(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] | 완만 — 고르게 나눔 |
실전에서는 한 가지 문제가 더 있습니다. 점수가 크면 자체가 컴퓨터로 계산 불가능해져요. 은 오버플로(무한대 처리)됩니다. 해법은 모든 점수에서 최댓값을 빼고 시작하는 것: 임의의 상수 에 대해,
분자·분모에 같은 수 가 곱해지니 결과는 그대로예요. 로 잡으면 지수가 전부 0 이하가 되어 절대 오버플로되지 않습니다. 예를 들어 는 그대로는 계산 불가지만, 최댓값 1002를 빼면 이 되고,
이 "max 빼기 트릭"은 numpy·PyTorch 등 모든 라이브러리의 softmax 구현에 들어 있는 표준 기법입니다. 아래 실습의 코드에도 넣어뒀으니 확인해보세요.
STEP 1의 손계산을 코드로 재현합니다. 실행 버튼을 누르면 브라우저 안에서 파이썬이 돌아가고, 가중치 와 출력 이 그대로 나와야 합니다.
첫 실행 시 파이썬 런타임을 내려받습니다 (수 초 소요)
직접 해보기 — V를 바꾸면 출력이 어떻게 변하나요? d_k를 64로 늘리고 Q·K에 큰 값을 넣으면(예: 전부 3) 스케일링 전후의 softmax가 얼마나 달라지는지 scaled 대신 scores로 softmax를 걸어 비교해보세요.
언어에는 문법·의미·시제 같은 여러 관계가 동시에 작동합니다. 그래서 Transformer는 어텐션을 개 병렬로 돌리는데, 이때 차원을 늘리는 게 아니라 나눕니다. 원논문의 기본 설정은 이고, 각 헤드는
차원씩 나눠 갖습니다. 헤드 는 자기만의 투영 행렬 (각 )로 입력을 64차원 세계로 내려보내 어텐션을 계산하고, 8개 헤드의 출력(각 64차원)을 이어 붙인 512차원을 ()로 한 번 더 섞습니다(Vaswani et al. 2017, §3.2.2).
파라미터가 몇 개인지 하나하나 세봅시다.
| 구성 요소 | 크기 | 개수 | 파라미터 수 |
|---|---|---|---|
| 512 × 64 = 32,768 | 헤드 8개 | 262,144 | |
| 512 × 64 = 32,768 | 헤드 8개 | 262,144 | |
| 512 × 64 = 32,768 | 헤드 8개 | 262,144 | |
| 512 × 512 | 1개 | 262,144 | |
| 합계 | 1,048,576 |
어텐션은 모든 토큰을 동시에 바라보기 때문에, 그 자체로는 순서를 모릅니다. "개가 사람을 물었다"와 "사람이 개를 물었다"의 유사도 행렬이 (조사를 무시하면) 같아질 수 있어요. 그래서 입력 임베딩에 위치 신호를 더해주는데, 원논문(§3.5)의 정의는 이렇습니다. 위치 의 , 번째 성분에 대해,
로 줄여 직접 계산해봅시다. 분모는 성분 쌍마다 과 — 즉 첫 쌍은 각도가 라디안씩, 둘째 쌍은 라디안씩 도는 두 개의 시계입니다.
| pos | sin(pos) | cos(pos) | sin(pos/100) | cos(pos/100) |
|---|---|---|---|---|
| 0 | 0.0000 | 1.0000 | 0.0000 | 1.0000 |
| 1 | 0.8415 | 0.5403 | 0.0100 | 1.0000 |
| 2 | 0.9093 | −0.4161 | 0.0200 | 0.9998 |
| 3 | 0.1411 | −0.9900 | 0.0300 | 0.9996 |
※ 소수 4자리 반올림 (pos=1의 cos(0.01)은 정확히는 0.99995).
왜 이런 모양일까요? 두 가지 성질이 핵심이에요. 첫째, 파장의 스펙트럼. 성분 쌍마다 파장(한 바퀴 도는 데 걸리는 위치 수)이 에서 까지 기하급수로 길어집니다. 빨리 도는 시계(첫 쌍)는 이웃 위치를 촘촘히 구분하고, 느리게 도는 시계(뒤쪽 쌍)는 멀리 떨어진 위치를 구분해요 — 초침·분침·시침이 함께 시각을 표현하는 것과 같은 원리입니다. 그리고 sin/cos 값은 늘 안이라 위치가 아무리 커져도 신호가 폭주하지 않죠.
둘째, 상대 위치의 선형 표현(소개 수준). 삼각함수 덧셈정리에 따라 고정 간격 에 대해,
— 즉 " 칸 뒤의 위치 신호"는 현재 위치 신호에 에만 의존하는 회전 행렬(선형 변환)을 곱한 것입니다. 원논문은 이 성질 덕분에 모델이 "3칸 앞의 단어" 같은 상대 위치 참조를 쉽게 배울 것이라는 가설로 이 형태를 골랐다고 밝힙니다.
위 표의 값을 코드로 재현하고, 각 (sin, cos) 쌍이 항상 반지름 1인 원 위의 점이라는 것()도 확인합니다.
첫 실행 시 파이썬 런타임을 내려받습니다 (수 초 소요)
직접 해보기 — d_model을 8로, n_pos를 16으로 늘려 뒤쪽 성분일수록 값이 천천히 변하는(파장이 길어지는) 모습을 관찰해보세요. pos=100, 1000처럼 큰 위치를 넣어도 값이 [-1, 1]을 벗어나지 않는 것도 확인해보세요.
어텐션과 함께 Transformer 블록을 이루는 나머지 부품을 수식으로 정리합니다(개요 수준). 각 부속층(어텐션이든 FFN이든)은 다음 형태로 감싸입니다(Vaswani et al. 2017, §3.1).
가 잔차 연결(residual connection)입니다. 부속층은 를 통째로 새로 만드는 게 아니라 "고칠 부분(잔차)"만 계산해 원본에 더합니다. 입력이 그대로 통과하는 지름길이 있으니 기울기도 이 길로 잘 흘러, 층을 수십 개 쌓아도 학습이 됩니다(He et al. 2016의 아이디어). LayerNorm은 각 토큰 벡터의 성분들을 평균 0·분산 1로 맞춘 뒤 배율과 이동을 학습하는 정규화입니다(Ba et al. 2016).
학습되는 파라미터는 각 개씩 — LayerNorm 하나에 개입니다. 블록에는 어텐션 뒤·FFN 뒤로 2개가 있으니 2,048개.
마지막 부품인 FFN(피드포워드 신경망)은 각 위치(토큰)마다 독립적으로 적용되는 2층 신경망입니다(§3.3). 512차원을 2048차원으로 넓혔다가(ReLU) 다시 512차원으로 좁힙니다.
| 구성 요소 | 크기 | 파라미터 수 |
|---|---|---|
| 512 × 2048 | 1,048,576 | |
| 2048 | 2,048 | |
| 2048 × 512 | 1,048,576 | |
| 512 | 512 | |
| 합계 | 2,099,712 |
이제 부품별 계산을 모읍니다. 원논문 기본 설정(, , )의 인코더 층 1개는,
| 부품 | 계산 | 파라미터 수 | 비중 |
|---|---|---|---|
| Multi-Head Attention | 4 × 512² | 1,048,576 | 33.3% |
| FFN | 512·2048 + 2048 + 2048·512 + 512 | 2,099,712 | 66.7% |
| LayerNorm × 2 | 2 × (2 × 512) | 2,048 | 0.1% |
| 층 1개 합계 | 3,150,336 | 100% | |
| × 6층 | 3,150,336 × 6 | 18,902,016 |
같은 셈법을 함수로 만들어두면 어떤 모델이든 즉시 셀 수 있습니다. 아래 실습에서 직접 확인해보세요.
첫 실행 시 파이썬 런타임을 내려받습니다 (수 초 소요)
직접 해보기 — GPT-2 small 급 설정(d_model=768, h=12, d_ff=3072, 12층)을 넣으면 몇 개가 나오나요? h를 8에서 16으로 바꿔도 총합이 변하지 않는 것(STEP 3의 결론)도 확인해보세요.
보기를 누르면 즉시 정답과 해설이 나옵니다. 헷갈리면 해당 STEP으로 돌아가 보세요.
Q1. Attention 공식의 QKᵀ 가 만드는 행렬의 (i, j) 성분이 뜻하는 것은?
Q2. √d_k 로 나누는 이유로 가장 정확한 것은?
Q3. softmax를 계산할 때 모든 점수에서 최댓값을 빼는(max 빼기) 이유는?
Q4. d_model = 512, h = 8 인 Multi-Head Attention에서 각 헤드의 차원 d_k와, 어텐션 블록 전체(W_Q·W_K·W_V·W_O)의 파라미터 수는?
Q5. Positional Encoding에 sin/cos 함수를 쓴 이유로 원논문이 제시한 가설은?
※ 워크드 예제의 Q·K·V 숫자는 개념 이해를 위해 저자가 구성한 예시이며, softmax 가중치·PE 값은 소수 4자리 반올림으로 표기했습니다. 공식·차원·파라미터 구성과 분산 논거는 위 원논문을 그대로 따르며, 파라미터 수는 본문의 계산기로 재검증했습니다.