【多模态大模型】LLaMA in arXiv 2023

news2024/9/21 16:24:48

一、引言

论文: LLaMA: Open and Efficient Foundation Language Models
作者: Meta AI
代码: LLaMA
特点: 该方法在Transformer的基础上增加了Pre-normalization (RMSNorm)、SwiGLU activation function (SwiGLU)、Rotary Embeddings (RoPE)、FlashAttention。

⚠️ 在学习该方法前,建议补充BatchNorm、LayerNorm、位置编码、Attention的相关知识。

二、详情

Transformer和LLaMA的结构图如下:

可见,其结构差异主要体现在如下方面:

  • Transformer采用了左编码器+右解码器(Encoder+Decoder)的结构,LLaMA采用了仅解码器(Decoder-only)的结构。由于仅包含解码器不需要与编码器输出交互,故LLaMA去掉了Transformer中Decoder中间的交叉Multi-Head Attention和Add & Norm。
  • LLaMA采用了归一化前置(Pre-normalization)的策略,将归一化操作放在了注意力、FFN前并在线性映射前增加了一个归一化。此外,LLaMA还将LayerNorm替换为了RMSNorm
  • LLaMA将绝对位置编码替换为了旋转位置编码,即RoPE,这是一种只对Q和K进行位置编码的方式。
  • 为加速训练,LLaMA引入了FlashAttention
  • LLaMA将ReLU替换为了SwiGLU

2.1 RMSNorm

均方根归一化RMSNorm简化了LayerNorm的计算。

要了解RMSNorm,首先需回顾LayerNorm的公式:

其中, x \boldsymbol{x} x为输入的token序列, E [ x ] = 1 n ∑ i = 1 n x i {\bf E}\boldsymbol{[x]}=\frac{1}{n}\sum_{i=1}^{n}\boldsymbol{x}_i E[x]=n1i=1nxi V a r [ x ] = 1 n ∑ i = 1 n ( x i − E [ x ] ) 2 {\bf Var}\boldsymbol{[x]}=\sqrt{\frac{1}{n}\sum_{i=1}^n(\boldsymbol{x}_i-{\bf E}\boldsymbol{[x]})^2} Var[x]=n1i=1n(xiE[x])2 x \boldsymbol{x} x的均值和有偏方差, ϵ \boldsymbol{\epsilon} ϵ用来防止分母为0, γ \boldsymbol{\gamma} γ β \boldsymbol{\beta} β是可学习的参数用来缩放和平移。

RMSNorm简化了LayerNorm的计算,其公式如下:

其中, R M S [ x ] = 1 n ∑ i = 1 n x i 2 {\bf RMS}\boldsymbol{[x]}=\sqrt{\frac{1}{n}\sum_{i=1}^{n}\boldsymbol{x}_i^2} RMS[x]=n1i=1nxi2 是均方根。

可见,RMSNormLayerNorm主要有如下差别:

  • RMSNorm无需计算均值 E [ x ] {\bf E}[\boldsymbol{x}] E[x]
  • RMSNorm将有偏方差 V a r [ x ] {\bf Var[\boldsymbol{x}]} Var[x]替换为了均方根 R M S [ x ] {\bf RMS[\boldsymbol{x}]} RMS[x]
  • RMSNorm无需平移项 γ \boldsymbol{\gamma} γ

LayerNorm一样,RMSNorm也能以句子或单词(token)为单位进行归一化,如下给出了以token为单位的代码示例。

import torch
import torch.nn as nn


class MyRMSNorm(nn.Module):
    def __init__(self, hidden_dim, eps=1e-8):
        super().__init__()
        # 防止分母计算为0
        self._eps = eps
        # 仿射变换参数,缩放norm后的数据分布
        self._gamma = nn.Parameter(torch.ones(hidden_dim))

    def forward(self, input):
        # input(N,L,C)
        ms = input.pow(2).mean(dim=-1, keepdim=True)  # 计算均方,token-wise
        input = input / torch.sqrt(ms + self._eps)  # 执行标准化
        return input * self._gamma  # 仿射变换


if __name__ == '__main__':
    batch_size = 4
    length = 2
    hidden_dim = 3
    input = torch.rand(4, 2, 3)

    myRMSN = MyRMSNorm(hidden_dim=hidden_dim)
    MyO = myRMSN(input)

    pytorchRMSN = nn.RMSNorm(normalized_shape=hidden_dim, elementwise_affine=False)  # 不使用可学习的gamma和beta
    pytorchO = pytorchRMSN(input)

    print(MyO == pytorchO)

2.2 RoPE

