在AMD GPU上加速大型语言模型的Flash Attention

news2024/11/29 4:46:34

Accelerating Large Language Models with Flash Attention on AMD GPUs — ROCm Blogs

引言

在这篇博客文章中,我们将指导您如何在AMD GPU上安装Flash Attention,并提供与在PyTorch中标准SDPA比较其性能的基准测试。我们还将测量Hugging Face中多个大型语言模型(LLM)的端到端预填充延迟。
为了理解Flash Attention及其基准测试结果的重要性,让我们首先深入了解一下推动了变压器架构成功的注意力机制。这种机制是编码器和解码器块的关键组成部分,使得变压器在包括自然语言处理、计算机视觉和音频任务在内的广泛AI领埄中出类拔萃。

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

Transformer模型中使用的注意力机制被称为尺度点积注意力(SDPA)。SDPA的公式如下,其中_Q_、_K_和_V_是查询、键和值矩阈,_dₖ_是键向量的尺寸:

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

SDPA使得模型在生成输出的每个元素时都能够关注输入的不同部分,使模型能够捕获长距离依赖关系并处理长度不等的序列。

SDPA的成本

尽管SDPA已证明其效用,但它在对较长序列(例如整本书或长视频)进行建模时面临挑战,由于其时间和内存复杂度是二次方的 —— 以输入序列长度 N 的 _O(N²)_。例如,如果你将序列长度加倍,计算所需的时间将增加四倍,并且需要四倍的高带宽内存(HBM)读写次数。由于SDPA的巨大计算成本,其在处理长上下文任务时的适用性受到限制。

针对精简注意力机制计算成本的研究工作相当多,涵盖了采用稀疏和低秩技术来逼近注意力。稀疏注意力技术消除了注意力矩阵中某些条目,而低秩方法将注意力矩阵分解为更小的低秩组件。尽管这些方法可以将计算需求降低至接近线性时间,但由于质量上的折中和与标准SDPA相比实际时钟速度提升有限,它们没有得到广泛采用。

内存瓶颈

近似注意力算法有限的时钟速度提升的主要原因是重点在于减少浮点运算(FLOPs)而不是解决内存访问开销。有趣的是,如果您在GPU上对SDPA进行分析,您会发现大部分时间被丢弃、softmax和掩蔽操作消耗,而不是计算密集型的矩阵乘法。这一意外结果可以通过变压器被内存带宽而不是计算速度所限制来解释。数据移动是您所需的一切揭示了像矩阵乘法这样的计算密集型操作构成了总FLOPs的99.8%以上,但只占总运行时间的61%。出人意料的是,内存密集型操作(如统计归一化和元素级函数,例如丢弃和掩蔽)消耗了变压器模型运行时间剩余的40%。这些内存密集型操作只占总FLOPs的0.02%。

简而言之,读写内存花费的时间比实际计算要多。这些结果突显了在基于变压器的模型中解决内存瓶颈的重要性。

Flash Attention

为了解决这些内存瓶颈问题,Tri Dao提出了Flash Attention,这是一种计算精确关注度的硬件感知关注算法。它通过减少对GPU存储器的关注矩阵的读写次数,并尽可能多地在芯片上进行计算,以解决内存瓶颈。这需要在没有完整输入的情况下计算softmax,并且不存储来自前向传播的关注矩阵。Flash Attention通过将输入分成块,并使用分块在芯片上逐步计算块softmax来实现这一目标。此外,它采用重计算通过仅存储来自前向传播的softmax归一化因子来快速重新计算注意力。所有这些操作都合并到单个GPU内核中,从而实现显著的加速和减少内存使用。深入了解Flash Attention,我们推荐阅读原始论文:Flash Attention:高速且内存高效的精确注意力计算,具备输入输出意识。您可以在GitHub 仓库中找到本博客文章中使用的所有文件和脚本。

先决条件

要运行这篇博客,您需要以下条件:
Linux:请见支持的Linux发行版
ROCm 5.7+:请见安装说明
PyTorch 2.3+:请见安装说明
支持的AMD GPU:请见兼容GPU列表

