devlos
Devlos Archive
devlos
전체 방문자
오늘
어제
08-11 14:00

최근 글

  • 분류 전체보기 (120)
    • 프로젝트 (1)
    • MSA 설계 & 도메인주도 설계 (9)
    • AI (4)
      • [LLMSO] Hands-On LLM 서빙과 최적.. (4)
    • 클라우드 컴퓨팅 & NoSQL (96)
      • [K8S Deploy] K8S 디플로이 스터디 (8)
      • [Cilium Study] 실리움 스터디 (8)
      • [KANS] 쿠버네티스 네트워크 심화 스터디 (12)
      • [T101] 테라폼 4기 스터디 (8)
      • [CICD] CICD 맛보기 스터디 (3)
      • [T101] 테라폼 기초 입문 스터디 (6)
      • [AEWS] Amazon EKS 워크숍 스터디 (7)
      • [PKOS] 쿠버네티스 실무 실습 스터디 (7)
      • Kubernetes (13)
      • Docker (7)
      • Redis (1)
      • Jenkins (3)
      • Terraform (1)
      • Ansible (4)
      • Kafka (1)
    • 프로그래밍 (7)
      • Spring Boot (5)
      • Broker (1)
    • 성능과 튜닝 (1)
    • ALM (0)
    • 기타 (2)

인기 글

태그

  • t101 4기
  • MSA
  • 데브옵스
  • PKOS
  • 쿠버네티스
  • 쿠버네티스 스터디
  • Kubernetes
  • CloudNet@
  • cilium
  • docker
  • kOps
  • DevOps
  • 도커
  • ansible
  • 테라폼

티스토리

최근 댓글

hELLO · Designed By 정상우.
devlos

Devlos Archive

[1주차-LLMSO]LLM 기초 동작 원리 (2026.08.02)
AI/[LLMSO] Hands-On LLM 서빙과 최적화 스터디

[1주차-LLMSO]LLM 기초 동작 원리 (2026.08.02)

2026. 8. 8. 11:15
반응형
LLM 기초 — 동작 원리 인터랙티브 학습자료
LLM 기초 · 동작 원리 들어가며 1 가중치 2 역전파 3 GPU 4 Transformer 5 텐서·임베딩 6 GPT-2 해부 7 입력 만들기 8 Attention 9 Head·MLP 10 Residual·LN 11 출력 12 Gradient 13 dtype·양자화 14 정리 마치며
LLM 서빙 스터디 1주차 · LLM 기초 학습자료

숫자 몇 개가
말을 하게 되기까지.

가중치 하나짜리 신경에서 시작해서, GPT-2 Small의 124,439,808개 숫자가 다음 단어 하나를 고르는 순간까지 — 중간을 건너뛰지 않고 이어서 봅니다.

수식은 전부 실었습니다. 다만 본문에는 두지 않고 접는 상자 안에 넣었고, 기호 하나하나를 한국어로 읽는 법까지 붙였습니다. 맨 끝에 수식 한눈에 보기 표도 있습니다.

이 자료가 지키는 것

① 수식은 본문 밖, 하지만 빠짐없이. 본문은 수식 없이 읽히고, 수식은 ▸ 상자를 열면 전부 나옵니다.
② 전문 용어는 영어 그대로. logit을 억지로 번역하지 않습니다. 대신 뜻을 한국어로 풀어둡니다.
③ 그림 14개 전부 조작 가능. 슬라이더를 끝까지 밀어보는 게 이 자료의 핵심입니다.
④ 장마다 세 줄 요약. 거기만 읽어도 넘어갈 수 있게.

들어가며

읽는 시간 약 50분 대상 코드는 쓰지만 수학·딥러닝은 낯선 개발자 사전 지식 없어도 됨 그림 14개 · 전부 조작 가능

"LLM은 다음 단어를 예측하는 모델이다." 이 문장은 다들 압니다. 그런데 어떻게 예측하는지로 넘어가면, 대부분의 설명이 갑자기 softmax(QKᵀ/√d)V 같은 걸 던지고 지나갑니다.

반대로 "수식 없이" 설명하는 자료들은 비유에서 멈춥니다. 읽을 땐 이해되는데, 막상 hidden_size나 temperature 옵션을 만나면 그 비유가 어디에 붙는 이야기였는지를 모릅니다.

이 글은 그 사이를 메우려고 썼습니다. 본문은 비유와 작은 숫자로만 가고, 수식은 접는 상자 안에 전부 넣되 기호를 하나씩 읽어드립니다. 둘 다 있어야 나중에 코드와 설정값을 만났을 때 연결이 됩니다.

다루는 범위는 가중치·역전파 · GPU 병렬 · Transformer 구조 · 임베딩 · Attention · MLP · Residual/LayerNorm · 샘플링 · dtype/양자화입니다. 반대로 모델을 실제로 학습시키는 방법, 파인튜닝, 서빙 인프라 구축은 다루지 않습니다.

결론부터 세 줄
  1. 딥러닝은 "곱하고 더한 뒤, 틀린 만큼 숫자를 조금씩 고치는 일"의 반복입니다. 이게 1장부터 끝까지 변하지 않습니다.
  2. Transformer는 단어를 벡터로 바꾸고, 서로를 얼마나 볼지 내적으로 재고, softmax로 비율을 만들어 섞습니다. 이 셋이 전부입니다.
  3. 이 계산들이 서로 독립적이라 GPU에서 한꺼번에 돌아갑니다. 그래서 dtype과 양자화가 곧 서빙 비용이 됩니다.

읽는 방법

· 굵은 글씨만 따라가도 흐름이 이어집니다.

· 회색으로 접힌 상자(▸ 수식으로도 보고 싶다면)에 이 글의 모든 수식이 들어 있습니다. 안 열어도 본문은 완결됩니다.

· 수식만 한꺼번에 보고 싶다면 14장의 "수식 한눈에 보기" 표로 바로 가세요.

· 그림은 전부 슬라이더를 밀고 버튼을 눌러보는 용도입니다. 특히 끝까지 밀었을 때 무슨 일이 나는지가 제일 중요합니다.

먼저 기호부터 — 무서워할 것 없습니다

이 글에 나오는 기호는 이게 전부입니다. 지금 외울 필요는 없고, "아 그거" 정도만 되면 됩니다.

w · b
weight · bias
학습으로 정해지는 그냥 숫자. w는 곱하는 수, b는 더하는 수.
Σ
시그마 — 다 더해
"뒤에 오는 것들을 전부 합쳐라". 반복문의 sum입니다.
a · b
내적 (dot product)
같은 자리끼리 곱해서 전부 더하기. 두 벡터가 얼마나 닮았는지를 숫자 하나로.
Qkᵀ
위첨자 T — 전치
행렬을 가로세로 뒤집는 것. 곱셈 모양을 맞추려고 씁니다.
√d
루트 d
제곱근. 숫자가 너무 커지지 않게 나눠주는 용도로 나옵니다.
exp(x)
지수 함수
e의 x제곱. 큰 값은 훨씬 크게, 음수는 0에 가깝게 만듭니다.
∂L/∂w
편미분 — gradient
"w를 조금 바꾸면 오차 L이 얼마나 변하나". 수정 방향 화살표입니다.
η
에타 — learning rate
한 번에 얼마나 크게 고칠지. 보폭입니다.
μ · σ
뮤 · 시그마
평균과 표준편차(퍼진 정도). LayerNorm에서 나옵니다.
ℝV×d
실수 행렬 모양
"실수로 채워진 V행 d열짜리 표"라는 뜻. shape 표기입니다.
다 읽고 나면 이 세 가지에 답할 수 있어야 합니다

① 가중치는 어떻게 "학습"되며, 그 숫자들이 모델의 판단을 어떻게 결정하는가? (1·2·12장)
② 문장 하나가 GPT-2에 들어가 다음 단어 하나가 나오기까지, 데이터는 어떤 순서와 모양(shape)으로 변하는가? (5~11장)
③ 이 연산들은 왜 GPU에서 "병렬"로 처리되며, 그것이 dtype·양자화 선택과 어떤 관계인가? (3·13장)

— Part 1. 인공지능 개요 — 가장 작은 단위에서 출발합니다 —

01가중치 하나로 시작하는 신경망

한 줄 요약: 곱하고 더해서 기준을 넘으면 1. 신경망의 최소 단위는 이게 전부입니다.

왜 이걸 배우나
"신경망이 '학습한다'고 하는데, 정확히 무엇이 바뀌는 걸까?"

모델 파일 안에는 숫자가 수억 개 들어 있습니다. 그 숫자들이 왜 그 값이어야 하는지를 설명하려면, 숫자가 딱 두 개인 데서 시작하는 게 제일 빠릅니다.

이 장에서는 키와 몸무게로 어른/아이를 구분하는 신경 하나짜리 모델을 직접 조정해 보며, "가중치를 찾는다"는 말이 무슨 뜻인지 알게 됩니다.

이 장에 나오는 낯선 말
Weight (가중치) · w
입력에 곱하는 숫자. 클수록 "그 입력을 중요하게 본다"는 뜻입니다.
Bias (편향) · b
마지막에 더하는 숫자. 판단 기준선 자체를 위아래로 밀어줍니다.
역치 (threshold)
"이 값을 넘으면 1, 못 넘으면 0"의 경계선.
Neuron (신경) · Perceptron
입력들을 곱하고 더해서 하나의 출력을 내는 최소 단위.
활성화 함수 (activation)
더한 결과를 어떤 모양으로 눌러 내보낼지 정하는 함수. 여기서는 "넘으면 1"이 그 역할입니다.
학습 (training)
정답을 아는 데이터를 보여주며 w와 b를 조금씩 고쳐 나가는 과정.

비유 먼저 — 면접관의 채점표

면접관이 지원자를 볼 때 경력에 3점, 학점에 1점 하는 식으로 항목마다 배점을 둡니다. 그 배점이 weight입니다.

총점이 합격선을 넘으면 통과입니다. 그 선이 역치이고, 선 자체를 올리거나 내리는 게 bias입니다.

처음엔 배점이 엉터리일 수 있습니다. 그런데 합격/불합격 결과를 이미 아는 지원자 명단이 있다면? 틀릴 때마다 배점을 조금씩 고쳐 나갈 수 있습니다. 그게 학습입니다.

작은 숫자로 한 번

키와 몸무게를 0~1 사이 숫자로 바꿔 쓰겠습니다. 아이 데이터 하나: 키 0.45, 몸무게 0.46. 아래 첫 시도의 값들이 그대로 그림 1의 기본 상태입니다.

── 첫 시도: 가중치가 랜덤 ──
w(키) = 0.2   w(몸무게) = 0.6   역치 = 0.3
0.2 × 0.45 = 0.090
0.6 × 0.46 = 0.276
합계 = 0.366  // 0.3을 넘음
출력 = 1 (어른)  // 정답은 아이! 틀림

── 둘 다 0.5, 역치도 0.5로 바꾸면 ──
0.5 × 0.45 = 0.225
0.5 × 0.46 = 0.230
합계 = 0.455  // 0.5를 못 넘음
출력 = 0 (아이)  // 맞음 — 이게 "정답 가중치 보기" 버튼의 값입니다

여기서 중요한 건 구조는 하나도 안 바뀌었다는 점입니다. 곱하고 더하는 방식은 그대로이고, 숫자 두 개만 바뀌었습니다. GPT-2든 GPT-5든 이 문장이 그대로 성립합니다.

그림 1

어른/아이 구분기 — 가중치를 직접 찾아보기

↔ 슬라이더 + 드래그
해볼 것 — ① 슬라이더를 움직여 정답률 100%를 만들어 보세요. 파란 선(경계)이 두 무리 사이를 갈라야 합니다. ② 경계선 위의 파란 손잡이를 위아래로 끌면 역치 슬라이더가 따라 움직입니다. ③ 두 가중치를 모두 0으로 내려보세요. 어떤 데이터를 넣어도 같은 답만 나옵니다 — 학습 전 모델이 이 상태입니다.
관찰 포인트

가중치를 바꾸면 선의 기울기가, 역치를 바꾸면 선의 위치가 움직입니다.

사람이 규칙을 쓰지 않는다는 것

예전 방식은 사람이 규칙을 직접 짜는 것이었습니다 — "키 170 이상이고 몸무게 60 이상이면 어른".

신경망은 반대입니다. 입력(데이터) + 정답을 충분히 주면 모델이 규칙(최적의 가중치)을 스스로 찾아냅니다. 찾고 나면, 처음 보는 데이터에도 같은 규칙을 적용해 판단합니다.

수식으로도 보고 싶다면
y = f( w₁x₁ + w₂x₂ + b )

읽는 법 — x₁, x₂는 입력(키, 몸무게), w₁, w₂는 각각에 곱할 가중치, b는 마지막에 더하는 bias입니다. f는 활성화 함수로, 여기서는 "0보다 크면 1, 아니면 0"인 계단 함수입니다.

본문의 "역치 0.5를 넘으면 1"은 b = −0.5로 놓은 것과 같습니다. w₁x₁ + w₂x₂ − 0.5 > 0이 w₁x₁ + w₂x₂ > 0.5와 같은 말이니까요. 그래서 실제 구현에서는 역치를 따로 두지 않고 bias 하나로 흡수합니다.

입력이 n개라면   y = f( Σi=1..n wixi + b ) = f( w·x + b )

읽는 법 — Σ(시그마)는 "i를 1부터 n까지 바꿔가며 전부 더해라"입니다. 마지막의 w·x는 내적으로, 위 시그마와 똑같은 계산을 짧게 쓴 것입니다. 이 내적이 8장 Attention에서 다시 주인공으로 나옵니다.

1장 세 줄 요약
  1. 정의 — 신경 하나는 입력 × 가중치를 다 더하고 bias를 더한 뒤, 기준을 넘는지 보는 장치입니다.
  2. 성질 — 학습은 구조를 바꾸지 않습니다. 가중치라는 숫자만 고쳐 나갑니다.
  3. 쓰임 — 사람이 규칙을 쓰는 대신, 데이터 + 정답을 주면 모델이 규칙을 찾습니다.

— 여기까지: 신경 하나. 다음은 그걸 쌓았을 때 생기는 문제와 해법 —

02층을 쌓고, 오차를 되돌리기

한 줄 요약: 앞으로 계산해서 틀린 만큼을 뒤로 되돌리며 가중치를 고칩니다. 이걸 수억 번 반복합니다.

