본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Rotary Position Embedding, RoPE; 회전 위치 임베딩
쿼리와 키 벡터를 토큰 위치에 비례하는 각도만큼 회전시켜, 어텐션 점수가 두 토큰의 상대 거리에만 의존하게 만드는 위치 인코딩 방식

RoPE(Rotary Position Embedding, 회전 위치 임베딩)는 트랜스포머의 셀프 어텐션에서 쿼리·키 벡터를 위치에 따라 회전시켜 순서 정보를 주는 위치 인코딩 방식이다. 2021년 4월 중국 주이 테크놀로지(Zhuiyi Technology)의 쑤젠린(Jianlin Su) 등이 RoFormer 논문으로 발표했다.[1] 위치 벡터를 임베딩에 더하는 대신 각 층의 쿼리와 키에 회전 행렬을 곱하는데, 이렇게 하면 절대 위치로 회전했는데도 두 벡터의 내적에는 상대 위치만 남는다.

학습 매개변수가 없고, 상대 위치 정보를 주면서도 KV 캐시와 잘 맞는다는 점 때문에 EleutherAI의 GPT-J·GPT-NeoX, 구글 PaLM, 메타 LLaMA 계열 등 2020년대 디코더 전용 대형 언어 모델이 잇달아 채택했다.[2][3][4] 학습 길이보다 긴 입력에서 성능이 무너지는 약점이 있어, 회전 주파수를 고쳐 컨텍스트 윈도우를 늘리는 위치 보간, NTK 인지 보간, YaRN 같은 후속 기법이 이 문서의 후반부 주제다. 위치 정보 방식 전반의 비교는 위치 인코딩 문서 참고.

원 트랜스포머는 사인·코사인 위치 벡터 pm을 토큰 임베딩에 더했다.[5] 그러면 쿼리·키 내적 (xm+pm)⊤Wq⊤Wk(xn+pn)을 전개했을 때 내용과 위치가 뒤섞인 항 네 개가 생기고, 상대 거리 m−n은 간접적으로만 드러난다. 상대 위치 방식(Shaw 외 2018, T5의 상대 위치 편향)은 점수에 거리별 항을 따로 더해 이 문제를 풀지만, 어텐션 계산을 고쳐야 하고 선형 어텐션 같은 변형에는 넣기 어렵다.

RoPE는 질문을 거꾸로 세운다. 위치 m의 쿼리를 f(q,m), 위치 n의 키를 f(k,n)으로 바꿀 때, 내적이 g(q,k,m−n)처럼 상대 위치만의 함수가 되게 하는 f를 찾는 것이다. 2차원에서는 복소수 곱, 곧 회전이 정확히 이 조건을 만족한다.

2차원 벡터 x=(x1,x2)를 복소수 x1+ix2로 보면, 위치 m에서 각도 mθ만큼 돌리는 연산은 eimθ를 곱하는 것이다.

f(x,m)=Rmθx=(cos⁡mθ−sin⁡mθsin⁡mθcos⁡mθ)(x1x2)

회전 행렬은 직교하고 Ra⊤Rb=Rb−a이므로

⟨Rmθq,Rnθk⟩=q⊤Rmθ⊤Rnθk=q⊤R(n−m)θk

가 되어 점수에는 n−m만 남는다. 회전은 벡터 길이를 바꾸지 않으므로 내용 정보(크기)도 보존된다.

머리 차원(head dimension) d(짝수)를 2차원 쌍 d/2개로 나누고, 쌍마다 다른 주파수로 돌린다. 회전 행렬은 2×2 블록이 대각선에 놓인 블록 대각 행렬이다.

RΘ,md=diag⁡(Rmθ1,Rmθ2,…,Rmθd/2),θi=b−2(i−1)/d,i=1,…,d/2

기본값 b=10000은 원 트랜스포머의 사인·코사인 위치 인코딩에서 가져온 값이다.[1] 어텐션 점수는

qm⊤kn=(RΘ,mdWqxm)⊤(RΘ,ndWkxn)=xm⊤Wq⊤RΘ,n−mdWkxn

이다. 회전은 쿼리와 키에만 적용하고 값(value)에는 적용하지 않는다. 그리고 임베딩 층 한 번이 아니라 모든 층의 어텐션마다 적용한다.[4]

주파수와 파장

편집 원본 편집

