Tiny Universe - Llama3架构

Llama3和Llama2和Qwen2的整体架构相似,本篇文章主要讲解它们的一些主要不同点。

关于Qwen2架构可参考  Qwen2架构 学习笔记

llama3区别于llama2在模型层面的区别主要体现在全模型使用GQA。

基础知识

MLP

MLP(Multi-Layer Perceptron)多层感知机是一种前馈神经网络,由一个或多个全连接层组成。每个全连接层包含一组可学习的权重矩阵和偏置向量,用于将输入数据进行线性变换和非线性激活。MLP可以用于各种任务,如分类、回归等。

在大模型中,MLP通常作为基本的网络组件,用于构建更复杂的结构。例如,在Transformer中,前馈神经网络部分就是一个MLP。此外,MLP还可以与其他网络结构(如卷积神经网络)结合,形成更强大的模型。

典型的MLP包括包括三层:输入层、隐层和输出层,MLP神经网络不同层之间是全连接的( 全连接的意思就是:上一层的任何一个神经元与下一层的所有神经元都有连接)。

如图所示

Attention

在自注意力机制中,输入序列的每个元素首先通过三个不同的线性变换,分别生成 Query(查询)、Key(键)、和 Value(值)矩阵。这三个矩阵共同用于计算输入序列中各个元素之间的注意力权重。

假设输入序列为 X=[x1,x2,…,xn]X = [x_1, x_2, \dots, x_n]X=[x1​,x2​,…,xn​],这些元素经过线性变换后得到:

Q=XWQ,K=XWK,V=XWVQ = XW^Q, \quad K = XW^K, \quad V = XW^VQ=XWQ,K=XWK,V=XWV

其中:

  • XXX 是输入序列,每个元素是一个向量。
  • WQ,WK,WVW^Q, W^K, W^VWQ,WK,WV 分别是用于生成 Query、Key、Value 的可学习权重矩阵。

1.1 点积注意力(Scaled Dot-Product Attention)

通过 Q、K 矩阵,计算每个输入与其他输入的相关性。具体公式如下:

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

其中:

  • Q 是查询矩阵。
  • K 是键矩阵,K^\top 是键矩阵的转置。
  • V 是值矩阵。
  • d_k​ 是键向量的维度,\sqrt{d_k}​​ 是缩放因子,用于避免点积值过大导致 softmax 输出过小的梯度。

1.2 计算步骤解析

  1. 生成 Query、Key、Value 矩阵:输入序列经过不同的线性变换,生成对应的 Q、K、V 矩阵。
  2. 计算点积:对 Query 和 Key 矩阵进行点积,得到输入序列中每个元素与其他元素之间的相关性分数。
  3. 缩放与归一化:将相关性分数除以 dk\sqrt{d_k}dk​​ 进行缩放,并通过 softmax 归一化,得到注意力权重。
  4. 加权求和:将注意力权重与 Value 矩阵相乘,得到最终的上下文向量。
import torch
import torch.nn as nn
import torch.nn.functional as Fclass ScaledDotProductAttention(nn.Module):def __init__(self, d_k):super(ScaledDotProductAttention, self).__init__()self.d_k = d_kdef forward(self, Q, K, V, mask=None):# Q, K, V: batch_size x seq_len x d_kscores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))if mask is not None:scores = scores.masked_fill(mask == 0, -1e9)  # Apply the mask (optional)attn_weights = F.softmax(scores, dim=-1)  # softmax over the last dimensionoutput = torch.matmul(attn_weights, V)  # Weighted sum of valuesreturn output, attn_weights

多头注意力机制通过并行计算多个注意力头来捕捉不同子空间的特征。每个头独立生成自己的 Q、K、V 矩阵,进行自注意力计算,然后将各个头的结果拼接起来,通过一个线性层投影到最终输出。

2.1 多头注意力公式

对于多头注意力机制,公式如下:

\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)W^O

其中每个头的计算方式为:

\text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)

  • W_i^Q, W_i^K, W_i^V 是每个头的可学习权重。
  • W^O 是用于拼接后投影的线性变换矩阵。

多头注意力机制允许模型在不同的子空间中关注输入序列的不同部分,从而增强了模型的表达能力。

class MultiHeadAttention(nn.Module):def __init__(self, d_model, num_heads):super(MultiHeadAttention, self).__init__()assert d_model % num_heads == 0, "d_model must be divisible by num_heads"self.d_k = d_model // num_headsself.num_heads = num_heads# Define weight matrices for Q, K, V and output projectionself.W_Q = nn.Linear(d_model, d_model)self.W_K = nn.Linear(d_model, d_model)self.W_V = nn.Linear(d_model, d_model)self.fc_out = nn.Linear(d_model, d_model)# Attention moduleself.attention = ScaledDotProductAttention(self.d_k)def forward(self, Q, K, V, mask=None):batch_size = Q.size(0)# Perform linear transformation and split into multiple headsQ = self.W_Q(Q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)K = self.W_K(K).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)V = self.W_V(V).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)# Apply scaled dot-product attention to each headattn_output, attn_weights = self.attention(Q, K, V, mask)# Concatenate heads and apply final linear projectionattn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k)output = self.fc_out(attn_output)return output, attn_weights

 RMS正则化

RMSProp(Root Mean Square Propagation)是对学习率进行自适应调整的优化器,用来应对不稳定的梯度更新问题。它通过引入梯度平方的指数加权移动平均来动态调整每个参数的学习率。

1.1 RMSProp 的更新公式

对于每个参数 \theta_i​,RMSProp 的更新公式如下:

E[g^2]_t = \beta E[g^2]_{t-1} + (1 - \beta) g_t^2