왜 이걸 배우나
"가중치를 '조금씩 고친다'는데, 어느 방향으로 얼마나 고칠지는 누가 정하나?"

1장에서는 제가 슬라이더를 직접 움직였습니다. 가중치가 2개니까 가능했습니다. 1억 개라면요? 손으로는 불가능합니다.

이 장에서는 컴퓨터가 스스로 방향을 찾는 방법인 경사하강법과 역전파를 봅니다. 이게 "학습"의 실체입니다.

이 장에 나오는 낯선 말
은닉층 (hidden layer)
입력층과 출력층 사이에 낀 층. 여기가 여러 겹이면 딥(deep)러닝입니다.
Forward pass (순전파)
입력을 넣어 앞에서 뒤로 계산해 출력을 얻는 것.
Loss (손실) · 오차
모델의 답이 정답에서 얼마나 빗나갔는지를 나타내는 숫자 하나. 작을수록 좋습니다.
Gradient (그래디언트) · 기울기
"이 가중치를 조금 키우면 오차가 늘어나나 줄어드나"를 알려주는 값. 수정 방향 화살표입니다.
Backward pass (역전파)
오차를 출력 쪽에서 입력 쪽으로 되돌리며 각 가중치의 gradient를 구하는 것.
Learning rate (학습률) · η
한 번에 얼마나 크게 고칠지. 너무 작으면 안 움직이고, 너무 크면 튕겨 나갑니다.
Epoch (에포크)
학습 데이터 전체를 한 바퀴 다 본 것을 1 에포크라고 합니다.

비유 먼저 — 안개 낀 산에서 내려오기

안개가 짙어 아무것도 안 보이는 산 중턱에 서 있습니다. 목표는 가장 낮은 곳으로 가는 것입니다. 여기서 낮은 곳 = 오차가 작은 곳입니다.

보이는 건 없지만, 발밑의 기울기는 느낄 수 있습니다. "이쪽이 내리막이네" — 그 방향으로 한 걸음 갑니다. 그리고 다시 발밑을 느낍니다. 이걸 반복하면 결국 골짜기에 도착합니다. 이게 경사하강법입니다.

보폭이 learning rate입니다. 너무 좁으면 해가 질 때까지 못 내려가고, 너무 넓으면 골짜기를 훌쩍 뛰어넘어 반대편 산비탈로 올라가 버립니다.

작은 숫자로 한 번

현재 Weight = 0.50
Gradient    = 0.20  // 양수 = 키우면 오차가 는다
학습률 η    = 0.10
──────────────────
새 Weight = 0.50 − 0.10 × 0.20 = 0.48
// gradient가 양수라 가중치를 줄이는 쪽으로 이동

── gradient가 음수라면 ──
새 Weight = 0.50 − 0.10 × (−0.20) = 0.52
// 반대로 키우는 쪽으로 이동

공식은 "현재값 − 학습률 × gradient" 하나입니다. 이 한 줄이 파라미터 1,750억 개에 똑같이 적용됩니다.

그림 2

경사하강법 — 보폭을 잘못 잡으면 생기는 일

↔ 드래그 + 슬라이더 + 재생
해볼 것 — ① 재생을 누르면 공이 골짜기로 굴러갑니다. 초록 점이 목적지(오차 최소)입니다. ② 학습률을 0.05 이하로 내려보세요. 너무 느려서 정해진 횟수 안에 도착을 못 합니다. ③ 학습률을 최대로 밀어보세요. 골짜기를 뛰어넘어 점점 튕겨 나갑니다(발산). 실제 학습에서 loss가 NaN이 되는 상황이 이겁니다.

층을 쌓으면 왜 좋아지나

신경 하나는 직선 하나만 그을 수 있습니다(그림 1에서 보셨듯이). 세상의 대부분은 직선 하나로 안 갈립니다.

그래서 신경을 여러 개, 여러 층으로 쌓습니다. 데이터는 입력층 → 은닉층 → 출력층으로 흘러가고, 층이 깊어질수록 점점 복잡한 특징을 잡아냅니다.

이미지 인식이 대표적입니다. 앞쪽 층은 선과 곡선 같은 단순한 것을 찾고, 뒤쪽 층은 그것들을 조합해 "1은 직선이 강하고 2는 곡선이 강하다" 같은 판단을 합니다. 사람이 "직선을 찾아라"라고 시킨 적이 없는데 스스로 그렇게 됩니다.

학습 루프 전체

① 데이터를 넣고 앞으로 계산 (forward pass) → 출력
② 정답과 비교해 오차(loss) 계산
③ 오차를 출력 쪽부터 거꾸로 되돌리며 각 가중치의 gradient 계산 (backward pass)
④ 모든 가중치를 w − η·gradient로 수정
⑤ ①로 돌아가서 반복

"출력에 가까운 층부터 고친다"는 게 역전파의 핵심입니다. 출력 쪽 오차를 알아야 그 앞 층이 얼마나 기여했는지 계산할 수 있기 때문입니다.

수식으로도 보고 싶다면
L = (1/N) Σ ( ŷ − y )²    ← 평균제곱오차(MSE)

읽는 법 — ŷ(와이 햇)은 모델의 예측, y는 정답입니다. 차이를 제곱하는 이유는 부호를 없애고, 크게 틀린 걸 더 크게 벌주기 위해서입니다. N은 데이터 개수, 앞의 1/N은 평균을 내는 것입니다.

w ← w − η · ∂L/∂w

읽는 법 — ←는 "왼쪽을 오른쪽 값으로 바꿔라"(대입)입니다. ∂L/∂w(라운드 L 라운드 w)는 편미분으로, "w를 아주 조금 바꾸면 L이 얼마나 변하나"를 뜻합니다. 이게 gradient입니다. η(에타)가 learning rate입니다.

앞에 마이너스가 붙는 이유 — gradient는 오차가 커지는 방향을 가리킵니다. 우리는 줄이고 싶으니 반대로 갑니다.

∂L/∂w₁ = (∂L/∂ŷ) · (∂ŷ/∂h) · (∂h/∂w₁)    ← 연쇄 법칙(chain rule)

읽는 법 — 층이 여러 겹이면 gradient를 곱셈으로 이어서 구합니다. 출력 쪽 항부터 계산해 앞으로 전달하기 때문에 역전파(backward)라고 부릅니다.

이 곱셈이 계속 이어지면서 값이 점점 작아지는 문제가 생기는데(gradient vanishing), 그걸 막는 장치가 10장의 Residual Connection입니다.

2장 세 줄 요약
  1. 정의 — 학습은 forward → loss → backward → 가중치 수정의 반복입니다.
  2. 성질 — 수정 공식은 w ← w − η · gradient 하나이고, 파라미터가 몇 개든 똑같이 적용됩니다. 학습률이 너무 크면 발산합니다.
  3. 쓰임 — 층을 깊게 쌓으면 앞쪽은 단순한 특징을, 뒤쪽은 복잡한 판단을 스스로 맡게 됩니다.

— 여기까지: 학습의 원리. 다음은 이 엄청난 양의 곱셈을 누가 감당하는가 —

03왜 GPU인가 — SIMD · SIMT · warp

한 줄 요약: 서로 상관없는 곱셈이 수십억 개 있으면, 느린 일꾼 1만 명이 빠른 일꾼 8명을 이깁니다.

왜 이걸 배우나
"딥러닝은 왜 CPU가 아니라 GPU로 하나? CPU가 더 빠르지 않나?"

코어 하나만 보면 CPU가 훨씬 빠릅니다. 그런데도 GPU를 씁니다. 이유를 알면 왜 batching이 효과적인지, 왜 dtype이 성능을 좌우하는지까지 한 번에 풀립니다.

이 장에서는 SIMD · SIMT · warp가 무엇인지, 그리고 Transformer의 어떤 성질이 GPU와 맞아떨어지는지를 봅니다.

이 장에 나오는 낯선 말
Core (코어) · ALU
실제로 계산하는 연산 유닛. CPU는 몇 개~수십 개, GPU는 수천~수만 개입니다.
Thread (쓰레드)
코어가 처리해야 할 작업 하나.
SIMD
Single Instruction Multiple Data — 하나의 명령으로 여러 데이터를 한꺼번에 처리하는 방식.
SIMT
Single Instruction Multiple Threading — 쓰레드를 묶어서 코어 그룹에 배분하는 방식. 쓰레드가 코어보다 훨씬 많을 때 씁니다.
Warp (워프)
SIMT에서 한 덩어리로 묶인 쓰레드 그룹. NVIDIA는 보통 32개(자료에 따라 128개 코어 그룹 단위로 설명하기도 합니다)입니다.
CUDA
GPU에 그래픽 말고 일반 계산을 시키는 NVIDIA의 프로그래밍 환경.
Latency hiding
한 워프가 메모리를 기다리는 동안 다른 워프를 돌려 코어를 놀리지 않는 기법.

비유 먼저 — 교수 한 명 vs 초등학생 100명

미적분 문제 하나를 풀어야 한다면? 교수 한 명이 압도적으로 빠릅니다. 초등학생 100명은 못 풉니다.

그런데 두 자리 덧셈 10만 개라면? 이야기가 완전히 뒤집힙니다. 교수는 아무리 빨라도 혼자 10만 번을 해야 하고, 초등학생 100명은 동시에 1,000개씩 나눠 합니다.

딥러닝은 정확히 후자입니다. 어려운 계산이 아니라 쉬운 곱셈과 덧셈이 어마어마하게 많은 일입니다. 그리고 결정적으로 — 서로 순서를 기다릴 필요가 없습니다.

독립적이어야 나눌 수 있습니다

GPU가 원래 그래픽용인 이유가 여기 있습니다. 화면의 픽셀 하나하나는 서로 독립적으로 계산할 수 있습니다. 옆 픽셀이 끝날 때까지 기다릴 필요가 없습니다.

Transformer의 계산도 대부분 그렇습니다. 9장에서 볼 12개의 attention head는 서로를 안 봅니다. MLP는 토큰마다 완전히 독립입니다. 그래서 통째로 나눠 던질 수 있습니다.

그림 3

CPU vs GPU — 일의 성격이 승자를 정한다

↔ 슬라이더 + 재생
해볼 것 — ① 재생을 누르고 두 줄을 비교하세요. CPU는 코어가 빠르지만 몇 개 안 됩니다. ② 작업 개수를 최소로 줄여보세요. 작업이 적으면 CPU가 이깁니다 — GPU는 나눠주는 준비 시간이 오히려 손해입니다. ③ 작업 개수를 최대로 밀어보세요. GPU가 압도합니다. 오른쪽 warp 수도 같이 보세요.

SIMD에서 SIMT, 그리고 warp까지

  1. SIMD — 명령 하나로 데이터 여러 개 코어 100개에 쓰레드를 하나씩 배정합니다. 쓰레드가 100개일 때는 깔끔합니다.
  2. 문제 — 쓰레드가 백만 개라면? 코어는 1,000개인데 쓰레드가 100만 개면, 하나씩 배정하는 방식으로는 관리가 안 됩니다.
  3. SIMT — 묶어서 배분 쓰레드도 묶고 코어도 묶어서 그룹 단위로 배치합니다. 이 쓰레드 묶음이 warp입니다.
  4. 덤 — 기다리는 시간을 채운다 워프가 메모리를 기다리면 바로 다음 워프를 실행합니다. 코어가 노는 시간이 줄어듭니다.

RTX 3090에는 코어가 1만 개 넘게 들어 있고, 128개씩 그룹으로 묶여 있습니다. 그 그룹 안에서 워프들이 차례로 처리됩니다.

이 장이 뒤에서 다시 나오는 지점

9장 — MLP는 토큰끼리 계산이 섞이지 않아 완전 병렬입니다.
13장 — 숫자를 16비트로 줄이면 같은 시간에 두 배를 실어 나릅니다. GPU에는 fp16 전용 회로(Tensor Core)도 있습니다.
한 문장으로: "독립적이면 나눌 수 있고, 작으면 더 많이 나를 수 있다."

숫자로도 보고 싶다면
행렬곱 C = A·B  (A는 M×K, B는 K×N)  →  곱셈 M·N·K 번

읽는 법 — M×K는 "M행 K열"입니다. 결과 C의 칸 하나를 채우려면 K번 곱하고 더해야 하고, 그런 칸이 M×N개 있습니다. 그래서 총 M·N·K번입니다.

GPT-2 Small의 attention 한 층만 봐도 768×768 행렬 곱셈이 여러 번 나옵니다. 768×768×768 ≈ 4.5억 번의 곱셈입니다. 이게 12개 블록 × 여러 곳에서 반복됩니다.

병렬 처리 시간 ≈ 전체 작업량 ÷ 동시에 처리 가능한 개수  +  준비 비용

읽는 법 — 뒤의 준비 비용이 핵심입니다. GPU는 일을 나눠주는 데 고정 비용이 듭니다. 그래서 작업이 적으면 CPU가 이깁니다. 그림 3에서 확인하실 수 있습니다.

3장 세 줄 요약
  1. 정의 — GPU는 느린 코어를 아주 많이 가진 장치이고, SIMD → SIMT → warp 방식으로 쓰레드를 묶어 배분합니다.
  2. 성질 — 나눌 수 있으려면 계산이 서로 독립적이어야 합니다. Transformer의 대부분이 그렇습니다.
  3. 쓰임 — 작업이 적으면 CPU가 낫습니다. 많고 독립적일수록 GPU가 압도합니다. 이 성질이 batching과 dtype 선택으로 이어집니다.

— Part 2. LLM 기초 소개 — 이제 언어 쪽으로 넘어갑니다 —

04AI에서 LLM까지, 그리고 Transformer의 자리

한 줄 요약: Transformer는 인코더와 디코더로 되어 있고, GPT는 그중 디코더만 씁니다.

왜 이걸 배우나
"AI · 머신러닝 · 딥러닝 · 생성형 AI · LLM — 이 말들은 서로 무슨 관계지?"

뉴스에서 섞여 쓰이지만 포함 관계가 있습니다. 그리고 그 맨 안쪽에 Transformer라는 구조가 있고, GPT는 그 절반만 씁니다.

이 장에서는 용어의 자리를 정리하고, BERT와 GPT가 왜 다르게 생겼는지를 알게 됩니다. 이 차이가 8·9장에서 다시 나옵니다.