i번째 쌍의 파장(한 바퀴 도는 데 드는 토큰 수)은 λi=2π/θi=2πb2(i−1)/d다. d=128,b=10000이면 첫 쌍은 약 6.3토큰마다 한 바퀴를 돌고, 마지막 쌍의 파장은 약 5만 4천 토큰이다. 앞쪽(고주파) 쌍은 가까운 토큰 사이의 순서를, 뒤쪽(저주파) 쌍은 먼 거리의 위치를 구분한다. 이 구분은 뒤에 나오는 컨텍스트 확장 기법의 핵심이 된다.

RoFormer 논문은 이 주파수 배치에서 상대 거리가 커질수록 내적의 상한이 줄어드는 장거리 감쇠(long-term decay) 성질이 생긴다고 보였다. 멀리 떨어진 토큰일수록 연결이 약해야 한다는 직관과 맞는 성질이다.[1]

실제 코드는 행렬을 만들지 않고 원소별 곱으로 계산한다. 쌍 (x2i,x2i+1)에 대해 x′=x⊙cos⁡+rot⁡(x)⊙sin⁡ 꼴이다. 다만 어느 두 차원을 한 쌍으로 묶는지에 따라 구현이 두 갈래로 나뉜다.

방식 쌍을 이루는 차원 대표 구현
인터리브(interleaved) 인접한 (0,1),(2,3),… 메타 공식 LLaMA 코드. 벡터를 복소수 텐서로 보고 torch.polar로 만든 회전값을 곱한다.[6]
절반 분할(rotate_half) 앞 절반과 뒤 절반 (0,d/2),(1,d/2+1),… GPT-NeoX, 허깅 페이스 Transformers의 rotate_half·apply_rotary_pos_emb[7]

두 방식은 차원 순서만 다를 뿐 수학적으로 같다. 그래서 허깅 페이스의 LLaMA 변환 스크립트는 메타 체크포인트를 옮길 때 쿼리·키 가중치 행을 재배열(permute)해 절반 분할 방식에 맞춘다.[8] 체크포인트를 다른 프레임워크로 옮기거나 직접 구현할 때 이 순서를 섞으면 오류 없이 성능만 무너지므로 가장 흔한 함정이다.

아래는 절반 분할 방식의 최소 구현이다(PyTorch 2.x). 마지막 두 줄은 점수가 상대 거리에만 의존하는지 확인한다.

import torch

def rope_inv_freq(head_dim, base=10000.0):
    # theta_i = base^(-2i/d), i = 0 .. d/2-1
    return 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim))

def rope_cos_sin(positions, inv_freq):
    freqs = torch.outer(positions.float(), inv_freq)   # (T, d/2)
    emb = torch.cat([freqs, freqs], dim=-1)            # (T, d) 절반 분할 배치
    return emb.cos(), emb.sin()

def rotate_half(x):
    x1, x2 = x.chunk(2, dim=-1)
    return torch.cat([-x2, x1], dim=-1)

def apply_rope(x, cos, sin):
    # x: (batch, heads, T, head_dim), cos/sin: (T, head_dim)
    return x * cos + rotate_half(x) * sin

d = 64
inv = rope_inv_freq(d)
q, k = torch.randn(d), torch.randn(d)
def score(m, n):
    cm, sm = rope_cos_sin(torch.tensor([m]), inv)
    cn, sn = rope_cos_sin(torch.tensor([n]), inv)
    return (apply_rope(q, cm[0], sm[0]) @ apply_rope(k, cn[0], sn[0])).item()
print(score(10, 3), score(107, 100))   # 거리 7로 같으므로 같은 값

추론할 때 키는 회전을 적용한 상태로 KV 캐시에 저장한다. 키의 위치는 나중에 바뀌지 않으므로 다시 계산할 필요가 없다. 예외는 뒤에 나오는 동적 스케일링처럼 주파수가 길이에 따라 바뀌는 경우로, 이때는 회전 전 키를 캐시해야 한다.[9]

머리 차원 전부가 아니라 일부에만 RoPE를 적용하기도 한다. GPT-NeoX-20B는 GPT-J를 따라 임베딩 차원의 앞 25%에만 회전을 적용했고, 초기 실험에서 이것이 성능과 계산 효율의 균형이 가장 좋았다고 밝혔다.[2] 허깅 페이스 설정에서는 partial_rotary_factor로 지정한다. 딥시크의 멀티 헤드 잠재 어텐션은 압축 구조와 RoPE가 충돌하는 문제를 피하려고 RoPE 전용 차원을 따로 떼어 내는 분리 RoPE(decoupled RoPE)를 쓴다.

성질과 장단점

