Skip to main content

위치 인코딩 (Positional Encoding)

학습 목표

  • Transformer에 위치 인코딩이 필요한 이유를 설명할 수 있다
  • 사인/코사인 위치 인코딩(Sinusoidal)의 수학적 원리를 이해한다
  • 학습 가능한 위치 임베딩(Learned Positional Embedding)의 장단점을 안다
  • RoPE (Rotary Position Embedding)의 핵심 아이디어를 설명할 수 있다
  • ALiBi (Attention with Linear Biases)의 접근 방식을 이해한다
  • 각 방법의 적합한 사용 시나리오를 판단할 수 있다

왜 중요한가

Transformer는 RNN과 달리 토큰을 순차적으로 처리하지 않습니다. Self-Attention은 순서와 무관하게 모든 토큰 쌍의 관계를 동시에 계산합니다. 이는 병렬 처리의 장점을 주지만, 치명적인 문제가 있습니다. “나는 너를 좋아한다”와 “너를 나는 좋아한다”를 구분할 수 없습니다. Self-Attention의 출력은 입력 순서에 대해 순열 불변(Permutation Invariant)이기 때문입니다. 즉, 입력 토큰의 순서를 바꿔도 어텐션 결과가 동일합니다. 따라서 별도의 위치 정보를 주입해야 하며, 이것이 위치 인코딩(Positional Encoding)의 역할입니다. 위치 인코딩 방식의 선택은 모델의 성능, 특히 학습 시 보지 못한 긴 시퀀스에 대한 일반화(Length Generalization) 능력에 직접적인 영향을 미칩니다.

핵심 개념

사인/코사인 위치 인코딩 (Sinusoidal)

원 Transformer 논문(Vaswani et al., 2017)에서 제안한 방식입니다. 각 위치 pospos와 차원 ii에 대해 사인/코사인 함수를 사용합니다. PE(pos,2i)=sin(pos100002i/dmodel)PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) PE(pos,2i+1)=cos(pos100002i/dmodel)PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) 여기서:
  • pospos: 시퀀스 내 토큰의 위치 (0, 1, 2, …)
  • ii: 차원 인덱스 (0, 1, …, dmodel/21d_{\text{model}}/2 - 1)
  • dmodeld_{\text{model}}: 모델 차원 (원 논문: 512)
설계 직관:
  • 짝수 차원에는 사인, 홀수 차원에는 코사인을 사용합니다
  • 낮은 차원(작은 ii)은 빠른 주기로 진동하여 국소적 위치 정보를 포착합니다
  • 높은 차원(큰 ii)은 느린 주기로 진동하여 전역적 위치 정보를 포착합니다
  • 주기가 2π2\pi에서 100002π10000 \cdot 2\pi까지 기하급수적으로 증가합니다
상대적 위치 표현: 임의의 고정 오프셋 kk에 대해 PEpos+kPE_{pos+k}PEposPE_{pos}의 선형 변환으로 표현할 수 있습니다. 이는 모델이 상대적 위치 관계를 학습할 수 있는 가능성을 제공합니다.
import torch
import math

class SinusoidalPositionalEncoding(torch.nn.Module):
    """사인/코사인 위치 인코딩 (원 Transformer 논문)"""
    def __init__(self, d_model, max_len=5000, dropout=0.1):
        super().__init__()
        self.dropout = torch.nn.Dropout(p=dropout)

        # 위치 인코딩 행렬 생성 (학습되지 않음)
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()
        div_term = torch.exp(
            torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)
        )

        pe[:, 0::2] = torch.sin(position * div_term)  # 짝수 차원
        pe[:, 1::2] = torch.cos(position * div_term)  # 홀수 차원

        pe = pe.unsqueeze(0)  # (1, max_len, d_model)
        self.register_buffer('pe', pe)  # 학습 파라미터가 아닌 버퍼로 등록

    def forward(self, x):
        """x: (batch, seq_len, d_model)"""
        x = x + self.pe[:, :x.size(1)]
        return self.dropout(x)

학습 가능한 위치 임베딩 (Learned Positional Embedding)

BERT, GPT 등에서 사용하는 방식으로, 위치 인코딩을 고정 함수가 아닌 학습 가능한 파라미터로 설정합니다. PE=Embedding(pos)PE = \text{Embedding}(pos) 각 위치에 대한 임베딩 벡터를 랜덤 초기화하고 학습 과정에서 최적화합니다.
class LearnedPositionalEncoding(torch.nn.Module):
    """학습 가능한 위치 임베딩"""
    def __init__(self, d_model, max_len=512, dropout=0.1):
        super().__init__()
        self.dropout = torch.nn.Dropout(p=dropout)
        self.pe = torch.nn.Embedding(max_len, d_model)  # 학습 가능

    def forward(self, x):
        """x: (batch, seq_len, d_model)"""
        seq_len = x.size(1)
        positions = torch.arange(seq_len, device=x.device)
        x = x + self.pe(positions)
        return self.dropout(x)