\theta_t = \theta_{t-1} - \frac{\eta}{\sqrt{E[g^2]_t + \epsilon}} g_t

其中:

  • g_t​ 是当前的梯度。
  • E[g^2]_t​ 是梯度平方的指数加权平均。
  • \eta 是学习率。
  • \epsilon 是为了避免除以零的一个小值(通常取 10^{-8})。
  • \beta 是用于控制移动平均的超参数(通常取值为 0.9)。

1.2 工作机制

RMSProp 通过记录梯度的平方并对其进行加权平均来调节学习率。这样,学习率对频繁更新的参数变小,而对变化不大的参数保持相对较大,从而提高收敛速度,减少训练过程中的振荡。

二、正则化的引入

为了防止模型过拟合,常用的正则化技术包括 L2 正则化(也称为权重衰减)和 RMSProp 的动态调整能力相结合。

2.1 L2 正则化

L2 正则化通过在损失函数中加入一个与参数值相关的惩罚项,限制模型参数的过大增长,避免过拟合。L2 正则化的损失函数形式为:

L(\theta) = L_{\text{data}}(\theta) + \lambda \|\theta\|_2^2

其中:

  • L_{\text{data}}(\theta) 是原始损失函数(如交叉熵损失或均方误差)。
  • \lambda 是正则化系数,控制正则化强度。
  • \|\theta\|_2^2​ 是参数的 L2 范数(权重平方和)。

2.2 RMSProp 与 L2 正则化的结合

在 RMSProp 优化器的基础上,L2 正则化会通过梯度更新过程中引入额外的权重惩罚项,公式如下:

\theta_t = \theta_{t-1} - \eta \left( \frac{g_t}{\sqrt{E[g^2]_t + \epsilon}} + \lambda \theta_{t-1} \right)

这里,L2 正则化的影响体现在梯度更新项中,加入了 \lambda \theta_{t-1}​,即对参数 \theta 进行衰减,迫使参数值趋向较小值,以减少模型的复杂度和过拟合风险。

ROPE

Transformer 中,由于模型没有卷积和循环结构,因此需要引入位置编码来捕捉序列中词与词之间的相对位置信息。常见的 绝对位置编码(absolute positional encoding) 方式如下:

PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{\frac{2i}{d}}}\right)

PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{\frac{2i}{d}}}\right)

其中 pos 是序列中单词的位置,i 是嵌入维度索引,d 是嵌入的维度大小。

局限性:

  • 不能捕捉相对位置信息:绝对位置编码只对词的位置进行编码,不能直接体现词与词之间的相对位置。
  • 对于长序列效果较差:随着序列长度的增加,绝对位置编码容易失效。

二、RoPE 的基本思想

RoPE 的核心思想是通过旋转向量来引入位置信息。具体来说,RoPE 将每个词的嵌入向量根据其在序列中的位置进行旋转变换,从而隐式地引入位置信息,并能够自然地捕捉词与词之间的相对位置关系。

RoPE 的旋转变换方式如下:

  • 通过定义一个二维旋转矩阵,对嵌入向量的每一对维度进行旋转。
  • 旋转的角度与词在序列中的位置有关,因此词的位置信息被编码在旋转变换中。

二维旋转矩阵

RoPE 在嵌入向量的维度上,每两个维度作为一对,通过旋转矩阵对其进行旋转,公式如下:

对于第 i 维度,位置为 pos,角度 \theta = \frac{pos}{10000^{\frac{2i}{d}}}​:

z' = z \cdot \text{RoPE}(pos)

其中 \text{RoPE}(pos) 代表旋转变换。该变换通过旋转矩阵的方式作用于每对嵌入维度 (2i,2i+1):

\begin{bmatrix} z_{2i}' \\ z_{2i+1}' \end{bmatrix} = \begin{bmatrix} \cos(\theta) & -\sin(\theta) \\ \sin(\theta) & \cos(\theta) \end{bmatrix} \begin{bmatrix} z_{2i} \\ z_{2i+1} \end{bmatrix}

通过这个旋转操作,嵌入向量的每对维度根据位置 pos 被旋转了不同的角度,从而将位置信息编码到向量中。

2.1 相对位置关系的捕捉

RoPE 的最大优势是能够捕捉词与词之间的相对位置信息,而不需要显式地对相对位置进行编码。对于任意两个词 wiw_iwi​ 和 wjw_jwj​,它们经过 RoPE 编码后的相对位置可以通过旋转向量的差值自然得到。因此,RoPE 能够在长序列中更好地建模词语之间的依赖关系。

三、RoPE 的数学原理

RoPE 将绝对位置信息通过旋转的方式变换到词嵌入中,这种变换具有以下两个重要性质:

  1. 相对不变性:RoPE 的旋转编码使得向量之间的相对角度仅与词语的相对位置有关,而不是绝对位置。这意味着,无论句子中的词语在什么位置,它们的相对关系都能被捕捉到。

  2. 周期性和旋转不变性:由于 RoPE 的编码基于正弦和余弦函数,编码本身具有周期性。这使得 RoPE 能够在长序列任务中更好地处理序列中词语的位移问题。

四、RoPE 的代码实现

下面是 RoPE 的简单实现,基于 PyTorch