편집 원본 편집
항목 내용
매개변수 없음. 학습형 위치 임베딩과 달리 최대 길이 표를 둘 필요가 없다.
상대 위치 점수가 m−n에만 의존. 문장을 앞뒤로 밀어도 같은 패턴을 쓸 수 있다.
절대 위치 각 토큰은 절대 위치로 회전하므로, 파장이 학습 길이보다 긴 저주파 차원에는 사실상 절대 위치 정보가 남는다(YaRN 논문의 관찰).[9]
계산 비용 쿼리·키에 원소별 곱 두 번. 어텐션 커널(예: 플래시 어텐션)을 고칠 필요가 없다.
선형 어텐션 호환 점수에 편향을 더하는 방식이 아니라 쿼리·키 자체를 바꾸므로 선형 어텐션에도 넣을 수 있다. RoFormer 논문은 Performer에 RoPE를 넣어 수렴이 빨라졌다고 보고했다.[1]
외삽 학습 길이를 넘는 위치에서는 학습 중 본 적 없는 회전각 조합이 나와 성능이 급격히 떨어진다.

RoFormer 논문의 실험은 규모가 작다. WMT 2014 영어-독일어 번역에서 BLEU 27.3(기본 트랜스포머)을 27.5로 올렸고, BERT 사전 학습에서 손실이 더 빨리 줄었다고 보고했다. 저자들 스스로 왜 수렴이 빨라지는지는 충분히 설명하지 못했다고 한계로 적었다.[1] RoPE가 표준이 된 것은 이 논문의 수치보다는 GPT-J, GPT-NeoX, PaLM, LLaMA 같은 대형 모델에서 쓰여 검증된 덕이 크다. PaLM 논문은 RoPE가 긴 시퀀스에서 더 낫다는 점을 채택 이유로 들었다.[3]

컨텍스트 확장

편집 원본 편집

RoPE 모델을 학습 길이 L보다 긴 L′에서 쓰려면 회전각이 학습 중 본 범위를 벗어나지 않게 해야 한다. 이하 s=L′/L을 확장 배수라 한다.

위치 보간 (PI)

편집 원본 편집

메타의 천서우위안(Shouyuan Chen) 등은 2023년 6월 위치 번호를 줄이는 위치 보간(Position Interpolation, PI)을 제안했다. 위치 m을 mL/L′로 바꿔 모든 위치를 학습 범위 안으로 밀어 넣는 것이다. 외삽(extrapolation)하면 어텐션 점수가 터무니없이 커질 수 있지만 보간의 상한은 외삽보다 약 600배 작다는 이론 분석을 함께 냈다. LLaMA 7B~65B의 컨텍스트를 2048에서 최대 32768로 늘리는 데 1000단계 이내의 미세 조정이면 충분했다.[10] 같은 시기 개인 개발자 kaiokendev도 같은 아이디어를 공개했다.[9]

PI의 약점은 모든 주파수를 똑같이 s배 늦춘다는 점이다. 고주파 차원까지 늦어지면 가까운 토큰 사이의 위치 구분이 흐려지고, 미세 조정을 해도 s≈8을 넘으면 출력이 나빠지기 시작했다.[9]

NTK 인지 보간과 기저 조정

편집 원본 편집

"NTK 인지(NTK-aware)" 보간은 2023년 레딧 사용자 bloc97이 공개한 방법으로, 위치 대신 기저 b를 키운다. 고주파는 거의 그대로 두고 저주파일수록 많이 늦추는 효과가 난다.

b′=b⋅sd/(d−2)

미세 조정 없이도 PI보다 잘 동작했지만, 일부 차원이 학습 범위를 살짝 벗어나 외삽되므로 미세 조정까지 하면 PI보다 못한 경우가 있었다. 같은 해 emozilla가 공개한 동적 NTK(Dynamic NTK)는 고정 배수 대신 지금 시퀀스 길이 l′에 맞춰 s=max⁡(1,l′/L)를 매 순전파마다 다시 정한다.[9] 두 방법 모두 논문 없이 커뮤니티에서 먼저 퍼졌고, YaRN 논문이 정리했다.

메타의 Code Llama(2023)는 기저를 10,000에서 1,000,000으로 올리고 16,384 토큰 시퀀스로 미세 조정해, 최대 10만 토큰에서도 안정적으로 동작했다고 보고했다. 논문은 이를 "조정된 기저 주파수"(adjusted base frequency, ABF)라 불렀다.[11] 라마 3은 사전 학습부터 기저를 500,000으로 잡았다.[12] 처음부터 긴 문맥을 염두에 두고 저주파 파장을 늘려 두는 셈이다.

NTK-by-parts와 YaRN

편집 원본 편집

