Mục lục
- Mục tiêu bài học
- Vì sao self-attention không biết thứ tự
- Ý tưởng Positional Encoding
- Sinusoidal PE — công thức Vaswani 2017
- Tính chất hình học của sinusoidal
- Vì sao chọn sin và cos
- Code sinusoidal PE bằng PyTorch
- Visualize heatmap PE
- Learned positional embedding
- Absolute vs relative PE
- RoPE — Rotary Position Embedding
- Vì sao RoPE encode relative position
- Code RoPE cơ bản
- ALiBi — Attention with Linear Biases
- Long context — YaRN, NTK scaling
- So sánh — lựa chọn 2024-2025
- PE cho ảnh, audio, video
- Common bug
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài này, bạn cần trả lời được:
- Vì sao self-attention nguyên gốc không phân biệt được "I love you" với "you love I".
- Sinusoidal PE encode position bằng cách nào, công thức sin / cos chính xác là gì.
- Khác biệt giữa absolute PE và relative PE — và vì sao relative thường tốt hơn cho long context.
- RoPE hoạt động ra sao, vì sao thành modern default trong Llama, Mistral, Qwen, DeepSeek.
- YaRN / NTK scaling là gì, mở rộng context window từ 4K lên 128K kiểu nào.
- Khi viết Transformer tự tay, lỗi PE phổ biến nhất là gì.
Bài 10 và 11 đã giả định input \( X \) đã có thông tin vị trí; bài này bù chính xác chỗ trống đó.
Vì sao self-attention không biết thứ tự
Gọi \( X = [x_1, x_2, \dots, x_n] \) là dãy embedding đầu vào. Self-attention tính \( Q = XW^Q, K = XW^K, V = XW^V \) rồi softmax. Mọi phép toán đều là dot product giữa các hàng — không có bước nào tham chiếu chỉ số \( i \) riêng biệt.
Hệ quả: nếu hoán vị các hàng của \( X \) bằng một ma trận \( P \) (permutation matrix), output cũng bị hoán vị tương ứng nhưng nội dung không đổi. Self-attention là permutation equivariant:
\[ \text{SelfAttention}(PX) = P \cdot \text{SelfAttention}(X) \]
Ví dụ cụ thể: hai câu "I love you" và "you love I" cùng dùng 3 token I, love, you, embedding giống nhau, chỉ khác thứ tự. Set các vector hàng của \( X \) là như nhau (chỉ permute), nên với một model bag-of-words attention, output cho vị trí "love" sẽ giống hệt — model không phân biệt nổi.
Trong NLP, thứ tự quyết định nghĩa. Vaswani et al. 2017 vì vậy phải thêm signal vị trí trước khi đưa \( X \) vào lớp attention đầu tiên. Đây chính là positional encoding.
Ý tưởng Positional Encoding
Mỗi vị trí \( i \in \{0, 1, \dots, n-1\} \) được gán một vector \( \text{PE}(i) \in \mathbb{R}^{d_{\text{model}}} \). Vector này cộng vào token embedding trước khi đưa vào Transformer block:
\[ x_i' = x_i + \text{PE}(i) \]
Hai vị trí khác nhau \( \Rightarrow \) hai vector PE khác nhau \( \Rightarrow \) hai input vào attention khác nhau. Self-attention không cần thay đổi — chỉ cần input đã chứa signal vị trí, model học cách dùng signal đó.
Cách thay thế là concat thay vì cộng (\( x_i' = [x_i; \text{PE}(i)] \)) — ít dùng vì làm tăng \( d_{\text{model}} \). Cộng được chọn vì không tăng số chiều và thực nghiệm cho kết quả tương đương (Vaswani et al. 2017).
Bốn nhóm cách tính \( \text{PE}(i) \) thông dụng:
- Sinusoidal — công thức cố định, không học (Bước 4).
- Learned — embedding table có thể học (Bước 9).
- RoPE — không cộng vào X mà xoay Q, K (Bước 11).
- ALiBi — không cộng vào X mà thêm bias vào attention score (Bước 14).
Sinusoidal PE — công thức Vaswani 2017
Vaswani et al. 2017 đề xuất công thức cố định (không có tham số học):
\[ \text{PE}(i, 2k) = \sin\!\left(\frac{i}{10000^{2k / d_{\text{model}}}}\right) \]
\[ \text{PE}(i, 2k+1) = \cos\!\left(\frac{i}{10000^{2k / d_{\text{model}}}}\right) \]
Trong đó:
- \( i \) là vị trí trong chuỗi, \( i = 0, 1, 2, \dots \).
- \( k \) là chỉ số cặp dimension, \( k = 0, 1, \dots, d_{\text{model}}/2 - 1 \).
- \( d_{\text{model}} \) là chiều ẩn (vd 512, 768, 4096).
- 10000 là hằng số chọn theo paper, chi phối khoảng tần số.
Cặp dimension \( (2k, 2k+1) \) chia sẻ cùng tần số góc \( \omega_k = 10000^{-2k/d_{\text{model}}} \). Một là sin của \( i \omega_k \), một là cos của \( i \omega_k \) — về sau ta thấy đây chính là chìa khoá để model "đọc" được khoảng cách tương đối.
Ví dụ \( d_{\text{model}} = 4 \), \( i = 5 \):
\[ \text{PE}(5) = \left[\sin(5), \cos(5), \sin(5 / 100), \cos(5 / 100)\right] \]
Hai chiều đầu dao động nhanh theo \( i \), hai chiều sau gần như không đổi với \( i \) trong khoảng ngắn.
Tính chất hình học của sinusoidal
Tần số góc của cặp dimension \( k \):
\[ \omega_k = \frac{1}{10000^{2k/d_{\text{model}}}} \]
Bước sóng tương ứng (số position để hoàn thành 1 chu kỳ):
\[ \lambda_k = \frac{2\pi}{\omega_k} = 2\pi \cdot 10000^{2k/d_{\text{model}}} \]
Với \( d_{\text{model}} = 512 \):
- \( k = 0 \): \( \lambda_0 = 2\pi \approx 6.28 \) position — dao động rất nhanh, phân biệt vị trí lân cận.
- \( k = 255 \): \( \lambda_{255} \approx 2\pi \cdot 10000 \approx 62800 \) — gần như hằng số trên khoảng vài chục nghìn position, phân biệt "vùng" lớn trong chuỗi.
Tập hợp tần số phủ một dải logarit từ rất ngắn tới rất dài. Mỗi position \( i \) có "vân tay" đặc trưng là tổ hợp giá trị sin/cos ở mọi tần số. Hai position khác nhau cho hai vân tay khác nhau (trong phạm vi không trùng chu kỳ ở mọi tần số đồng thời — thực tế không xảy ra với \( n \) vừa phải).
Vì sao chọn sin và cos
Vaswani et al. 2017 chọn sin / cos vì hai tính chất hữu ích:
- Định nghĩa cho mọi \( i \in \mathbb{R} \). Khác với learned PE phải fix max length, sinusoidal có thể tính cho position lớn hơn max length lúc train. Tuy về lý thuyết "extrapolate" được, thực nghiệm cho thấy model train với context ngắn thường vẫn không generalize tốt sang context rất dài — đây là động lực để RoPE / ALiBi ra đời.
- Linear transformation cho relative position. Với mỗi offset \( \delta \) cố định, tồn tại ma trận \( M_\delta \) sao cho \( \text{PE}(i + \delta) = M_\delta \cdot \text{PE}(i) \) — vì sin / cos có công thức cộng góc:
\[ \sin(i\omega + \delta\omega) = \sin(i\omega)\cos(\delta\omega) + \cos(i\omega)\sin(\delta\omega) \]
\[ \cos(i\omega + \delta\omega) = \cos(i\omega)\cos(\delta\omega) - \sin(i\omega)\sin(\delta\omega) \]
Trong không gian cặp \( (\sin, \cos) \), shift position bằng \( \delta \) là xoay một góc \( \delta \omega \). Self-attention dùng dot product, dot product giữ thông tin "khoảng cách góc" — model có thể học để rút ra signal relative position dù chỉ thấy absolute position ở input.
Code sinusoidal PE bằng PyTorch
Một implementation chuẩn của sinusoidal PE:
import math
import torch
def sinusoidal_pe(max_len: int, d_model: int) -> torch.Tensor:
pe = torch.zeros(max_len, d_model)
pos = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) # (max_len, 1)
div_term = torch.exp(
torch.arange(0, d_model, 2, dtype=torch.float)
* (-math.log(10000.0) / d_model)
) # (d_model/2,)
pe[:, 0::2] = torch.sin(pos * div_term)
pe[:, 1::2] = torch.cos(pos * div_term)
return pe # (max_len, d_model)
pe = sinusoidal_pe(max_len=512, d_model=64)
print(pe.shape) # torch.Size([512, 64])
print(pe[0, :8]) # row position 0: [0, 1, 0, 1, 0, 1, 0, 1]
print(pe[1, :8]) # row position 1
Lưu ý:
div_termdùng exp/log để tránh tính \( 10000^x \) trực tiếp — số hơn ổn định khi \( d_{\text{model}} \) lớn.- Ở position \( i = 0 \): \( \sin(0) = 0, \cos(0) = 1 \) cho mọi tần số. Hàng đầu của PE luôn là \( [0, 1, 0, 1, \dots] \).
- PE thường khởi tạo một lần và lưu như buffer (
register_buffer), không cần grad.
Cách dùng trong forward:
x = token_embedding(input_ids) # (B, n, d_model)
x = x + pe[: x.size(1)].unsqueeze(0) # broadcast theo batch
Visualize heatmap PE
Vẽ ma trận \( (\text{max\_len}, d_{\text{model}}) \) thành heatmap:
import matplotlib.pyplot as plt
pe = sinusoidal_pe(max_len=100, d_model=64)
plt.imshow(pe.numpy(), aspect="auto", cmap="RdBu")
plt.colorbar()
plt.xlabel("dimension index")
plt.ylabel("position i")
plt.title("Sinusoidal PE — max_len=100, d_model=64")
plt.show()
Pattern điển hình quan sát được:
- Cột bên trái (\( k \) nhỏ) dao động sin/cos rất nhanh theo trục dọc — wavelength ngắn.
- Cột bên phải (\( k \) lớn) gần như đồng màu trên một dải position lớn — wavelength rất dài.
- Hai hàng position kế cận có vector PE gần nhau theo cosine similarity — model "biết" hai vị trí lân cận có quan hệ gần.
- Cosine similarity giữa \( \text{PE}(i) \) và \( \text{PE}(j) \) giảm theo \( |i - j| \) — tài liệu trực quan rất tốt để tin rằng PE encode được khoảng cách.
Learned positional embedding
Cách thứ hai: coi PE là bảng embedding học cùng model, y hệt token embedding nhưng index bằng position thay vì token id.
import torch.nn as nn
class LearnedPE(nn.Module):
def __init__(self, max_len: int, d_model: int):
super().__init__()
self.pe = nn.Embedding(max_len, d_model)
def forward(self, x): # x: (B, n, d_model)
n = x.size(1)
pos = torch.arange(n, device=x.device) # (n,)
return x + self.pe(pos).unsqueeze(0) # broadcast batch
Tham số: \( \text{max\_len} \cdot d_{\text{model}} \). Vd GPT-2 với n_positions = 1024, d_model = 768 → 786.432 tham số chỉ cho PE.
Dùng trong: BERT, GPT-2, GPT-3, ViT (Dosovitskiy et al. 2020).
Ưu điểm: model có thể học pattern PE phù hợp với data thay vì bị ép vào sin / cos.
Hạn chế quan trọng:
- Phải fix max length trước. Position vượt quá max → không có embedding, phải truncate.
- Không extrapolate được. Position chưa thấy trong training là vector ngẫu nhiên — model không xử lý nổi.
- Tham số thừa với context dài. PE cho context 128K ở \( d = 4096 \) tốn 0.5B tham số.
Đây là lý do các model 2023+ chuyển sang RoPE / ALiBi.
Absolute vs relative PE
Absolute PE: encode chính chỉ số vị trí 0, 1, 2, ... Sinusoidal Vaswani 2017 và learned PE của GPT-2 đều thuộc loại này. Mỗi token cộng một vector cố định theo vị trí của nó.
Relative PE: thay vì gán vector cho từng position, mô hình hóa khoảng cách \( i - j \) giữa hai token trực tiếp trong attention score:
\[ s_{ij} = \frac{q_i \cdot k_j}{\sqrt{d_k}} + b(i - j) \]
với \( b(\cdot) \) là hàm phụ thuộc relative distance. Đề xuất sớm: Shaw et al. 2018 (arXiv:1803.02155) thêm vector học được cho mỗi distance bucket.
Vì sao relative thường tốt hơn cho long context:
- Pattern attention thực ra phụ thuộc khoảng cách nhiều hơn vị trí tuyệt đối. "Tính từ đứng trước danh từ" liên quan đến \( i - j = -1 \), không liên quan vị trí 5 hay 5000.
- Khi context window mở rộng, relative encoding không cần định nghĩa lại vector cho vị trí mới — chỉ cần biết max distance.
- Translation invariance: nếu shift toàn bộ chuỗi, output cho từng cặp token không đổi.
RoPE và ALiBi đều là cách thực thi relative PE tinh gọn và hiệu quả hơn Shaw 2018.
RoPE — Rotary Position Embedding
Su et al. 2021 (RoFormer, arXiv:2104.09864) đưa ra Rotary Position Embedding — modern default cho mọi LLM lớn 2023-2025 (Llama, Mistral, DeepSeek, Qwen, GLM, Yi, Gemma 2).
Thay vì cộng vector vào X, RoPE xoay Q và K theo position trước khi tính dot product. Chia mỗi vector \( d_k \) chiều thành các cặp \( (x_{2k}, x_{2k+1}) \) và xoay cặp đó một góc \( i \theta_k \):
\[ \begin{pmatrix} x_{2k}' \\ x_{2k+1}' \end{pmatrix} = \begin{pmatrix} \cos(i\theta_k) & -\sin(i\theta_k) \\ \sin(i\theta_k) & \phantom{-}\cos(i\theta_k) \end{pmatrix} \begin{pmatrix} x_{2k} \\ x_{2k+1} \end{pmatrix} \]
với tần số cơ sở giống sinusoidal:
\[ \theta_k = 10000^{-2k/d_k}, \qquad k = 0, 1, \dots, d_k/2 - 1 \]
Áp dụng riêng cho \( Q \) và \( K \), không đụng \( V \). Token ở vị trí \( i \) có vector Q, K bị xoay góc \( i\theta_k \) theo cặp.
Vì sao RoPE encode relative position
Xét một cặp dimension. Coi \( (x_{2k}, x_{2k+1}) \) như số phức \( z = x_{2k} + i x_{2k+1} \). Phép xoay góc \( \alpha \) tương ứng nhân \( z \) với \( e^{i\alpha} \).
Token vị trí \( i \) có query \( q_i \cdot e^{i \cdot i \theta_k} \), token vị trí \( j \) có key \( k_j \cdot e^{i \cdot j \theta_k} \). Dot product thực (lấy phần thực của tích liên hợp):
\[ \langle \text{RoPE}(q_i, i), \text{RoPE}(k_j, j) \rangle = \text{Re}\!\left( q_i \cdot \overline{k_j} \cdot e^{i(i-j)\theta_k} \right) \]
Dot product chỉ phụ thuộc \( q_i, k_j \) và hiệu \( i - j \) — KHÔNG phụ thuộc \( i, j \) riêng biệt. Đây là tính chất quan trọng: RoPE encode absolute position lúc apply, nhưng attention score nhận được lại là hàm của relative distance. Translation invariance "miễn phí".
Ba ưu điểm thực tế:
- Relative implicit, không thêm tham số. Khác Shaw 2018 cần bảng embedding distance, RoPE chỉ là phép xoay deterministic.
- Compatible với Flash Attention. Vì chỉ biến đổi Q, K trước attention, không can thiệp cấu trúc attention matrix — Flash Attention chạy bình thường.
- Extrapolate khá tốt khi kết hợp với scaling (Bước 15). Train 4K, dùng 16K-128K với fine-tune nhẹ.
Code RoPE cơ bản
Implementation tối giản RoPE (theo style Llama):
import torch
def build_rope_cache(max_len: int, d_k: int, base: float = 10000.0):
half = d_k // 2
freqs = 1.0 / (base ** (torch.arange(0, half).float() / half)) # (d_k/2,)
pos = torch.arange(max_len).float() # (max_len,)
angles = torch.outer(pos, freqs) # (max_len, d_k/2)
return torch.cos(angles), torch.sin(angles)
def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor):
# x: (B, n, d_k), cos/sin: (n, d_k/2)
x_even = x[..., 0::2]
x_odd = x[..., 1::2]
rot_even = x_even * cos - x_odd * sin
rot_odd = x_even * sin + x_odd * cos
out = torch.empty_like(x)
out[..., 0::2] = rot_even
out[..., 1::2] = rot_odd
return out
B, n, d_k = 1, 16, 64
cos, sin = build_rope_cache(max_len=n, d_k=d_k)
q = torch.randn(B, n, d_k)
k = torch.randn(B, n, d_k)
q_rope = apply_rope(q, cos, sin)
k_rope = apply_rope(k, cos, sin)
scores = q_rope @ k_rope.transpose(-2, -1) / d_k ** 0.5 # attention score đã có relative info
Implementation production (Llama 3, Mistral) dùng torch.view_as_complex hoặc kernel CUDA chuyên dụng, nhanh hơn ~2x so với bản tự cài. transformers Hugging Face có sẵn LlamaRotaryEmbedding.
RoPE chỉ thay đổi Q, K trong mỗi attention layer; không cộng PE vào X như sinusoidal. Token embedding đi thẳng vào Transformer block, không qua bước "+ PE".
ALiBi — Attention with Linear Biases
Press et al. 2022 (arXiv:2108.12409) đề xuất cách đơn giản hơn nữa: bỏ hẳn PE, chỉ trừ một lượng tuyến tính theo khoảng cách vào attention score trước softmax.
\[ s_{ij}^{(h)} = \frac{q_i \cdot k_j}{\sqrt{d_k}} - m_h \cdot |i - j| \]
với \( m_h \) là slope cố định cho head \( h \) — chọn theo công thức trong paper, mỗi head một giá trị khác nhau để cover các "scale" attention khác nhau. Càng xa, score càng nhỏ → softmax càng nhỏ → token xa bị "attenuate".
Đặc điểm:
- Không học tham số nào riêng cho PE. Chỉ là bias deterministic.
- Extrapolate cực tốt: model train 1024 token chạy ổn ở 4096 hoặc xa hơn mà không cần fine-tune (Press et al. 2022, Section 4).
- Hiện diện trong: BLOOM (BigScience 2022), MPT (MosaicML 2023), một số biến thể nội bộ.
- Hạn chế: bias tuyến tính ép decay quá mạnh — không tốt cho task cần dependency rất xa. Đa số LLM 2024-2025 vẫn chọn RoPE thay vì ALiBi.
Long context — YaRN, NTK scaling
Train RoPE ở context 4K nhưng cần inference ở 32K hoặc 128K — vị trí 100K chưa hề xuất hiện trong train, sin/cos ở các tần số cao "rối loạn" so với phân bố model đã thấy. Kết quả: perplexity tăng vọt, output kém.
Giải pháp ra đời 2023-2024 — đều xoay quanh ý tưởng "kéo dãn" các tần số của RoPE:
- Position Interpolation (Chen et al. 2023, arXiv:2306.15595): scale position xuống \( i \mapsto i / s \) trước khi áp RoPE, với \( s = \text{new\_len} / \text{train\_len} \). Đơn giản, cần fine-tune ngắn.
- NTK-aware scaling (bloc97 2023, community): thay vì scale đều, scale base của \( \theta_k \) sao cho tần số cao bị ảnh hưởng nhiều hơn tần số thấp — bảo toàn high frequency cho local pattern.
- YaRN (Peng et al. 2023, arXiv:2309.00071): kết hợp NTK với "attention scaling" thêm hệ số \( \sqrt{\log_n L} \) vào softmax. State-of-the-art context extension 2024 — Llama 2 4K → 64K, Llama 3 → 128K.
Thực tế: Llama 3.1 dùng YaRN, Qwen 2.5 dùng YaRN biến thể, DeepSeek-V3 dùng "YaRN-style scaling". Mistral và Mixtral mở rộng context bằng kết hợp interpolation + sliding window. Khả năng kéo context lên 128K-1M trên RoPE phụ thuộc gần như hoàn toàn vào các scaling kỹ thuật này.
So sánh — lựa chọn 2024-2025
Tổng kết các nhóm PE và model dùng:
PE type Param? Extrapolate Modern use
─────────────────────────────────────────────────────────────────────
Sinusoidal no kém paper gốc; ít model mới dùng
Learned absolute yes không BERT, GPT-2/3, ViT
Shaw relative yes trung bình T5 (biến thể bucket)
ALiBi no rất tốt BLOOM, MPT
RoPE no* tốt (+scaling) Llama, Mistral, Qwen, DeepSeek,
Gemma 2, GLM, Yi, Phi-3
RoPE + YaRN/NTK no* rất tốt Llama 3.1 128K, Qwen 2.5
(*RoPE không có tham số học của riêng PE, nhưng sử dụng tần số cố định)
Hướng dẫn lựa chọn khi tự build:
- Đọc hiểu paper Transformer gốc / build educational: sinusoidal.
- Encoder-only ngắn (BERT-like, classification): learned absolute vẫn ổn.
- Decoder LLM 2024+: RoPE là default. Cần long context → RoPE + YaRN.
- Cần extrapolate cực mạnh, chấp nhận tradeoff dependency xa yếu: ALiBi.
PE cho ảnh, audio, video
PE không giới hạn ở text 1D. Mọi modality biến vào Transformer đều cần signal vị trí.
- Image — ViT (Dosovitskiy et al. 2020): ảnh chia thành patch 16x16, mỗi patch là một "token". Vị trí là cặp \( (\text{row}, \text{col}) \). ViT gốc dùng 1D learned PE đánh số patch tuần tự; biến thể dùng 2D sinusoidal (row PE + col PE) hoặc 2D RoPE (Heo et al. 2024).
- Audio — Whisper (Radford et al. 2022): spectrogram chia frame theo trục thời gian, mỗi frame là token. PE là 1D theo time, dùng sinusoidal.
- Video: vừa có không gian (row, col) vừa có thời gian (frame). Cần 3D PE hoặc tách thành 2D spatial + 1D temporal. Sora và họ video transformer dùng các biến thể 3D RoPE.
- Graph: position không có thứ tự tự nhiên — dùng Laplacian positional encoding hoặc Random Walk PE.
Nguyên lý chung không đổi: tìm cách inject signal "tôi ở đâu" vào input của Transformer.
Common bug
- Quên cộng PE vào X. Model train được nhưng loss giảm chậm, attention pattern không bao giờ hình thành theo vị trí. Test sanity: shuffle token trong câu input, nếu output không đổi nhiều → đang thiếu PE.
- Shape mismatch. PE \( (\text{max\_len}, d_{\text{model}}) \), X \( (B, n, d_{\text{model}}) \) — phải broadcast đúng. Lỗi phổ biến: quên unsqueeze batch dim, hoặc dùng
pe[:n]thay vìpe[:n].unsqueeze(0). - Cộng PE với scale sai. Một số implementation nhân token embedding với \( \sqrt{d_{\text{model}}} \) trước rồi mới cộng PE (theo Vaswani 2017, Section 3.4). Quên scale → PE lấn át embedding ở init.
- Dùng sinusoidal cho context dài không scale. Train 2K, inference 16K, không fine-tune — perplexity tăng vài lần. Cần Position Interpolation hoặc đổi sang RoPE + YaRN.
- RoPE apply sai cặp dimension. Một số implementation split half-half \( (x[:d/2], x[d/2:]) \) thay vì interleave \( (x[0::2], x[1::2]) \). Hai cách khác nhau về layout; trộn lẫn weight train kiểu này load vào model viết kiểu kia → model không converge.
- Quên gọi
register_buffercho PE không học. Khi.to(device), PE vẫn ở CPU → lỗi runtime "expected device cuda but got cpu".
Bài tập
- Cài hàm
sinusoidal_pe(max_len, d_model)như Bước 7. Tính \( \text{PE}(0), \text{PE}(10), \text{PE}(100) \) với \( d_{\text{model}} = 32 \). In ra L2 norm của từng vector và cosine similarity giữa từng cặp. Cosine similarity có giảm theo khoảng cách không? - Cài
LearnedPEnhư Bước 9 vớimax_len=512, d_model=128. Khởi tạo random, đếm tham số bằngsum(p.numel() for p in module.parameters()). So sánh với param count tay tính. - Implement RoPE cho 1 cặp dimension (\( d_k = 2 \)). Lấy vector \( q = [1, 0] \), apply RoPE ở position \( i = 1, 2, 4, 8 \) với base 10000. Kiểm tra \( q' \) có norm vẫn = 1 (rotation bảo toàn norm).
- Visualize sinusoidal PE \( (\text{max\_len}=100, d_{\text{model}}=64) \) bằng matplotlib (Bước 8). Đính kèm heatmap vào notebook và bình luận pattern bạn quan sát.
- So sánh thực nghiệm absolute vs RoPE: train 2 model tiny Transformer (1-2 layer, \( d = 64 \)) trên character-level next-token prediction với context 64. Một dùng sinusoidal cộng vào X, một dùng RoPE trên Q/K. Plot training loss; quan sát có khác biệt rõ ràng không (kết quả có thể không quá khác ở scale nhỏ — đó cũng là một bài học).
- Đọc Section 3.5 paper Vaswani et al. 2017 (arXiv:1706.03762) và Section 3 paper RoFormer (Su et al. 2021, arXiv:2104.09864). Liệt kê 3 điểm khác biệt cơ bản giữa hai phương pháp.
Tóm tắt
- Self-attention permutation equivariant: \( \text{SelfAttention}(PX) = P \cdot \text{SelfAttention}(X) \). Không có khái niệm thứ tự nếu chỉ nhìn vào X.
- Positional Encoding inject vị trí: cộng vector vào X, hoặc xoay Q/K, hoặc thêm bias vào score.
- Sinusoidal PE Vaswani 2017: \( \text{PE}(i, 2k) = \sin(i / 10000^{2k/d}) \), \( \text{PE}(i, 2k+1) = \cos(i / 10000^{2k/d}) \). Wavelength phủ dải logarit từ \( 2\pi \) tới hàng vạn position.
- Learned PE:
nn.Embedding(max_len, d_model). Dùng trong BERT, GPT-2/3, ViT gốc. Không extrapolate. - RoPE (Su et al. 2021): xoay cặp \( (x_{2k}, x_{2k+1}) \) một góc \( i\theta_k \) ở Q và K. Dot product \( q_i \cdot k_j \) chỉ phụ thuộc \( i - j \) — relative position implicit. Modern default cho Llama, Mistral, Qwen, DeepSeek, Gemma 2.
- ALiBi (Press et al. 2022): không PE, chỉ thêm bias \( -m_h |i - j| \) vào score. Extrapolate rất tốt; dùng trong BLOOM, MPT.
- Long context: Position Interpolation, NTK-aware scaling, YaRN — kéo RoPE từ 4K lên 32K-128K-1M với fine-tune nhẹ.
- PE cho non-text: 2D row+col cho ViT, 1D time cho Whisper audio, 3D space-time cho video, Laplacian cho graph.
- Common bug: quên PE, sai shape broadcast, sai layout RoPE half-vs-interleave, quên register_buffer.
Bài 13 ghép self-attention, multi-head, residual + LayerNorm và FFN lại thành một Transformer block hoàn chỉnh — đơn vị xếp chồng tạo nên BERT, GPT, Llama.
- Vaswani et al. 2017 — Attention Is All You Need (arXiv:1706.03762)
- Shaw et al. 2018 — Self-Attention with Relative Position Representations (arXiv:1803.02155)
- Su et al. 2021 — RoFormer: Enhanced Transformer with Rotary Position Embedding (arXiv:2104.09864)
- Press et al. 2022 — Train Short, Test Long: Attention with Linear Biases / ALiBi (arXiv:2108.12409)
- Chen et al. 2023 — Extending Context Window via Position Interpolation (arXiv:2306.15595)
- Peng et al. 2023 — YaRN: Efficient Context Window Extension (arXiv:2309.00071)
- Dosovitskiy et al. 2020 — An Image Is Worth 16x16 Words / ViT (arXiv:2010.11929)
- Heo et al. 2024 — Rotary Position Embedding for Vision Transformer (arXiv:2403.13298)
- Radford et al. 2022 — Robust Speech Recognition / Whisper (arXiv:2212.04356)
- Llama 3 Technical Report (arXiv:2407.21783)
- Hugging Face Transformers — Llama documentation
- Jay Alammar 2018 — The Illustrated Transformer
- Kazemnejad 2019 — Transformer Architecture: The Positional Encoding