import torch
import mathdef apply_rope(x, seq_len, dim):"""RoPE实现, 将嵌入向量进行旋转变换x: 输入的词嵌入向量,形状为 (batch_size, seq_len, dim)seq_len: 序列长度dim: 嵌入向量维度"""position_ids = torch.arange(0, seq_len, dtype=torch.float).unsqueeze(1)theta = torch.arange(0, dim, 2, dtype=torch.float) / dimtheta = 1.0 / (10000 ** theta)  # 位置编码的角度angle = position_ids * theta  # 每个位置对应的角度# 构造旋转矩阵,将嵌入向量的每对维度旋转sin_angle = torch.sin(angle)cos_angle = torch.cos(angle)# 对每对维度进行旋转变换x1 = x[..., 0::2] * cos_angle - x[..., 1::2] * sin_anglex2 = x[..., 0::2] * sin_angle + x[..., 1::2] * cos_anglereturn torch.cat([x1, x2], dim=-1)# 示例使用
batch_size = 2
seq_len = 4
dim = 6
x = torch.randn(batch_size, seq_len, dim)rope_encoded = apply_rope(x, seq_len, dim)
print(rope_encoded)

解释:

  1. apply_rope 函数对输入的词嵌入向量 xxx 进行 RoPE 变换。该向量形状为 (batch_size, seq_len, dim),表示一个 batch 中多条序列的嵌入。
  2. position_ids 是每个词的位置,theta 是用于旋转的角度。
  3. 通过 sincos 分别对每对维度进行旋转,最后得到经过 RoPE 编码的词嵌入。

Transformer

Transformer是一种基于自注意力机制(Self-Attention Mechanism)的神经网络架构,最初由Vaswani等人在2017年提出,用于解决序列到序列(Sequence-to-Sequence)的问题。Transformer的主要特点是放弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),而是完全依赖于自注意力机制来捕捉输入序列中的长距离依赖关系。

Transformer的核心组件是自注意力层(Self-Attention Layer),它允许模型在不同位置的输入之间建立动态的关联。自注意力层的输出是一个加权和,其中权重是根据输入之间的相似性计算得到的。这使得模型能够关注到与当前位置相关的其他位置的信息。

除了自注意力层,Transformer还包括前馈神经网络(Feed-Forward Neural Network)和残差连接(Residual Connection)等组件。这些组件共同构成了Transformer的编码器(Encoder)和解码器(Decoder)部分,分别用于处理输入序列和生成输出序列。

 

 Llama3 架构

引入库文件

import math
import struct
import inspect
from dataclasses import dataclass
from typing import Any, Optional, Tupleimport numpy as np
import torch
import torch.nn.functional as F
from torch import nn

模型参数定义

@dataclass
class ModelArgs:dim: int = 4096n_layers: int = 6n_heads: int = 6n_group: Optional[int] = 3vocab_size: int = 4096hidden_dim: Optional[int] = Nonemultiple_of: int = 256  # MLP层隐层维度的指定计算参数(见FFN层)norm_eps: float = 1e-5max_seq_len: int = 2048dropout: float = 0.0
  1. dim: int = 4096: 这是一个整数类型的属性,表示模型的维度,默认值为4096。在深度学习模型中,维度通常指的是输入、输出或中间层的特征数量。

  2. n_layers: int = 6: 表示模型中堆叠的层数,默认为6层。

  3. n_heads: int = 6: 表示多头注意力机制中头的数量,默认为6。在Transformer模型中,多头注意力可以并行处理信息,提高模型的表达能力。

  4. n_group: Optional[int] = 3: 这是一个可选的整数类型属性,表示分组的数量,默认为3。在某些模型中,可能会将数据分组处理以提高效率或性能。

  5. vocab_size: int = 4096: 表示词汇表的大小,默认为4096。在自然语言处理任务中,词汇表包含了模型能够识别的所有词汇。

  6. hidden_dim: Optional[int] = None: 这是一个可选的整数类型属性,表示隐藏层的维度。在某些模型中,这个值可能会被用来指定中间层的大小,但在这里它默认为None,意味着可能在其他地方定义或根据其他参数计算。

  7. multiple_of: int = 256: 这是一个整数类型的属性,用于指定MLP(多层感知机)层隐层维度的计算参数。在设计模型时,有时需要确保某些维度是某个数的倍数,以便于硬件优化或模型设计。

  8. norm_eps: float = 1e-5: 这是一个浮点数类型的属性,表示归一化时的epsilon值,默认为0.00001。在归一化操作中,epsilon用于防止除以零的情况发生。

  9. max_seq_len: int = 2048: 表示模型能够处理的最大序列长度,默认为2048。在处理文本数据时,这个参数限制了模型能够处理的最长文本长度。

  10. dropout: float = 0.0: 表示在训练过程中随机丢弃(dropout)的比率,默认为0.0,即不进行dropout。Dropout是一种正则化技术,用于防止模型过拟合。

RMS正则化

class RMSNorm(torch.nn.Module):def __init__(self, dim: int, eps: float):super().__init__()self.eps = epsself.weight = nn.Parameter(torch.ones(dim))def _norm(self, x):return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)def forward(self, x):output = self._norm(x.float()).type_as(x)return output * self.weight
  1. class RMSNorm(torch.nn.Module): 这行代码声明了一个继承自torch.nn.Module的类RMSNormtorch.nn.Module是所有神经网络模块的基类,提供了一些基本的功能,如参数管理和前向传播。

  2. def __init__(self, dim: int, eps: float): 这是类的构造函数,用于初始化RMSNorm层。它接受两个参数:

    • dim: int: 表示输入特征的维度。
    • eps: float: 一个很小的正数,用于数值稳定性,防止除以零。
  3. super().__init__(): 这行代码调用父类torch.nn.Module的构造函数,完成基本的初始化。

  4. self.eps = eps: 将传入的eps值保存为类的属性。

  5. self.weight = nn.Parameter(torch.ones(dim)): 创建一个参数weight,它是一个形状为(dim,)的一维张量,初始值为1。nn.Parameter是PyTorch中用于定义可学习的参数的类。

  6. def _norm(self, x): 这是一个辅助函数,用于计算RMSNorm的归一化操作。它接受一个输入张量x,并返回归一化后的张量。

    • x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps): 这是RMSNorm的核心计算步骤。
      • x.pow(2): 计算输入x的元素平方。
      • .mean(-1, keepdim=True): 计算最后一个维度(通常是特征维度)的均值,并保持维度不变。
      • torch.rsqrt(...): 计算倒数平方根(即平方根的倒数)。
      • + self.eps: 加上一个小的正数eps,以提高数值稳定性。
  7. def forward(self, x): 这是前向传播函数,用于定义模块如何处理输入数据并产生输出。

    • output = self._norm(x.float()).type_as(x): 首先将输入x转换为浮点数类型,然后调用_norm函数进行归一化。type_as(x)确保输出张量与输入张量具有相同的数据类型。
    • return output * self.weight: 最后,将归一化后的输出乘以权重weight,得到最终的输出。

