Chapter 01

벡터와 행렬

신경망이 보는 세상에는 사진도, 문장도, 사용자도 없다. 오직 숫자 배열과 그 배열을 다른 배열로 바꾸는 규칙만 있다. 앞의 것이 벡터이고 뒤의 것이 행렬이다. GPT가 다음 단어를 고르는 과정도 결국 수천 차원 벡터들의 내적과 거대한 행렬곱의 연속이다. 이 장에서는 이후 모든 장 — 임베딩, 어텐션, Transformer, 양자화, 서빙 — 에서 쓰일 선형대수의 직관을 손으로 만지며 쌓는다.

왜 벡터인가: 모든 것은 숫자 배열이다

컴퓨터가 고양이 사진을 '본다'는 것은 픽셀 밝기 숫자들을 읽는다는 뜻이다. 28×28 흑백 손글씨 숫자 이미지는 784개의 숫자이고, 224×224 컬러 사진은 224×224×3 = 150,528개의 숫자다. 1초짜리 16 kHz 음성은 16,000개의 숫자다. 이 숫자들을 한 줄로 늘어놓으면 벡터(vector)가 된다. 784개 숫자로 된 이미지는 784차원 공간의 점 하나다.

단어는 조금 다르다. '고양이'라는 글자 자체에는 숫자가 없다. 그래서 언어 모델은 어휘의 각 토큰에 학습 가능한 벡터를 하나씩 배정한다. GPT-2 small은 토큰마다 768개, Llama 3 8B는 4096개의 숫자를 쓴다. 이것을 임베딩(embedding)이라 하며 4장의 주제다. 추천 시스템에서는 사용자의 취향을 '액션 4점, 로맨스 0점, SF 3점…' 같은 벡터로 쓰고, 영화도 같은 축 위의 벡터로 쓴다.

이미지 [0.05, 0.20, 0.25, …, 0.20] 픽셀 밝기를 한 줄로 펼친다 28×28 → 784차원 단어(토큰) 고양이 [ 0.21, -0.73, 0.05, …, 0.48] 표에서 그 토큰의 행을 꺼낸다 GPT-2 768 · Llama 3 8B 4096차원 사용자 취향 액로SF코공 [4,0,3, 1,0] 축마다 의미가 있는 점수 5차원 세 경우 모두 '공간 속 한 점'이 된다 → 가까움 = 비슷함, 방향 = 성질
그림 1-1. 서로 다른 데이터가 같은 형식, 곧 고정 길이의 숫자 배열이 된다. 이미지는 원래 숫자이고, 단어는 학습된 표에서 벡터를 꺼내며, 취향은 사람이 정한 축의 점수다. 일단 벡터가 되면 '비슷함'을 거리나 각도로 잴 수 있다.

벡터의 각 성분이 사는 축들이 이루는 공간을 특징 공간(feature space)이라 한다. 특징 공간의 진짜 힘은 기하학이 의미를 갖게 된다는 데 있다. 비슷한 사진은 가까운 점, 비슷한 단어는 비슷한 방향, 취향이 맞는 영화는 사용자 벡터와 각도가 작은 벡터다. 학습이란 결국 이 공간을 잘 만들어서 '의미상 가까운 것'이 '기하학적으로 가까운 것'이 되도록 숫자를 조정하는 과정이라고 봐도 좋다.

표기

이 책에서 벡터는 굵은 소문자 \(\mathbf x\), 행렬은 대문자 \(W\), 스칼라는 보통 글씨 \(a\)로 쓴다. 벡터는 기본적으로 열벡터(세로)이며 \(\mathbf x \in \mathbb R^d\)는 "실수 \(d\)개로 된 벡터"라는 뜻이다. 코드(NumPy·PyTorch)에서는 벡터를 1차원 배열 shape (d,)로, 여러 벡터를 행으로 쌓은 2차원 배열 shape (n, d)로 다룬다.

벡터의 기본 연산: 덧셈, 스칼라배, 크기, 방향

벡터 연산은 성분별로 한다. 두 벡터의 덧셈은 같은 위치 성분끼리 더하고, 스칼라배는 모든 성분에 같은 수를 곱한다. 기하학적으로 덧셈은 화살표를 이어 붙이는 것(또는 평행사변형의 대각선), 스칼라배는 화살표를 늘이거나 줄이는 것이다. 음수를 곱하면 방향이 반대가 된다.

$$\mathbf a + \mathbf b = \begin{bmatrix} a_1 + b_1 \\ a_2 + b_2 \\ \vdots \\ a_d + b_d \end{bmatrix},\qquad c\,\mathbf a = \begin{bmatrix} c\,a_1 \\ c\,a_2 \\ \vdots \\ c\,a_d \end{bmatrix},\qquad \lVert \mathbf a \rVert = \sqrt{a_1^2 + a_2^2 + \cdots + a_d^2}$$
\(\lVert \mathbf a\rVert\)는 벡터의 크기(길이, L2 노름). 피타고라스 정리를 \(d\)차원으로 확장한 것이다. 크기로 나눈 \(\hat{\mathbf a} = \mathbf a / \lVert\mathbf a\rVert\)는 길이 1인 단위벡터로, 방향 정보만 남긴다.
덧셈 = 이어 붙이기 (평행사변형) a = (2.0, 0.4) b (0.6, 1.4) a + b = (2.6, 1.8) b를 a 끝에 이어 붙여도 같다 스칼라배 = 늘이기·뒤집기 2v v −v v̂ (길이 1) 2v는 방향이 같고 길이가 2배, −v는 정반대
그림 1-2. 왼쪽: \(\mathbf a + \mathbf b\)는 \(\mathbf b\)를 \(\mathbf a\)의 끝에 이어 붙인 끝점이며, 두 벡터가 만드는 평행사변형의 대각선이다. 오른쪽: 스칼라배는 같은 직선 위에서 길이만 바꾼다. 크기로 나눈 단위벡터 \(\hat{\mathbf v}\)는 '방향'만 담는다.

