Danh sách bài viết

Bài 12: Positional Encoding — đưa thông tin thứ tự vào model

Self-attention dùng dot product nên permutation invariant — đảo trật tự token không thay đổi output. Positional Encoding (PE) là cách inject thông tin vị trí: cộng vector vị trí vào token embedding, hoặc "xoay" Q, K theo position. Bài tổng hợp 4 nhóm PE đang dùng — sinusoidal (Vaswani 2017), learned (GPT-2/3), RoPE (Llama, Mistral, Qwen, DeepSeek — modern default) và ALiBi (BLOOM, MPT), kèm YaRN/NTK scaling cho long context, code PyTorch, common bug, PE cho ViT và audio.

25/05/2026
14 phút đọc
1 lượt xem
1

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 đó.

2

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.

3

Ý 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).
4

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.

5

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).

6

Vì sao chọn sin và cos

Vaswani et al. 2017 chọn sin / cos vì hai tính chất hữu ích:

  1. Đị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.
  2. 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.

7

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_term dù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
8

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.
9

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.

10

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.

11

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.

12

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ẹ.
13

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".

14

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.
15

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.

16

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.
17

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.

18

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_buffer cho PE không học. Khi .to(device), PE vẫn ở CPU → lỗi runtime "expected device cuda but got cpu".
19

Bài tập

  1. 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?
  2. Cài LearnedPE như Bước 9 với max_len=512, d_model=128. Khởi tạo random, đếm tham số bằng sum(p.numel() for p in module.parameters()). So sánh với param count tay tính.
  3. 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).
  4. 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.
  5. 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).
  6. Đọ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.
20

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.