이 장에 나오는 낯선 말
AI · ML · DL
인공지능 ⊃ 머신러닝 ⊃ 딥러닝. 바깥이 넓은 개념이고 안으로 갈수록 구체적입니다.
Pretraining (사전 훈련)
대량의 정답 없는 텍스트로 먼저 크게 학습하는 단계. 결과물을 베이스 모델이라 합니다.
Fine-tuning (미세 튜닝)
베이스 모델을 특정 작업에 맞춰 다듬는 추가 학습.
Encoder (인코더)
입력 텍스트를 문맥이 담긴 숫자 벡터로 바꾸는 쪽.
Decoder (디코더)
그 벡터를 받아 출력 텍스트를 만들어내는 쪽.
Self-Attention
문장 안의 토큰들이 서로에게 가중치를 매기는 장치. 8장의 주제입니다.
Autoregressive (자기회귀)
자기가 만든 출력을 다시 자기 입력으로 넣어 한 단어씩 이어가는 방식.

비유 먼저 — 통역사와 이야기꾼

인코더는 통역사의 듣는 귀입니다. 한국어 문장을 듣고 "이 말의 뜻은 이런 것"이라는 머릿속 표현으로 바꿉니다.

디코더는 말하는 입입니다. 그 머릿속 표현을 받아 영어 문장으로 뱉어냅니다. 번역기는 둘 다 필요합니다.

그런데 이야기를 이어 쓰는 일이라면요? 들을 원문이 따로 없습니다. 지금까지 쓴 것을 보고 다음을 이어가면 됩니다. 그래서 GPT는 입만 가져갔습니다 — 디코더 전용(decoder-only) 구조입니다.

그림 4

인코더 · 디코더 · 그리고 GPT가 고른 절반

↔ 버튼 3개
해볼 것 — ① Transformer(원본)에서 인코더와 디코더가 둘 다 켜져 있는 걸 보세요. 번역이 원래 용도입니다. ② BERT를 눌러보세요. 인코더만 남고, 각 토큰이 양쪽을 다 봅니다(격자가 꽉 참). ③ GPT를 눌러보세요. 디코더만 남고, 격자의 오른쪽 위가 잘립니다 — 미래를 못 보게 막은 것입니다. 8장의 Mask가 이겁니다.
관찰 포인트

구조가 다른 게 아니라 어느 쪽을 쓰고, 무엇을 가리느냐가 다릅니다.

용어의 자리

용어무엇관계
AI사람의 지능을 흉내 내는 기술 전부가장 바깥
ML (머신러닝)데이터에서 규칙을 스스로 찾는 방식AI 안쪽
DL (딥러닝)층을 깊게 쌓은 신경망 (2장)ML 안쪽
GenAI새로운 내용을 만들어내는 모델DL 안쪽(대체로)
LLM대규모 언어 모델가장 안쪽

훈련은 두 단계입니다

  1. Pretraining — 사전 훈련 인터넷 규모의 정답 없는 텍스트로 "다음 단어 맞히기"를 반복합니다. 결과물이 베이스 모델(파운데이션 모델)입니다. 여기에 돈과 시간의 대부분이 들어갑니다.
  2. Fine-tuning — 미세 튜닝 그 베이스 모델을 특정 목적(대화, 요약, 코딩)에 맞게 추가로 다듬습니다. 상대적으로 훨씬 적은 데이터와 비용으로 됩니다.
보충 — Transformer 원본 구조 (2017)

구글 논문 "Attention Is All You Need"에서 소개된 심층 신경망 구조입니다. 인코더는 입력 텍스트를 문맥 정보가 담긴 벡터로 인코딩하고, 디코더는 그 벡터를 받아 출력 텍스트를 생성합니다.

둘 다 self-attention 메커니즘으로 연결된 많은 층으로 되어 있습니다. 이 덕분에 모델이 시퀀스 안의 토큰들에 서로 다른 가중치를 부여할 수 있고, 멀리 떨어진 단어 사이의 관계까지 포착합니다.

BERT와 GPT — 훈련 방식이 다릅니다

BERT는 인코더 기반입니다. 문장 중간의 단어를 가리고 양쪽 문맥으로 맞히게 훈련합니다. 그래서 이해·분류에 강합니다.

GPT는 디코더 기반입니다. 왼쪽만 보고 다음 단어를 맞히게 훈련합니다. 그래서 생성에 강하고, 자기회귀 모델이 됩니다 — 이전 출력을 입력으로 써서 계속 이어갑니다.

수식으로도 보고 싶다면
GPT 목표:   max   Σt  log P( xt | x1, …, xt−1 )

읽는 법 — P(A | B)는 "B가 주어졌을 때 A일 확률"입니다. 즉 "앞의 토큰들이 주어졌을 때 지금 토큰이 나올 확률"을 최대로 만드는 게 목표입니다. log를 씌우는 이유는 아주 작은 확률들을 곱할 때 숫자가 0으로 뭉개지는 걸 막기 위해서입니다.

BERT 목표:   max   Σt∈mask log P( xt | x≠t )

읽는 법 — 조건 부분이 x≠t, 즉 가린 자리를 뺀 나머지 전부입니다. GPT는 앞쪽만, BERT는 양쪽 다 — 이 조건 하나가 두 모델의 성격을 갈랐습니다.

실제 loss는 여기에 마이너스를 붙인 cross entropy입니다. 최대화를 최소화 문제로 바꾸면 2장의 w ← w − η·∂L/∂w를 그대로 쓸 수 있기 때문입니다.

4장 세 줄 요약
  1. 정의 — AI ⊃ ML ⊃ DL ⊃ LLM이고, LLM의 뼈대는 2017년의 Transformer입니다.
  2. 성질 — Transformer는 인코더 + 디코더인데, BERT는 인코더만(양방향·이해), GPT는 디코더만(단방향·생성) 씁니다.
  3. 쓰임 — 훈련은 pretraining → fine-tuning 두 단계이고, 비용의 대부분은 앞 단계에 들어갑니다.

— 여기까지: 구조의 자리. 다음은 글자를 어떻게 숫자로 바꾸는가 —

05텐서와 임베딩 — 글자를 숫자로

한 줄 요약: 신경망은 글자를 못 읽습니다. 단어를 숫자 여러 개짜리 화살표로 바꿔야 합니다.

왜 이걸 배우나
"모델이 '고양이'라는 단어를 이해한다는 게, 컴퓨터 안에서는 무슨 상태를 말하는 걸까?"

1~3장에서 본 건 전부 숫자 곱셈이었습니다. 그런데 입력이 글자라면 곱할 수가 없습니다. 그래서 먼저 숫자로 바꾸는 단계가 필요합니다.

이 장에서는 텐서라는 그릇과 임베딩이라는 변환을 봅니다. 여기서 나오는 [배치, 토큰, 768] 표기가 7장부터 계속 따라다닙니다.

이 장에 나오는 낯선 말
Scalar · Vector · Matrix
숫자 하나 · 숫자를 한 줄로 늘어놓은 것 · 표 모양으로 늘어놓은 것.
Tensor (텐서)
위의 것들을 더 높은 차원으로 일반화한 것. 계산 관점에서는 다차원 데이터 그릇입니다.
Rank (랭크) · 차수
텐서의 차원 개수. 스칼라 0, 벡터 1, 행렬 2, 그 이상은 그냥 "3D 텐서"라고 부릅니다.
Shape (모양)
각 차원의 크기 목록. [2, 5, 768]처럼 씁니다.
Embedding (임베딩)
데이터를 벡터 형태로 바꾸는 것. 이 글에서는 단어 → 숫자 벡터입니다.
Vocabulary (어휘 사전)
모델이 아는 토큰의 목록. GPT-2는 50,257개입니다.
Cosine similarity
두 벡터가 같은 방향을 보는 정도. 1에 가까울수록 비슷한 뜻입니다.

비유 먼저 — 지도 위의 좌표

도시를 위도와 경도 두 숫자로 나타내면, 지도에 점을 찍을 수 있습니다. 그리고 가까운 점끼리는 실제로도 가까운 도시입니다.

임베딩은 똑같은 일을 단어에 합니다. 다만 축이 2개가 아니라 768개입니다. 그리고 이 좌표는 사람이 정한 게 아니라 학습으로 정해집니다.

학습이 잘 되면 뜻이 비슷한 단어가 가까운 자리에 놓입니다. "왕 − 남자 + 여자 ≈ 여왕" 같은 이야기가 여기서 나옵니다.

작은 숫자로 한 번

── 어휘 사전 ──
"고양이" → 토큰 ID 15496

── 임베딩 행렬에서 15496번 행을 꺼냄 ──
[0.12, -0.31, 0.82, … , 0.07]  // 숫자 768개

── shape 변화 ──
문장 토큰 10개 → [10, 768]
배치 2개까지 → [2, 10, 768]
// Block 12개를 지나도 마지막 차원은 768로 유지

임베딩 행렬은 [50257, 768]짜리 거대한 표입니다. 단어마다 한 줄씩 있고, 토큰 ID로 그 줄을 꺼내오는 것이 전부입니다. 이 표를 보통 We라고 부르고, 처음엔 랜덤한 값이었다가 학습으로 채워집니다.

그림 5

텐서의 모양과, 임베딩 공간에서의 거리

↔ 슬라이더 + 드래그
해볼 것 — ① 위쪽 rank 슬라이더를 0→3으로 밀어보세요. 스칼라 → 벡터 → 행렬 → 3D 텐서로 모양이 커집니다. ② 아래쪽 지도에서 주황 점("그것")을 끌어 보세요. 가까이 간 단어와의 cosine similarity가 1에 가까워집니다. ③ 정반대편으로 끌어보세요. 유사도가 음수가 됩니다 — 방향이 반대라는 뜻입니다.
관찰 포인트

실제 임베딩은 768차원입니다. 여기서는 2차원으로 줄여 보여줄 뿐이지만, 거리 개념은 같습니다.

보충 — 왜 768개나 되는 숫자가 필요한가

축이 2개면 "크다/작다" 같은 것 두 가지만 표현할 수 있습니다. 단어의 뜻에는 품사, 감정, 시제, 격식, 분야 … 수많은 축이 필요합니다.

768은 GPT-2 Small의 선택이고, 모델이 커질수록 늘어납니다 — GPT-3는 12,288차원입니다. 축이 많을수록 미묘한 차이를 담을 수 있지만 메모리와 계산량도 함께 늘어납니다.

수식으로도 보고 싶다면
We ∈ ℝV×d   (V = 50,257   d = 768)

읽는 법 — ∈는 "~에 속한다", ℝV×d는 "실수로 채워진 V행 d열 행렬"입니다. 즉 "임베딩 행렬은 50,257행 768열짜리 실수 표"라는 뜻입니다.

et = We[ id(t) ]  ∈ ℝd

읽는 법 — 토큰 t의 임베딩은 임베딩 행렬에서 그 토큰 ID 번째 행을 꺼낸 것입니다. 계산이 아니라 표 조회(lookup)입니다. 결과는 768개짜리 벡터 하나입니다.

cos(a, b) = (a·b) / (‖a‖ ‖b‖)

읽는 법 — a·b는 내적, ‖a‖(양쪽 두 줄)는 그냥 길이입니다. 길이로 나누는 이유는 크기를 빼고 방향만 보기 위해서입니다. 결과는 −1에서 1 사이이고, 1이면 같은 방향, 0이면 무관, −1이면 정반대입니다.

참고로 내적은 이렇게 계산합니다. 8장 Attention의 핵심이 되니 한 번 봐두면 좋습니다.

a·b = a₁b₁ + a₂b₂ + … + adbd = Σi aibi
5장 세 줄 요약
  1. 정의 — 텐서는 다차원 숫자 그릇이고, 임베딩은 단어를 숫자 벡터로 바꾸는 것입니다.
  2. 성질 — 임베딩은 계산이 아니라 [50257, 768] 표에서 한 줄을 꺼내는 조회입니다. 그 표의 값은 학습으로 채워집니다.
  3. 쓰임 — 이후 모든 단계의 데이터 모양은 [배치, 토큰, 768]이고, 12개 블록을 지나도 마지막 차원은 유지됩니다.

— Part 3. GPT-2 Small 소개 — 이제 실제 모델 하나를 통째로 뜯어봅니다 —

06GPT-2 Small 해부 — 124M은 어디서 나오나

한 줄 요약: 파라미터 1억 2천만 개는 마법이 아니라, 여섯 종류 부품의 덧셈입니다.

왜 이걸 배우나
"124M 파라미터라는데, 그 숫자들이 어디에 몇 개씩 들어 있는 걸까?"

"파라미터가 많다 = 똑똑하다"로만 알고 넘어가면, 나중에 hidden_size나 n_layer를 만났을 때 무엇이 얼마나 늘어나는지 감이 안 옵니다.

이 장에서는 GPT-2 Small의 스펙을 놓고 124,439,808개를 직접 더해봅니다. 한 번 맞춰보면 모델 크기에 대한 감각이 완전히 달라집니다.

이 장에 나오는 낯선 말
Context length (문맥 길이)
한 번에 다룰 수 있는 최대 토큰 수. GPT-2 Small은 1,024입니다.
Embedding dim (임베딩 차원) · d
토큰 하나를 표현하는 숫자의 개수. GPT-2 Small은 768입니다.
Transformer Block
Attention + MLP 한 세트. GPT-2 Small에는 12개가 쌓여 있습니다.
Attention Head
Attention을 여러 갈래로 나눠 계산하는 단위. 블록당 12개입니다.
FFN / MLP
블록 안에서 토큰마다 정보를 한 번 더 가공하는 작은 신경망. 중간 차원 3,072입니다.
Weight tying
입력 임베딩과 출력층이 같은 가중치를 공유하는 기법. 파라미터를 크게 아낍니다.
Logits (로짓)
어휘 하나하나에 대한 날것의 점수. 확률로 바꾸려면 softmax를 씌웁니다.

비유 먼저 — 아파트 한 동의 설계

아파트 한 층의 구조가 정해져 있고, 그게 12층 쌓여 있다고 해봅시다. 총 세대수는 층당 세대수 × 12 + 1층 로비입니다.

GPT-2도 같습니다. 블록 하나의 파라미터 수가 정해져 있고, 그게 12번 반복됩니다. 거기에 로비에 해당하는 임베딩이 따로 붙습니다.

재미있는 건 로비가 생각보다 크다는 점입니다 — 어휘가 5만 개나 되기 때문에 임베딩만으로 전체의 약 31%를 차지합니다.

스펙 먼저