뺄셈 \(\mathbf a - \mathbf b\)는 \(\mathbf b\)의 끝에서 \(\mathbf a\)의 끝으로 가는 화살표다. 그래서 두 점 사이의 거리는 \(\lVert\mathbf a - \mathbf b\rVert\)로 잰다. 언어 모델 임베딩에서 유명한 "king − man + woman ≈ queen" 같은 유추는, 두 벡터의 차이가 '성별'이라는 방향을 담고 있다는 가설을 뺄셈과 덧셈으로 확인하는 것이다(4장).

아래 놀이터에서 두 벡터를 직접 움직여 보자. 덧셈·뺄셈뿐 아니라 다음 절의 주인공인 내적과 투영도 함께 계산해 둔다.

SIMULATOR

2D 벡터 놀이터: 덧셈·뺄셈·투영·내적

a, b 끝점을 끌어 보세요
예시 배치
a · b—
|a|, |b|—
사잇각 θ—
cos θ (코사인 유사도)—
투영 길이 a·b/|b|—
거리 |a − b|—
해볼 것: ① a를 b 둘레로 한 바퀴 돌려 보자. 내적은 θ < 90°에서 양수(초록), 90°에서 0(회색), 90°를 넘으면 음수(빨강)로 바뀐다. 투영 그림자의 방향도 같이 뒤집힌다. ② b의 방향은 그대로 두고 길이만 두 배로 늘려 보자. 내적은 두 배가 되지만 cos θ와 투영 길이는 그대로다. 이것이 내적과 코사인 유사도의 차이다. ③ '직교'를 누르면 a·b = 0이고, 이때 |a − b|² = |a|² + |b|²가 되는지(피타고라스) 읽은 값으로 확인해 보자.

내적 = 유사도 = 투영

이 책 전체에서 가장 많이 등장하는 연산 하나를 꼽으라면 내적(dot product, inner product)이다. 정의는 단순하다. 같은 위치 성분끼리 곱해서 모두 더한다. 그런데 이 단순한 계산이 기하학적으로 두 가지 의미를 동시에 갖는다.

$$\mathbf a \cdot \mathbf b \;=\; \mathbf a^{\top}\mathbf b \;=\; \sum_{i=1}^{d} a_i b_i \;=\; \lVert\mathbf a\rVert\,\lVert\mathbf b\rVert\cos\theta$$
\(\theta\)는 두 벡터의 사잇각. 2·3차원에서는 코사인 법칙으로 증명되고, 고차원에서는 오른쪽 식이 거꾸로 '각도의 정의'가 된다.
θ a b |a| cos θ = a·b / |b| a가 b 방향으로 드리운 그림자 수직선 θ에 따른 부호 θ < 90° → a·b > 0비슷한 방향 θ = 90° → a·b = 0직교 (무관) θ > 90° → a·b < 0반대 경향
그림 1-3. 내적의 두 얼굴. \(\mathbf a\cdot\mathbf b\)를 \(\lVert\mathbf b\rVert\)로 나누면 \(\mathbf a\)를 \(\mathbf b\) 방향으로 투영한 그림자의 (부호 있는) 길이가 된다. 크기를 모두 나누면 \(\cos\theta\), 곧 방향이 얼마나 비슷한지를 나타내는 −1~+1 사이의 수가 된다.

첫째, 내적은 투영이다. \(\mathbf b\)가 단위벡터라면 \(\mathbf a\cdot\mathbf b\)는 정확히 '\(\mathbf a\)가 \(\mathbf b\) 방향으로 얼마나 뻗어 있는가'다. 벡터의 \(i\)번째 성분 \(a_i\) 자체도 \(\mathbf a\)와 \(i\)번째 축 단위벡터의 내적이다. 둘째, 내적은 유사도다. 방향이 같으면 크고, 직교하면 0, 반대면 음수다. 신경망의 뉴런 하나는 입력 벡터와 가중치 벡터의 내적을 계산하는 장치이고(2장), 어텐션의 점수 \(\mathbf q\cdot\mathbf k\)는 "이 질의(query)가 저 키(key)와 얼마나 맞는가"를 내적으로 잰 것이다(5장).

세 가지 '가까움': 내적, 코사인 유사도, 유클리드 거리

검색이나 추천에서 "가장 비슷한 것"을 고를 때 쓰는 척도는 보통 세 가지다.

척도식크기(노름)에 대한 반응주로 쓰는 곳
내적\(\mathbf a\cdot\mathbf b\)길수록 유리 (인기·강도까지 반영)어텐션 점수, 행렬 분해 추천, MIPS 검색
코사인 유사도\(\dfrac{\mathbf a\cdot\mathbf b}{\lVert\mathbf a\rVert\lVert\mathbf b\rVert}\)무시 (방향만 본다)문장 임베딩 검색, RAG (10장)
유클리드 거리\(\lVert\mathbf a-\mathbf b\rVert\)크기가 비슷해야 가깝다k-NN, 군집화, 이미지 특징