旋转位置编码RoPE使用绝对位置信息设计旋转规则,使旋转后的数据能够表达相对位置信息。

要了解RoPE,首先我们来了解一下二维空间的旋转。如下图:

其中, X = [ ρ cos ⁡ ϕ , ρ sin ⁡ ϕ ] X=[\rho\cos\phi,\rho\sin\phi] X=[ρcosϕ,ρsinϕ]是一个二维向量,逆时针旋转 θ \theta θ度变成 X R ( θ ) XR(\theta) XR(θ)。此时 R ( θ ) = [ cos ⁡ θ ,   sin ⁡ θ − sin ⁡ θ ,   cos ⁡ θ ] R(\theta)=\left[\begin{matrix}\cos\theta,~\sin\theta\\-\sin\theta,~\cos\theta\end{matrix}\right] R(θ)=[cosθ, sinθsinθ, cosθ],证明如下:

X R ( θ ) = [ ρ cos ⁡ ϕ , ρ sin ⁡ ϕ ] [ cos ⁡ θ ,   sin ⁡ θ − sin ⁡ θ ,   cos ⁡ θ ] = ρ [ cos ⁡ ϕ cos ⁡ θ − sin ⁡ ϕ sin ⁡ θ , cos ⁡ ϕ sin ⁡ θ + sin ⁡ ϕ cos ⁡ θ ] = [ ρ cos ⁡ ( ϕ + θ ) , ρ sin ⁡ ( ϕ + θ ) ] XR(\theta)=[\rho\cos\phi,\rho\sin\phi]\left[\begin{matrix}\cos\theta,~\sin\theta\\-\sin\theta,~\cos\theta\end{matrix}\right]\\=\rho[\cos\phi\cos\theta-\sin\phi\sin\theta,\cos\phi\sin\theta+\sin\phi\cos\theta]=[\rho\cos(\phi+\theta),\rho\sin(\phi+\theta)] XR(θ)=[ρcosϕ,ρsinϕ][cosθ, sinθsinθ, cosθ]=ρ[cosϕcosθsinϕsinθ,cosϕsinθ+sinϕcosθ]=[ρcos(ϕ+θ),ρsin(ϕ+θ)]

可见, X X X X R ( θ ) XR(\theta) XR(θ)仅差一个 θ \theta θ,所以二维空间逆时针旋转 θ \theta θ度可通过 R ( θ ) R(\theta) R(θ)实现。

旋转只改变角度,不改变长度。

RoPE将旋转应用在了注意力模块的查询 Q Q Q K K K上。它将第 i i i个查询 Q i Q_i Qi旋转 i θ i\theta iθ的角度,再将第 j j j个键 K j K_j Kj旋转 j θ j\theta jθ的角度,那么 Q i K j T Q_iK_j^T QiKjT就会变成一个与相对位置 i − j i-j ij相关的值。推导过程如下:

i i i j j j是查询 Q i Q_i Qi K j K_j Kj的绝对位置, i − j i-j ij是它们的相对位置。

然而, Q i Q_i Qi K j K_j Kj的维度通常都是大于2的,我们假设它是 D D D D D D是2的整数倍,于是我们可以将 Q i Q_i Qi K j K_j Kj分别划分为 d = D 2 d=\frac{D}{2} d=2D个子空间,每个子空间都是二维的。

下图给出了一个 D = 10 D=10 D=10的例子,我们将 Q i Q_i Qi K j K_j Kj分为5个子空间并分配1个包括5个角度的旋转序列 Θ = ( θ 1 , θ 2 , ⋯   , θ 5 ) \Theta=(\theta_1,\theta_2,\cdots,\theta_5) Θ=(θ1,θ2,,θ5),每个子空间的旋转角度是在对应旋转序列的基础上乘以 i i i j j j

将其扩展到 d d d个子空间,可以得到如下信息:

其中, X i X_i Xi代指 Q i Q_i Qi K j K_j Kj。此时,这种旋转仍然具有相对位置的表达能力,证明如下:

显然,上面的 R ( i Θ ) R(i\Theta) R(iΘ)过于稀疏,为了提升计算效率,通常 d d d个子空间的旋转使用下式表达:

为避免token数过多, i θ k i\theta_k iθk j θ k j\theta_k jθk重叠导致相对位置得不到表达(同一个子空间 k k k,绝对位置 i i i j j j不同, i θ k − j θ k = 2 m π i\theta_k-j\theta_k=2m\pi iθkjθk=2时重叠, m m m是一个整数),RoPE使用了一个递减的等比数列作为 θ \theta θ序列,如下:

θ k \theta_k θk是递减的,这表示token中前几个子空间的旋转角度较大,越往后旋转角度越小。

事实上,为了方便我们通常不是将相邻的两个值划分至同一子空间,而是将D分为前后两个部分,前后各取一个依次组成子空间,例如[q0,q1,q2,q3]被划分为[q0,q2], [q1,q3]而不是[q0,q1], [q2,q3]。以下为使用这种方式进行子空间划分的RoPE代码:

from torch.nn import functional as F
import torch.nn as nn
import torch
import math


class Rotator:
    """根据hidden_dim,和position_ids 生成对应的旋转位置编码, 和论文中定义略有不同,一个个二维的子空间被
    分割到了前后两部分,分别进行旋转,然后拼接起来
    """
    def __init__(self, D, position_ids):
        """ position_ids: [seq_len], D 和单个头的hidden_dim对应 """
        base = 10000
        d = D / 2
        B = base ** (1/d)
        theta_base = 1.0 / (B ** (torch.arange(0, d)))    # 等比数列, $\Theta$
        thetas = position_ids.outer(theta_base)  # [seq_len, D/2]
        # 这里的子空间划分与讲解不同,[q0,q1,q2,q3] -> [q0,q2],[q1,q3]是两个子空间而不是[q0,q1],[q2,q3]
        full_thetas = torch.cat((thetas, thetas), dim=-1)  # [seq_len, D]
        self.cos = full_thetas.cos()
        self.sin = full_thetas.sin()


    def rotate(self, x):
        """
        x: [bs, num_attention_heads, seq_len, D]
        q: [bs, num_attention_heads, seq_len, D]
        cos: [seq_len, D]
        [x,y] @ [[cos, sin], [-sin, cos]] = [x*cos-y*sin, ycos+x*sin] =[x,y]*cos+[-y, x]*sin
        """
        return x * self.cos + Rotator.reverse_half(x) * self.sin


    @staticmethod
    def reverse_half(q):
        """ q: [bs, num_attention_heads, seq_len, D] trick2 """
        u = q[..., :q.shape[-1] // 2]  # 认为是各个二维子空间的第一维的向量集结
        v = q[..., q.shape[-1] // 2:]   # 认为是各个二维子空间的第二维的向量集结
        return torch.cat((-v, u), dim=-1)


if __name__ == "__main__":
    batch_size = 2
    num_heads = 3
    D = 6  # 单个头的token向量长度
    hidden_dim = D * num_heads
    seq_len = 4
    position_ids = torch.arange(seq_len)
    rotator = Rotator(D, position_ids)

    x = torch.randn((batch_size, seq_len, hidden_dim))
    # 对每个头分别进行旋转,[batch_size,seq_len,hidden_dim] -> [batch_size,seq_len,num_heads,D] -> [batch_size,num_heads,seq_len,D]
    x = x.view(batch_size, seq_len, num_heads, D).transpose(1, 2)
    x = rotator.rotate(x)

2.3 FlashAttention

FlashAttention以分块的形式进行注意力计算,避免了SRAM和HBM之间频繁读写导致的时间浪费。

详情请参考我之前的博客FlashAttention in NeurIPS 2022。

2.4 SwiGLU

激活函数SwiGLU是门控线性单元(Gated Linear Units, GLU)的变体,下图红框中表达了GLU的计算过程:

可见,GLU会先使用两个带偏执的线性层映射输入 x \boldsymbol{x} x,分别记为 x W 1 + b 1 \boldsymbol{xW_1+b_1} xW1+b1 x W 2 + b 2 \boldsymbol{xW_2+b_2} xW2+b2;其中一个线性映射后会跟一个非线性激活函数sigmoid,记为 σ ( x W 1 + b 1 ) \sigma(\boldsymbol{xW_1+b_1}) σ(xW1+b1);然后将左右两边的结果对应元素相乘即完成了GLU,记为 σ ( x W 1 + b 1 ) ⊗ ( x W 2 + b 2 ) \sigma(\boldsymbol{xW_1+b_1})\otimes(\boldsymbol{xW_2+b_2}) σ(xW1+b1)(xW2+b2)

SwiGLUGLU做了两点改进:

  • 去掉了两个线性映射的偏执项,此时公式变成 σ ( x W 1 ) ⊗ ( x W 2 ) \sigma(\boldsymbol{xW_1})\otimes(\boldsymbol{xW_2}) σ(xW1)(xW2)
  • sigmoid替换为了Swish,此时公式变成 Swish β ( x W 1 ) ⊗ ( x W 2 ) \text{Swish}_{\beta}(\boldsymbol{xW_1})\otimes(\boldsymbol{xW_2}) Swishβ(xW1)(xW2)

Swish的公式为 Swish β ( a ) = a σ ( β a ) = a 1 + e − β a \text{Swish}_{\beta}(a)=a\sigma(\beta a)=\frac{a}{1+e^{-\beta a}} Swishβ(a)=(βa)=1+eβaa,在不同的 β \beta β下该非线性激活函数的曲线如下:

可见,当 β \beta β较大时,该曲线与ReLU十分接近;当 β = 1 \beta=1 β=1时,小于0但接近0的曲线变得更光滑且非单调。

SwiGLU则选用了 β = 1 \beta=1 β=1Swish,于是我们得到SwiGLU的公式如下:
Swish ( x W 1 ) ⊗ ( x W 2 ) = x W 1 1 + e − x W 1 ⊗ x W 2 \text{Swish}(\boldsymbol{xW_1})\otimes(\boldsymbol{xW_2})=\frac{\boldsymbol{xW_1}}{1+e^{-\boldsymbol{xW_1}}}\otimes\boldsymbol{xW_2} Swish(xW1)(xW2)=1+exW1xW1xW2

致谢:

本博客仅做记录使用,无任何商业用途,参考内容如下:
解密旋转位置编码:数学基础、代码实现与绝对编码一体化探索
一文为你深度解析 LLaMA2 模型架构
Llama改进之——SwiGLU激活函数

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

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

相关文章

Redis远程字典服务器(5) —— list类型详解

目录 一,基本情况 二,list常用命令 2.1 lpush,lrange 2.2 对于“下标越界”的思考 2.3 lpushx,rpush,rpushx 2.4 lpop,rpop 2.5 lindex,linsert,llen 2.6 lrem 2.7 ltrim…

【JavaEE】深入浅出:Spring Boot配置文件全解析

目录 SpringBoot 配置⽂件配置⽂件作⽤SpringBoot配置⽂件 配置⽂件快速⼊⼿配置⽂件的格式properties 配置⽂件说明properties 基本语法读取配置⽂件properties 缺点分析 yml 配置⽂件说明yml 基本语法yml 使⽤进阶yml 配置不同数据类型及 null配置对象配置集合配置Map yml优缺…

clickhouse集群+Zk优化-解决只读模式,主节点磁盘增长快

问题1:数据库进入只读模式 最近在项目中使用clickhouse的时候,遇到了一个批量插入后报错的问题。报错的内容是数据库进入了只读模式,导致数据写不进去。发现有大量的批量写入报错日志信息。(关键异常信息:DB::Exceptio…

数据库行转列

一、行转列 1、使用case…when…then 2、使用SUM(IF()) 生成列 3、使用SUM(IF()) 生成列 WITH ROLLUP 生成汇总行 4、使用SUM(IF()) 生成列,直接生成汇总结果,不再利用子查询 5、使用SUM(IF()) 生成列 UNION 生成汇总行,并利用 IFNULL将汇总行标题…

【安卓】Service生命周期与前台活动

文章目录 Service生命周期使用前台Service 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。 点击跳转到网站。 Service生命周期 在项目的任何位置调用了Context的startService()方法,相应的Se…

安卓好软-----开源的跳开屏广告工具 无需root 权限

GKD 开源项目。 目前最新版本是1.8.0 这款工具是一款免费开源简洁多规则的自动跳过广告的软件。简而言之,基于预设的定时更新订阅规则快照功能,实现识别并自动点击跳过任何开屏广告及点击关闭应用内部任何弹窗广告,如关闭某些APP开屏和内含推…

ITSS服务经理与ITSS服务工程师的岗位职责分析

信息技术服务标准(ITSS)是一套全面而系统的规范,旨在指导和标准化信息技术服务的提供。 由信息技术服务标准工作组精心制定,这些标准不仅总结了行业的最佳实践,还提升了从事信息技术服务研发与应用的各类组织自主创新…

iOS的App启动详细过程(底层知识)

1.虚拟内存 & ASLR 在早期计算机中数据是直接通过物理地址访问的,这就造成了下面两个问题 1.内存不够用 2.数据安全问题 内存不够 ---> 虚拟内存 虚拟内存就是通过创建一张物理地址和虚拟地址的映射表来管理内存,提高了CPU利用率,…

【Python快速入门和实践012】Python常用脚本-目标检测之查看数据集标签类别及对应数量

一、功能介绍 这段代码的功能是从指定的目录中读取所有的XML文件&#xff0c;并统计这些文件中特定标签&#xff08;<object>标签内的<name>标签&#xff09;的内容和出现次数。 二、代码 import os import xml.etree.ElementTree as ET import globdef count_nu…

SAK-TC277TP-64F200N DC:32位RAM微控制器、常用于消费者应用

描述&#xff1a; SAK-TC277TP-64F200N DC属于第一代Aurix TC27xT产品。其创新多核心架构基于多达三个独立32位TriCore CPU&#xff0c;专为满足极高的安全标准&#xff0c;同时大幅提高性能而设计。TC27xT系列产品配备200 MHz TriCore、5V 或3.3V 单电压供电和强大的通用定时器…

【Linux】中的软件安装:深入探索RPM、SRPM与YUM

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Linux的起源与发展 2、RPM、SRPM与YUM的简要介…

【Redis】数据类型详解及其应用场景

目录 Redis 常⻅数据类型预备知识基本全局命令小结 数据结构和内部编码单线程架构引出单线程模型为什么单线程还能这么快 Redis 常⻅数据类型 Redis 提供了 5 种数据结构&#xff0c;理解每种数据结构的特点对于 Redis 开发运维⾮常重要&#xff0c;同时掌握每种数据结构的常⻅…

【大数据】智慧园区大数据云平台整体建设方案(Word原件)

第一章 项目建设背景及现状 第二章 园区创新发展趋势 第三章 工业园区大数据存在的问题 第四章 智慧工业园区大数据建设目的 第五章 智慧园区总体构架 第六章 系统核心组件 第七章 智慧工业园区大数据平台规划设计 获取方式&#xff1a;本文末个人名片直接获取。 软件资料清单列…

springboot使用aop或Jackson进行数据脱敏

1.aop 启动类加EnableAspectJAutoProxy 自定义注解&#xff0c;在实体类中使用表示被脱敏字段 建立aop切面类 可能这里gpt会建议你用Pointcut("execution(public * com.xx.aop..*.get*(..))")这种方式拦截&#xff0c;这种我试了&#xff0c;拦截不住。猜测在mvc返…

灵办AI免费ChatGPT4人工智能浏览器插件快速便捷(多功能)

灵办AI就是您所需的最佳助手&#xff01;我们为您带来了一款多功能AI工具&#xff0c;不仅能为您提供精准翻译&#xff0c;还能满足您的对话需求、智能续写、AI搜索、文档阅读、代码生成与修正等多种需求。灵办 AI&#xff0c;真正让工作和学习变得轻松高效&#xff01; 推荐使…

Android高版本抓包总结

方案1 CharlesVirtualXposedJustTrustMe 推荐使用三星手机此方案 VirtualXposed下载链接&#xff1a;https://github.com/android-hacker/VirtualXposed/releases JustTrustMe下载链接&#xff1a;https://github.com/Fuzion24/JustTrustMe/releases/ 下载完成后使用adb命令…

我的吃鸡日志 中2 从菜鸟到专家

hey&#xff0c;我又来啦&#xff01; 我的吃鸡日志中1之复仇计划见这个。 &#xff08;游戏入口&#xff1a;和平精英38.0 快乐星空&#xff09; 苦学两年半。。。。。。 hey hey hey&#xff0c;这次我必须赢&#xff01; 打开游戏ing。。。。。。 作战ing。。。。 先在…

43-设计规则:铺铜规则

1、铺铜规则设置 铺铜规则[plane]&#xff1a; PowerPlane Connect Style[负片层连接方式]: PlaneConnect&#xff1a;Direct Connect/ 高级设置->过孔改成完全连接 Power Plane Clearance[负片层间距设置]: PlaneClearance&#xff1a; 8mil Polygon Connect Style[正片层…

电动汽车和混动汽车DC-DC转换器的创新设计与测试方法

汽车 DC-DC 转换器市场规模将达到187亿美元&#xff0c;年复合增长率为10%。 DC-DC 转换器是汽车的重要组成部分&#xff0c;它可以通过电压转换为各种车载系统供电&#xff0c;例如日益复杂的车载信息娱乐系统、使用驾驶辅助系统&#xff08;ADAS&#xff09;实现的增强安全功…

VMware虚拟机H群晖7.2懒人包

目录 0. 准备 1. 下载 2. 解压 3. 导入VMware 4.开机 5.查找设备 6.登录初始化 随着DSM系统的升级,群辉NAS很多组件变哈很大,有的已经放弃不再支持,有的与其它功能合并,甚至新开发的组件仅仅支持DSM7以上。为了体验新的组件,因此有必要再安装依噶DSM7.x以上的群辉,…