항목GPT-2 Small뜻
vocab size50,257어휘 사전 크기
context length1,024최대 입력 토큰 수
embedding dim768토큰 하나 = 숫자 768개
layers (Blocks)12Transformer Block 12개
attention heads12블록당 12갈래 (768 ÷ 12 = 64)
FFN dim3,072768 → 3,072 → 768 (4배로 넓혔다 줄임)
파라미터124,439,808약 1.24억 개

참고로 형제 모델들은 이렇습니다 — Medium 355M(24블록·1024차원), Large 774M(36블록·1280차원), XL 1.5B(48블록·1600차원). 블록 수와 차원만 키운 것이지 구조는 같습니다.

작은 숫자로 한 번 — 124,439,808 맞춰보기

── 임베딩 ──
토큰 임베딩 = 50,257 × 768 = 38,597,376
위치 임베딩 = 1,024 × 768 =    786,432

── 블록 1개 ──
Q·K·V  = 768 × (3×768) + 2,304 = 1,771,776
출력 투영 = 768 × 768 + 768       =   590,592
MLP 확장 = 768 × 3,072 + 3,072   = 2,362,368
MLP 축소 = 3,072 × 768 + 768     = 2,360,064
LayerNorm × 2 = 2 × (2 × 768)     =     3,072
블록 합계 = 7,087,872

── 전체 ──
블록 12개 = 7,087,872 × 12 = 85,054,464
마지막 LayerNorm = 1,536
──────────────────
38,597,376 + 786,432 + 85,054,464 + 1,536
= 124,439,808  // 정확히 일치!

출력층(50,257개 점수를 만드는 층)이 안 보이는 이유는 토큰 임베딩과 가중치를 공유하기 때문입니다(weight tying). 안 그랬으면 3,860만 개가 더 붙었을 겁니다.

그림 6

파라미터 계산기 — 무엇을 키우면 무엇이 늘어나나

↔ 슬라이더 4개
해볼 것 — ① 기본값(768 / 12 / 50257 / 1024)이 GPT-2 Small입니다. 총합이 124,439,808인지 확인해 보세요. ② 차원 d만 두 배로 밀어보세요. 파라미터는 두 배가 아니라 거의 네 배가 됩니다 — 블록 안의 항들이 대부분 d의 제곱이기 때문입니다. ③ 블록 수만 최대로 밀어보세요. 임베딩 비중이 확 줄고 블록이 전부를 차지합니다. 큰 모델의 모양이 왜 그런지 보입니다.
이 숫자들은 전부 무엇인가

124,439,808개는 대부분 이런 것들입니다 — 토큰 임베딩 Weight, Query Weight, Key Weight, Value Weight, Attention 출력 Weight, FFN Weight, LayerNorm의 Weight와 Bias.

이 수많은 숫자가 학습되면서 언어의 패턴을 저장합니다. 비교하자면 GPT-3는 1,750억 개(96블록 · 12,288차원 · 헤드 96개 · context 2,048)입니다. 구조는 같고 규모만 다릅니다.

context length가 메모리에 미치는 영향

context length를 줄이면 위치 임베딩과 Attention 계산에 필요한 메모리도 줄어듭니다. 파라미터 수에서 위치 임베딩이 차지하는 몫은 작지만(78만 개), Attention은 길이의 제곱으로 커지기 때문에 실행 중 메모리는 크게 달라집니다.

참고로 토큰은 단어 하나와 같지 않습니다. 영어 단어 하나가 여러 토큰으로 쪼개질 수 있고, 공백이나 문장부호도 토큰의 일부가 됩니다.

수식으로도 보고 싶다면
Pblock = 12d² + 13d     (d = 임베딩 차원, FFN 확장 배수 4 기준)

읽는 법 — 블록 하나의 파라미터 수입니다. 어디서 12d²가 나오는지 보면 — Q·K·V가 3d², attention 출력이 d², MLP가 4d² + 4d² = 8d². 합쳐서 12d²입니다. 뒤의 13d는 bias와 LayerNorm 같은 자잘한 항들입니다.

d = 768을 넣으면 12×768² + 13×768 = 7,077,888 + 9,984 = 7,087,872 — 본문 계산과 정확히 같습니다.

Ptotal = V·d  +  C·d  +  L·(12d² + 13d)  +  2d

읽는 법 — V는 vocab size, C는 context length, L은 블록 수입니다. 순서대로 토큰 임베딩 · 위치 임베딩 · 블록 12개 · 마지막 LayerNorm입니다.

여기서 중요한 성질 하나 — d는 제곱으로, L은 1차로 들어갑니다. 그래서 차원을 두 배 키우면 블록 부분이 네 배가 되고, 블록 수를 두 배 키우면 두 배가 됩니다. 그림 6에서 이걸 직접 확인하실 수 있습니다.

6장 세 줄 요약
  1. 정의 — GPT-2 Small은 vocab 50,257 · d 768 · 블록 12 · 헤드 12 · FFN 3,072이고 파라미터는 124,439,808개입니다.
  2. 성질 — 파라미터는 임베딩 + 블록 × 12의 덧셈이며, 블록 부분은 차원의 제곱에 비례합니다.
  3. 쓰임 — 출력층은 토큰 임베딩과 가중치를 공유(weight tying)해서 3,860만 개를 아꼈습니다.

— Part 4. Transformer Explainer — 여기서부터는 문장 하나가 지나가는 길을 순서대로 따라갑니다 —

07입력 만들기 — 토큰 임베딩 + 위치 인코딩

한 줄 요약: Attention은 순서를 모릅니다. 그래서 "몇 번째인지"를 숫자로 따로 더해줘야 합니다.

왜 이걸 배우나
"'개가 사람을 물었다'와 '사람이 개를 물었다'는 단어가 똑같은데, 모델은 어떻게 구분하지?"

5장에서 단어를 벡터로 바꿨습니다. 그런데 순서 정보가 어디에도 없습니다. Attention은 모든 토큰을 한꺼번에 보기 때문에(1장 회의실 비유), 가만두면 단어 순서를 완전히 무시합니다.

이 장에서는 순서를 어떻게 숫자로 심어 넣는지를 봅니다. 답은 놀라울 만큼 단순합니다 — 그냥 더합니다.

이 장에 나오는 낯선 말
Tokenization (토큰화)
글자를 토큰으로 쪼개고 ID를 붙이는 첫 단계.
Token Embedding
토큰 ID로 임베딩 표에서 벡터를 꺼내는 것 (5장).
Positional Encoding (위치 인코딩)
"이 토큰이 몇 번째인지"를 나타내는 벡터. 토큰 임베딩에 더해서 씁니다.
Learned vs Sinusoidal
위치 벡터를 학습으로 채우는 방식(GPT-2)과 sin/cos 공식으로 만드는 방식(원본 Transformer).
Input Embedding
토큰 임베딩 + 위치 인코딩을 합친 블록에 들어갈 최종 입력.

비유 먼저 — 좌석 번호가 적힌 명찰

회의실에 사람들이 앉아 있습니다. 각자 이름표를 달고 있죠 — 그게 토큰 임베딩입니다.

그런데 회의록을 나중에 읽는 사람은 누가 몇 번째로 말했는지를 모릅니다. 그래서 이름표 옆에 좌석 번호를 같이 적습니다. 그게 위치 인코딩입니다.

재미있는 건 따로 붙이는 게 아니라 이름표에 덧칠한다는 점입니다. 같은 "고양이"라도 1번 자리에 앉으면 조금 다른 벡터, 5번 자리에 앉으면 또 조금 다른 벡터가 됩니다.

작은 숫자로 한 번

── "The weather is" 라는 입력 ──
토큰화    → ["The", " weather", " is"]
토큰 ID   → [464, 6193, 318]

── 토큰 임베딩 (표에서 꺼냄) ──
"The"     → [ 0.12, -0.31, 0.82, …] // 768개

── 위치 인코딩 (0번 자리) ──
pos 0     → [ 0.00,  1.00, 0.00, …]
──────────────────
입력 벡터 → [ 0.12,  0.69, 0.82, …] // 그냥 더함

최종 shape = [배치, 3, 768]

"더한다"가 이상하게 느껴질 수 있습니다. 뜻과 위치가 섞여버리지 않나? 실제로 섞이지만, 768차원은 충분히 넓어서 모델이 둘을 분리해 읽는 법을 학습합니다.

그림 7

위치 인코딩 — 자리마다 다른 무늬

↔ 슬라이더 + 클릭
해볼 것 — ① 위쪽 열지도에서 가로줄 하나가 한 자리(position)입니다. 줄마다 무늬가 다릅니다 — 그래서 위치를 구분할 수 있습니다. ② 열지도의 줄 하나를 클릭하면 아래에 그 위치의 벡터가 펼쳐집니다. 왼쪽 칸일수록 빠르게, 오른쪽으로 갈수록 느리게 진동합니다. ③ "차원 수"를 줄여보세요. 무늬가 단순해져서 가까운 자리끼리 구분이 어려워집니다.
관찰 포인트

GPT-2는 이 무늬를 공식이 아니라 학습으로 얻습니다. 여기서는 원본 Transformer의 sin/cos 방식을 보여줍니다.

GPT-2는 위치 벡터도 "학습"합니다

원본 Transformer는 sin/cos 공식으로 위치 벡터를 만들었습니다(위 그림). GPT-2는 다릅니다 — [1024, 768]짜리 위치 임베딩 표를 따로 두고, 토큰 임베딩처럼 학습으로 채웁니다.

6장에서 계산한 786,432개(1,024 × 768)가 바로 이겁니다. 학습 전에는 아무 의미 없는 랜덤 값이었다가, 학습이 끝나면 가까운 자리끼리 비슷한 벡터가 되어 있습니다.

수식으로도 보고 싶다면
xt = We[ id(t) ]  +  Wp[ pos(t) ]

읽는 법 — We는 토큰 임베딩 표 [50257, 768], Wp는 위치 임베딩 표 [1024, 768]입니다. 토큰 ID로 한 줄, 위치 번호로 한 줄을 꺼내서 그냥 더합니다. 이게 GPT-2 방식입니다.

원본 Transformer의 sin/cos 방식은 이렇습니다.

PE(pos, 2i)  = sin( pos / 100002i/d )
PE(pos, 2i+1) = cos( pos / 100002i/d )

읽는 법 — pos는 몇 번째 자리인지, i는 벡터 안에서 몇 번째 칸인지입니다. 짝수 칸에는 sin, 홀수 칸에는 cos을 넣습니다.

100002i/d로 나누는 게 핵심입니다. i가 작으면(왼쪽 칸) 나누는 수가 작아서 빠르게 진동하고, i가 크면(오른쪽 칸) 나누는 수가 커져서 아주 천천히 진동합니다. 시계의 초침·분침·시침처럼 서로 다른 주기를 겹쳐 위치를 유일하게 표현하는 것입니다.

이 방식의 장점은 학습 때 본 적 없는 긴 위치에도 값을 만들 수 있다는 점입니다. GPT-2의 학습 방식은 그게 안 되는 대신 데이터에 맞는 무늬를 스스로 찾습니다.

7장 세 줄 요약
  1. 정의 — 블록에 들어갈 입력은 토큰 임베딩 + 위치 인코딩입니다. 말 그대로 더합니다.
  2. 성질 — Attention은 순서를 모르기 때문에 위치를 따로 심어줘야 합니다. 자리마다 다른 무늬여야 구분됩니다.
  3. 쓰임 — 원본은 sin/cos 공식, GPT-2는 [1024, 768] 학습된 표를 씁니다. shape는 [배치, 토큰, 768]로 확정됩니다.

— 여기까지: 입력 준비 완료. 다음은 이 글의 심장 —

08Self-Attention — Q·K·V와 내적

한 줄 요약: "내가 찾는 것"과 "네가 가진 표식"을 내적해서, 누구를 얼마나 볼지 정합니다.

왜 이걸 배우나
"'눈이 내린다'와 '눈이 아프다' — 같은 '눈'인데 모델은 어떻게 다르게 이해하지?"

단어 하나의 임베딩만으로는 알 수 없습니다. 주변 단어를 봐야 합니다. 그 "보는 일"을 하는 장치가 self-attention입니다.

이 장에서는 Q·K·V가 각각 무엇이고, 왜 하필 내적으로 관계를 재는지, 그리고 Mask와 Softmax가 무슨 일을 하는지를 작은 숫자로 직접 따라가 봅니다. 이 글에서 제일 중요한 장입니다.

이 장에 나오는 낯선 말
Query (Q) · 쿼리
"내가 지금 찾고 있는 것"을 나타내는 벡터. 검색창에 친 말과 비슷합니다.
Key (K) · 키
"나는 이런 걸 갖고 있다"는 표식 벡터. 검색 결과의 제목과 비슷합니다.
Value (V) · 값
"내가 실제로 넘겨줄 내용" 벡터. 검색 결과의 본문과 비슷합니다.
Attention Score
Q와 K를 내적해서 나온 날것의 관련도 점수.
Scaling · √dk
점수를 헤드 차원의 제곱근으로 나누는 것. 값이 너무 커지는 걸 막습니다.
Causal Mask
미래 토큰의 점수에 아주 큰 음수를 더해 못 보게 막는 장치.
Softmax
점수들을 전부 양수로 만들고 합이 1이 되게 바꾸는 함수.
Attention Weight
softmax를 거친 최종 비율. "이 토큰을 몇 퍼센트 참고할지"입니다.

비유 먼저 — 도서관 사서

도서관에서 "고양이 기르는 법"을 찾는다고 해봅시다. 이게 Query입니다.

책장마다 책등에 제목이 적혀 있습니다. 이게 Key입니다. 내 질문과 책등을 맞춰보면 어떤 책이 관련 있는지 점수가 매겨집니다 — 이게 내적입니다.

그리고 실제로 펼쳐서 읽는 내용이 Value입니다. 관련도 높은 책은 많이, 낮은 책은 조금 참고해서 섞은 결과가 그 토큰의 새 표현이 됩니다.

Self-attention의 "self"는 Q도 K도 V도 전부 같은 문장에서 나온다는 뜻입니다. 문장이 스스로에게 질문하고 스스로 답하는 셈입니다.

왜 하필 내적인가

두 벡터의 같은 자리끼리 곱해서 전부 더하면, 두 벡터가 얼마나 같은 방향을 보는지가 숫자 하나로 나옵니다.

같은 방향이면 곱이 전부 양수라 합이 커지고, 반대 방향이면 음수가 섞여 합이 작아집니다. 즉 유사한 정보끼리는 값이 커지고 다른 정보는 줄어듭니다. 이 성질 하나로 "얼마나 연관되었나"를 잽니다.