ROPE相关

def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0):freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))t = torch.arange(end, device=freqs.device) freqs = torch.outer(t, freqs).float() freqs_cos = torch.cos(freqs) freqs_sin = torch.sin(freqs) return freqs_cos, freqs_sindef reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):ndim = x.ndimassert 0 <= 1 < ndimassert freqs_cis.shape == (x.shape[1], x.shape[-1])shape = [d if i == 1 or i == ndim - 1 else 1 for i, d in enumerate(x.shape)]return freqs_cis.view(shape)def apply_rotary_emb(xq: torch.Tensor,xk: torch.Tensor,freqs_cos: torch.Tensor,freqs_sin: torch.Tensor
) -> Tuple[torch.Tensor, torch.Tensor]:# 重塑 xq 和 xk,使其与复数表示相匹配xq_r, xq_i = xq.float().reshape(xq.shape[:-1] + (-1, 2)).unbind(-1)xk_r, xk_i = xk.float().reshape(xk.shape[:-1] + (-1, 2)).unbind(-1)# 重塑形为了广播freqs_cos = reshape_for_broadcast(freqs_cos, xq_r)freqs_sin = reshape_for_broadcast(freqs_sin, xq_r)# 应用旋转嵌入xq_out_r = xq_r * freqs_cos - xq_i * freqs_sinxq_out_i = xq_r * freqs_sin + xq_i * freqs_cosxk_out_r = xk_r * freqs_cos - xk_i * freqs_sinxk_out_i = xk_r * freqs_sin + xk_i * freqs_cos# 讲最后两维度拉平。xq_out = torch.stack([xq_out_r, xq_out_i], dim=-1).flatten(3)xk_out = torch.stack([xk_out_r, xk_out_i], dim=-1).flatten(3)return xq_out.type_as(xq), xk_out.type_as(xk)

1. precompute_freqs_cis 函数

这个函数用于预先计算频率和余弦正弦值,这些值将用于旋转位置编码。

  • 参数:

    • dim: 整数,表示维度。
    • end: 整数,表示序列的长度。
    • theta: 浮点数,用于计算频率的参数,默认值为10000.0。
  • 过程:

    • 使用torch.arange生成一个从0开始到dim的序列,步长为2,然后除以dim并取其倒数,再对结果进行theta次方的倒数,得到频率。
    • 使用torch.arange(end, device=freqs.device)生成一个从0到end-1的序列,用于与频率进行外积运算,得到每个位置的频率。
    • 计算外积的结果的余弦和正弦值。
  • 返回值:

    • 返回两个张量:freqs_cosfreqs_sin,分别包含所有位置的余弦和正弦值。

2. reshape_for_broadcast 函数

这个函数用于调整频率张量的形状,使其可以与输入张量进行广播操作。

  • 参数:

    • freqs_cis: 频率张量。
    • x: 输入张量。
  • 过程:

    • 检查freqs_cis的形状是否与x的第二和最后一维匹配。
    • 根据x的维度生成一个新的形状,其中第二和最后一维保持不变,其他维度设置为1,以便进行广播。
  • 返回值:

    • 返回重新形状后的频率张量。

3. apply_rotary_emb 函数

这个函数用于应用旋转位置编码到输入张量。

  • 参数:

    • xqxk: 输入张量,通常代表查询(query)和键(key)。
    • freqs_cosfreqs_sin: 余弦和正弦频率张量。
  • 过程:

    • xqxk重塑并分解为实部和虚部。
    • 使用reshape_for_broadcast调整频率张量的形状。
    • 应用旋转公式,其中实部和虚部分别乘以余弦和正弦值,然后进行适当的加减操作。
    • 将结果重新堆叠并拉平。
  • 返回值:

    • 返回旋转编码后的xqxk张量。
# 定义输入x, n_rep是需要重复的次数,在这里一般是组数
def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:bs, slen, n_kv_heads, head_dim = hidden_states.shape# dont need repeat here means multi head attentionif n_rep == 1:return hidden_states# first we expand x to (bs, seq_len, head, group, head_dim)hidden_states = hidden_states[:, :, :, None, :].expand(bs, slen, n_kv_heads, n_rep, head_dim)# reshape make head -> head * groupreturn hidden_states.reshape(bs, slen, n_kv_heads * n_rep, head_dim)

这段代码是一个Python函数,它定义了一个名为 repeat_kv 的函数,这个函数接受两个参数:hidden_statesn_rephidden_states 是一个PyTorch张量(tensor),它代表神经网络中的隐藏状态;n_rep 是一个整数,表示需要重复的次数,通常用于多头注意力机制中的组数。