특성SinusoidalLearned
파라미터 수0 (고정)max_len×dmodel\text{max\_len} \times d_{\text{model}}
외삽(Extrapolation)가능 (이론적)불가 (학습된 범위 내만)
유연성고정 패턴데이터에 적응
성능원 논문에서 유사일반적으로 약간 우수
원 Transformer 논문에서는 두 방식의 성능이 거의 동일했다고 보고합니다. 하지만 BERT(512), GPT-2(1024) 등 대부분의 후속 모델은 학습 가능한 위치 임베딩을 선택했습니다.

RoPE (Rotary Position Embedding)

Su et al. (2021)이 제안한 RoPE는 위치 정보를 벡터의 회전으로 인코딩합니다. LLaMA, Mistral, Qwen 등 최신 대규모 언어모델에서 널리 사용됩니다. 핵심 아이디어: Query와 Key 벡터를 위치에 따라 회전시키면, 내적 결과가 상대적 위치에만 의존하게 됩니다. fq(xm,m),fk(xn,n)=g(xm,xn,mn)\langle f_q(x_m, m), f_k(x_n, n) \rangle = g(x_m, x_n, m - n) 여기서 m,nm, n은 절대 위치이지만, 내적 결과는 상대 위치 mnm - n의 함수가 됩니다. 2D 회전 행렬 적용: 인접한 두 차원을 하나의 쌍으로 묶어, 위치 mm에서의 회전을 적용합니다. (qm(2i)qm(2i+1))=(cosmθisinmθisinmθicosmθi)(q(2i)q(2i+1))\begin{pmatrix} q_m^{(2i)} \\ q_m^{(2i+1)} \end{pmatrix} = \begin{pmatrix} \cos m\theta_i & -\sin m\theta_i \\ \sin m\theta_i & \cos m\theta_i \end{pmatrix} \begin{pmatrix} q^{(2i)} \\ q^{(2i+1)} \end{pmatrix} 여기서 θi=100002i/d\theta_i = 10000^{-2i/d}는 Sinusoidal과 유사한 주파수 스케일링을 사용합니다.
def apply_rotary_emb(xq, xk, freqs_cis):
    """RoPE 적용 (간소화 버전)"""
    # xq, xk: (batch, seq_len, num_heads, d_k)
    # freqs_cis: (seq_len, d_k//2) 복소수

    # 실수 텐서를 복소수로 변환: 인접 차원 쌍 → 복소수
    xq_complex = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))
    xk_complex = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))

    # 복소수 곱으로 회전 적용
    xq_out = torch.view_as_real(xq_complex * freqs_cis).flatten(-2)
    xk_out = torch.view_as_real(xk_complex * freqs_cis).flatten(-2)

    return xq_out.type_as(xq), xk_out.type_as(xk)


def precompute_freqs_cis(d_k, max_len, theta=10000.0):
    """RoPE 주파수 사전 계산"""
    freqs = 1.0 / (theta ** (torch.arange(0, d_k, 2).float() / d_k))
    t = torch.arange(max_len)
    freqs = torch.outer(t, freqs)
    return torch.polar(torch.ones_like(freqs), freqs)  # e^(i*theta)
RoPE의 장점:
  • 상대적 위치 인코딩: 내적이 자연스럽게 상대 위치에 의존
  • 외삽 가능성: NTK-Aware Scaling, YaRN 등으로 학습 길이를 넘어서 확장 가능
  • 구현 효율성: 추가 파라미터 없이, Q와 K에만 적용

ALiBi (Attention with Linear Biases)

Press et al. (2022)이 제안한 ALiBi는 위치 인코딩을 입력에 더하는 대신, 어텐션 점수에 직접 위치 기반 바이어스를 추가합니다. softmax(qiKT+m[0,1,2,,(i1)])\text{softmax}\left(\mathbf{q}_i \mathbf{K}^T + m \cdot [0, -1, -2, \dots, -(i-1)]\right)
  • mm: 헤드별 고정 기울기 (예: 12,14,18,\frac{1}{2}, \frac{1}{4}, \frac{1}{8}, \dots)
  • 기울기는 기하급수적으로 감소하여, 각 헤드가 서로 다른 범위의 거리를 담당합니다
  • 먼 토큰일수록 큰 음수 바이어스가 추가되어, 자연스럽게 근접 토큰에 더 주목합니다