작은 숫자로 한 번

토큰 3개, 차원 4개짜리 아주 작은 예로 해보겠습니다. 지금 2번 토큰을 처리하는 중입니다.

Q₂ = [1.0, 0.5, -0.2, 0.3]  // 내가 찾는 것

K₁ = [0.9, 0.4, -0.1, 0.2] → 내적 = 1.18
K₂ = [0.2, 0.1,  0.9, 0.0] → 내적 = 0.07
K₃ = [0.1, 0.0,  0.2, 0.1] → 내적 = 0.09 // 미래 토큰!

── ÷ √4 = ÷2 (스케일링) ──
[0.59, 0.035, 0.045]

── Mask: 미래는 -∞ ──
[0.59, 0.035, -∞]

── Softmax ──
[0.635, 0.365, 0.000]  // 합이 1

출력 = 0.635×V₁ + 0.365×V₂ + 0×V₃

K₁과의 내적이 제일 큰 이유는 Q₂와 방향이 거의 같기 때문입니다. 그래서 1번 토큰의 내용을 63.5%만큼 가져옵니다. 미래인 3번은 완전히 0입니다.

그림 8

Attention 한 스텝 — 내적에서 출력까지 전부 손으로

↔ 슬라이더 + 토글
해볼 것 — ① Q 슬라이더 네 개를 움직여 보세요. 어떤 K와 방향이 맞으면 그 막대가 길어지고 최종 비율도 커집니다. ② Mask 끄기를 눌러보세요. 미래 토큰(K₃)에도 비율이 생깁니다 — BERT가 이 상태입니다(4장). ③ 스케일링 끄기를 눌러보세요. 점수 차이가 두 배로 벌어져 1등 쪽으로 더 쏠립니다(63.5% → 75.2%). 실제 모델은 dk가 64라 이 효과가 훨씬 커집니다 — √d로 나누는 이유입니다.

Mask — 미래를 가리는 방법

GPT는 다음 단어를 맞히는 훈련을 합니다. 그런데 "나는 오늘 학교에 갔다"를 학습할 때 "오늘"을 처리하면서 "학교에 갔다"를 보면 답을 미리 본 셈이라 훈련이 안 됩니다.

그래서 각 토큰이 자기와 그 앞만 보게 막습니다.

토큰 1 → 토큰 1만 참고
토큰 2 → 토큰 1~2 참고
토큰 3 → 토큰 1~3 참고
토큰 4 → 토큰 1~4 참고

구현은 의외로 단순합니다. 내적은 그대로 하고, 미래 자리의 점수에 아주 큰 음수를 더합니다. 그러면 다음 단계의 softmax에서 exp(아주 큰 음수) ≈ 0이 되어 비율이 0이 됩니다.

Softmax — 점수를 비율로

내적 결과는 음수도 있고 크기도 제각각입니다. 이대로는 "몇 퍼센트 참고할지"로 쓸 수 없습니다.

softmax는 두 가지를 합니다 — 전부 양수로 만들고(exp), 합이 1이 되게 나눕니다. 그러면 그대로 비율이 됩니다.

수식으로도 보고 싶다면
Q = XWQ + bQ    K = XWK + bK    V = XWV + bV

읽는 법 — X는 7장에서 만든 입력 [토큰 수, 768]입니다. 여기에 서로 다른 가중치 세 개를 곱해서 Q·K·V를 만듭니다. 같은 입력에서 나오지만 가중치가 달라서 다른 벡터가 됩니다. 이 가중치들이 학습 대상입니다.

Attention(Q, K, V) = softmax( ( QKᵀ / √dk ) + M ) V

읽는 법 — 하나씩 뜯어보면 —

· QKᵀ — Q와 K의 전치를 곱합니다. 결과는 [토큰 수, 토큰 수] 정사각 행렬이고, i행 j열이 "토큰 i가 토큰 j를 볼 점수"입니다. 이게 본문의 내적 표를 한 번에 계산한 것입니다.

· ÷ √dk — dk는 헤드 하나의 차원(GPT-2 Small은 64)입니다. 차원이 크면 내적 값도 자연히 커지는데, 그대로 두면 softmax가 한 곳으로만 쏠립니다. √64 = 8로 나눠서 적당한 크기로 맞춥니다. 그림 8에서 직접 확인해 보세요.

· + M — M은 mask 행렬입니다. 볼 수 있는 자리는 0, 미래 자리는 −∞(실제로는 −10⁹ 같은 큰 음수)를 더합니다.

· softmax(…)V — 비율을 구한 뒤 V의 가중합을 냅니다.

softmax(z)i = exp(zi) / Σj exp(zj)

읽는 법 — 각 값에 exp를 씌우고(전부 양수가 됨), 전부 더한 값으로 나눕니다(합이 1이 됨).

여기서 mask가 왜 −∞인지 보입니다 — exp(−∞) = 0이라 분자가 0이 되고, 그래서 비율이 정확히 0이 됩니다.

계산량: QKᵀ 는  L × L × dk  번의 곱셈  →  O(L²d)

읽는 법 — L은 토큰 수입니다. 모든 토큰 쌍을 봐야 하므로 L의 제곱입니다. 토큰이 2배가 되면 계산은 4배가 됩니다. LLM 서빙에서 긴 프롬프트가 비싼 이유가 이것입니다.

8장 세 줄 요약
  1. 정의 — 각 토큰에서 Q·K·V를 만들고, Q와 K의 내적으로 관련도를 잰 뒤 V를 그 비율로 섞습니다.
  2. 성질 — 점수는 √dk로 나누고, 미래 자리에는 큰 음수(mask)를 더한 뒤 softmax로 비율을 만듭니다.
  3. 쓰임 — 모든 토큰 쌍을 보므로 계산은 길이의 제곱으로 커집니다. 이게 긴 입력이 비싼 근본 이유입니다.

— 여기까지: Attention 한 갈래. 다음은 그걸 12갈래로 나누고, 그 뒤에 붙는 것 —

09Multi-Head와 MLP — 나눠 보고, 넓혔다 줄이기

한 줄 요약: 같은 문장을 12가지 관점으로 동시에 보고, 그다음 토큰마다 따로 정리합니다.

왜 이걸 배우나
"Attention을 왜 한 번만 하지 않고 12번이나 하지? 그리고 그 뒤의 MLP는 뭘 하는 걸까?"

8장의 attention은 한 가지 기준으로만 관계를 봅니다. 그런데 언어에는 문법 관계, 의미 관계, 위치 관계가 동시에 있습니다.

이 장에서는 왜 여러 갈래로 나누는지, 그리고 attention 뒤에 붙는 MLP가 왜 필요한지를 봅니다. 참고로 GPT-3에서는 전체 파라미터의 3분의 2가 MLP에 있습니다.

이 장에 나오는 낯선 말
Multi-Head Attention
Attention을 여러 갈래(head)로 나눠 동시에 계산하는 것.
Head dim · dk
헤드 하나가 담당하는 차원 수. GPT-2 Small은 768 ÷ 12 = 64입니다.
Concat (연결)
여러 헤드의 결과를 옆으로 이어붙이는 것. 64 × 12 = 768로 원래 크기가 됩니다.
MLP / FFN
Multi-Layer Perceptron / Feed-Forward Network. 토큰마다 독립적으로 정보를 가공하는 작은 신경망.
GELU
Gaussian Error Linear Unit. 부드러운 활성화 함수. GPT-2가 ReLU 대신 씁니다.
ReLU
음수는 전부 0, 양수는 그대로. 꺾인 직선 모양입니다.

비유 먼저 — 12명의 검토자

계약서 하나를 검토한다고 해봅시다. 법무 담당은 조항의 위험을 보고, 재무 담당은 금액을, 영업 담당은 일정을 봅니다.

혼자 다 보는 것보다 12명이 각자 관점으로 동시에 보고 나중에 의견을 합치는 편이 낫습니다. 그게 multi-head입니다.

다만 각자에게 계약서 전체를 다 주지는 않습니다. 768개 정보를 12명이 64개씩 나눠 맡습니다. 그래서 총량은 그대로입니다.

그리고 MLP는 그 회의가 끝난 뒤 각자 자기 자리에서 쓰는 정리 노트입니다. 다른 사람과 상의하지 않고 혼자 정리합니다 — 그래서 완전히 병렬로 돌릴 수 있습니다(3장).

작은 숫자로 한 번

── Multi-Head 나누기 ──
전체 차원 768 ÷ 헤드 12 = 64
헤드 하나가 64차원 담당
12 × 64 = 768  // 다시 합치면 원래 크기

── MLP 통과 ──
768 → 3,072 → 768
3,072 = 768 × 4  // 4배로 넓혔다가 되돌림

── 파라미터 비교 (블록 1개) ──
Attention 관련 ≈ 2.36M
MLP 관련      ≈ 4.72M  // 두 배

"넓혔다 줄인다"가 이상하게 느껴질 수 있습니다. 넓은 작업 공간에서 여러 특징을 찾아낸 뒤, 다시 768개로 정리하는 과정이라고 보시면 됩니다. 이 넓은 공간이 사실 정보를 담아두는 곳이기도 합니다.

그림 9

헤드 나누기와 MLP — 그리고 GELU vs ReLU

↔ 슬라이더 + 토글
해볼 것 — ① 헤드 수를 바꿔보세요. 768 ÷ 헤드 수가 헤드 차원이 됩니다. 나눠떨어지지 않으면 경고가 뜹니다. ② 헤드를 1개로 줄여보세요. 한 관점으로만 보게 됩니다. 12개로 늘리면 총 차원은 그대로인데 관점만 늘어납니다. ③ 아래 GELU / ReLU를 번갈아 눌러보세요. ReLU는 0에서 꺾이고, GELU는 부드럽게 휩니다 — 음수 쪽 기울기가 0이 되지 않습니다.
관찰 포인트

헤드를 늘려도 총 파라미터는 거의 같습니다. 나누는 방식만 달라집니다.

헤드들은 무엇을 나눠 보나

개념적으로는 이런 식으로 이해할 수 있습니다.

Head 1: 가까운 단어 관계
Head 2: 주어와 동사 관계
Head 3: 대명사가 가리키는 대상
Head 4: 문장 위치 관계

다만 실제 헤드의 역할이 이렇게 딱 나뉘지는 않습니다. 사람이 지정한 게 아니라 학습으로 알아서 갈라진 것이라, 하나의 헤드가 여러 역할을 섞어 하는 경우가 대부분입니다.

12개 헤드의 결과는 각각 [토큰, 64]이고, 이들을 옆으로 이어붙여 [토큰, 768]로 만든 뒤 출력 가중치 WO를 한 번 더 곱해 섞습니다.

Attention과 MLP는 하는 일이 다릅니다

Attention — 다른 단어와의 관계를 보는 단계입니다. 토큰끼리 정보를 주고받습니다.

MLP — 각 단어 자체의 의미를 더 잘 정리하고 변환하는 단계입니다. 토큰끼리 정보를 주고받지 않습니다. 그래서 완전히 병렬로 계산됩니다.

GPT-3에서는 전체 파라미터의 약 2/3가 MLP에 있습니다. "사실 정보를 담아두는 공간"은 attention보다 MLP 쪽이 더 크다는 뜻이기도 합니다.

왜 ReLU가 아니라 GELU인가

ReLU는 음수를 전부 0으로 만듭니다. 그러면 그 자리의 gradient도 0이 되어 2장의 역전파에서 학습 신호가 끊깁니다.

GELU는 부드러운 곡선이라 음수 쪽에서도 gradient가 완전히 0이 되지 않습니다. 덕분에 최적화가 더 잘 되고, 파라미터를 미묘하게 조절할 수 있습니다.

수식으로도 보고 싶다면
headi = Attention( XWQ(i), XWK(i), XWV(i) )

읽는 법 — 헤드마다 자기만의 WQ, WK, WV를 갖습니다. 같은 입력 X에서 출발하지만 가중치가 달라서 다른 관점이 됩니다.

MultiHead(X) = Concat( head₁, …, headh ) WO

읽는 법 — Concat은 옆으로 이어붙이기입니다. [토큰, 64]짜리 12개를 붙이면 [토큰, 768]이 되고, 마지막에 WO(768×768)를 곱해 헤드들의 결과를 섞어줍니다.

dk = d / h   →   768 / 12 = 64

읽는 법 — 헤드를 늘리면 헤드 차원이 줄어듭니다. 총량은 보존됩니다. 그래서 헤드 수를 바꿔도 파라미터는 거의 안 변합니다.

FFN(x) = W₂ · GELU( W₁x + b₁ ) + b₂

읽는 법 — W₁은 768 → 3,072로 넓히는 행렬, W₂는 3,072 → 768로 줄이는 행렬입니다. 가운데 GELU가 비선형을 넣어줍니다 — 이게 없으면 두 번 곱한 게 한 번 곱한 것과 같아져서 층을 쌓는 의미가 사라집니다.

GELU(x) ≈ 0.5x · ( 1 + tanh[ √(2/π) · ( x + 0.044715x³ ) ] )

읽는 법 — 외울 필요는 전혀 없습니다. 형태만 보시면 됩니다. x가 크면 거의 x 그대로, x가 아주 작은 음수면 거의 0, 그 사이는 부드럽게 이어집니다. ReLU를 매끄럽게 만든 버전이라고 보면 됩니다.

ReLU(x) = max(0, x)

비교용입니다. 0에서 뾰족하게 꺾입니다. 그림 9에서 두 곡선을 겹쳐 보실 수 있습니다.

9장 세 줄 요약
  1. 정의 — Multi-Head는 768차원을 12 × 64로 나눠 각기 다른 관점으로 attention을 계산한 뒤 다시 이어붙입니다.
  2. 성질 — MLP는 768 → 3,072 → 768로 넓혔다 줄이며, 토큰끼리 섞이지 않아 완전 병렬입니다. 파라미터의 대부분이 여기 있습니다.
  3. 쓰임 — 활성화 함수로 GELU를 쓰는 이유는 음수 쪽 gradient가 0이 되지 않아 학습이 잘 되기 때문입니다.

— 여기까지: 블록의 두 주역. 다음은 그 블록을 12층 쌓기 위해 필요한 장치들 —

10Residual · LayerNorm · Dropout — 깊게 쌓기 위한 장치

한 줄 요약: 층을 깊게 쌓으면 정보와 학습 신호가 사라집니다. 세 가지 장치가 그걸 막습니다.