函数的主要步骤如下:

  1. 首先,函数获取 hidden_states 张量的形状,这包括批次大小(bs)、序列长度(slen)、键值头数(n_kv_heads)和每个头的维度(head_dim)。

  2. 如果 n_rep 等于 1,这意味着不需要重复,函数直接返回原始的 hidden_states 张量。

  3. 如果需要重复(即 n_rep 大于 1),函数将 hidden_states 张量扩展到一个新的形状 (bs, slen, n_kv_heads, n_rep, head_dim)。这是通过在 hidden_states 张量中添加一个新的维度并使用 expand 方法来实现的。expand 方法不会实际复制数据,而是创建一个新的视图,其中重复了原始数据。

  4. 然后,函数将扩展后的张量重新塑形(reshape),将头数(n_kv_heads)和重复次数(n_rep)相乘,得到一个新的头数。这样,每个头的维度(head_dim)保持不变。

  5. 最后,函数返回重新塑形后的张量。

Attention

class Attention(nn.Module):def __init__(self, args: ModelArgs):super().__init__()self.group = args.n_groupself.heads = args.n_headsself.kv_heads = args.n_heads // args.n_groupassert args.n_heads % self.kv_heads == 0self.head_dim = args.dim // args.n_headsself.wq = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)self.wk = nn.Linear(args.dim, self.kv_heads * self.head_dim, bias=False)self.wv = nn.Linear(args.dim, self.kv_heads * self.head_dim, bias=False)self.wo = nn.Linear(args.n_heads * self.head_dim, args.dim, bias=False)self.attn_dropout = nn.Dropout(args.dropout)self.resid_dropout = nn.Dropout(args.dropout)self.dropout = args.dropoutmask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float("-inf"))mask = torch.triu(mask, diagonal=1)self.register_buffer("mask", mask)def forward(self,x: torch.Tensor,freqs_cos: torch.Tensor,freqs_sin: torch.Tensor,):bsz, seqlen, _ = x.shape# QKVxq, xk, xv = self.wq(x), self.wk(x), self.wv(x)xq = xq.view(bsz, seqlen, self.heads, self.head_dim)xk = xk.view(bsz, seqlen, self.kv_heads, self.head_dim)xv = xv.view(bsz, seqlen, self.kv_heads, self.head_dim)# RoPE relative positional embeddingsxq, xk = apply_rotary_emb(xq, xk, freqs_cos, freqs_sin)# grouped multiquery attention: expand out keys and valuesxk = repeat_kv(xk, self.group)  # (bs, seqlen, n_local_heads, head_dim)xv = repeat_kv(xv, self.group)  # (bs, seqlen, n_local_heads, head_dim)# make heads into a batch dimensionxq = xq.transpose(1, 2)  # (bs, n_local_heads, seqlen, head_dim)xk = xk.transpose(1, 2)xv = xv.transpose(1, 2)# 先不使用flash attn,从零走一遍流程!scores = torch.matmul(xq, xk.transpose(2, 3)) / math.sqrt(self.head_dim)assert hasattr(self, 'mask')scores = scores + self.mask[:, :, :seqlen, :seqlen]   # (bs, n_local_heads, seqlen, cache_len + seqlen)scores = F.softmax(scores.float(), dim=-1).type_as(xq)scores = self.attn_dropout(scores)output = torch.matmul(scores, xv)  # (bs, n_local_heads, seqlen, head_dim)# restore time as batch dimension and concat headsoutput = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)# 最终送入output层并正则,得到最终结果。output = self.wo(output)output = self.resid_dropout(output)return output

构造函数 __init__:

  1. 接受一个 ModelArgs 类型的参数 args,这个参数包含了模型的各种配置。
  2. 初始化分组数 self.group,头数 self.heads,键值头数 self.kv_heads,以及每个头的维度 self.head_dim
  3. 定义线性层 self.wqself.wkself.wvself.wo 分别用于计算查询(Q)、键(K)、值(V)和输出(O)。
  4. 初始化 Dropout 层 self.attn_dropout 和 self.resid_dropout
  5. 创建一个上三角掩码 mask 并注册为缓冲区,这个掩码用于在自注意力计算中防止位置信息的泄露。

前向传播函数 forward:

  1. 接受输入张量 x 和两个频率张量 freqs_cosfreqs_sin,这些张量用于计算相对位置编码。
  2. 计算查询(Q)、键(K)、值(V)并通过线性层进行变换。
  3. 将 Q、K、V 重塑为适合多头注意力的形状。
  4. 应用相对位置编码(RoPE)到 Q 和 K。
  5. 使用 repeat_kv 函数扩展 K 和 V,以适应分组的多头注意力。
  6. 将 Q、K、V 的头维度转换为批次维度。
  7. 计算 Q 和 K 的点积,然后应用掩码和 softmax 函数来获得注意力分数。
  8. 将注意力分数应用到 V 上,得到输出。
  9. 将输出的头维度合并,并应用输出线性层和残差连接的 Dropout。
  10. 返回最终的输出张量。

这个注意力机制的特点是:

  • 使用分组多头注意力,其中每个组内的头共享相同的键和值。
  • 引入了相对位置编码(RoPE),这是一种编码序列中相对位置信息的方法,可以增强模型对序列顺序的理解。
  • 使用 Dropout 来防止过拟合,并在训练过程中增加正则化。

FFN网络

class FeedForward(nn.Module):def __init__(self, dim: int, hidden_dim: int, multiple_of: int, dropout: float):super().__init__()if hidden_dim is None:hidden_dim = 4 * dimhidden_dim = int(2 * hidden_dim / 3)hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of)self.w1 = nn.Linear(dim, hidden_dim, bias=False)self.w2 = nn.Linear(hidden_dim, dim, bias=False)self.w3 = nn.Linear(dim, hidden_dim, bias=False)self.dropout = nn.Dropout(dropout)def forward(self, x):return self.dropout(self.w2(F.silu(self.w1(x)) * self.w3(x)))