YaRN 논문(Nous Research 등, 2023년 8월 공개, ICLR 2024)은 차원마다 처리를 달리하는 "NTK-by-parts"를 정식화했다. 학습 길이 동안 몇 바퀴 도는지 r(d)=L/λd를 기준으로

γ(r)={0,r<α1,r>βr−αβ−α,otherwise,θd′=(1−γ(r(d)))θds+γ(r(d))θd

로 바꾼다. 많이 도는 고주파 차원(r>β)은 건드리지 않고, 한 바퀴도 못 도는 저주파 차원(r<α)은 PI처럼 보간하며, 그 사이는 선형으로 섞는다. 라마 계열에는 α=1,β=32가 잘 맞았다.[9]

YaRN은 여기에 어텐션 온도 보정을 더한다. 문맥이 길어지면 소프트맥스 분포가 퍼지므로 로짓을 t로 나누는데, 라마 계열 권장값은

1/t=0.1ln⁡s+1

이다. 이 값을 쿼리·키 양쪽의 cos/sin에 곱하면 어텐션 코드를 고치지 않고도 같은 효과가 나며 추가 비용이 없다.[9] 논문은 이전 방법보다 토큰 10배, 학습 단계 2.5배 적은 자원으로 확장했고, 원 사전 학습 데이터의 0.1% 미만으로 미세 조정해 라마 2 7B·13B를 64K·128K 컨텍스트로 늘렸다고 보고했다. 딥시크 V2는 YaRN(s=40,α=1,β=32)으로 4K 컨텍스트를 128K로 늘렸다.[13]

라마 3.1 방식

편집 원본 편집

라마 3.1은 NTK-by-parts와 비슷한 구간별 스케일링을 썼다. 메타 공식 코드의 apply_scaling은 원래 길이 8192를 기준으로, 파장이 8192보다 긴 주파수는 8로 나누고, 2048(=8192/4)보다 짧은 주파수는 그대로 두며, 그 사이는 부드럽게 섞는다(배수 8, low_freq_factor 1, high_freq_factor 4).[6] 사전 학습 중 컨텍스트를 8K에서 128K까지 6단계에 걸쳐 늘렸고 이 단계에 약 8천억 토큰을 썼다.[12]

방법 바꾸는 것 고주파 차원 저주파 차원 미세 조정 비고
위치 보간(PI) 위치 m→m/s 늦춤(근거리 구분 약화) 보간 필요(1000단계 이내) 구현 가장 단순
NTK 인지 기저 b→bsd/(d−2) 거의 유지 일부 외삽 없이도 동작 최적 기저를 실험으로 찾아야 함
동적 NTK 길이에 따라 s를 매번 갱신 거의 유지 일부 외삽 없음 추론 시점 기법. 회전 전 키를 캐시해야 함
기저 조정(ABF) 기저를 크게(예: 106) 유지 늦춤 긴 문맥 미세 조정과 함께 Code Llama, 라마 3(5×105)
NTK-by-parts 차원별 램프 보간 유지 보간 소량 α,β 조정
YaRN NTK-by-parts + 온도 유지 보간 소량(원 데이터 0.1% 미만) 딥시크 V2 등
라마 3.1 파장 구간별 스케일 유지 8로 나눔 장문 사전 학습 rope_type="llama3"
LongRoPE 차원별 배수를 탐색으로 결정 탐색 결과 탐색 결과 점진적 2048K 토큰까지 확장 보고[14]

아래는 PI·NTK 인지·YaRN의 주파수 계산을 논문 수식대로 옮긴 것이다. 위의 rope_inv_freq를 그대로 쓴다.

import math

def pi_inv_freq(head_dim, base, s):
    return rope_inv_freq(head_dim, base) / s            # 위치 m -> m/s 와 같다

def ntk_aware_inv_freq(head_dim, base, s):
    return rope_inv_freq(head_dim, base * s ** (head_dim / (head_dim - 2)))

def yarn_inv_freq(head_dim, base, s, orig_len, alpha=1.0, beta=32.0):
    inv = rope_inv_freq(head_dim, base)
    r = orig_len * inv / (2 * math.pi)                  # 학습 길이 동안 도는 바퀴 수 L/lambda
    gamma = ((r - alpha) / (beta - alpha)).clamp(0, 1)
    inv_new = (1 - gamma) * inv / s + gamma * inv
    mscale = 0.1 * math.log(s) + 1.0                    # sqrt(1/t): cos, sin 에 곱한다
    return inv_new, mscale

허깅 페이스 Transformers는 이 변형들을 설정값으로 고른다. 지원하는 rope_type은 default, linear(PI), dynamic, yarn, longrope, llama3이다. 4.x 버전은 설정 키가 rope_scaling, 5.x 버전은 rope_parameters다.[15]

{"rope_type": "yarn", "factor": 4.0, "original_max_position_embeddings": 32768, "rope_theta": 1000000.0}

다른 위치 방식과 비교

편집 원본 편집
방식 넣는 곳 상대 위치 매개변수 길이 외삽
사인·코사인 절대 위치 입력 임베딩에 더함 간접 없음 약함
학습형 절대 위치(BERT, GPT-2) 입력 임베딩에 더함 없음 최대 길이×차원 불가(표 밖 위치 없음)
T5 상대 위치 편향 어텐션 점수에 거리 구간별 학습 편향 직접 구간 수×헤드 수 보통
ALiBi 어텐션 점수에 거리 비례 음의 편향 직접 없음 강함. 1024로 학습해 2048에서 같은 퍼플렉서티[16]
RoPE 각 층 쿼리·키를 회전 직접(내적에 내장) 없음 약함. 보간 기법으로 보완

실무 선택 기준

편집 원본 편집
  • 새 디코더 모델을 만든다면 LLaMA, PaLM, GPT-NeoX, 딥시크 등이 검증한 RoPE에서 출발하는 것이 안전하다. 목표 컨텍스트가 길면 처음부터 기저를 크게(라마 3은 500,000) 잡는다.
  • 이미 학습된 RoPE 모델을 늘릴 때: 미세 조정 없이 당장 2배 안팎이 필요하면 동적 NTK, 소량 미세 조정이 가능하면 YaRN, 구현을 최소로 하려면 PI가 무난하다. 모델 카드가 권하는 설정이 있으면 그것을 따른다.
  • 컨텍스트를 늘리면 짧은 입력 성능이 떨어지는지 꼭 확인한다. PI 논문과 라마 3 보고서 모두 원래 길이 안의 성능 유지 여부를 따로 평가했다.[10][12]
  • 가중치를 변환할 때는 인터리브와 절반 분할 중 어느 배치인지 먼저 확인한다.
  1. ↑ 1.0 1.1 1.2 1.3 1.4 Jianlin Su 외, 〈RoFormer: Enhanced Transformer with Rotary Position Embedding〉, 2021. arXiv:2104.09864
  2. ↑ 2.0 2.1 Sid Black 외, 〈GPT-NeoX-20B: An Open-Source Autoregressive Language Model〉, 2022. arXiv:2204.06745
  3. ↑ 3.0 3.1 Aakanksha Chowdhery 외, 〈PaLM: Scaling Language Modeling with Pathways〉, 2022. arXiv:2204.02311
  4. ↑ 4.0 4.1 Hugo Touvron 외, 〈LLaMA: Open and Efficient Foundation Language Models〉, 2023. arXiv:2302.13971
  5. ↑ Ashish Vaswani 외, 〈Attention Is All You Need〉, NeurIPS 2017. arXiv:1706.03762
  6. ↑ 6.0 6.1 meta-llama/llama-models, models/llama3/model.py
  7. ↑ huggingface/transformers, modeling_llama.py
  8. ↑ huggingface/transformers v4.40.0, convert_llama_weights_to_hf.py
  9. ↑ 9.0 9.1 9.2 9.3 9.4 9.5 9.6 Bowen Peng, Jeffrey Quesnelle, Honglu Fan, Enrico Shippole, 〈YaRN: Efficient Context Window Extension of Large Language Models〉, ICLR 2024. arXiv:2309.00071
  10. ↑ 10.0 10.1 Shouyuan Chen 외, 〈Extending Context Window of Large Language Models via Positional Interpolation〉, 2023. arXiv:2306.15595
  11. ↑ Baptiste Rozière 외, 〈Code Llama: Open Foundation Models for Code〉, 2023. arXiv:2308.12950
  12. ↑ 12.0 12.1 12.2 Llama Team, AI @ Meta, 〈The Llama 3 Herd of Models〉, 2024. arXiv:2407.21783
  13. ↑ DeepSeek-AI, 〈DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model〉, 2024. arXiv:2405.04434
  14. ↑ Yiran Ding 외, 〈LongRoPE: Extending LLM Context Window Beyond 2 Million Tokens〉, 2024. arXiv:2402.13753
  15. ↑ huggingface/transformers, modeling_rope_utils.py
  16. ↑ Ofir Press, Noah A. Smith, Mike Lewis, 〈Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation〉, ICLR 2022. arXiv:2108.12409