왜 이걸 배우나
"블록을 12개나 통과하면, 처음 입력한 정보는 남아 있을까?"

매 층에서 벡터를 완전히 새로 만들면 원래 정보가 씻겨 나갑니다. 그리고 2장에서 본 역전파의 곱셈이 12번 이어지면 gradient가 0에 수렴합니다.

이 장에서는 Transformer가 이 두 문제를 어떻게 푸는지, 그리고 Dropout이 왜 필요한지를 봅니다. 겉보기엔 사소한 장치들인데 없으면 안 됩니다.

이 장에 나오는 낯선 말
Residual Connection (잔차 연결)
계산 결과에 원래 입력을 다시 더해주는 연결. skip connection · shortcut connection이라고도 합니다.
Gradient vanishing (그래디언트 소실)
역전파가 층을 거듭할수록 gradient가 점점 작아져 앞쪽 층이 학습되지 않는 현상.
Layer Normalization (LayerNorm)
벡터 안의 숫자들을 평균 0, 퍼짐 1 정도로 정리하는 것.
μ (평균) · σ (표준편차)
숫자들의 중심과 퍼진 정도.
γ · β (감마 · 베타)
정규화 후 다시 크기와 위치를 조절하는 학습 파라미터. LayerNorm의 weight와 bias입니다.
Dropout
학습할 때 일부 신호를 무작위로 잠깐 꺼버리는 기법.
Overfitting (과적합)
학습 데이터에는 잘 맞는데 새 데이터에서는 틀리는 상태.

비유 먼저 — 원본을 놓지 않는 편집

Residual은 문서 편집과 비슷합니다. 교정자가 12명 있는데, 각자 앞 사람 원고를 완전히 새로 쓴다면 마지막에는 원문이 남아 있지 않습니다.

대신 이렇게 하면 어떨까요 — "원문 + 내가 고칠 부분"만 넘깁니다. 그러면 12명을 거쳐도 원문이 살아 있고, 각자는 차이(잔차)만 만들면 됩니다.

LayerNorm은 글자 크기를 맞추는 일입니다. 어떤 교정자가 유난히 큰 글씨로 쓰면 다음 사람이 읽기 힘듭니다. 넘기기 전에 크기를 표준으로 맞춰줍니다.

Dropout은 가림막 연습입니다. 일부러 몇 사람을 빼고 작업시켜 보면, 특정 한 명에게만 의존하지 않는 팀이 됩니다.

작은 숫자로 한 번

── Residual ──
x = [1.0, 2.0, 3.0]  // 블록 입력
Attention(x) = [0.1, -0.3, 0.2]
출력 = x + Attention(x) = [1.1, 1.7, 3.2]
// 새 정보를 얹었을 뿐, 원래 값이 살아 있음

── LayerNorm ──
x = [1.0, 2.0, 3.0]
평균 μ = 2.0   표준편차 σ ≈ 0.816
(1.0−2.0)/0.816 = -1.22
(2.0−2.0)/0.816 =  0.00
(3.0−2.0)/0.816 =  1.22
결과 = [-1.22, 0.00, 1.22] // 평균 0, 퍼짐 1

LayerNorm은 토큰 하나의 768개 숫자 안에서 계산합니다. 다른 토큰이나 다른 배치와는 상관없습니다 — 그래서 이름이 "Layer"Norm입니다.

그림 10

층을 깊게 쌓으면 — 신호와 gradient는 어떻게 되나

↔ 슬라이더 + 토글 2개
해볼 것 — ① 아무것도 안 켠 상태에서 층 수를 12까지 올려보세요. 파란 선(신호)과 자홍 선(gradient)이 바닥으로 가라앉습니다. ② Residual을 켜보세요. 두 선이 살아남습니다. 이게 12층을 쌓을 수 있는 이유입니다. ③ LayerNorm도 켜보세요. 아래 분포 막대가 가운데로 모입니다 — 값이 튀지 않게 정리된 상태입니다.

블록 안에서의 순서

GPT-2의 한 블록 안에서 이 장치들은 이렇게 배치됩니다.

입력 x
 ├─ LayerNorm → Multi-Head Attention ─┐
 └──────────────────────────────────┴─(+)→ y
y
 ├─ LayerNorm → MLP ────────────────┐
 └──────────────────────────────────┴─(+)→ 출력

정리하면 Attention 출력 = x + Attention(LayerNorm(x)), MLP 출력 = y + MLP(LayerNorm(y))입니다. LayerNorm이 블록당 두 번 들어가고, 6장에서 계산한 2 × (2 × 768)이 이것입니다.

Residual이 진짜로 하는 일

원래 컴퓨터 비전의 잔차 신경망(ResNet)에서 gradient 소실 문제를 풀려고 나온 아이디어입니다.

2장에서 gradient는 층을 거슬러 올라가며 곱해진다고 했습니다. 0.8을 12번 곱하면 0.069, 48번 곱하면 0.00002입니다 — 앞쪽 층은 사실상 학습이 안 됩니다.

Residual은 gradient가 층을 건너뛰는 지름길을 만들어줍니다. 곱셈 사슬을 우회하는 경로가 생겨서 앞쪽까지 신호가 도달합니다.

Dropout — 한 사람에게 의존하지 않게

학습할 때 일부 신호를 무작위로 끕니다. 시험공부할 때 한 문제 유형만 외우지 못하게 힌트를 일부 가리고 풀게 하는 것과 같습니다.

Dropout이 없으면 모델이 특정 연결 하나에 지나치게 의존할 수 있습니다. "이 특징 하나만 있으면 무조건 정답" 같은 식으로요. 그러면 학습 데이터는 잘 맞히지만 새 데이터에서 틀립니다 — 과적합입니다.

Dropout을 쓰면 어떤 연결이든 꺼질 수 있으니 모델이 여러 특징을 함께 쓰려고 합니다. "A가 꺼져도 B와 C로 판단" 하는 식입니다.

중요 — Dropout은 학습할 때만 켭니다. 추론할 때는 끕니다. PyTorch의 model.eval()이 하는 일 중 하나가 이것입니다.

수식으로도 보고 싶다면
y = x + F(x)    ← Residual Connection

읽는 법 — F가 Attention이든 MLP든 상관없습니다. 결과를 덮어쓰는 게 아니라 원래 x에 더합니다. 그래서 F는 "정답"이 아니라 "고쳐야 할 차이(잔차, residual)"만 만들면 됩니다.

역전파에서 왜 도움이 되는지 —

∂y/∂x = 1 + ∂F/∂x

읽는 법 — 앞의 1이 핵심입니다. ∂F/∂x가 아무리 작아져도 gradient가 최소한 1은 통과합니다. 곱셈 사슬이 0으로 무너지지 않습니다.

LayerNorm(x) = γ · (x − μ) / √(σ² + ε)  +  β

읽는 법 — μ는 그 벡터 768개 숫자의 평균, σ는 표준편차(퍼진 정도)입니다. 평균을 빼서 가운데를 0으로 옮기고, 표준편차로 나눠 퍼짐을 1로 맞춥니다.

ε(엡실론)은 아주 작은 수(예: 1e−5)로, 0으로 나누는 사고를 막는 안전장치입니다.

γ(감마)와 β(베타)는 학습되는 파라미터입니다. 정규화로 다 똑같이 만들어놓고 끝내면 표현력이 줄어드니, 모델이 다시 필요한 만큼 늘리고 옮길 수 있게 열어둔 손잡이입니다. 768차원이면 γ 768개 + β 768개 = 1,536개이고, 이게 6장 계산의 2 × 768입니다.

μ = (1/d) Σi xi     σ² = (1/d) Σi (xi − μ)²

읽는 법 — 평균은 다 더해서 개수로 나눈 것, 분산은 평균에서 떨어진 거리를 제곱해 평균낸 것입니다. 그 제곱근이 표준편차입니다.

Dropout:   yi = xi · mi / (1−p),   mi ~ Bernoulli(1−p)

읽는 법 — mi는 0 아니면 1인 무작위 스위치입니다. 확률 p로 꺼집니다(0). 1/(1−p)로 나눠주는 이유는 꺼진 만큼 나머지를 키워 평균 크기를 유지하기 위해서입니다. 그래야 추론할 때 dropout을 꺼도 값의 규모가 맞습니다.

10장 세 줄 요약
  1. 정의 — Residual은 y = x + F(x)로 원래 입력을 살려 보내고, LayerNorm은 벡터 안 숫자를 평균 0·퍼짐 1로 정리합니다.
  2. 성질 — Residual은 gradient에 1이라는 통로를 만들어 소실을 막습니다. 그래서 12층, 96층까지 쌓을 수 있습니다.
  3. 쓰임 — Dropout은 학습 중에만 일부 신호를 꺼서 과적합을 막습니다. 추론에서는 끕니다.

— 여기까지: 블록 완성. 다음은 12번째 블록을 나온 뒤 마지막 한 걸음 —

11출력 — Logit에서 다음 단어까지

한 줄 요약: 50,257개 점수를 확률로 바꾸고, 그중 하나를 고릅니다. 어떻게 고르냐가 문장의 성격을 정합니다.

왜 이걸 배우나
"같은 질문을 두 번 했는데 답이 다르게 나옵니다. 모델이 헷갈리는 건가?"

아닙니다. 모델이 내놓는 건 단어 하나가 아니라 확률 분포이고, 거기서 어떻게 하나를 뽑을지는 우리가 정합니다.

이 장에서는 temperature, top_k, top_p가 정확히 무엇을 하는 옵션인지를 눈으로 봅니다. API를 쓰다 만나는 그 값들입니다.

이 장에 나오는 낯선 말
Logit (로짓)
어휘 하나하나에 대한 날것의 점수. 음수도 있고 합이 1도 아닙니다.
Softmax
점수를 합이 1인 확률로 바꾸는 함수 (8장에서 나왔습니다).
Temperature (온도) · T
점수를 나누는 값. 작으면 뾰족해지고(보수적), 크면 평평해집니다(창의적).
Top-k
점수 상위 k개만 남기고 나머지는 버리는 방식.
Top-p (Nucleus Sampling)
누적 확률이 p를 넘을 때까지만 후보로 남기는 방식.
Greedy decoding
항상 제일 높은 하나만 고르는 방식. 같은 입력이면 항상 같은 답이 나옵니다.

비유 먼저 — 주사위의 눈금을 바꾸기

모델은 매 순간 50,257면짜리 주사위를 하나 만들어 냅니다. 면마다 넓이가 다릅니다 — 넓은 면이 확률 높은 단어입니다.

Temperature는 면의 넓이 차이를 조절하는 손잡이입니다. 낮추면 넓은 면이 더 넓어지고 좁은 면은 거의 사라집니다 — 뻔하지만 안전한 답이 나옵니다. 높이면 면들이 고르게 평평해집니다 — 뜻밖의 단어가 나올 수 있습니다.

Top-k와 Top-p는 아예 주사위 면 개수를 줄이는 방식입니다. Top-k는 "넓은 순서로 10면만 남겨", Top-p는 "넓이 합이 80% 될 때까지만 남겨"입니다.

작은 숫자로 한 번

── 마지막 토큰의 logit (일부) ──
좋다: 6.2   춥다: 5.8   덥다: 5.3   맑다: 4.9   자동차: 1.1

── T = 1.0 (그대로) ──
[35%, 25%, 15%, 10%, 0.4%]

── T = 0.5 (÷0.5 = ×2) ──
점수 차이가 두 배로 벌어짐
[58%, 26%, 9%, 4%, 0.0%] // 1등에 쏠림

── T = 2.0 (÷2) ──
[27%, 22%, 17%, 14%, 4%]  // 평평해짐

── Top-p = 0.8 ──
35 → 60 → 75 → 85%  // 맑다에서 80% 넘음
후보 4개만 남기고 나머지 제거

Top-p 계산이 헷갈릴 수 있는데, 확률 높은 순서로 더해 나가다가 누적이 p를 넘는 순간까지 포함하고 자릅니다.

그림 11

Temperature · Top-k · Top-p — 세 손잡이를 직접

↔ 슬라이더 3개 + 버튼
해볼 것 — ① Temperature를 0.1까지 내려보세요. 1등만 남고 나머지가 사실상 0%가 됩니다 — greedy와 같아집니다. ② Temperature를 2.0 이상으로 올려보세요. 막대들이 평평해져서 엉뚱한 단어도 뽑힐 수 있게 됩니다. ③ Top-k를 3으로, Top-p를 0.6으로 줄여보세요. 잘린 후보가 회색으로 바뀝니다. 뽑기를 여러 번 눌러 결과가 달라지는 것도 확인해 보세요.

마지막 단계의 흐름

  1. 마지막 토큰의 벡터만 꺼낸다 12번째 블록을 나오면 [배치, 토큰, 768]입니다. 여기서 맨 마지막 토큰의 768개만 씁니다. 나머지는 버립니다.
  2. 출력 가중치를 곱해 logit을 만든다 768 → 50,257. 이 가중치는 토큰 임베딩과 공유합니다(6장 weight tying). shape는 [배치, 토큰, 50257]이 됩니다.
  3. Temperature로 나누고 Top-k / Top-p로 후보를 자른다 잘린 후보에는 큰 음수를 넣습니다 — 8장의 mask와 똑같은 수법입니다.
  4. Softmax로 확률을 만들고 하나를 뽑는다 뽑힌 토큰 ID를 다시 임베딩 벡터로 바꿔 다음 생성 단계의 입력에 붙입니다 — 자기회귀(4장)입니다.
헷갈리기 쉬운 지점

Temperature는 확률을 "바꾸는" 게 아니라 logit을 "나누는" 것입니다. 나눈 뒤에 softmax를 씌우기 때문에 결과적으로 분포 모양이 달라집니다.

그리고 Top-k와 Top-p는 동시에 쓸 수 있습니다. 보통 둘 다 적용한 뒤 남은 후보에서 뽑습니다. temperature=0은 사실상 greedy(항상 1등)를 뜻하는 관례적 표기입니다.

수식으로도 보고 싶다면
z = hlast · Wout + b     (Wout = Weᵀ)

읽는 법 — hlast는 마지막 토큰의 768차원 벡터, Wout은 768 → 50,257 변환입니다. 괄호 안이 weight tying입니다 — 입력 임베딩 행렬 [50257, 768]을 전치해서 그대로 재사용합니다. 결과 z가 logit입니다.

pi = exp( zi / T ) / Σj exp( zj / T )