起步

在这篇博客中,我们将使用rocm/pytorch-nightly Docker镜像并在容器中构建Flash Attention。为了开始,让我们拉取它。

docker pull rocm/pytorch-nightly:latest
docker run -it --network=host --group-add=video \
            --ipc=host --cap-add=SYS_PTRACE \
            --security-opt seccomp=unconfined \
            --device /dev/kfd --device /dev/dri \
            rocm/pytorch-nightly:latest

接下来安装我们需要的库。

pip install -q transformers accelerate matplotlib

要安装带有ROCm支持的Flash Attention,我们不能简单地运行
pip install flash-attn,因为它安装的版本与AMD GPU不兼容。相反,我们需要克隆AMD的`flash-attention`仓库并从源码构建它。

git clone --recursive https://github.com/ROCm/flash-attention.git
cd flash-attention
MAX_JOBS=$((`nproc` - 1)) pip install -v .

接下来,导入我们需要的库。

import torch
import numpy as np
from tqdm import tqdm
import torch.nn.functional as F
from matplotlib import pyplot as plt
from transformers import AutoTokenizer, AutoModelForCausalLM

基准测试注意力

随着ROCm为PyTorch 2.3发布,Flash Attention现已直接集成到F.scaled_dot_product_attention功能中。默认情况下,当调用`F.scaled_dot_product_attention`并传入查询、键和值矩阵时,它现在将使用Flash Attention计算注意力分数。

为了准确基准测试PyTorch的Flash Attention,我们首先创建一个原始的`scaled_dot_product_attention`函数,以急切模式计算注意力分数。值得注意的是,`F.scaled_dot_product_attention`中找到的其他尺度点积注意力变种在不同程度上融合了注意力操作,而我们在这里有意避免了这一点,以符合原始Flash Attention论文的方法论。

def scaled_dot_product_attention(query, key, value, attn_mask=None, is_causal=False, dropout_p=0.0, scale=None):
    """
    Computes the scaled dot product attention between query, key, and value tensors in PyTorch eager mode.

    Args:
        query (torch.Tensor): The query tensor of shape (batch_size, n_heads, seq_len, hidden_dim).
        key (torch.Tensor): The key tensor of shape (batch_size, n_heads, seq_len, hidden_dim).
        value (torch.Tensor): The value tensor of shape (batch_size, n_heads, seq_len, hidden_dim).
        attn_mask (torch.Tensor, optional): The attention mask tensor of shape (batch_size, n_heads, seq_len, seq_len). Defaults to None.
        is_causal (bool, optional): Whether to apply a causal attention mask. Defaults to False.
        dropout_p (float, optional): The dropout probability. Defaults to 0.
        scale (float, optional): The scale factor for the dot product. Defaults to None.

    Returns:
        torch.Tensor: The output tensor of shape (batch_size, n_heads, seq_len, hidden_dim).
    """

    # Calculate the scale factor
    scale_factor = 1 / np.sqrt(query.size(-1)) if scale is None else scale
    attn_weight = (query @ key.transpose(-2, -1) * scale_factor)
    
    # Create the attention mask
    attn_mask = torch.ones(query.shape[0], query.shape[1], query.shape[2], query.shape[2], dtype=torch.bool, device=device).tril(diagonal=0) if is_causal else attn_mask
    attn_weight = attn_weight.masked_fill_(~attn_mask, -torch.inf) if attn_mask is not None else attn_weight
      
    # Compute the scaled dot product attention
    attn_weight = torch.softmax(attn_weight, dim=-1)
    attn_weight = torch.dropout(attn_weight, dropout_p, train=False)

    return attn_weight @ value

为了测试`scaled_dot_product_attention`函数并将其与Flash Attention进行基准测试,我们首先定义一些关键参数。具体来说,我们设置注意力头数为32,嵌入头维度为128。这些设置被选中以匹配常见的7B个因果变压器模型。根据这些关键参数,我们创建查询、键和值矩阵,以测试我们的`scaled_dot_product_attention`计算的注意力分数是否与PyTorch的`F.scaled_dot_product_attention`计算的分数匹配。