构造函数 __init__:

  1. 接受参数 dim(输入和输出维度)、hidden_dim(中间层维度)、multiple_of(维度对齐因子)、dropout(Dropout 比率)。
  2. 如果 hidden_dim 没有提供(即 None),则使用默认的计算方式来确定它的值。这个默认值是输入维度的四倍,然后通过一个转换公式来确保它是 multiple_of 的整数倍。
  3. 定义三个线性层 self.w1self.w2 和 self.w3self.w1 将输入维度映射到 hidden_dimself.w2 将 hidden_dim 映射回 dim,而 self.w3 再次将 dim 映射到 hidden_dim
  4. 初始化 Dropout 层 self.dropout

前向传播函数 forward:

  1. 接受输入张量 x
  2. 首先,x 通过 self.w1 线性层。
  3. 然后,使用 SiLU(Sigmoid线性单元)激活函数(也称为 Swish)处理 self.w1(x) 的输出。
  4. 接着,将 self.w3(x) 的结果与 SiLU 激活后的结果相乘。
  5. 最后,将乘积通过 self.w2 线性层,并通过 self.dropout 应用 Dropout,然后返回最终的输出。

这个前馈网络的特点是:

  • 使用了 SiLU 激活函数,这是一种平滑的非单调激活函数,有助于模型的训练和泛化。
  • 通过 self.w3 引入了一个额外的线性变换,这在某些情况下可以增加模型的表达能力。
  • 使用 Dropout 来防止过拟合,并在训练过程中增加正则化。

decoder-layer

class TransformerBlock(nn.Module):def __init__(self, layer_id: int, args: ModelArgs):super().__init__()self.n_heads = args.n_headsself.dim = args.dimself.head_dim = args.dim // args.n_headsself.attention = Attention(args)self.feed_forward = FeedForward(dim=args.dim,hidden_dim=args.hidden_dim,multiple_of=args.multiple_of,dropout=args.dropout,)self.layer_id = layer_idself.attention_norm = RMSNorm(args.dim, eps=args.norm_eps)self.ffn_norm = RMSNorm(args.dim, eps=args.norm_eps)def forward(self, x, freqs_cos, freqs_sin):h = x + self.attention.forward(self.attention_norm(x), freqs_cos, freqs_sin)out = h + self.feed_forward.forward(self.ffn_norm(h))

构造函数 __init__:

  1. 接受参数 layer_id(表示当前层的编号)和 args(包含模型配置的 ModelArgs 类型)。
  2. 初始化头数 self.n_heads,维度 self.dim,以及每个头的维度 self.head_dim
  3. 创建一个 Attention 实例 self.attention,用于实现自注意力机制。
  4. 创建一个 FeedForward 实例 self.feed_forward,用于实现前馈网络。
  5. 初始化两个 RMSNorm 实例 self.attention_norm 和 self.ffn_norm,用于层归一化。

前向传播函数 forward:

  1. 接受输入张量 x 和两个频率张量 freqs_cosfreqs_sin,这些张量用于相对位置编码。
  2. 首先,将输入 x 通过层归一化 self.attention_norm,然后传递给自注意力层 self.attention
  3. 将自注意力层的输出与输入 x 相加,实现残差连接。
  4. 将残差连接的结果传递给另一个层归一化 self.ffn_norm,然后传递给前馈网络 self.feed_forward
  5. 将前馈网络的输出与残差连接的结果相加,再次实现残差连接。
  6. 返回最终的输出张量。

这个变换器块的特点是:

  • 使用了残差连接和层归一化,这有助于改善训练过程中的梯度流动,防止梯度消失或爆炸问题。
  • 自注意力机制允许模型在序列的不同位置之间动态地分配不同的注意力权重。
  • 前馈网络提供了额外的非线性变换能力。
class Transformer(nn.Module):last_loss: Optional[torch.Tensor]def __init__(self, params: ModelArgs):super().__init__()self.params = paramsself.vocab_size = params.vocab_sizeself.n_layers = params.n_layersself.tok_embeddings = nn.Embedding(params.vocab_size, params.dim)  # 其weight形状为(vocab,dim)self.dropout = nn.Dropout(params.dropout)self.layers = torch.nn.ModuleList()for layer_id in range(params.n_layers):self.layers.append(TransformerBlock(layer_id, params))self.norm = RMSNorm(params.dim, eps=params.norm_eps)self.output = nn.Linear(params.dim, params.vocab_size, bias=False)  # 维数也为(vocab,dim)--x·W^T# 将模型的嵌入层(embedding layer)和输出层(unembedding layer)的权重共享,即 "权重共享" 或 "weight tying"self.tok_embeddings.weight = self.output.weight # 来源论文: https://paperswithcode.com/method/weight-tying# some useful precompute for the RoPE relative positional embeddingsfreqs_cos, freqs_sin = precompute_freqs_cis(self.params.dim // self.params.n_heads, self.params.max_seq_len)self.register_buffer("freqs_cos", freqs_cos, persistent=False)self.register_buffer("freqs_sin", freqs_sin, persistent=False)# init all weightsself.apply(self._init_weights)# apply special scaled init to the residual projections, per GPT-2 paperfor pn, p in self.named_parameters():if pn.endswith('w3.weight') or pn.endswith('wo.weight'):torch.nn.init.normal_(p, mean=0.0, std=0.02/math.sqrt(2 * params.n_layers))# Initialize attribute for the loss of the last forward call. This will be set if the forward is called with a targets tensor.self.last_loss = Nonedef _init_weights(self, module):if isinstance(module, nn.Linear):torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)if module.bias is not None:torch.nn.init.zeros_(module.bias)elif isinstance(module, nn.Embedding):torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)def forward(self, tokens: torch.Tensor, targets: Optional[torch.Tensor] = None) -> torch.Tensor:_bsz, seqlen = tokens.shapeh = self.tok_embeddings(tokens)h = self.dropout(h)freqs_cos = self.freqs_cos[:seqlen]freqs_sin = self.freqs_sin[:seqlen]for layer in self.layers:# print('loging')h = layer(h, freqs_cos, freqs_sin)h = self.norm(h)if targets is not None:# 有targets则计算losslogits = self.output(h)self.last_loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)else:# 在推理阶段,只抽取最后一行--预测下一个token即可logits = self.output(h[:, [-1], :]).reshape(_bsz,-1) # note: using list [-1] to preserve the time dimself.last_loss = Nonereturn logits

