satopikac's site

Back

从0构建大模型(1)Blur image

1781000560717

走完大模型构建全流程1#

1781090225151

RMS Norm#

RMSNORM 正则(归一化)

如果输入X过大过小,导致梯度爆炸、消失。

归一化,标准差变1

RMSnorm比传统Norm少了均值的相关计算

yi=xii==1nxi2+σγy_i=\frac{x_i}{\sqrt{\sum_{i==1}^n x_i^2}+\sigma}*\gamma

(其中,γ\gamma是可学习的参数)

代码实现:

RoPE#

文本的先后位置与语义有很大关系,因此需要位置编码(相对位置、绝对位置)

使用相对位置编码,旋转位置编码(RoPE、YaRN)

为什么 Transformer 需要位置编码#

核心问题:Self-Attention 的置换不变性#

Transformer 的核心组件是 Self-Attention(自注意力机制)。其计算公式为:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

其中:

  • Q=XWQQ = XW_QK=XWKK = XW_KV=XWVV = XW_V
  • XRn×dX \in \mathbb{R}^{n \times d} 是输入序列的嵌入矩阵,nn 是序列长度,dd 是嵌入维度

关键观察:Self-Attention 本质上是对输入序列做加权求和,而加权求和操作具有置换不变性(Permutation Invariance)

具体来说,如果我们打乱输入序列的顺序:

X=ΠXX' = \Pi X

其中 Π\Pi 是任意置换矩阵,则:

Attention(XWQ,XWK,XWV)=ΠAttention(XWQ,XWK,XWV)\text{Attention}(X'W_Q, X'W_K, X'W_V) = \Pi \cdot \text{Attention}(XW_Q, XW_K, XW_V)

这意味着:无论输入序列中 token 的顺序如何,Self-Attention 的输出(在重排后)完全相同

为什么这成为问题#

语言是有序的。同一个句子中词语的顺序不同,含义完全不同:

句子含义
猫追狗猫是施动者
狗追猫狗是施动者

如果模型无法区分位置,那么”猫追狗”和”狗追猫”在模型看来就是相同的输入——这显然无法完成任何有意义的语言理解任务。

位置编码的目标是打破置换不变性,将序列中每个 token 的位置信息注入到模型中。形式化地说,我们需要一个函数 ff,使得:

hi=xi+f(i)h_i = x_i + f(i)

或者更一般地:

hi=g(xi,i)h_i = g(x_i, i)

其中 ii 是位置索引,hih_i 是注入了位置信息的表示。这样,模型就能区分不同位置上的 token。

3. RoPE 详细理论推导#

3.1 设计目标#

RoPE 的设计目标是:找到一种位置编码函数 f(x,m)f(x, m),使得 query 向量和 key 向量的内积仅依赖于相对位置

f(q,m),f(k,n)=g(q,k,mn)\langle f(q, m), f(k, n) \rangle = g(q, k, m - n)

其中 m,nm, n 分别是 query 和 key 的绝对位置,gg 是某个只与相对位置 mnm - n 有关的函数。

3.2 二维情况的推导#

为了建立直觉,我们先从最简单的二维情况开始。

3.2.1 问题设定#

对于二维向量 q=(q0,q1)Tq = (q_0, q_1)^T,我们希望找到函数 f(q,m)f(q, m),使得:

f(q,m)Tf(k,n)=g(q,k,mn)f(q, m)^T f(k, n) = g(q, k, m - n)

3.2.2 旋转矩阵的直觉#

在二维空间中,一个自然的选择是旋转操作

f(q,m)=R(m)qf(q, m) = R(m) \cdot q

其中 R(m)R(m) 是旋转角度为 mθm\theta 的旋转矩阵:

R(m)=(cos(mθ)sin(mθ)sin(mθ)cos(mθ))R(m) = \begin{pmatrix} \cos(m\theta) & -\sin(m\theta) \\ \sin(m\theta) & \cos(m\theta) \end{pmatrix}

3.2.3 验证条件#

计算内积:

f(q,m)Tf(k,n)=(R(m)q)T(R(n)k)=qTR(m)TR(n)k=qTR(m)R(n)k=qTR(nm)k\begin{aligned} f(q, m)^T f(k, n) &= (R(m)q)^T (R(n)k) \\ &= q^T R(m)^T R(n) k \\ &= q^T R(-m) R(n) k \\ &= q^T R(n - m) k \end{aligned}

这里利用了旋转矩阵的性质:

  • R(m)T=R(m)R(m)^T = R(-m)(转置等于反向旋转)
  • R(m)R(n)=R(nm)R(-m) R(n) = R(n - m)(旋转的复合)

因此,g(q,k,mn)=qTR(nm)kg(q, k, m - n) = q^T R(n - m) k,它确实只依赖于相对位置 mnm - n!

3.3 推广到高维#

3.3.1 分块旋转#

对于 dd 维向量(dd 为偶数),我们将 dd 维空间分成 d/2d/2 个二维子空间,每个子空间独立应用旋转:

RΘ,m=(R1(m)000R2(m)000Rd/2(m))R_{\Theta, m} = \begin{pmatrix} R_1(m) & 0 & \cdots & 0 \\ 0 & R_2(m) & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & R_{d/2}(m) \end{pmatrix}

其中每个 Ri(m)R_i(m) 是二维旋转矩阵:

Ri(m)=(cos(mθi)sin(mθi)sin(mθi)cos(mθi))R_i(m) = \begin{pmatrix} \cos(m\theta_i) & -\sin(m\theta_i) \\ \sin(m\theta_i) & \cos(m\theta_i) \end{pmatrix}

3.3.2 频率的选择#

每个二维子空间对应一个旋转频率 θi\theta_i。RoPE 采用与原始 Transformer 正弦编码类似的几何级数:

θi=100002i/d,i=0,1,,d/21\theta_i = 10000^{-2i/d}, \quad i = 0, 1, \ldots, d/2 - 1

展开来说:

  • θ0=1\theta_0 = 1(最高频,对应前两个维度)
  • θ1=100002/d\theta_1 = 10000^{-2/d}
  • θ2=100004/d\theta_2 = 10000^{-4/d}
  • \cdots
  • θd/21=10000(d2)/d100001\theta_{d/2-1} = 10000^{-(d-2)/d} \approx 10000^{-1}(最低频,对应最后两个维度)

直觉理解

  • 高频分量(小 ii):旋转速度快,对位置变化敏感,捕捉局部位置关系
  • 低频分量(大 ii):旋转速度慢,对位置变化不敏感,捕捉全局位置关系

这类似于傅里叶变换中不同频率分量的作用。

3.3.3 完整的旋转矩阵#

将位置 mm 处的 dd 维向量 x=(x0,x1,x2,x3,,xd2,xd1)Tx = (x_0, x_1, x_2, x_3, \ldots, x_{d-2}, x_{d-1})^T 应用 RoPE:

f(x,m)=RΘ,mxf(x, m) = R_{\Theta, m} \cdot x

展开为:

f(x,m)=(x0cos(mθ0)x1sin(mθ0)x0sin(mθ0)+x1cos(mθ0)x2cos(mθ1)x3sin(mθ1)x2sin(mθ1)+x3cos(mθ1)xd2cos(mθd/21)xd1sin(mθd/21)xd2sin(mθd/21)+xd1cos(mθd/21))f(x, m) = \begin{pmatrix} x_0 \cos(m\theta_0) - x_1 \sin(m\theta_0) \\ x_0 \sin(m\theta_0) + x_1 \cos(m\theta_0) \\ x_2 \cos(m\theta_1) - x_3 \sin(m\theta_1) \\ x_2 \sin(m\theta_1) + x_3 \cos(m\theta_1) \\ \vdots \\ x_{d-2} \cos(m\theta_{d/2-1}) - x_{d-1} \sin(m\theta_{d/2-1}) \\ x_{d-2} \sin(m\theta_{d/2-1}) + x_{d-1} \cos(m\theta_{d/2-1}) \end{pmatrix}

RoPE 的核心数学性质#

性质 1:相对位置性#

f(q,m),f(k,n)=Re[j=0d/21qjkˉjei(mn)θj]\langle f(q, m), f(k, n) \rangle = \text{Re}\left[\sum_{j=0}^{d/2-1} q_j \bar{k}_j \cdot e^{i(m-n)\theta_j}\right]

内积只依赖于相对位置 mnm - n

性质 2:远程衰减#

随着相对距离 mn|m - n| 增大,内积趋向于衰减。这是因为不同频率分量 ei(mn)θje^{i(m-n)\theta_j} 的贡献会相互抵消(类似于多频信号的干涉效应)。

数学上可以证明,当 dd 足够大时:

f(q,m),f(k,n)Csin(dΔ/2)dsin(Δ/2),Δ=(mn)θ0\langle f(q, m), f(k, n) \rangle \approx C \cdot \frac{\sin(d \cdot \Delta / 2)}{d \cdot \sin(\Delta / 2)}, \quad \Delta = (m-n) \cdot \theta_0

这是一个随 mn|m - n| 增大而衰减的振荡函数。

性质 3:线性复杂度#

RoPE 的计算不需要额外的参数,只需要对 Q 和 K 的每个位置应用旋转,时间复杂度为 O(nd)O(nd)

RoPE 实现详解#

核心实现思路#

直接构造 d×dd \times d 旋转矩阵效率低下。RoPE 的高效实现利用了一个关键观察:旋转操作可以分解为逐对的元素级操作

对于每对 (x2i,x2i+1)(x_{2i}, x_{2i+1})

x2i=x2icos(mθi)x2i+1sin(mθi)x'_{2i} = x_{2i} \cos(m\theta_i) - x_{2i+1} \sin(m\theta_i) x2i+1=x2isin(mθi)+x2i+1cos(mθi)x'_{2i+1} = x_{2i} \sin(m\theta_i) + x_{2i+1} \cos(m\theta_i)

高效版本(避免交错操作)#

高效的做法是利用数学恒等式:

x2i=x2icosx2i+1sinx'_{2i} = x_{2i} \cos - x_{2i+1} \sin x2i+1=x2isin+x2i+1cosx'_{2i+1} = x_{2i} \sin + x_{2i+1} \cos

可以重写为:

x=xcos+rotate_half(x)sinx' = x \cdot \cos + \text{rotate\_half}(x) \cdot \sin

其中 rotate_half(x)\text{rotate\_half}(x)[x0,x1,x2,x3,][x_0, x_1, x_2, x_3, \ldots] 变为 [x1,x0,x3,x2,][-x_1, x_0, -x_3, x_2, \ldots]

def apply_rotary_pos_emb_efficient(q, k, cos, sin):
    """高效 RoPE 实现"""

    def rotate_half(x):
        x1 = x[..., : x.shape[-1] // 2]
        x2 = x[..., x.shape[-1] // 2 :]
        return torch.cat([-x2, x1], dim=-1)

    cos = cos.unsqueeze(0).unsqueeze(2)
    sin = sin.unsqueeze(0).unsqueeze(2)

    q_embed = q * cos + rotate_half(q) * sin
    k_embed = k * cos + rotate_half(k) * sin

    return q_embed, k_embed
python

4.3 完整的 Transformer 层示例#

YaRN (Yet another RoPE extensioN)#

YaRN 结合了 NTK 和注意力缩放,对不同频率应用不同的缩放策略:

  • 低频分量:使用插值(缩放位置)
  • 高频分量:保持不变

1781165434358

FFN层#

1781247162995

(SiLU(XW1)XW2)W3(\text{SiLU}(XW_1) \cdot XW_2)W_3

为了保证总参数为8l^2,三个W都把维度调整到83l\frac{8}{3}l

SiLU=x1+ex\text{SiLU}=\frac{x}{1+e^{-x}}

动态学习率

余弦退火方法

LoRA

h=Wx0+BAx0h=Wx_0+BAx_0

损失函数设计#

llm的末端实际是一个分类问题(自回归),根据前t个的词预测第t+1个词,

t+1词的真实值是one-hot向量,预测logits是此表长度的归一化的概率分布,

对两个向量求交叉熵损失,

loss=p(x)logq(x)loss=-\sum p(x) \log q(x)

p(x)是真实值,q(x)是预测值

这样代入可知,单个token的loss=logqt(x)-\log q_t(x)

对于所有token总预测损失是loss=logqt(x)=logqt(x)loss=-\sum \log q_t(x)=-\log \prod q_t(x)qt(x)是似然函数\prod q_t(x)是似然函数

大语言模型的核心任务是 自回归预测 ——给定前文,预测下一个词的概率分布:

P(wt∣w1,w2,,wt−1;θ)

整个训练集上的目标函数是 最大化所有词的联合对数似然 ,即最小化其负对数似然,即为上文所说的交叉熵损失。

从0构建大模型(1)
https://satopikac.github.io/blog/llm_build/1_start
Author satopikac
Published at 2026年6月9日
Comment seems to stuck. Try to refresh?✨