셋은 하나의 항등식으로 묶인다. 거리의 제곱을 전개하면

$$\lVert\mathbf a-\mathbf b\rVert^2 = \lVert\mathbf a\rVert^2 + \lVert\mathbf b\rVert^2 - 2\,\mathbf a\cdot\mathbf b \quad\xrightarrow{\;\lVert\mathbf a\rVert=\lVert\mathbf b\rVert=1\;}\quad 2 - 2\cos\theta$$
모든 벡터를 길이 1로 정규화하면 내적 = 코사인 유사도이고, 거리는 코사인의 단조 감소 함수다. 세 척도의 순위가 완전히 같아진다.

그래서 실무 벡터 DB는 임베딩을 미리 L2 정규화해 저장하고 내적만 계산하는 경우가 많다. 반대로 정규화하지 않으면 세 척도는 서로 다른 순위를 내놓는다. 내적은 '전반적으로 강한' 벡터를 선호하고(모든 장르 점수가 높은 대작), 거리는 '크기까지 비슷한' 벡터를 선호한다. 아래 시뮬레이터에서 직접 확인해 보자. 영화와 점수는 교육용으로 만든 작은 예시다.

SIMULATOR

취향 벡터 추천: 척도에 따라 순위가 바뀐다

막대를 위아래로 끌어 취향을 바꾸세요
취향 크기 바꾸기 (방향 유지)
취향 프리셋

순위 목록에서 영화를 누르면 그 영화의 벡터가 막대 위에 테두리로 겹쳐진다.

사용자 벡터 u—
|u|—
선택 영화 cos / 내적 / 거리—
세 척도 1위가 일치?—
해볼 것: ① 'SF 액션광'에서 내적 1위는 모든 장르가 강한 「종합선물 블록버스터」인데, 코사인 1위는 방향이 가장 닮은 영화다. ② '× 0.5'를 여러 번 눌러 취향 크기를 줄이면 코사인 순위는 꿈쩍도 하지 않지만 거리 순위는 점수가 작은 소품 영화 쪽으로 이동한다. ③ 'L2 정규화'를 켜면 세 목록의 순서가 완전히 같아진다 — 위의 항등식 \(\lVert\mathbf a-\mathbf b\rVert^2 = 2-2\cos\theta\) 그대로다.
왜 코사인이 기본값이 되었나

문장 임베딩의 길이는 문장 길이·단어 빈도 같은 '의미와 무관한' 요인에 영향을 받기 쉽다. 방향만 보는 코사인 유사도는 이런 잡음에 덜 민감하다. 반면 어텐션의 \(\mathbf q\cdot\mathbf k\)처럼 크기에도 정보가 실린 경우(확신이 클수록 큰 점수)에는 정규화하지 않은 내적을 쓰고, 대신 \(\sqrt{d}\)로 나눠 크기를 다스린다(5장).

행렬 = 선형 변환

행렬을 '숫자를 직사각형으로 늘어놓은 표'로만 보면 선형대수가 지루해진다. 더 좋은 관점은 행렬을 공간을 변형하는 함수로 보는 것이다. 2×2 행렬 \(A\)는 평면 위의 모든 점 \(\mathbf x\)를 새 점 \(A\mathbf x\)로 보낸다. 이 변환은 선형(linear)이다. 즉 \(A(\mathbf x+\mathbf y) = A\mathbf x + A\mathbf y\), \(A(c\mathbf x) = cA\mathbf x\)가 성립하며, 기하학적으로는 "격자선이 직선으로 남고, 평행하고 등간격이며, 원점이 고정되는" 변환이다.

선형성 덕분에 변환 전체가 단 두 벡터로 결정된다. 기저 벡터 \(\hat\imath=(1,0)\)과 \(\hat\jmath=(0,1)\)이 어디로 가는지만 알면, 임의의 \(\mathbf x = x_1\hat\imath + x_2\hat\jmath\)는 \(A\mathbf x = x_1(A\hat\imath) + x_2(A\hat\jmath)\)로 간다. 그리고 \(A\hat\imath\)는 \(A\)의 첫 번째 열, \(A\hat\jmath\)는 두 번째 열이다.

$$A = \begin{bmatrix} a & b \\ c & d\end{bmatrix},\qquad A\begin{bmatrix}x_1\\x_2\end{bmatrix} = x_1\begin{bmatrix}a\\c\end{bmatrix} + x_2\begin{bmatrix}b\\d\end{bmatrix}$$
행렬의 열 = 기저 벡터의 도착지. 행렬-벡터 곱은 '열들의 가중합'이다. 같은 계산을 행 관점에서 보면 결과의 \(i\)번째 성분은 \(A\)의 \(i\)번째 행과 \(\mathbf x\)의 내적이다.
변환 전 (항등) î ĵ 넓이 1인 단위 정사각형 A 변환 후 A = [[1.3, 0.6], [0.3, 1.0]] Aî = (1.3, 0.3) Aĵ = (0.6, 1.0) 넓이 = det A = 1.3·1.0 − 0.6·0.3 = 1.12
그림 1-4. 행렬은 격자 전체를 한 번에 변형한다. 격자선은 직선·평행·등간격을 유지하고 원점은 제자리다. 행렬의 두 열이 곧 \(\hat\imath\)와 \(\hat\jmath\)의 새 위치이며, 단위 정사각형은 두 열이 만드는 평행사변형이 된다. 그 넓이가 행렬식이다.

