포지셔널 인코딩
Positional Encoding
시퀀스 내 위치 정보를 벡터로 인코딩. Transformer가 순서를 이해하게 함.
Positional Encoding
시퀀스 내 위치 정보를 벡터로 인코딩. Transformer가 순서를 이해하게 함.
포지셔널 인코딩(Positional Encoding)은 Transformer 아키텍처에서 시퀀스 내 토큰의 순서 정보를 모델에 제공하는 핵심 기법입니다. RNN이나 LSTM과 달리 Transformer는 Self-Attention 메커니즘을 사용해 모든 토큰을 동시에 처리하기 때문에, 별도의 위치 정보 없이는 "나는 사과를 먹었다"와 "사과를 나는 먹었다"를 구분할 수 없습니다. 포지셔널 인코딩은 각 토큰의 임베딩에 위치 정보를 더해 이 문제를 해결합니다.
2017년 "Attention Is All You Need" 논문에서 처음 제안된 사인/코사인 기반 포지셔널 인코딩은 서로 다른 주파수의 sin과 cos 함수를 조합해 각 위치에 고유한 벡터를 할당합니다. PE(pos, 2i) = sin(pos / 10000^(2i/d))와 PE(pos, 2i+1) = cos(pos / 10000^(2i/d)) 공식을 사용하며, 이 방식은 학습 없이도 임의의 길이 시퀀스를 처리할 수 있고, 상대적 위치 관계를 선형 변환으로 표현할 수 있다는 장점이 있습니다.
2024-2025년 현재 다양한 변형이 발전했습니다. RoPE(Rotary Position Embedding)는 LLaMA, Mistral 등에서 사용되며 상대적 위치 정보를 효과적으로 인코딩합니다. ALiBi(Attention with Linear Biases)는 Attention 점수에 직접 위치 편향을 추가해 외삽(extrapolation) 성능이 뛰어납니다. 학습 가능한 위치 임베딩은 BERT, GPT에서 사용되며 데이터로부터 최적의 위치 표현을 학습합니다. 최근에는 컨텍스트 길이를 100K 이상으로 확장하기 위한 YaRN, Streaming LLM 등의 기법도 등장했습니다.
실무에서 포지셔널 인코딩 선택은 모델 성능에 직접적인 영향을 미칩니다. 긴 문서 처리가 필요한 RAG 시스템에서는 RoPE나 ALiBi를 선호하고, Vision Transformer에서는 2D 좌표를 인코딩하기 위한 별도의 설계가 필요합니다. 최근 GPT-4 Turbo와 Claude 3 등은 128K 이상의 컨텍스트를 지원하는데, 이는 효율적인 포지셔널 인코딩 기법의 발전 덕분입니다.
PyTorch를 사용한 사인/코사인 포지셔널 인코딩과 RoPE 구현 예제입니다.
import torch
import torch.nn as nn
import math
class SinusoidalPositionalEncoding(nn.Module):
"""원본 Transformer 사인/코사인 포지셔널 인코딩"""
def __init__(self, d_model: int, max_len: int = 5000, dropout: float = 0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
# 위치 인코딩 행렬 생성 [max_len, d_model]
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
# 10000^(2i/d_model) 계산
div_term = torch.exp(
torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
)
# sin은 짝수 인덱스, cos은 홀수 인덱스
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: torch.Tensor) -> torch.Tensor:
"""x: [batch_size, seq_len, d_model]"""
x = x + self.pe[:, :x.size(1), :]
return self.dropout(x)
class RotaryPositionalEncoding(nn.Module):
"""RoPE: Rotary Position Embedding (LLaMA, Mistral 등에서 사용)"""
def __init__(self, dim: int, max_len: int = 2048, base: int = 10000):
super().__init__()
self.dim = dim
self.max_len = max_len
# 주파수 계산
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
self.register_buffer('inv_freq', inv_freq)
# 캐시 생성
self._build_cache(max_len)
def _build_cache(self, seq_len: int):
t = torch.arange(seq_len, device=self.inv_freq.device)
freqs = torch.einsum('i,j->ij', t, self.inv_freq)
emb = torch.cat((freqs, freqs), dim=-1)
self.register_buffer('cos_cached', emb.cos())
self.register_buffer('sin_cached', emb.sin())
def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple:
"""Query와 Key에 회전 변환 적용"""
seq_len = q.shape[1]
cos = self.cos_cached[:seq_len].unsqueeze(0)
sin = self.sin_cached[:seq_len].unsqueeze(0)
q_embed = (q * cos) + (self._rotate_half(q) * sin)
k_embed = (k * cos) + (self._rotate_half(k) * sin)
return q_embed, k_embed
def _rotate_half(self, x: torch.Tensor) -> torch.Tensor:
x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
return torch.cat((-x2, x1), dim=-1)
# 사용 예시
if __name__ == "__main__":
batch_size, seq_len, d_model = 2, 128, 512
# 사인/코사인 인코딩
sinusoidal_pe = SinusoidalPositionalEncoding(d_model)
x = torch.randn(batch_size, seq_len, d_model)
x_with_pos = sinusoidal_pe(x)
print(f"Sinusoidal PE 출력: {x_with_pos.shape}")
# RoPE
rope = RotaryPositionalEncoding(dim=64) # head_dim
q = torch.randn(batch_size, seq_len, 8, 64) # 8 heads
k = torch.randn(batch_size, seq_len, 8, 64)
q_rope, k_rope = rope(q, k)
print(f"RoPE 적용 후 Q: {q_rope.shape}, K: {k_rope.shape}")
| 방식 | 최대 컨텍스트 | 외삽 성능 | 사용 모델 |
|---|---|---|---|
| Sinusoidal (Absolute) | 학습 시 길이 | 보통 | 원본 Transformer |
| Learned Embedding | 학습 시 길이 | 낮음 | BERT, GPT-2 |
| RoPE | 8K~128K+ | 우수 | LLaMA, Mistral, Qwen |
| ALiBi | 무제한 | 매우 우수 | BLOOM, MPT |
| YaRN (RoPE 확장) | 128K+ | 우수 | LLaMA 2 Long |
Learned Positional Embedding을 사용하는 모델(BERT, GPT-2)은 학습 시 max_length를 초과하면 위치 임베딩이 없어 성능이 급격히 저하됩니다. 반드시 모델의 최대 컨텍스트 길이를 확인하세요.
포지셔널 인코딩의 차원은 토큰 임베딩 차원(d_model)과 정확히 일치해야 합니다. 더하기 연산이 불가능하면 런타임 에러가 발생합니다.
문서 QA, 코드 분석 등 긴 시퀀스 처리가 필요하면 처음부터 RoPE 기반 모델(LLaMA 3, Mistral, Claude)을 선택하세요. 나중에 컨텍스트를 확장하는 것보다 훨씬 안정적입니다.