batch_size = 1
seq_len = 64
num_heads = 32
embed_dim = 128
dtype = torch.float16
device = torch.device("cuda")

query = torch.rand(batch_size, num_heads, seq_len, embed_dim, device=device, dtype=dtype)
key = torch.rand(batch_size, num_heads, seq_len, embed_dim, device=device, dtype=dtype)
value = torch.rand(batch_size, num_heads, seq_len, embed_dim, device=device, dtype=dtype)
eager = scaled_dot_product_attention(query, key, value, is_causal=True)
flash = F.scaled_dot_product_attention(query, key, value, is_causal=True)
assert torch.allclose(eager, flash, rtol=1e-03,atol=1e-03)

我们自己编写的简化版`scaled_dot_product_attention`函数的输出与PyTorch的输出相匹配。现在我们定义了一个名为`bench_attention`的函数,用来测量计算给定序列长度的多头注意力所需的平均时间。

def bench_attention(seq_len, flash=False, num_repeats=256):
    """
    Measures the average time (in ms) required to compute multi-head attention for sequences of a given length.

    Args:
        seq_len (int): The length of the input sequence.
        flash (bool, optional): Whether to use the FlashAttention algorithm. Defaults to False.
        num_repeats (int, optional): The number of times to repeat the attention computation for timing purposes. Defaults to 256.

    Returns:
        float: The average time (in ms) required to compute multi-head attention for sequences of length seq_len.
    """
    
    if flash:
        mha = F.scaled_dot_product_attention
    else:
        mha = scaled_dot_product_attention
        
    query = torch.rand(batch_size, num_heads, seq_len, embed_dim, device=device, dtype=dtype)
    key = torch.rand(batch_size, num_heads, seq_len, embed_dim, device=device, dtype=dtype)
    value = torch.rand(batch_size, num_heads, seq_len, embed_dim, device=device, dtype=dtype)

    start = torch.cuda.Event(enable_timing=True)
    end = torch.cuda.Event(enable_timing=True)

    # Warmup
    for _ in range(4):
        _ = mha(query, key, value, is_causal=True)

    start.record()
    for _ in range(num_repeats):
        _ = mha(query, key, value, is_causal=True)   
    end.record()
    torch.cuda.synchronize()
    
    return start.elapsed_time(end) / num_repeats

现在,让我们来为范围在256至4096(包含端点)的序列长度基准测试Flash Attention与未融合SDPA实现的性能。

context_len = np.arange(256,4096,64)
flash = np.zeros(context_len.shape)
standard = np.zeros(context_len.shape)

for i,l in enumerate(tqdm(context_len)):
    flash[i] = bench_attention(l,flash=True)
    standard[i] = bench_attention(l,flash=False)

绘制结果。

plt.plot(context_len, standard/flash)
plt.xlabel('Sequence length')
plt.ylabel('Speedup')
plt.title('Flash Attention vs. Standard Attention, head_size=128, n_heads=32, bs=1') 
plt.show()

图表显示,与原始的注意力实现相比,PyTorch中的Flash Attention提供了显著的速度提升,最高达到了2-8倍。值得注意的是,这种加速与原始Flash Attention实现中观察到的加速相匹配。此外,随着序列长度的增加,加速比也随之增加,因为简单的SDPA对序列长度的复杂度是二次方的。

这段代码展示了如何使用自定义的注意力基准测试函数来对比Flash Attention和普通的SDPA实现。最后,通过绘制图表,我们可以可视化Flash Attention相对于标准注意力实现在不同序列长度下的加速比。这显示了Flash Attention显著减少了多头注意力计算的时间,这对于处理大型数据集尤其重要。

基准测试大型语言模型(LLMs)