행렬식과 고유벡터

행렬식(determinant) \(\det A = ad - bc\)는 변환이 넓이를 몇 배로 바꾸는지를 나타낸다. 단위 정사각형이 넓이 \(|\det A|\)의 평행사변형이 되고, 모든 도형의 넓이가 같은 비율로 변한다. 부호가 음수면 공간이 뒤집힌다(거울상: \(\hat\imath\)에서 \(\hat\jmath\)로 도는 방향이 반시계에서 시계로 바뀜). \(\det A = 0\)이면 평면이 직선이나 점으로 찌그러져 정보가 사라지고, 되돌리는 역행렬이 존재하지 않는다.

고유벡터(eigenvector)는 변환 후에도 자기 방향을 유지하는 특별한 벡터다. \(A\mathbf v = \lambda\mathbf v\)를 만족하며, \(\lambda\)(고유값)는 그 방향으로 늘어나는 배율이다. 2×2에서 고유값은 \(\lambda^2 - (\mathrm{tr}A)\lambda + \det A = 0\)의 근이다. 회전처럼 모든 방향이 돌아가 버리는 변환은 실수 고유벡터가 없다. 고유벡터는 이 장 끝의 PCA에서 '데이터가 가장 넓게 퍼진 방향'으로 다시 등장한다.

SIMULATOR

2×2 변환 격자: 기저를 끌어 공간을 변형하기

î, ĵ 끝점을 끌어 보세요
프리셋 (부드럽게 이동)
현재 행렬 A
det A (넓이 배율)—
방향—
고유값 λ₁, λ₂—
tr A—
해볼 것: ① ĵ 끝점을 î 쪽으로 끌어 두 벡터가 한 직선에 놓이게 해 보자. 평행사변형이 납작해지며 det → 0, 'F' 글자가 선분으로 뭉개진다(정보 손실). ② ĵ를 î의 반대편(시계 방향)으로 넘기면 det가 음수가 되고 'F'가 거울상으로 뒤집힌다 — '반사' 프리셋과 비교해 보자. ③ '대칭 늘이기'에서 점선(고유벡터) 위의 점은 변환 후에도 같은 직선 위에 있다. '회전 30°'에서는 고유벡터가 사라진다(복소 고유값). ④ '투영'은 det = 0이고 고유값이 1과 0이다: 한 방향은 그대로, 다른 방향은 완전히 사라진다.

행렬곱 = 변환의 합성 = 내적의 표

변환 \(B\)를 하고 나서 변환 \(A\)를 하면, 결과도 하나의 선형 변환이고 그 행렬이 행렬곱 \(AB\)다. 적용 순서는 오른쪽에서 왼쪽이다: \((AB)\mathbf x = A(B\mathbf x)\). 일반적으로 \(AB \ne BA\)다 — 회전하고 전단하는 것과 전단하고 회전하는 것은 다른 결과를 낳는다.

계산 관점에서 \(A\)가 \(m\times k\), \(B\)가 \(k\times n\)이면 \(C = AB\)는 \(m\times n\)이고, 각 성분은 \(A\)의 행과 \(B\)의 열의 내적이다. 안쪽 차원 \(k\)가 맞아야 곱할 수 있고, 곱하고 나면 그 차원은 '소모'되어 사라진다.

$$C_{ij} = \sum_{p=1}^{k} A_{ip}\,B_{pj} \qquad (m\times k)\cdot(k\times n) \to (m\times n),\qquad \text{FLOPs} = 2mnk$$
결과 성분 \(mn\)개 각각에 곱셈 \(k\)번과 덧셈 \(k\)번(엄밀히는 \(k-1\)번)이 든다. 곱셈-덧셈 한 쌍(FMA, multiply-accumulate)을 2 FLOPs로 세는 것이 업계 관례다.
A (m × k) i행 B (k × n) j열 × = C (m × n) C_ij C_ij = (A의 i행) · (B의 j열) — 길이 k인 두 벡터의 내적
그림 1-5. 행렬곱의 '행·열 내적' 관점. 결과 행렬의 칸 하나를 채우려면 \(A\)의 한 행 전체와 \(B\)의 한 열 전체가 필요하다. 모든 칸이 서로 독립적으로 계산되므로 수천 개의 연산기가 동시에 나눠 처리하기 좋다 — GPU가 잘하는 일이다.

아래에서 작은 \(A(3\times4)\cdot B(4\times2)\)를 직접 계산해 보자. 결과 칸을 누르면 그 칸에 쓰인 행과 열이 강조되고, '단계 재생'은 곱셈-덧셈을 하나씩 수행하며 FLOPs를 센다.

SIMULATOR

행렬곱 단계 시각화: C = A · B

