File size: 4,722 Bytes
c1a46f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d62693
 
 
 
 
 
 
 
 
 
 
c1a46f7
 
 
 
 
 
 
 
4d62693
 
c1a46f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d62693
 
 
 
 
 
 
 
 
 
 
 
 
c1a46f7
 
d572bbd
 
 
 
 
 
 
4d62693
d572bbd
4d62693
 
 
 
 
c1a46f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d62693
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""
位置编码模块 — Person B 负责实现

包含:
1. SinusoidalPositionalEncoding: 经典正弦余弦位置编码
2. RotaryPositionalEmbedding (RoPE): 旋转位置编码

RoPE 是当前最前沿的位置编码方案,被 LLaMA, GPT-NeoX 等大模型广泛采用。
优势: 更好地捕获相对位置信息,支持长度外推。
"""

from __future__ import annotations

import math

import torch
import torch.nn as nn


class SinusoidalPositionalEncoding(nn.Module):
    """
    经典正弦余弦位置编码 (Vaswani et al., 2017)。

    PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
    PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

    TODO [Person B]: 实现以下内容:
    1. 在 __init__ 中预计算位置编码矩阵 [max_seq_len, d_model]
    2. 注册为 buffer (不参与梯度更新)
    3. forward(x): 返回 x + pe[:, :x.size(1), :]
    """

    def __init__(self, d_model: int, max_seq_len: int = 5000, dropout: float = 0.1):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)

        pe = torch.zeros(max_seq_len, d_model)
        position = torch.arange(0, max_seq_len, dtype=torch.float).unsqueeze(1)
        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_seq_len, d_model]
        self.register_buffer("pe", pe)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Args:
            x: [B, L, D]
        Returns:
            x + positional_encoding: [B, L, D]
        """
        x = x + self.pe[:, : x.size(1), :]
        return self.dropout(x)


class RotaryPositionalEmbedding(nn.Module):
    """
    旋转位置编码 (RoPE) — Su et al., 2021

    核心思想: 通过旋转变换将位置信息编码到 Q, K 向量中。
    q' = q * cos(θ) + rotate_half(q) * sin(θ)
    k' = k * cos(θ) + rotate_half(k) * sin(θ)

    TODO [Person B]: 实现以下内容:

    __init__:
    1. 计算频率: inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2) / dim))
    2. 注册为 buffer

    _compute_rope(seq_len):
    1. 计算 position indices: t = [0, 1, ..., seq_len-1]
    2. 计算 freqs = torch.outer(t, inv_freq)
    3. 构建 cos_cached, sin_cached

    apply_rotary_pos_emb(q, k):
    1. 对 q 和 k 应用旋转变换
    2. 返回旋转后的 q', k'

    参考: https://arxiv.org/abs/2104.09864
    """

    def __init__(self, dim: int, max_seq_len: int = 2048, base: float = 10000.0):
        super().__init__()
        self.dim = dim
        self.max_seq_len = max_seq_len
        self.base = base

        inv_freq = 1.0 / (
            base ** (torch.arange(0, dim, 2).float() / dim)
        )
        self.register_buffer("inv_freq", inv_freq)

        # 预缓存 cos/sin
        self._cached_seq_len = 0
        self._cached_cos: torch.Tensor | None = None
        self._cached_sin: torch.Tensor | None = None

    def _compute_rope(self, seq_len: int, device: torch.device):
        # Recompute if seq_len grew OR cache is stale OR device changed (e.g. after .to(cuda))
        cache_stale = (
            seq_len > self._cached_seq_len
            or self._cached_cos is None
            or self._cached_cos.device != device
        )
        if cache_stale:
            t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
            freqs = torch.outer(t, self.inv_freq.to(device))  # [seq_len, dim//2]
            emb = torch.cat((freqs, freqs), dim=-1)  # [seq_len, dim]
            self._cached_cos = emb.cos()[None, None, :, :]  # [1, 1, seq_len, dim]
            self._cached_sin = emb.sin()[None, None, :, :]  # [1, 1, seq_len, dim]
            self._cached_seq_len = seq_len
        return self._cached_cos, self._cached_sin

    @staticmethod
    def _rotate_half(x: torch.Tensor) -> torch.Tensor:
        """将 x 的后半部分取反并与前半部分交换。"""
        x1, x2 = x.chunk(2, dim=-1)
        return torch.cat((-x2, x1), dim=-1)

    def apply_rotary_pos_emb(
        self,
        q: torch.Tensor,  # [B, nhead, L, d_k]
        k: torch.Tensor,  # [B, nhead, L, d_k]
    ) -> tuple[torch.Tensor, torch.Tensor]:
        """
        对 Q, K 应用 RoPE。

        Returns:
            (q_rotated, k_rotated)
        """
        cos, sin = self._compute_rope(q.size(2), q.device)
        q_embed = (q * cos[:, :, : q.size(2), :]) + (
            self._rotate_half(q) * sin[:, :, : q.size(2), :]
        )
        k_embed = (k * cos[:, :, : k.size(2), :]) + (
            self._rotate_half(k) * sin[:, :, : k.size(2), :]
        )
        return q_embed, k_embed