现在,让我们比较在Hugging Face中启用和禁用Flash Attention时多个大型语言模型的端到端预填充(prefill)延迟。参考
[Hugging Face的文档](https://huggingface.co/docs/transformers/en/perf_infer_gpu_one)
来检查你的模型是否支持Flash Attention。如果支持,可以通过在调用`from_pretrained`时设置`attn_implementation="flash_attention_2"来启用它。值得注意的是,Hugging Face当前使用的是原始的flash_attn`库,而不是PyTorch的Flash Attention。我们将检查结果,看看Flash Attention对整体性能的影响。

首先,我们需要创建一个名为`bench_llm`的函数来测量在Hugging Face中指定的因果语言模型的端到端延迟,给定一个序列长度。

def bench_llm(seq_len, model_name, max_new_tokens=128, flash=False, num_repeats=256):
    """
    Benchmark the end-to-end latency of a large language model (LLM) in Hugging Face, with Flash Attention enabled or disabled.

    Args:
        seq_len (int): Length of the input sequence.
        model_name (str): Name of the pre-trained LLM to use.
        max_new_tokens (int, optional): Maximum number of new tokens to generate. Defaults to 128.
        flash (bool, optional):
        Whether to use flash attention mechanism (if supported by the model).
        num_repeats (int, optional):
        Number of times to repeat the inference for averaging. Defaults to 256.

    Returns:
        float: The average end-to-end latency in seconds.
    """

    if flash:
        mech = "flash_attention_2"
    else:
        mech = "eager"

    model = AutoModelForCausalLM.from_pretrained(
        model_name, 
        torch_dtype=torch.bfloat16, 
        attn_implementation=mech,
        device_map="cuda",
    )
    
    token_ids = {
                    'input_ids': torch.randint(1, 10000, size=(1, seq_len), device='cuda'),
                    'attention_mask': torch.ones(1, seq_len, device='cuda')
    }
    
    start = torch.cuda.Event(enable_timing=True)
    end = torch.cuda.Event(enable_timing=True)

    pad_token_id = model.config.eos_token_id
    
    # Warmup
    for _ in range(4):
        _ = model.generate(**token_ids, max_new_tokens=max_new_tokens, pad_token_id=pad_token_id)

    torch.cuda.synchronize()
    start.record()
    for _ in range(num_repeats):
        _ = model.generate(**token_ids, max_new_tokens=max_new_tokens, pad_token_id=pad_token_id) 
    end.record()
    torch.cuda.synchronize()
    
    return start.elapsed_time(end) / num_repeats

现在我们准备好了,可以对Hugging Face中的Mistral-7B、Llama-3-8B和Phi-2等模型进行端到端延迟基准测试,比较启用和禁用Flash Attention的性能。我们将测量序列长度为512、1024和2048的性能,以评估Flash Attention随序列长度增加的影响。如果你想评估更多或更少的LLMs,只需要添加或修改模型名即可。

seq_lens = np.array([512, 1024, 2048])
model_names = ['mistralai/Mistral-7B-v0.1', 'NousResearch/Meta-Llama-3-8B', 'microsoft/phi-2']
prefill_flash = np.zeros((len(model_names),len(seq_lens)))
prefill_standard = np.zeros((len(model_names),len(seq_lens)))
for j, model_name in enumerate(tqdm(model_names)):
    for i, seq_len in enumerate(seq_lens):
        prefill_flash[j,i] = bench_llm(seq_len, model_name=model_name, max_new_tokens=1, flash=True)
        prefill_standard[j,i] = bench_llm(seq_len, model_name=model_name, max_new_tokens=1, flash=False)

现在让我们来绘制结果。

speedup_prefill = prefill_standard/prefill_flash

models = ["Mistral-7B", "Llama3-8B", "Phi-2"]
avg_speedup = {
    '512': speedup_prefill.T[0],
    '1024': speedup_prefill.T[1],
    '2048': speedup_prefill.T[2],
}

x = np.arange(len(avg_speedup))  
width = 0.25
multiplier = 0

fig, ax = plt.subplots(layout='constrained')

for attribute, measurement in avg_speedup.items():
    offset = width * multiplier
    rects = ax.bar(x + offset, measurement, width, label=attribute)
    ax.bar_label(rects, fmt='%.2f', padding=3)
    multiplier += 1

ax.legend(loc='upper left', ncols=1)
ax.set_xticks(x + width, models)
ax.set_ylabel('Speedup')
ax.set_title('Flash Attention vs Standard Attention Prefill Latency')
plt.savefig('benchmark-llm.png')
plt.show()

Flash Attention在与标准SDPA相比,显著降低了所有测试的大型语言模型(LLMs)的预填充(prefill)延迟。对于某些LLM,延迟的减少更为明显。此外,随着序列长度的增加,加速效果变得更加显著,这与我们在基准测试注意力模块时观察到的趋势相呼应。

总结

Flash Attention是一种快速且内存效率高的自注意力实现方式,精确且对硬件有意识。在本文中,我们演示了如何安装支持ROCm的Flash Attention,并以两种方式对其性能进行了基凌测试:
1. 作为一个独立模块,来测量Flash Attention算法相对于SDPA的速度提升。
2. 在Hugging Face中对多个LLMs的端到端预填充延迟进行评估,来衡量Flash Attention对模型整体预填充延迟的影响。
我们的结果表明,尤其是对于长序列,在AMD GPU上,Flash Attention提供了与原始注意力实现相比的显著速度提升。

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

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

相关文章

locale本地化库学习

std::locale 类型的对象(本地环境对象)是不可变刻面的一个不可变索引集。C 输入/输出库的每个流对象都与一个 std::locale 对象关联,并用它的各刻面来分析及格式化所有数据。另外,每个 std::basic_regex 对象也都与一个本地环境对…

【Gitlab】Gitlab MAC M1通过Docker Desktop安装教程

一、拉取镜像 docker pull yrzr/gitlab-ce-arm64v8 二、配置容器 2.1 配置Volumes 镜像下载完成后,可在Docker Desktop看到镜像 点击run,弹出以下界面,配置端口映射和目录挂载后,即可生成一个容器 配置Volumes Host PathCont…

51单片机-独立按键控制灯灯灯

目录 简介: 一. 1个独立按钮控制一个灯例子 二. 在加一个独立按键,控制第二个灯 三. 第一个开关 开灯, 第二个开关关灯 四. 点一下开灯,在点一下关灯 五. 总结 简介: 51 单片机具有强大的控制能力,而独立按键则提供了一种简单的输入方式。 当把独立按键与 …

Go微服务: 分布式之通过本地消息实现最终一致性和最大努力通知方案

通过本地消息实现最终一致性 1 )概述 我们的业务场景是可以允许我们一段时间有不一致的消息的状态的,并没有说必须特别高的这个消息的一致性比如说在TCC这个架构中,如果采用了消息的最终一致性,整体架构设计要轻松好多即便我们库…