构造函数 __init__:

  1. 接受参数 params,它是一个包含模型配置的 ModelArgs 类型。
  2. 初始化词汇表大小 self.vocab_size 和层数 self.n_layers
  3. 创建一个嵌入层 self.tok_embeddings,用于将词汇表中的单词映射到维度空间。
  4. 创建一个 Dropout 层 self.dropout
  5. 创建一个 TransformerBlock 层的列表 self.layers,每个层对应一个 TransformerBlock 实例。
  6. 创建一个层归一化层 self.norm
  7. 创建一个输出层 self.output,用于将变换器的输出映射回词汇表大小的维度空间。
  8. 实现权重共享,将嵌入层和输出层的权重设置为相同。
  9. 为相对位置编码预计算频率值,并将它们注册为缓冲区。
  10. 初始化所有权重,包括特殊的缩放初始化,用于残差投影。
  11. 初始化一个属性 self.last_loss 用于存储最后一次前向传播的损失。

前向传播函数 forward:

  1. 接受输入张量 tokens 和可选的目标张量 targets
  2. 计算嵌入层的输出 h 并应用 Dropout。
  3. 根据序列长度截取相对位置编码的频率值。
  4. 通过每个 TransformerBlock 层处理 h
  5. 应用层归一化。
  6. 如果提供了 targets,则计算交叉熵损失并将 self.last_loss 设置为该损失值。
  7. 如果没有提供 targets,则提取 h 的最后一行作为预测下一个 token 的 logits。
  8. 返回 logits。

这个变换器模型的特点是:

  • 使用权重共享来减少模型参数的数量,提高模型的性能。
  • 通过相对位置编码(RoPE)来引入序列中的位置信息。
  • 使用残差连接和层归一化来改善训练过程中的梯度流动。
  • 可以用于语言模型或其他需要处理序列数据的任务。

以上就是Llama3模型的架构讲解,感谢观看

Llama3模型结构原文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1539412.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

有毒有害气体检测仪的应用和性能_鼎跃安全

随着现代工业的不断发展和扩张&#xff0c;越来越多的企业涉及到有毒有害气体的生产、使用和处理。工业规模的扩大导致有毒有害气体的排放量增加&#xff0c;同时也增加了气体泄漏的风险。在发生火灾、爆炸或危险化学品泄漏等紧急事件时&#xff0c;救援人员需要迅速了解现场的…

生产管理电子看板如何助力工厂数字化转型

在当今快速发展的工业环境中&#xff0c;数字化转型已成为提升企业竞争力的关键因素之一。作为工厂管理的重要工具&#xff0c;生产管理电子看板在实现数字化转型方面发挥了不可或缺的作用。电子看板不仅优化了生产流程&#xff0c;还提高了决策效率&#xff0c;为企业带来了显…

什么是 IP 地址信誉?5 种改进方法

IP 地址声誉是营销中广泛使用的概念。它衡量 IP 地址的质量&#xff0c;这意味着您的电子邮件进入垃圾邮件或被完全阻止发送的可能性。 由于每个人都使用专用电子邮件提供商而不是直接通过 IP 地址进行通信&#xff0c;因此&#xff0c;这些服务可以跟踪和衡量发件人的行为质量…

heic图片怎么转化为jpg?教大家八种常见的heic转jpg方法!

heic图片怎么转化为jpg&#xff1f;谈及HEIC格式&#xff0c;这一新兴的图像编码方式以其卓越的压缩效率著称&#xff0c;它巧妙地运用了先进的编码技术&#xff0c;大幅缩减了图片占用的存储空间&#xff0c;然而&#xff0c;这枚科技果实也伴随着其独特的挑战&#xff0c;首要…

EMQX MQTT 服务器启用 SSL/TLS 安全连接,使用8883端口

1.提前下载安装openssl 2.新建openssl文件打开在命令行操作 3.按照下面的操作进行 MQTT 安全 作为基于现代密码学公钥算法的安全协议&#xff0c;TLS/SSL 能在计算机通讯网络上保证传输安全&#xff0c;EMQX 内置对 TLS/SSL 的支持&#xff0c;包括支持单/双向认证、X.509 证…

无法找到iutils.dll要怎么解决?四种修复iutils.dll的操作你知道么

当你收到“无法找到iutils.dll”的错误提示时&#xff0c;这表明你的系统缺失了一个关键的动态链接库文件&#xff0c;这可能阻止相关软件的正常启动和运行。面对这类问题&#xff0c;有多种方法可以帮助你追踪并恢复丢失的iutils.dll文件&#xff0c;从而使受影响的程序能够正…

Qt_布局管理器