B (4×2)
A (3×4)
C = AB (3×2)
C의 칸을 눌러 보세요.
단계 재생
값
현재 칸—
누적 FLOPs—
총 FLOPs = 2mnk2·3·2·4 = 48
실제 크기로 세어 보기 — (m × k)·(k × n)
해볼 것: ① A·B의 칸을 위아래로 끌면 값이 바뀌고 C가 즉시 다시 계산된다. A의 2행 값을 바꾸면 C의 2행만 변하는지 확인하자(행 i는 Aᵢ만 쓴다). ② 'B를 [I; 0]로'를 누르면 C는 A의 처음 두 열을 그대로 복사한다 — 행렬곱이 '열의 가중합'임을 보여 준다. ③ 아래 계산기에서 Llama 3 8B의 FFN 업 투영 하나만 해도 4096 토큰에 약 0.48 PFLOPs가 든다. H100 BF16 약 989 TFLOPS로도 이론상 최소 0.5 ms다.
왜 행렬곱 순서가 중요한가

\((AB)C = A(BC)\)는 결과가 같지만 연산량은 다를 수 있다. \(A\)가 \(1\times 4096\), \(B\)가 \(4096\times4096\), \(C\)가 \(4096\times 4096\)이면 \((AB)C\)는 \(2\cdot4096^2\cdot2 \approx 67\)M FLOPs인데 \(A(BC)\)는 \(BC\)만으로 \(2\cdot4096^3\approx 137\)G FLOPs다. 2000배 차이다. LoRA(작은 행렬 두 개의 곱으로 가중치 변화를 표현)가 효율적인 이유도 이런 순서·랭크 계산에 있다.

신경망 층 = y = Wx + b, 그리고 텐서 shape

이제 신경망의 기본 부품을 선형대수로 쓸 수 있다. 완전연결층(fully-connected, Linear, Dense layer)은 입력 벡터 \(\mathbf x\in\mathbb R^{d_\text{in}}\)를 받아 다음을 계산한다.

$$\mathbf y = W\mathbf x + \mathbf b,\qquad W\in\mathbb R^{d_\text{out}\times d_\text{in}},\;\; \mathbf b\in\mathbb R^{d_\text{out}}$$
\(W\)의 \(i\)번째 행 \(\mathbf w_i\)는 출력 뉴런 \(i\)의 가중치 벡터다. \(y_i = \mathbf w_i\cdot\mathbf x + b_i\) — 출력 하나하나가 입력과 '어떤 패턴'의 내적, 곧 '그 패턴과 얼마나 닮았나'다. 파라미터 수는 \(d_\text{out} d_\text{in} + d_\text{out}\).

여기에 비선형 활성화 함수를 끼워 쌓으면 신경망이 된다(2장). 실제로는 입력을 하나씩 넣지 않는다. 여러 샘플을 행으로 쌓은 행렬 \(X\in\mathbb R^{B\times d_\text{in}}\)를 한 번에 넣어 \(Y = XW^\top + \mathbf b\)를 계산한다. 이것이 배치(batch)다. 행렬-벡터 곱 여러 번이 행렬-행렬 곱 한 번이 되어, 가중치 \(W\)를 메모리에서 한 번 읽고 \(B\)번 재사용한다.

언어 모델에서는 차원이 하나 더 붙는다. 배치 \(B\)개의 문장 각각에 토큰이 \(T\)개 있고, 토큰마다 \(d\)차원 벡터가 있으니 데이터는 (B, T, d) 모양의 3차원 배열, 곧 텐서(tensor)다. Linear 층은 마지막 축에만 작용한다. 앞의 \(B\times T\)는 그냥 '독립적인 벡터 \(BT\)개'로 펼쳐서 하나의 큰 행렬곱 \((BT\times d)\cdot(d\times d_\text{out})\)으로 처리된다.

d = 768 T 1024 B = 8 X : (B, T, d) · W⊤ + b 768×3072 d_out = 3072 Y : (B, T, d_out) (B·T × d) · (d × d_out) = 8192 × 768 × 3072 → 2·8192·768·3072 ≈ 38.7 GFLOPs
그림 1-6. GPT-2 small의 FFN 첫 층을 예로 든 텐서 shape. 배치 8 × 시퀀스 1024 = 8192개의 토큰 벡터가 같은 가중치 행렬 하나를 공유한다. Linear 층은 마지막 축 \(d\)만 바꾸고 \(B\), \(T\) 축은 그대로 둔다.
모델 (공개 수치)d_modelFFN 차원층 수토큰당 대략 FLOPs (순전파)
GPT-2 small (2019)7683,07212약 0.25 GFLOPs
Llama 3 8B (2024)4,09614,33632약 16 GFLOPs

표의 마지막 열은 '파라미터 수 × 2'라는 어림셈이다. 파라미터 하나당 토큰마다 곱셈-덧셈 한 번(2 FLOPs)을 하기 때문이다(GPT-2 small 약 124M, Llama 3 8B 약 8.03B 파라미터). 즉 LLM 추론 연산의 대부분은 행렬곱이고, 어텐션 점수 계산 \(QK^\top\)과 가중합 \(AV\)도 행렬곱이다(5장).

GPU는 행렬곱 기계다

행렬곱은 하드웨어 입장에서 이상적인 작업이다. 결과 칸들이 서로 독립이라 대규모 병렬화가 쉽고, 같은 데이터를 여러 번 재사용하므로 메모리에서 가져온 바이트당 연산이 많다. \(n\times n\) 행렬 두 개를 곱하면 데이터는 \(O(n^2)\)인데 연산은 \(O(n^3)\)이다. NVIDIA H100 SXM은 BF16 행렬곱 전용 유닛(Tensor Core)으로 약 989 TFLOPS(dense)를 내지만, 메모리 대역폭은 약 3.35 TB/s다. 바이트당 약 300 FLOPs 이상을 해야 연산기가 쉬지 않는다. 배치가 작은 LLM 디코딩은 이 조건을 못 채워 메모리 대역폭에 묶이는데, 이것이 8장의 KV 캐시와 11장 루프라인 분석의 출발점이다.

