어텐션 점수에는 토큰의 순서가 들어 있지 않습니다. 정수 위치를 주파수가 10배씩 줄어드는 사인·코사인 쌍으로 적는 이유와 파장이 만드는 해상도 사다리, 그리고 회전행렬의 직교성만으로 RoPE의 상대 위치 불변성을 증명합니다.
PALDYN Team//18 MIN READ
43MATH
중급
위치인코딩 · RoPE
지난 글까지 어텐션 식의 기호를 하나씩 뜯었습니다. 그런데 그 여정 내내 한 번도 쓰이지 않은 정보가 있습니다. 토큰의 순서입니다.
QKT 의 (i,j) 성분은 i 번째 질의와 j 번째 키의 내적입니다. 입력 토큰의 순서를 바꾸면 Q 와 K 의 행이 같이 바뀌므로 이 행렬은 행과 열이 함께 뒤섞인 것이 됩니다 — 값이 새로 생기지도, 사라지지도 않습니다.
이것을 순열 동변성(permutation equivariance)이라고 부릅니다. 입력을 섞은 만큼 출력도 똑같이 섞여 나온다는 뜻입니다. 좋은 성질처럼 들리지만 언어에서는 재앙입니다. "개가 사람을 물었다"와 "사람이 개를 물었다"가 어텐션에게는 같은 재료 뭉치입니다.
그래서 위치를 수로 적어 벡터에 실어 보냅니다. 이 글은 그 두 가지 방법의 수학만 다룹니다 — 사인파 인코딩의 주파수 사다리가 왜 기하급수적으로 줄어드는지, 그리고 RoPE가 쓰는 회전이 왜 상대 위치만 남기는지의 증명입니다. 구현과 긴 문맥 외삽 문제는 위치 인코딩이 맡으므로 여기서는 유도만 합니다.
소박한 세 방법이 왜 안 되는가
위치 p=0,1,2,… 를 그대로 벡터에 더하는 것부터 생각해 봅니다.
방법
무엇이 깨지는가
p 를 그대로 더한다
값이 위로 열려 있다. 길이 512로 학습하면 p=4000 은 한 번도 본 적 없는 크기의 수다
p/L 로 정규화한다
같은 5번째 토큰이 길이 10에서는 0.5, 길이 1000에서는 0.005다. 위치의 뜻이 문장 길이마다 달라진다
원핫 벡터로 준다
차원이 최대 길이에 비례해 커지고, 서로 다른 두 위치의 내적이 언제나 0이라 "가깝다"는 정보가 없다
원하는 것 세 가지가 여기서 나옵니다. 값이 유계일 것, 길이에 무관할 것, 그리고 가까운 위치끼리는 비슷하되 구별은 될 것.
앞의 둘을 한 번에 주는 것이 각도입니다. sin 과 cos 은 인자가 아무리 커져도 [−1,1] 안에 있고, 위치를 각도로 읽으면 문장 길이는 계산에 아예 등장하지 않습니다.
주파수 사다리
d 차원 위치 벡터를 d/2 개의 (sin,cos) 쌍으로 채웁니다. i 번째 쌍이 쓰는 각속도를 θi 라고 하면 — 각속도는 위치가 한 칸 갈 때 각도가 얼마나 도는가입니다 —
PE(p)2i=sin(pθi),PE(p)2i+1=cos(pθi)
이고, 원래 논문이 고른 θi 가 이것입니다.
θi=10000−2i/d,i=0,1,…,2d−1
지수가 i 에 대해 1차이므로 θi 는 기하급수적으로 줄어듭니다. 한 칸 옆으로 갈 때마다 같은 비율로 느려진다는 뜻입니다. 각속도 대신 파장으로 읽으면 더 분명합니다. 파장은 각도가 한 바퀴 2π 를 도는 데 필요한 위치의 수입니다.
λi=θi2π=2π⋅100002i/d
d=8 로 손으로 채워 봅니다. 2i/d 가 0,0.25,0.5,0.75 이므로 100002i/d 이 정확히 1,10,100,1000 입니다.
쌍 i
θi
파장 λi
한 바퀴 도는 데 걸리는 토큰 수
0
1
2π
약 6.3개
1
0.1
20π
약 63개
2
0.01
200π
약 628개
3
0.001
2000π
약 6283개
자동차 주행거리계와 같은 구조입니다. 맨 오른쪽 자리는 1 km마다 한 바퀴를 돌고, 그 왼쪽은 10 km마다, 그다음은 100 km마다 돕니다. 빠른 자리만 보면 12 km와 22 km를 구별하지 못하지만 자리를 다 읽으면 유일하게 정해집니다. 여기서는 10진법 대신 각도를, 자릿수 대신 (sin,cos) 쌍을 씁니다.
d=8, p=0…3 의 실제 값을 적어 보면 사다리가 하는 일이 보입니다.
p
sinp
cosp
sin0.1p
cos0.1p
sin0.01p
cos0.01p
sin0.001p
cos0.001p
0
0.0000
1.0000
0.0000
1.0000
0.0000
1.0000
0.0000
1.0000
1
0.8415
0.5403
0.0998
0.9950
0.0100
1.0000
0.0010
1.0000
2
0.9093
−0.4161
0.1987
0.9801
0.0200
0.9998
0.0020
1.0000
3
0.1411
−0.9900
0.2955
0.9553
0.0300
0.9996
0.0030
1.0000
빠른 쌍이 이웃을 가르고 느린 쌍이 문단을 가릅니다.i=0 은 한 칸만 가도 값이 크게 흔들려 바로 옆 토큰을 구별해 주고, i=3 은 세 칸을 가도 cos 이 소수 넷째 자리까지 1이라 이웃끼리는 사실상 같습니다. 대신 p 가 수천 단위로 벌어져야 비로소 움직이므로 문서의 앞뒤를 가릅니다. 어느 한 쌍도 혼자서는 위치를 못 정하고, 사다리 전체가 있어야 정해집니다.
오른쪽 열은 세 줄이 같고 왼쪽 열은 다릅니다. 위에서 유도한 그대로입니다 — pm⋅pn 은 n−m 에만 의존하지만, 교차항 q⋅pn 과 pm⋅k 가 절대 위치를 끌고 들어옵니다.
정리
어텐션 점수 QKT 는 순열 동변이라 토큰 순서를 못 본다. 순서는 벡터에 수로 실어 넣어야 한다.
위치를 각도로 적으면 값이 [−1,1] 로 유계이고 문장 길이가 식에서 사라진다. θi=10000−2i/d 는 파장이 2π 에서 2π⋅104 까지 기하급수적으로 늘어나는 사다리를 만들고, 빠른 쌍이 이웃을, 느린 쌍이 문서 규모를 가른다.
사인파 인코딩은 k 칸 이동이 고정된 행렬 곱이고 PE(m)⋅PE(n)=∑icos((n−m)θi) 로 상대 위치만 본다. 하지만 더해서 쓰면 교차항 둘이 절대 위치를 다시 끌고 들어온다.
RoPE는 더하는 대신 차원쌍마다 R(mθi) 로 돌린다. 그러면 ⟨R(mθ)q,R(nθ)k⟩=qTR((n−m)θ)k 이고, 전치·직교성·각도 덧셈 세 줄이 증명의 전부다.
남은 점수는 내적과 부호 있는 넓이를 cosδθ 와 sinδθ 로 섞은 것이다. 위치가 정하는 것은 섞는 비율뿐이다.
여기까지가 7단원의 마지막입니다. 어텐션 식 한 줄에 들어 있던 기호를 지도에 적힌 순서대로 전부 닫았습니다.
다음 단원은 그 식을 학습시키는 쪽입니다. 손실이라는 함수가 파라미터 공간 위에 만드는 지형이 어떻게 생겼는지, 그리고 그 위를 내려가는 걸음의 크기를 무엇이 정하는지를 봅니다. 다음 글에서 볼록성을 다변수로 확장하고, 신경망 손실이 왜 볼록이 아닌데도 학습이 되는지를 고차원의 확률로 설명합니다.
실수를 2^b개 격자에 사상할 때 오차의 분산이 왜 Δ²/12인지 유도하고, 그것이 비트당 6.02dB라는 SNR로 번역되는 과정을 실측과 대조했습니다. 이상치 하나가 나머지 값의 유효 비트를 어떻게 먹는지, 그리고 int4에서 성능이 무너지는 지점을 오차 예산으로 미리 계산하는 법까지.
최댓값 빼기, 로그 공간, log1p·expm1, 분산의 두 공식, 정규화의 ε, fp32 누산, 역행렬 대신 solve — 프레임워크가 몰래 해 주는 일곱 가지를 하나씩 꺼내 각각 어떤 고장을 막는지 직접 재 봤습니다. 수식을 그대로 옮긴 코드가 왜 라이브러리보다 나쁜지에 대한 목록입니다.
0.1 + 0.2가 0.3이 아닌 이유부터 시작해 머신 엡실론을 유도하고, 같은 16비트인데 fp16과 bf16이 서로 다른 지점에서 터지는 이유, 비슷한 수를 뺄 때 유효자리가 사라지는 파괴적 상쇄, 그리고 1,000만 개를 순서만 바꿔 더했을 때 오차가 백만 배 갈리는 실험까지 직접 재 봤습니다.