읽는 법 — temperature가 적용된 softmax입니다. T < 1이면 z를 나눈 값이 커져서 차이가 벌어지고 → 뾰족한 분포. T > 1이면 작아져서 차이가 줄고 → 평평한 분포. T = 1이면 그냥 softmax입니다.

T → 0이면 1등만 1이 되고 나머지는 0이 됩니다. 이게 greedy입니다.

Top-k:   zi ← −∞   (i ∉ 상위 k개)
Top-p:   확률 내림차순 정렬 후  Σ p ≥ pthreshold  가 되는 지점까지만 유지

읽는 법 — 둘 다 후보를 자르는 방식이고, 자른 뒤 남은 것들로 다시 softmax를 해서 합이 1이 되게 만듭니다.

차이는 고정된 개수냐, 상황에 따라 유동적이냐입니다. 확률이 한 곳에 쏠린 상황에서 Top-k=10은 쓸모없는 9개까지 남기지만, Top-p=0.8은 1~2개만 남깁니다. 그래서 Top-p를 선호하는 경우가 많습니다.

다음 토큰   tnext ~ Categorical(p)

읽는 법 — ~는 "이 분포에서 무작위로 뽑는다"입니다. 그래서 같은 프롬프트라도 매번 결과가 달라집니다.

11장 세 줄 요약
  1. 정의 — 마지막 토큰의 벡터를 50,257개 logit으로 바꾸고, softmax로 확률을 만든 뒤 하나를 뽑습니다.
  2. 성질 — Temperature는 logit을 나눠 분포를 뾰족하게/평평하게 만들고, Top-k · Top-p는 후보 자체를 잘라냅니다.
  3. 쓰임 — 뽑힌 토큰은 다시 임베딩으로 바뀌어 다음 스텝의 입력이 됩니다. 이 반복이 문장을 만듭니다.

— Part 5. 참고 — 자주 헷갈리는 용어와, 서빙으로 이어지는 이야기 —

12Parameter · Weight · Gradient — 용어 정리

한 줄 요약: Weight는 Parameter의 일부이고, Gradient는 파라미터가 아니라 수정 안내값입니다.

왜 이걸 배우나
"파라미터 124M이라는데, weight가 124M이라는 건가? gradient도 저장되는 건가?"

세 단어가 섞여 쓰이는데 포함 관계가 있고, 저장 여부도 다릅니다. 이걸 구분해야 "학습에는 메모리가 왜 몇 배 더 드는가" 같은 질문에 답할 수 있습니다.

이 장에서는 용어를 정리하고, GPU 여러 대로 학습할 때 gradient가 어떻게 오가는지까지 봅니다.

이 장에 나오는 낯선 말
Parameter (파라미터)
모델이 학습해서 저장하는 숫자 전체. weight, bias, embedding, LayerNorm의 γ·β가 다 포함됩니다.
Weight (가중치)
그중 입력에 곱하는 숫자. 파라미터의 부분집합입니다.
Gradient (그래디언트)
각 파라미터를 어느 방향으로 얼마나 고칠지 알려주는 임시 값. 모델의 지식이 아닙니다.
Data Parallel Training
같은 모델을 여러 GPU에 복사하고 데이터를 나눠 학습하는 방식.
All-Reduce
각 GPU의 gradient를 모아서 평균 내고 다시 나눠주는 통신 연산.
동기식 장벽 (synchronization barrier)
모두가 끝나야 다음으로 갈 수 있는 지점. 제일 느린 쪽이 전체 속도를 정합니다.
가중치 공개 (open weight)
학습이 끝난 숫자 파일을 내려받을 수 있게 공개하는 것.

비유 먼저 — 레시피와 수정 메모

Parameter는 요리사의 레시피 전체입니다 — 재료 분량, 불 세기, 시간까지 다.

Weight는 그중 재료 분량 부분입니다. 레시피의 일부이지 전부가 아닙니다.

Gradient는 손님 반응을 보고 적어둔 "소금 조금 줄일 것" 같은 메모입니다. 레시피 자체가 아니라 레시피를 고치기 위한 임시 안내이고, 한 번 고치고 나면 버립니다.

포함 관계

Parameter  // 학습 가능한 숫자 전체
├── Weight        // 곱하는 숫자
├── Bias          // 더하는 숫자
├── Embedding 값  // 5·7장의 표
└── LayerNorm의 γ, β // 10장

// Gradient는 여기 없습니다 — 매번 새로 계산되고 버려집니다
용어무엇인가학습 중 바뀌나쉬운 비유
Token입력 문장의 조각아니요문제지의 단어
Weight입력 영향을 조절하는 숫자예판단 기준
Parameter모델이 학습하는 숫자 전체예머릿속 설정 전체
Gradient파라미터 수정 방향과 크기매 학습마다 새로 계산수정 안내 화살표
그림 12

여러 GPU로 학습하면 — gradient가 오가는 길

↔ 슬라이더 + 재생
해볼 것 — ① 재생을 눌러 한 스텝을 따라가 보세요. 계산(초록) → gradient 교환(자홍) → 평균 → 같은 값으로 갱신 순서입니다. ② GPU 수를 늘려보세요. 계산 시간은 줄지만 통신 시간은 늘어납니다. 오른쪽 비율을 보세요. ③ "가장 느린 GPU" 슬라이더를 올려보세요. 한 대만 느려도 전체가 그만큼 밀립니다 — 동기식 장벽입니다.

Gradient 한 번 적용해 보기

2장에서 본 공식 그대로입니다. 다시 한번 —

현재 Weight = 0.50   Gradient = 0.20   학습률 = 0.10
새 Weight = 0.50 − 0.10 × 0.20 = 0.48
// gradient가 양수 → 가중치를 줄이는 쪽

Gradient = −0.20 이라면
새 Weight = 0.50 − 0.10 × (−0.20) = 0.52
// gradient가 음수 → 가중치를 키우는 쪽
데이터 병렬 학습 — 여러 GPU가 같은 모델을 갖는 방식

모델을 여러 GPU에 똑같이 복사하고, 데이터만 나눠서 각자 학습합니다. 순서는 이렇습니다.

GPU-A: Batch-A로 forward / backward
GPU-B: Batch-B로 forward / backward
           ↓
각 GPU가 gradient 계산
           ↓
네트워크로 gradient 교환 (All-Reduce)
           ↓
평균 gradient 계산
           ↓
모든 GPU가 같은 값으로 weight 갱신

여기서 네트워크에 세 가지 요구가 생깁니다.
① 높은 순간 대역폭 — gradient를 짧은 시간에 다 보내야 해서 링크를 거의 가득 씁니다.
② 낮은 지연시간 — All-Reduce가 끝나야 다음 단계로 가므로 지연이 곧 학습 속도입니다.
③ 동기식 장벽 — 가장 느린 GPU나 링크가 전체 step 시간을 결정합니다.

보충 — 가중치 공개 모델과 비공개 모델

모델 구조가 두뇌의 설계도라면, 가중치는 학습으로 채워진 실제 지식에 가깝습니다. 구조만 있으면 빈 두뇌이고, 구조 + 학습된 가중치가 있어야 문장을 생성할 수 있는 모델이 됩니다.

가중치 공개 모델은 그 숫자 파일을 내려받아 내 서버나 PC에서 직접 실행할 수 있습니다. 보통 model.safetensors, pytorch_model.bin, *.gguf, 또는 여러 개로 분할된 weight 파일로 배포됩니다.

가중치 비공개 모델은 다운로드가 안 되고, 제공 회사의 서버에서만 실행됩니다. 사용자는 API로 요청만 보냅니다.

주의 — 가중치를 공개했다고 반드시 완전한 오픈소스는 아닙니다. 상업적 사용이나 재배포 허용 범위는 라이선스를 따로 확인해야 합니다.

수식으로도 보고 싶다면
θ ← θ − η · ∇θL

읽는 법 — θ(세타)는 파라미터 전체를 한 글자로 쓴 것입니다. ∇(나블라)는 "모든 파라미터에 대한 편미분을 모아놓은 것"으로, 2장의 ∂L/∂w를 파라미터 개수만큼 쌓아놓은 벡터입니다.

즉 "파라미터 1억 2천만 개를 한꺼번에 같은 규칙으로 고친다"를 한 줄로 쓴 것입니다.

데이터 병렬:   g = (1/N) Σk=1..N gk

읽는 법 — gk는 k번째 GPU가 자기 배치로 계산한 gradient입니다. 전부 더해서 GPU 개수로 나눕니다(평균). 이 평균값으로 모두가 갱신하기 때문에 모든 GPU의 모델이 항상 같은 상태로 유지됩니다.

한 step 시간 ≈ maxk( 계산시간k ) + 통신시간( All-Reduce )

읽는 법 — 앞의 max가 동기식 장벽입니다. 평균이 아니라 최댓값이라, GPU 한 대만 느려도 전체가 그만큼 느려집니다. 그림 12에서 확인하실 수 있습니다.

학습 시 메모리 ≈ 파라미터 + gradient + optimizer 상태 (보통 2배) ≈ 4배

읽는 법 — 그래서 추론보다 학습에 메모리가 훨씬 많이 듭니다. 추론은 파라미터만 있으면 되지만, 학습은 gradient와 optimizer 상태까지 들고 있어야 합니다.

12장 세 줄 요약
  1. 정의 — Parameter ⊃ Weight이고, Parameter에는 bias·embedding·LayerNorm의 γ·β까지 포함됩니다.
  2. 성질 — Gradient는 파라미터가 아닙니다. 매 학습마다 새로 계산되는 수정 안내값이고, 쓰고 나면 버립니다.
  3. 쓰임 — 여러 GPU로 학습하면 gradient를 All-Reduce로 평균내 공유합니다. 가장 느린 GPU가 전체 속도를 정합니다.

— 여기까지: 학습 쪽 이야기. 마지막은 그 숫자들을 어떻게 저장하느냐 —

13dtype과 양자화 — 숫자를 얼마나 정밀하게 적을까

한 줄 요약: 같은 숫자를 더 작은 메모지에 적으면, 모델이 GPU에 올라가느냐 마느냐가 갈립니다.

왜 이걸 배우나
"dtype='float16'이 뭐길래 이걸 바꾸면 모델이 두 배 작아진다는 걸까?"

모델의 가중치와 계산 결과는 결국 숫자입니다. 그 숫자 하나를 몇 비트로 적을지가 dtype이고, 그게 곧 메모리와 속도입니다.

이 장에서는 dtype과 양자화가 무엇이 다른지, 그리고 그것이 서빙에서 어떤 차이를 만드는지를 봅니다. 3장의 GPU 이야기가 여기서 완성됩니다.

이 장에 나오는 낯선 말
dtype (자료형)
숫자 하나를 어떤 형식으로 몇 비트에 적을지. float32, float16, bfloat16 등.
부호 · 지수 · 가수
부동소수점 숫자의 세 부분. ± / 얼마나 큰 수까지(range) / 얼마나 세밀하게(precision)를 담당합니다.
Overflow · Underflow
너무 커서 표현 못 하고 무한대가 되는 것 / 너무 작아서 0으로 뭉개지는 것.
Quantization (양자화)
실수를 정해진 개수의 정수 칸에 끼워 맞추는 것. int8, int4 등.
Scale · Zero-point
양자화된 정수를 원래 값으로 되돌리는 환산표.
Calibration (보정)
양자화할 때 어떤 범위로 끼워 맞출지 정하려고 실제 데이터를 흘려보는 과정.
Tensor Core
NVIDIA GPU 안의 fp16 행렬곱 전용 회로. float32보다 훨씬 빠릅니다.

비유 먼저 — 메모지 크기, 그리고 색연필 개수

dtype은 숫자 하나를 적을 메모지 크기입니다.

float32는 A4 용지입니다. 소수점 아래까지 아주 정밀하게, 아주 크거나 작은 수도 넉넉히 적습니다. float16은 포스트잇 반쪽입니다. 공간이 절반이라 대충 자릿수만 적을 수 있고, 너무 크거나 작은 숫자는 아예 못 적습니다.

종이가 작아지면 창고(메모리)를 덜 차지하고, 택배 상자(메모리 대역폭)로 나를 때도 절반 무게라 빠릅니다. 이게 float16을 쓰는 이유입니다 — 정밀도를 조금 희생하고 속도와 메모리를 얻는 것.

양자화는 완전히 다른 이야기입니다. 메모지를 줄이는 게 아니라 기록 시스템 자체를 바꾸는 것입니다. 사진을 색연필 256자루로만 다시 칠하는 것과 비슷합니다. 원래의 연속적인 색을 정해진 팔레트 번호로 바꿔 적고, 나중에 환산표를 보고 복원합니다.

작은 숫자로 한 번

── 70B 모델의 가중치 메모리 ──
float32 (4byte) : 70,000,000,000 × 4 = 280 GB
float16 (2byte) : 70,000,000,000 × 2 = 140 GB
int8    (1byte) : 70,000,000,000 × 1 = 70 GB
int4   (0.5byte) : 70,000,000,000 × 0.5 = 35 GB

── int8 양자화 복원 공식 ──
실제값 ≈ (정수값 − zero_point) × scale
// 정수 하나 + 환산표만 저장

280GB와 35GB의 차이는 "GPU 여러 장이 필요하다"와 "노트북에서도 돈다"의 차이입니다. 같은 모델인데 말이죠.

그림 13

dtype과 양자화 — 메모리와 정밀도의 맞바꿈

↔ 슬라이더 + 버튼 5개
해볼 것 — ① 형식 버튼을 하나씩 눌러보세요. 위쪽 비트 배치와 아래쪽 메모리 막대가 같이 바뀝니다. ② bfloat16과 float16을 비교해 보세요. 총 비트는 같은데 지수와 가수의 배분이 다릅니다 — 그래서 표현 범위가 다릅니다. ③ int4를 눌러보세요. 오른쪽 곡선이 계단으로 뭉개집니다. 이게 양자화의 대가입니다.

세 가지 형식 비교

dtype총 비트지수가수표현 범위 / 유효숫자
float3232bit (4byte)8bit23bit±10⁻³⁸ ~ ±10³⁸ · 약 7자리
float1616bit (2byte)5bit10bit±6×10⁻⁵ ~ 65,504 · 약 3~4자리
bfloat1616bit (2byte)8bit7bitfloat32와 동일한 범위 · 약 2~3자리