设计模式-外观(门面)模式(结构型)

外观模式 外观模式又称门面模式(结构型模式),它是一个可以屏蔽系统复杂性的设计模式。俗话说没有什么问题是加一层“介质”解决不了的,如果有那就在加一层。在开发过程中肯定封装过Utils类,我认为这就是一种门面模式&…

Python魔法之旅-魔法方法(23)

目录 一、概述 1、定义 2、作用 二、应用场景 1、构造和析构 2、操作符重载 3、字符串和表示 4、容器管理 5、可调用对象 6、上下文管理 7、属性访问和描述符 8、迭代器和生成器 9、数值类型 10、复制和序列化 11、自定义元类行为 12、自定义类行为 13、类型检…

【C++11数据结构与算法】C++ 栈

C 栈(stack) 文章目录 C 栈(stack)栈的基本介绍栈的算法运用单调栈实战题LC例题:[321. 拼接最大数](https://leetcode.cn/problems/create-maximum-number/)LC例题:[316. 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) 栈的基…

如何使用ERC-20与Sui Coin标准创建Token

区块链使用tokens作为传递价值的基本手段。它们可以是区块链的原生交换单位,也可以是应用中的交换单位,甚至可以在游戏世界中用作货币。tokens还支持Sui和其他区块链上的强大DeFi活动。 以太坊使用ERC-20标准来创建tokens,借用智能合约&…

VueRouter3学习笔记

文章目录 1,入门案例2,一些细节高亮效果非当前路由会被销毁 3,嵌套路由4, 传递查询参数5,命名路由6,传递路径参数7,路径参数转props8,查询参数转props9,replace模式10&am…

ChatGPT-4o, 腾讯元宝,通义千问对比测试中文文化

国内的大模型应用我选择了国内综合实力最强的两个,一个是腾讯元宝,一个是通义千问。其它的豆包,Kimi,文心一言等在某些领域也有强于竞品的表现。 问一个中文文化比较基础的问题,我满以为中文文化chatGPT不如国内的大模型。可事实…

【安装笔记-20240608-Linux-动态域名更新服务之YDNS】

安装笔记-系列文章目录 安装笔记-20240608-Linux-动态域名更新服务之YDNS 文章目录 安装笔记-系列文章目录安装笔记-20240608-Linux-动态域名更新服务之YDNS 前言一、软件介绍名称:YDNS主页官方介绍 二、安装步骤测试版本:openwrt-23.05.3-x86-64注册填…

基于协调过滤算法商品推荐系统的设计

管理员账户功能包括:系统首页,个人中心,商品管理,论坛管理,商品资讯管理 前台账户功能包括:系统首页,个人中心,论坛,商品资讯,商家,商品 开发系统…

PyTorch学习6:多维特征输入

文章目录 前言一、模型说明二、示例1.求解步骤2.示例代码 总结 前言 介绍了如何处理多维特征的输入问题 一、模型说明 多维问题分类模型 二、示例 1.求解步骤 1.载入数据集:数据集用路径D:\anaconda\Lib\site-packages\sklearn\datasets\data下的diabetes.cs…

【C++ STL】模拟实现 string

标题:【C :: STL】手撕 STL _string 水墨不写bug (图片来源于网络) C标准模板库(STL)中的string是一个可变长的字符序列,它提供了一系列操作字符串的方法和功能。 本篇文章,我们将模拟实现STL的…

Polar Web【中等】写shell

Polar Web【中等】写shell Contents Polar Web【中等】写shell思路&探索EXP运行&总结 思路&探索 初看题目,预测需要对站点写入木马,具体操作需要在过程中逐步实现。 打开站点(见下图),出现 file_put_contents 函数,其…

pdf文件如何防篡改内容

PDF文件防篡改内容的方法有多种,以下是一些常见且有效的方法,它们可以帮助确保PDF文件的完整性和真实性: 加密PDF文档: 原理:通过设置密码来保护PDF文档,防止未经授权的访问和修改。注意事项:密…

如何对stm32查看IO功能。

有些同学对于别人的开发板的资源,或者IO口,或者串口等资源不知道怎么分配。 方法1、看硬石、野火、正点原子的开发板,看下他们的例子,那个资源用什么。自己多看几个原理图,多看几个视频,做一下笔记。以后依…

通过无障碍控制 Compose 界面滚动的实战和原理剖析

前言 针对 Compose UI 工具包,开发者不仅需要掌握如何使用新的 UI 组件达到 design 需求,更需要了解和实现与 UI 的交互逻辑。 比如 touch 事件、Accessibility 事件等等。 Compose 中对 touch 事件的处理和原理,笔者已经在《通过调用栈快…

Point-LIO:鲁棒高带宽激光惯性里程计

1. 动机 现有系统都是基于帧的,类似于VSLAM系统,频率固定(例如10Hz),但是实际上LiDAR是在不同时刻进行顺序采样,然后积累到一帧上,这不可避免地会引入运动畸变,从而影响建图和里程计精度。此外…

NASA数据集——SARAL 近实时增值业务地球物理数据记录海面高度异常

SARAL Near-Real-Time Value-added Operational Geophysical Data Record Sea Surface Height Anomaly SARAL 近实时增值业务地球物理数据记录海面高度异常 简介 2020 年 3 月 18 日至今 ALTIKA_SARAL_L2_OST_XOGDR 这些数据是近实时(NRT)&#xff…