def get_alibi_slopes(num_heads):
    """ALiBi 헤드별 기울기 계산"""
    # 2의 거듭제곱 중 가장 가까운 값
    closest_power_of_2 = 2 ** math.floor(math.log2(num_heads))
    base = 2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3)))
    powers = torch.arange(1, closest_power_of_2 + 1)
    slopes = torch.pow(base, powers)

    if closest_power_of_2 != num_heads:
        extra_base = 2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3)))
        extra_powers = torch.arange(1, 2 * (num_heads - closest_power_of_2) + 1, 2)
        extra_slopes = torch.pow(extra_base, extra_powers)
        slopes = torch.cat([slopes, extra_slopes])

    return slopes


def get_alibi_bias(seq_len, num_heads):
    """ALiBi 어텐션 바이어스 행렬 생성"""
    slopes = get_alibi_slopes(num_heads)  # (num_heads,)
    # 상대 위치 행렬: position_i - position_j
    positions = torch.arange(seq_len)
    relative_positions = positions.unsqueeze(0) - positions.unsqueeze(1)  # (seq_len, seq_len)
    # 미래 마스크 적용 (인과적 어텐션의 경우)
    relative_positions = relative_positions.clamp(max=0)  # 양수는 0으로
    # (num_heads, seq_len, seq_len)
    alibi = slopes.unsqueeze(1).unsqueeze(1) * relative_positions.unsqueeze(0)
    return alibi
ALiBi의 장점:
  • 우수한 외삽 성능: 학습 시보다 긴 시퀀스에서도 성능 저하가 적음
  • 추가 파라미터 없음: 기울기가 고정 하이퍼파라미터
  • 단순한 구현: 어텐션 점수에 바이어스를 더하기만 하면 됨

위치 인코딩 방법 비교

방법절대/상대학습 여부외삽사용 모델발표
Sinusoidal절대비학습이론적 가능원 Transformer2017
Learned절대학습불가BERT, GPT-22018
RoPE상대비학습확장 기법과 결합LLaMA, Mistral, Qwen2021
ALiBi상대비학습우수BLOOM, MPT2022
RoPE 자체는 학습 범위를 벗어난 위치에서 성능이 저하됩니다. 이를 해결하기 위한 확장 기법들이 있습니다.
  • Position Interpolation (PI): 위치 인덱스를 원래 범위로 압축하여 보간합니다
  • NTK-Aware Scaling: 주파수 기저(θ\theta)를 조절하여 고주파/저주파를 동시에 처리합니다
  • YaRN (Yet another RoPE extensioN): NTK + 어텐션 스케일링을 결합합니다
  • Dynamic NTK: 시퀀스 길이에 따라 동적으로 기저를 조절합니다
이러한 기법 덕분에 RoPE 기반 모델(LLaMA 등)이 학습 시 4K 토큰에서 추론 시 128K+ 토큰까지 확장할 수 있습니다.
이론적으로는 가능하지만, 순서 정보가 완전히 사라져 문장 내 단어 순서를 구분할 수 없게 됩니다. 일부 연구에서는 위치 인코딩 없이도 어느 정도 성능을 보이지만(특히 짧은 시퀀스에서), 일반적으로는 큰 성능 저하가 발생합니다.
주파수의 범위를 결정하는 하이퍼파라미터입니다. 10000을 사용하면 가장 빠른 주파수의 파장이 2π2\pi이고 가장 느린 주파수의 파장이 100002π10000 \cdot 2\pi가 됩니다. 이 범위가 자연어의 다양한 거리 패턴을 포착하기에 적합하다는 경험적 결과에 기반합니다. RoPE도 동일한 기저 값(10000)을 사용합니다.
자연어에서 의미적 관계는 절대 위치보다 상대 위치에 더 의존합니다. “나는 학생이다”에서 “나는”과 “이다”의 관계는 문장 내 절대 위치(1번, 3번)보다 상대 거리(2칸 떨어져 있음)가 더 중요합니다. 상대적 위치 인코딩은 이러한 패턴을 더 잘 일반화하며, 특히 학습 시 보지 못한 긴 시퀀스에서 성능이 우수합니다.

체크리스트

  • Transformer에 위치 인코딩이 필요한 이유(순열 불변성)를 설명할 수 있다
  • Sinusoidal PE의 수식에서 각 요소(pos, i, 10000)의 역할을 이해했다
  • Learned PE와 Sinusoidal PE의 장단점을 비교할 수 있다
  • RoPE의 핵심 아이디어(벡터 회전 → 상대 위치)를 설명할 수 있다
  • ALiBi의 접근 방식(어텐션 바이어스)을 이해했다
  • 외삽(Extrapolation) 문제가 왜 중요한지 설명할 수 있다
  • 각 방법의 적합한 사용 시나리오를 판단할 수 있다

다음 문서