目录 1、QVBoxLayout垂直布局 1.1 QVBoxLayout的使用 1.2 多个布局管理器 2、QHBoxLayout水平布局 2.1 QHBoxLayout的使用 2.2 嵌套的Layout 3、QGridLayout网格布局 3.1 QGridLayout的使用 3.2 设置控件大小比例 4、QFormLayout 4.1 QFormLayout的使用 5、…

基于Benes网络的SIMD同态密文任意重排

摘要 RLWE的密文使用了SIMD后极大的增加的同态加密的效率。同态加密通过加密一个向量&#xff0c;实现对明文的快速加法和乘法。然而&#xff0c;加密为一个密文的向量的内部元素之间&#xff0c;无法高效的操作。 如一个密文加密了 [ a , b , c ] [a,b,c] [a,b,c]&#xff0c…

arm64 中断处理流程

arm64 中断处理学习笔记 内核版本&#xff1a;5.15 平台&#xff1a;ARM64 汇编入口 中断产生的起始地点&#xff0c;中断向量表&#xff1a;arch/arm64/kernel/entry.S arm64 一共只有4种中断&#xff0c;sync为同步异常包含&#xff1a;(系统调用、数据中止、指令中止、栈…

YOLOv10轻量化快速涨点之改进AKConv

目录 1,什么是AKConv? 2,如何使用AKConv使YOLOv10快速长点? 2.1,在ultralytics-main/ultralytics/nn/modules/conv.py里面添加AKConv类 2.2,ultralytics-main/ultralytics/nn/modules/conv.py添加如下 2.3 在E:\czc\YOLOv10\ultralytics-main\ultralytics\nn\tasks.p…

闪回科技二度冲刺港股,深陷盈利困境,雷军看走眼了?

一台手机的流通循环&#xff0c;起于产线&#xff0c;止于废弃。 废弃是消费电子产品生命周期的最后一步&#xff0c;但是过去没有产业链玩家会把这一步骤当作产业链的一环。而商业机会恰恰藏在这样“反常识”的领域。 中国循环经济协会的数据表明&#xff0c;当前国内废旧手…

扒吉他谱的基本方法 扒吉他谱软件哪个好用

在音乐的浩瀚海洋中&#xff0c;吉他以其独特的魅力成为了众多音乐爱好者的首选伴侣。而学会扒谱&#xff0c;则如同掌握了一把开启音乐宝库的钥匙&#xff0c;不仅能够深化对音乐的理解&#xff0c;还能激发个人创作的灵感。扒谱&#xff0c;即通过耳朵聆听&#xff0c;将听到…

诗文发布模板(python代码打造键盘录入诗文自动排版,MarkDown源码文本)

python最好用的f-string&#xff0c;少量代码打造键盘录入诗文自动排版。 (笔记模板由python脚本于2024年09月19日 19:11:50创建&#xff0c;本篇笔记适合喜欢写诗的pythoner的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&am…

git使用“保姆级”教程1——简介及配置项设置

一、git介绍 Git是一个开源的分布式版本控制系统&#xff0c;用于&#xff1a;敏捷高效地处理任何或小或大的项目。Git 是Linus Torvalds 为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。版本控制&#xff1a; 版本控制&#xff08;Revision control&#xff…

Angular: ‘ng’ is not recognized as an internal or external command

背景 运行新项目的前端angular2项目时&#xff0c;需要全局安装angular-cli&#xff0c;然后使用ng serve --open命令启动项目。我安装好angular-cli后&#xff0c;在cmd里输入ng命令&#xff0c;死活无法识别。 解决过程 我按照网上的说法&#xff0c;去配置npm环境变量&am…

毕业论文写作会用到的AI软件!一定不能错过的18个网站!(务必收藏)

AI毕业论文写作它可以提供论文摘要、大纲、选题确立等多种写作辅助&#xff0c;还能帮助我们完成开题报告、实验报告、辩论灵感等内容。无论是文章纠正、批改&#xff0c;还是改写降重&#xff0c;它都能轻松搞定。甚至连论文致谢、创新创业计划书等都能为我们提供帮助。 以下…

近期值得关注的3个线性时序模型及其未来发展综述

前言 从Transformer架构提出以来&#xff0c;时间序列领域几乎绝大多数模型是在Transformer架构基础改进。但自注意力机制计算复杂过高&#xff0c;同时位置编码对时序信息表示不完全一直是问题。与之相比&#xff0c;线性模型有以下优势&#xff1a; 可解释性&#xff1a;数学…

颠覆想象!ReHiFace-S实现实时高保真换脸

颠覆想象&#xff01;ReHiFace-S实现实时高保真换脸 ReHiFace-S&#x1f680;&#xff0c;实时高保真换脸技术&#x1f31f;&#xff0c;开源易用&#x1f4bb;&#xff0c;支持ONNX和摄像头模式&#x1f4f8;&#xff0c;让数字人生成更真实✨&#xff01;体验前沿科技&#…

启发式生成最佳轨迹ReGentS:超32个智能体生成现实世界的安全关键驾驶场景

Abstract 基于机器学习的自动驾驶系统经常面临安全关键场景的挑战&#xff0c;而这些场景在真实世界的数据中较为罕见&#xff0c;从而阻碍了其大规模部署。虽然增加真实世界训练数据的覆盖范围可以解决这个问题&#xff0c;但代价高昂且存在危险。本研究通过轨迹优化&#xf…

OpenAI震撼发布新一代AI模型o1系列:解锁推理与数学新纪元

在科技界翘首以盼的目光中&#xff0c;OpenAI终于揭开了其新一代AI模型系列的神秘面纱——o1系列&#xff0c;这一系列模型以其前所未有的能力&#xff0c;在解决复杂推理与数学问题上展现出了卓越的性能。周四&#xff0c;OpenAI向部分尊贵的付费用户率先开放了o1-preview与o1…