bfloat16이 왜 나왔는지가 이 표에 다 있습니다. 가수(세밀함)를 더 줄이는 대신 지수(범위)는 float32만큼 유지했습니다. 큰 수·작은 수를 그대로 다룰 수 있어서 학습 중 오버플로우에 안전합니다.

LLM 서빙에서 왜 중요한가

① 모델 크기가 절반 — 70B 모델이 float32면 280GB, float16이면 140GB입니다. GPU에 올라가느냐 마느냐가 여기서 갈립니다.

② KV 캐시도 절반 — 토큰을 하나씩 만드는 단계는 메모리 대역폭이 병목인데, float16이면 읽어오는 데이터가 절반이라 실제로 빨라집니다.

③ 전용 회로 가속 — NVIDIA GPU에는 fp16 연산에 특화된 Tensor Core가 있어서 float32보다 훨씬 빠르게 행렬곱을 처리합니다(3장).

공짜는 아닙니다

포스트잇에 억지로 자세한 숫자를 적으려다 뭉개지듯, float16에도 문제가 생깁니다.

· 값이 65,504를 넘으면 inf로 튑니다 (overflow)
· 아주 작은 gradient가 0으로 반올림됩니다 (underflow) — 특히 학습에서 치명적입니다
· 그래서 학습에는 bfloat16이나 mixed precision을 더 선호합니다

반면 추론은 이미 학습된 가중치를 그대로 쓰는 것이라 오버플로우 위험이 상대적으로 적어서 float16을 무난하게 씁니다.

dtype 변환 vs 양자화

구분dtype 변환 (fp32 → fp16/bf16)양자화 (fp → int8/int4)
표현 방식여전히 부동소수점 (연속적)정수 + scale/zero-point (팔레트 방식)
압축률2배4배(int8), 8배(int4) 이상
정밀도 손실상대적으로 적음, 구조적으로 안정더 큼. calibration 안 하면 성능 급락 가능
추가 작업거의 없음 (.half() 캐스팅)보정 데이터셋 또는 QAT(양자화 인식 학습) 필요
대표 기법그냥 dtype 캐스팅GPTQ · AWQ · bitsandbytes(NF4) · SmoothQuant · GGUF
하드웨어Tensor Core가 fp16/bf16 네이티브 지원int8/int4 지원 GPU 필요, 또는 연산 전 복원(dequant) 커널 필요
주 용도학습·추론 둘 다 기본값주로 추론 전용. 거대 모델을 저사양 GPU·엣지에 올릴 때
실무에서는 단계적으로

dtype 변환은 "같은 언어로 더 작게 쓰기"입니다 — 손실이 적고 거의 공짜입니다.
양자화는 "아예 다른 압축 언어로 번역하기"입니다 — 압축률은 훨씬 높지만 번역이 서툴면(calibration 부실) 정확도가 크게 왜곡됩니다.

그래서 보통 이 순서로 갑니다 — 먼저 float16/bfloat16으로 절반 줄이고, 그래도 메모리가 부족하면 양자화(int8 → int4)를 추가로 적용합니다.

int4까지 가면 팔레트 색이 16개 수준이라 정확도가 눈에 띄게 떨어질 수 있습니다. 그래서 GPTQ·AWQ 같은 똑똑한 팔레트 선택 알고리즘(어떤 가중치가 더 중요한지 분석해 오차를 최소화)이 필요합니다.

수식으로도 보고 싶다면
값 = (−1)부호 × 1.가수 × 2(지수 − bias)

읽는 법 — 부동소수점의 기본 구조입니다. 가수가 세밀함을, 지수가 다룰 수 있는 크기 범위를 정합니다. float16은 지수가 5비트뿐이라 최대 65,504까지밖에 못 갑니다.

메모리(GB) ≈ 파라미터 수 × (비트 수 / 8) / 10⁹

읽는 법 — 비트를 8로 나눠 바이트로 바꾸고, 파라미터 수를 곱합니다. 70B × 2byte = 140GB. 그림 13에서 직접 계산해 보실 수 있습니다.

양자화:   q = round( x / s ) + z     복원:   x̂ = ( q − z ) · s

읽는 법 — s는 scale(한 칸의 크기), z는 zero-point(0이 어느 칸인지)입니다. round가 핵심입니다 — 반올림하는 순간 원래 값이 사라집니다. 그래서 x ≠ x̂이고, 그 차이가 양자화 오차입니다.

int8이면 q는 −128 ~ 127 사이 256개 칸 중 하나입니다. int4면 16개 칸뿐입니다 — 그래서 int4는 똑똑한 알고리즘 없이는 위험합니다.

s = ( xmax − xmin ) / ( qmax − qmin )

읽는 법 — scale은 "실제 값의 범위를 정수 칸 수로 나눈 것"입니다. 이 xmax·xmin을 어떻게 정하느냐가 calibration입니다. 실제 데이터를 흘려보며 값이 주로 어디 분포하는지 보고 정합니다. 범위를 잘못 잡으면 대부분의 값이 몇 개 칸에 몰려버려 정확도가 무너집니다.

13장 세 줄 요약
  1. 정의 — dtype은 숫자 하나를 적는 형식이고, 양자화는 실수를 정수 칸에 끼워 맞추는 다른 방식입니다.
  2. 성질 — bfloat16은 세밀함을 더 포기하는 대신 범위는 float32만큼 유지해 학습에 안전합니다. 양자화는 round에서 정보가 사라집니다.
  3. 쓰임 — 70B 모델이 280GB → 140GB → 70GB → 35GB로 줄어듭니다. 먼저 fp16, 그래도 부족하면 양자화가 실무 순서입니다.

— 여기까지: 부품을 다 봤습니다. 마지막으로 처음부터 끝까지 한 번에 —

14전체 정리 — 문장 하나가 다음 단어가 되기까지

한 줄 요약: "The weather is" 한 줄이 들어가서 다음 단어가 나오기까지, 정확히 열두 단계입니다.

왜 이걸 배우나
"부품은 다 봤는데, 그래서 처음부터 끝까지 순서대로 어떻게 흘러가는 거지?"

1장부터 13장까지 조각을 하나씩 봤습니다. 이제 그 조각들이 실제로 어떤 순서로 이어지는지를 한 화면에서 확인할 차례입니다.

이 장에서는 shape가 어떻게 변하는지를 따라가고, 이 글의 모든 수식을 한 표에 모아 정리합니다.

이 장에 나오는 낯선 말
Shape (모양)
텐서 각 차원의 크기 (5장). 이 장의 주인공입니다.
Batch (배치)
여러 입력을 한꺼번에 묶어 처리하는 단위. shape의 맨 앞 차원입니다.
Forward pass
입력에서 출력까지 한 번 통과시키는 것 (2장).

shape는 이렇게 변합니다

배치 2개, 토큰 5개짜리 입력을 예로 들면 —

입력 토큰 ID               [2, 5]
  ↓ 토큰 임베딩 + 위치 인코딩 (7장)
입력 벡터                  [2, 5, 768]
  ↓ Transformer Block 1 (8~10장)
                              [2, 5, 768]
  ↓ … Block 12까지 반복
                              [2, 5, 768]  // 끝까지 768 유지
  ↓ 출력층 (11장)
logits                     [2, 5, 50257]
  ↓ 마지막 토큰만 + softmax
다음 토큰 확률            [2, 50257]

중간 차원 768이 끝까지 유지된다는 게 핵심입니다. 블록은 값을 바꾸지 모양을 바꾸지 않습니다 — 그래서 12개든 96개든 똑같이 쌓을 수 있습니다.

그림 14

전체 흐름 — "The weather is" 가 "sunny"가 되기까지

↔ 재생 + 단계 이동
해볼 것 — ① 재생을 눌러 한 단계씩 따라가 보세요. 각 단계에서 데이터의 shape가 어떻게 변하는지 오른쪽에 뜹니다. ② 슬라이더로 아무 단계나 직접 짚어보세요. 그 단계가 몇 장에서 다룬 내용인지도 같이 표시됩니다. ③ 마지막 단계까지 가면 화살표가 처음으로 돌아갑니다 — 생성된 토큰이 다시 입력이 되는 자기회귀 구조입니다.
관찰 포인트

토큰 수와 배치는 변해도 768은 끝까지 그대로입니다. 마지막에만 50,257로 튀어 오릅니다.

수식 한눈에 보기

이 글에 나온 수식을 전부 모았습니다. 기호 읽는 법은 각 장의 접는 상자에 있습니다.

장수식한 줄로
1장 신경y = f(Σ wᵢxᵢ + b)곱하고 더한 뒤 기준을 넘는지 본다
2장 학습L = (1/N) Σ (ŷ − y)²
w ← w − η · ∂L/∂w
틀린 만큼 재고, 오차가 줄어드는 쪽으로 조금 이동
3장 행렬곱C = A·B → M·N·K 번의 곱셈계산량이 크지만 서로 독립이라 나눌 수 있다
4장 목표max Σ log P(xₜ | x₁…xₜ₋₁)앞을 보고 다음 토큰 확률을 최대로 (GPT)
5장 임베딩Wₑ ∈ ℝ^(V×d), eₜ = Wₑ[id(t)]
cos(a,b) = a·b / (‖a‖‖b‖)
단어 → 벡터는 표 조회. 거리는 방향으로 잰다
6장 파라미터P = V·d + C·d + L·(12d² + 13d) + 2d차원은 제곱으로, 층은 1차로 늘어난다
7장 위치xₜ = Wₑ[id] + W_p[pos]
PE(pos,2i) = sin(pos / 10000^(2i/d))
자리마다 다른 무늬를 그냥 더한다
8장 Attentionsoftmax(QKᵀ/√d_k + M)·V
softmax(z)ᵢ = exp(zᵢ) / Σ exp(zⱼ)
내적으로 관련도를 재고, 비율로 바꿔 섞는다
9장 Head·MLPMultiHead = Concat(head₁…head_h)·W_O
FFN(x) = W₂·GELU(W₁x + b₁) + b₂
나눠서 보고, 넓혔다 줄이며 정리한다
10장 안정화y = x + F(x), ∂y/∂x = 1 + ∂F/∂x
LN(x) = γ(x−μ)/√(σ²+ε) + β
원본을 살려 보내고, 크기를 표준으로 맞춘다
11장 샘플링pᵢ = exp(zᵢ/T) / Σ exp(zⱼ/T)온도로 분포를 뾰족하게/평평하게
12장 병렬 학습θ ← θ − η·∇θL
g = (1/N) Σ g_k
모든 파라미터에 같은 규칙. GPU끼리는 평균을 나눈다
13장 양자화q = round(x/s) + z, x̂ = (q−z)·s반올림하는 순간 원래 값이 사라진다
이 글 전체를 한 문장으로

LLM은 단어를 벡터로 바꾸고 → 내적으로 서로를 얼마나 볼지 정하고 → 비율대로 섞어 → 다시 정리하기를 12번(또는 96번) 반복한 뒤, 마지막에 어휘 전체에 점수를 매겨 하나를 뽑는 장치입니다.

그리고 그 안의 모든 숫자는 "틀린 만큼 조금씩 고치기"로 정해졌습니다. 1장의 어른/아이 구분기와 정확히 같은 원리입니다. 규모만 다릅니다.

이 글의 그림들에 대한 정직한 안내

그림 3(CPU vs GPU), 그림 10(신호 감쇠), 그림 12(GPU 병렬 학습)의 시간·수치는 개념을 보여주기 위한 단순화 모델입니다. 실제 하드웨어에서 측정한 값이 아닙니다.

반면 그림 6(파라미터 계산기)과 그림 13(메모리 계산)은 실제 공식 그대로라 기본값에서 124,439,808과 140GB가 정확히 나옵니다. 직접 확인해 보세요.

14장 세 줄 요약
  1. 정의 — 전체 흐름은 토큰화 → 임베딩+위치 → 블록 12개 → 출력층 → 샘플링이고, 그 결과가 다시 입력이 됩니다.
  2. 성질 — 중간 shape는 [배치, 토큰, 768]로 끝까지 유지되고, 마지막에만 50,257로 바뀝니다.
  3. 쓰임 — 모든 수식은 위 표에 모여 있습니다. 옵션 이름을 만나면 어느 단계의 이야기인지부터 짚으면 됩니다.

마치며

이번 자료에서는 LLM이 동작하는 원리를 가장 작은 단위에서부터 따라가 봤습니다. 가중치 두 개짜리 신경이 오차를 줄이며 스스로 기준을 찾는 과정에서 시작해, 그 아이디어가 층으로 쌓이고 Transformer 구조로 확장되어 결국 다음 단어의 확률 분포를 내놓기까지가 하나의 이야기로 이어진다는 점을 확인했습니다.

특히 임베딩·내적·softmax라는 세 가지 도구만으로 문맥 이해가 만들어진다는 점, 그리고 그 계산들이 서로 독립적이라 GPU에서 병렬로 돌아가기 때문에 dtype과 양자화 선택이 곧 서빙 비용이 된다는 점이 이론과 실전을 잇는 지점이었습니다. 수식은 어렵게 만들려고 있는 게 아니라, 같은 이야기를 짧게 적어둔 것이라는 것도 함께 확인했습니다.

틀린 곳이 있다면

댓글을 달아주세요. 제가 확인 후 수정하겠습니다. 이 자료는 CloudNet@ 스터디의 『LLM 기초』 자료를 바탕으로 재구성했으며, 원 자료가 참고한 bRd 3D · 3Blue1Brown · Transformer Explainer(poloclub) · 『밑바닥부터 만들면서 배우는 LLM』 1장 · Attention Is All You Need(2017)의 내용을 함께 담고 있습니다.

LLM 기초 · 동작 원리 · 라이브러리 없이 동작합니다

반응형

'AI > [LLMSO] Hands-On LLM 서빙과 최적화 스터디' 카테고리의 다른 글

[1주차-LLMSO]CH2. Large Language Model Serving (2026.08.02)  (0) 2026.08.08
[1주차-LLMSO]CH1. Introduction to Model Serving and Optimization (2026.08.02)  (0) 2026.08.08
[1주차-LLMSO]트랜스포머 모델을 이해하기 위한 기초 수학 정리  (0) 2026.08.07
    devlos
    devlos
    안녕하세요, Devlos 입니다. 새로 공부 중인 지식들을 공유하고, 명확히 이해하고자 블로그를 개설했습니다 :) 여러 DEVELOPER 분들과 자유롭게 지식을 공유하고 싶어요! 방문해 주셔서 감사합니다 😀 - DEVLOS -

    티스토리툴바