고차원의 직관: 거의 모든 것이 직교한다

2차원과 3차원에서 기른 직관은 768차원이나 4096차원에서 종종 틀린다. 가장 중요한 차이는 이것이다: 무작위로 뽑은 두 고차원 벡터는 거의 항상 거의 직교한다. 각 성분을 표준정규분포에서 독립으로 뽑은 두 벡터의 코사인 유사도는 평균 0, 분산 약 \(1/d\)인 분포를 따른다.

$$\cos\theta = \frac{\sum_i a_i b_i}{\lVert\mathbf a\rVert\lVert\mathbf b\rVert},\qquad \mathbb E[\cos\theta]=0,\quad \operatorname{Var}[\cos\theta] = \frac{1}{d},\qquad p(c)\propto (1-c^2)^{\frac{d-3}{2}}$$
분자는 평균 0인 항 \(d\)개의 합이라 표준편차가 \(\sqrt d\) 정도로 자라고, 분모는 \(\lVert\mathbf a\rVert\lVert\mathbf b\rVert\approx d\)로 자란다. 비율의 표준편차는 \(1/\sqrt d\). \(d=4096\)이면 약 0.016이다. 오른쪽은 정확한 분포의 밀도다.

이 사실은 두 가지 귀결을 낳는다. 축복: 거의 직교하는 방향이 사실상 무한히 많다. \(d\)차원에는 정확히 직교하는 방향이 \(d\)개뿐이지만, 서로 코사인이 0.1 이하인 '거의 직교' 방향은 \(d\)에 대해 지수적으로 많이 넣을 수 있다. 그래서 4096차원 임베딩 하나에 수만 개의 개념을 서로 간섭 적게 담을 수 있다(이른바 superposition 가설). 또 무작위 투영으로 차원을 크게 줄여도 거리가 대략 보존된다(Johnson–Lindenstrauss 보조정리). 저주: 거리가 집중된다. 무작위 점들 사이의 거리가 모두 비슷해져서, '가장 가까운 이웃'과 '가장 먼 이웃'의 차이가 상대적으로 작아진다. 단순 거리 기반 방법이 고차원에서 어려워지는 이유다.

부피도 이상하게 행동한다. 반지름 1인 \(d\)차원 공의 부피는 \(r^d\)에 비례하므로, 바깥 두께 \(\varepsilon\)짜리 껍질이 차지하는 비율은 \(1-(1-\varepsilon)^d\)다. \(d=1000\), \(\varepsilon=0.01\)이면 99.996%다. 고차원 공의 부피는 거의 전부 표면 근처에 있다. 고차원 가우시안 표본도 원점 근처가 아니라 반지름 \(\sqrt d\) 근처의 얇은 껍질에 몰려 있다.

SIMULATOR

차원의 저주와 축복: 랜덤 벡터 실험

위: 무작위 벡터 쌍의 코사인 유사도 히스토그램(성분 ~ N(0,1), 매번 실제로 표본을 뽑는다). 아래: 공 부피 중 바깥 껍질의 비율 1−(1−ε)d.

cos 표본 표준편차—
이론 1/√d—
|cos| < 0.1 비율—
거리 대비 (최대−최소)/최소—
껍질 부피 비율—
해볼 것: ① d = 2에서 히스토그램은 양 끝(±1)이 높은 U자다 — 평면에서 무작위 두 방향은 직교하기보다 오히려 나란하기 쉽다. d = 3이면 평평해지고, d를 키울수록 0 근처의 좁은 종으로 바뀐다. ② d = 768(GPT-2)과 4096(Llama 3)에서 표준편차가 1/√d와 맞는지 확인하자. ③ '거리 대비'는 무작위 점 300개 중 질의 점에서 가장 먼 점과 가장 가까운 점의 거리 차를 최소 거리로 나눈 값이다. d = 2에서는 수십~수백 배지만 d = 4096에서는 0.1 남짓으로 떨어진다(거리 집중). ④ ε = 0.01이어도 d가 수백을 넘으면 부피의 대부분이 껍질에 있다.

노름, 단위벡터, 정규화

벡터의 '크기'를 재는 방법은 하나가 아니다. 일반적인 \(L_p\) 노름(norm)은 다음과 같다.

$$\lVert\mathbf x\rVert_p = \Big(\sum_i |x_i|^p\Big)^{1/p},\qquad \lVert\mathbf x\rVert_1 = \sum_i|x_i|,\quad \lVert\mathbf x\rVert_2 = \sqrt{\textstyle\sum_i x_i^2},\quad \lVert\mathbf x\rVert_\infty = \max_i |x_i|$$
특별한 표시가 없으면 노름은 L2(유클리드 길이)를 뜻한다. L1은 '맨해튼 거리', L∞는 가장 큰 성분의 크기다.
11 L1: |x₁| + |x₂| = 1 (마름모) 꼭짓점이 축 위 → 희소한 해를 선호 (Lasso) L2: x₁² + x₂² = 1 (원) 회전해도 같다 → 방향에 공평 (weight decay) L∞: max(|x₁|, |x₂|) = 1 (정사각형) 가장 큰 성분만 본다 → 양자화의 absmax 스케일
그림 1-7. 세 노름의 '단위 공'(노름이 1인 점들의 집합). 같은 벡터라도 어떤 노름으로 재느냐에 따라 크기가 다르다. L∞는 9장에서 양자화 스케일을 정할 때, L1·L2는 3장에서 정규화(regularization) 항으로 다시 만난다.

벡터를 자기 L2 노름으로 나누는 것을 L2 정규화(L2 normalization)라 한다. 결과는 단위 구 위의 점이고, 크기 정보는 버리고 방향만 남는다. 앞에서 본 대로 정규화된 벡터끼리는 내적 = 코사인 유사도다.

신경망 내부에서도 비슷한 정규화가 끊임없이 일어난다. 층을 수십 개 거치는 동안 벡터의 크기가 폭주하거나 소멸하지 않도록, Transformer는 각 블록 앞에서 토큰 벡터의 크기를 다시 맞춘다. Llama 계열이 쓰는 RMSNorm은 이름 그대로 성분의 제곱평균제곱근(RMS)으로 나눈다.

$$\operatorname{RMSNorm}(\mathbf x) = \frac{\mathbf x}{\sqrt{\frac{1}{d}\sum_i x_i^2 + \epsilon}} \odot \mathbf g = \frac{\sqrt d\,\mathbf x}{\lVert\mathbf x\rVert_2}\odot\mathbf g\quad(\epsilon\to0)$$
결국 L2 정규화 후 \(\sqrt d\)를 곱하고, 학습되는 성분별 배율 \(\mathbf g\)를 곱한 것이다(\(\odot\)는 성분별 곱). LayerNorm은 여기에 평균 빼기가 더해진다. 자세한 역할은 6장에서 다룬다.
고차원에서의 크기 감각

성분이 대략 표준정규분포인 \(d\)차원 벡터의 길이는 약 \(\sqrt d\)다. 768차원이면 약 27.7, 4096차원이면 약 64다. 그래서 두 벡터의 내적은 \(d\)가 클수록 표준편차 \(\sqrt d\)로 커진다. 어텐션이 \(\mathbf q\cdot\mathbf k\)를 \(\sqrt{d_k}\)로 나누는 이유가 바로 이것이다 — 나누지 않으면 소프트맥스가 한 토큰에만 몰린다.

투영과 차원 축소: PCA 맛보기

3차원 물체의 그림자는 2차원 평면으로의 투영(projection)이다. 서로 직교하는 단위벡터 \(\mathbf u_1,\mathbf u_2\)가 만드는 평면으로 점 \(\mathbf x\)를 투영하면 평면 위 좌표는 \((\mathbf u_1\cdot\mathbf x,\;\mathbf u_2\cdot\mathbf x)\)다. 이것을 행렬로 쓰면 \(U^\top\mathbf x\)이고(\(U=[\mathbf u_1\;\mathbf u_2]\)는 \(3\times2\)), 원래 공간 안의 그림자 위치는 \(P\mathbf x = UU^\top\mathbf x\)다. \(P\)를 투영 행렬이라 하며 \(P^2=P\)(두 번 투영해도 같다)를 만족한다. 앞의 변환 격자 시뮬레이터의 '투영' 프리셋이 2D 버전이었다.

그렇다면 데이터를 잘 보여 주는 '좋은 그림자'는 어느 방향일까? 정보를 가장 많이 남기는 투영은 투영 후에도 점들이 가장 넓게 퍼져 있는(분산이 큰) 방향이다. 데이터를 평균이 0이 되게 옮긴 뒤 공분산 행렬 \(C = \frac1n X^\top X\)를 구하면, 단위벡터 \(\mathbf u\) 방향으로 투영한 분산은 \(\mathbf u^\top C\mathbf u\)다. 이를 최대화하는 \(\mathbf u\)가 \(C\)의 가장 큰 고유값에 해당하는 고유벡터이고, 그 고유값이 곧 그 방향의 분산이다. 이것이 주성분 분석(PCA, Principal Component Analysis)이다.

$$C\mathbf u_k = \lambda_k\mathbf u_k,\quad \lambda_1\ge\lambda_2\ge\cdots,\qquad \text{상위 } r\text{개 성분이 보존하는 분산 비율} = \frac{\lambda_1+\cdots+\lambda_r}{\lambda_1+\cdots+\lambda_d}$$
SIMULATOR

3D 점구름을 2D로: 가장 좋은 그림자 찾기

끌어서 시점(투영 평면)을 돌리세요
시점으로 이동
데이터 모양

화면 자체가 3D → 2D 투영이다. 점 색은 PC1 좌표(제1주성분 방향 위치).

고유값 λ₁, λ₂, λ₃—
현재 시점이 보존한 분산—
PCA 최댓값 (λ₁+λ₂)/Σλ—
해볼 것: ① 마우스로 돌려 가며 '현재 보존 분산'을 최대로 만들어 보자. 아무리 돌려도 PCA 최댓값을 넘지 못한다. ② '최악'을 누르면 길쭉한 시가를 정면에서 보게 되어 점들이 작은 원반으로 겹쳐 보인다 — 가장 큰 변화 방향을 버린 그림자다. ③ '팬케이크'는 λ₃가 매우 작아 2D 투영으로 거의 100%가 보존된다. 진짜 데이터도 이렇게 '얇은' 경우가 많아, 수백 차원 임베딩을 2~3차원으로 줄여 시각화할 수 있다(4장). ④ '공'은 모든 고유값이 비슷해 어떤 시점도 약 67%만 남긴다.

PCA는 '선형' 차원 축소다. 투영은 행렬곱 하나이므로 빠르고 되돌리기도 쉽다(\(UU^\top\)로 복원). 신경망의 많은 부분도 이렇게 해석할 수 있다. 어텐션 헤드는 \(d\)차원 토큰 벡터를 \(d_k = d/h\)차원 부분공간으로 투영해서 그 안에서만 비교하고(5장), LoRA는 가중치 변화가 낮은 랭크 부분공간에 있다고 가정한다. 이후 장에서 "투영"이라는 말이 나오면 행렬곱으로 다른(대개 더 작은) 공간의 좌표를 구하는 것이라고 읽으면 된다.

핵심 정리

  1. 이미지·소리·단어·취향은 모두 고정 길이 숫자 배열, 곧 특징 공간의 점이 된다. 학습은 의미상 가까운 것이 기하학적으로 가까워지도록 공간을 만드는 일이다.
  2. 내적 \(\mathbf a\cdot\mathbf b = \sum a_ib_i = \lVert\mathbf a\rVert\lVert\mathbf b\rVert\cos\theta\)는 투영이자 유사도다. 정규화하면 내적 = 코사인이고 \(\lVert\mathbf a-\mathbf b\rVert^2 = 2-2\cos\theta\)라 세 척도의 순위가 같아진다.
  3. 행렬은 선형 변환이며 열은 기저 벡터의 도착지다. \(\det\)는 넓이 배율(음수면 뒤집힘, 0이면 정보 손실), 고유벡터는 방향이 유지되는 축이다.
  4. 행렬곱은 변환의 합성이고 각 칸은 행·열 내적이다. \((m\times k)(k\times n)\)의 비용은 2mnk FLOPs이며, LLM 연산의 대부분이 이것이다.
  5. 신경망 층은 \(\mathbf y = W\mathbf x+\mathbf b\)이고, 실제로는 (B, T, d) 텐서의 마지막 축에 작용하는 큰 행렬곱이다. 데이터 재사용이 높은 행렬곱은 GPU Tensor Core에 이상적이다.
  6. 고차원에서 랜덤 벡터는 거의 직교하며(\(\cos\)의 표준편차 \(\approx 1/\sqrt d\)), 거리는 집중되고 부피는 껍질에 몰린다.
  7. 노름은 크기, L2 정규화는 방향만 남기기, RMSNorm은 \(\sqrt d\)배 된 L2 정규화다. 투영은 행렬곱이고, PCA는 공분산의 고유벡터로 분산을 최대한 보존하는 투영을 찾는다.

확인 퀴즈

1. 벡터 b의 방향은 그대로 두고 길이만 3배로 늘렸다. a·b와 코사인 유사도는 각각 어떻게 되는가?

\(\mathbf a\cdot(3\mathbf b) = 3(\mathbf a\cdot\mathbf b)\)이지만 코사인은 분모의 \(\lVert 3\mathbf b\rVert = 3\lVert\mathbf b\rVert\)가 이를 상쇄한다. 코사인 유사도는 방향만 본다.

2. 행렬 \(A = \begin{bmatrix}2 & 1\\ 1 & -1\end{bmatrix}\)이 평면에 하는 일로 옳은 것은?

\(\det A = 2\cdot(-1) - 1\cdot1 = -3\). 절댓값 3이 넓이 배율, 음수 부호는 방향(손잡이)이 뒤집힌다는 뜻이다.

3. \((128\times512)\)와 \((512\times256)\) 행렬을 곱하는 데 드는 FLOPs는?

\(2mnk = 2\times128\times256\times512 = 33{,}554{,}432\). 결과 칸 \(128\times256\)개 각각에 길이 512짜리 내적(곱셈·덧셈 각 512번)이 든다.

4. shape (32, 128, 768)인 텐서 X에 가중치 \(W\in\mathbb R^{3072\times768}\)인 Linear 층(\(XW^\top+\mathbf b\))을 적용한 결과의 shape은?

Linear 층은 마지막 축에만 작용한다. 내부적으로 \((32\cdot128)\times768\) 행렬과 \(768\times3072\) 행렬의 곱이 되고, 다시 (32, 128, 3072)로 접힌다.

5. 성분이 독립 표준정규분포인 1024차원 랜덤 벡터 두 개의 코사인 유사도는 대략 어느 범위에 몰려 있는가?

분산이 \(1/d\)이므로 표준편차는 \(1/\sqrt{1024} = 1/32 \approx 0.031\)이다. 대부분 ±0.1 안에 들어간다 — 고차원에서 랜덤 벡터는 거의 직교한다.

6. 모든 임베딩을 L2 정규화해 두었다. 질의와의 '유클리드 거리가 작은 순'과 '코사인 유사도가 큰 순' 순위의 관계는?

단위벡터끼리는 \(\lVert\mathbf a-\mathbf b\rVert^2 = 2 - 2\cos\theta\)다. 거리는 코사인의 단조 감소 함수이므로 순위가 완전히 같다. 내적 순위도 같다. 그래서 벡터 DB는 정규화 후 내적만 계산하곤 한다.