混合输入矩阵乘法的性能优化

83f954b11cc4fff858809e5b93dd49e7.jpeg

作者 | Manish Gupta

OneFlow编译

翻译|宛子琳、杨婷

AI驱动的技术正逐渐融入人们日常生活的各个角落,有望提高人们获取知识的能力,并提升整体生产效率。语言大模型(LLM)正是这些应用的核心。LLM对内存的需求很高,通常需要专用的硬件加速器,以高效地提供数百亿亿次浮点运算(Exaflops级别)的计算能力。本文将展示如何通过更有效地利用内存来解决计算方面的挑战。

LLM中的大部分内存和计算资源都消耗在了矩阵乘法操作中的权重上。使用范围更小的数据类型可以降低内存消耗,例如,将权重存储为8位整数(即U8或S8)的数据类型,相对于单精度(F32)能够减少4倍的内存占用,相对于半精度(F16)或bfloat16(BF16)能够减少2倍的内存占用。

此外,先前的研究表明,LLM模型采用S8格式的权重和F16格式的输入进行矩阵乘法运算,能够在保持可接受的准确性的同时提高效率。这一技术被称为仅权重量化(weight-only quantization),需要对带有混合输入的矩阵乘法进行高效实现,例如半精度输入与8位整数相乘。因为硬件加速器(包括GPU)支持一组固定的数据类型,因此,混合输入矩阵乘法需要通过软件转换来映射到硬件操作。

为此,本文重点关注将混合输入的矩阵乘法映射到NVIDIA Ampere架构上。我们提出了解决数据类型转换和布局一致性的软件技术,以有效地将混合输入矩阵乘法映射到硬件支持的数据类型和布局上。结果显示,在软件中进行额外工作的计算开销很小,并且可以实现接近硬件峰值的性能。本文所介绍的软件技术已在开源的NVIDIA/CUTLASS库(github.com/NVIDIA/cutlass/pull/1084)中发布。

554be68e2e54eb8c9e9b29e7a877a522.png

175亿参数的LLM模型在不同数据类型格式下的内存占用。

(本文作者为谷歌研究院高级软件工程师Manish Gupta。以下内容由OneFlow编译发布,转载请联系授权。原文:https://blog.research.google/2024/01/mixed-input-matrix-multiplication.html)

1

矩阵乘累加(matrix-multiply-accumulate)运算

当前的AI硬件加速器,如Google的TPU和NVIDIA的GPU,通过针对张量核心(Tensor Core)在硬件中本地执行矩阵乘运算(这些张量核心是专门加速矩阵运算的处理单元),尤其适用于AI工作负载。本文我们重点关注NVIDIA Ampere张量核心,它提供矩阵乘累加(mma)运算。在本文其余部分,mma指的是Ampere张量核心。在mma运算中,两个输入矩阵(称为操作数)所支持的数据类型、维度和数据布局在硬件中是固定的。这意味着,软件中不同的数据类型和更大维度的矩阵乘法是通过将问题划分为硬件所支持的数据类型、形状和布局实现的。

张量核心的mma运算通过指定两个输入矩阵(如下图所示的A和B)来计算生成结果矩阵C。mma运算本身支持混合精度。混合精度张量核心允许混合输入(A和B)数据类型与结果(C)数据类型。相比之下,混合输入矩阵乘法涉及混合输入数据类型,这在硬件上不受支持,因此需要通过软件实现。

32d72db38d379ddbdff0582d431232e3.png

对M乘K的输入矩阵A和K乘N的输入矩阵B进行的M乘N乘K的张量核心操作,

得到M乘N的输出矩阵C。

2

混合输入矩阵乘面临的挑战

为简化讨论,我们选择了混合输入矩阵乘法的一个具体示例:用户输入采用F16,模型权重采用U8(表示为F16 * U8)。本文讨论的技术适用于各种混合输入数据类型组合。

GPU程序员可以访问一系列内存,包括全局内存、共享内存和寄存器,这些内存按容量递减但速度递增的顺序排列。NVIDIA Ampere Tensor Core的mma操作从寄存器中获取输入矩阵。此外,输入和输出矩阵需要符合在一个名为warp的32个线程组内的数据布局。对于mma操作,warp内支持的数据类型和布局是固定的,因此要高效实现混合输入乘法,就需要在软件中解决数据类型转换和布局一致性问题。

数据类型转换

mma操作要求两个输入矩阵具有相同的数据类型。因此,在混合输入矩阵乘法中,当一个操作数以U8存储在全局内存中,而另一个以F16存储时,就需要进行从U8到F16的数据类型转换。这种转换将两个操作数转换为F16,从而将混合输入矩阵乘法映射到硬件支持的混合精度张量核心。鉴于权重的数量庞大,因此需要大量的转换操作,我们的技术展示了如何降低其时延并提高性能。

布局一致性

mma操作还要求两个输入矩阵的布局(即在一个warp的寄存器中的布局)符合硬件规范。在混合输入矩阵乘法(F16 * U8)中,U8数据类型的输入矩阵B的布局需要符合转换后的F16数据类型。这被称为布局一致性(layout conformance),需要通过软件实现。

下图展示了一个mma操作,它从寄存器中提取矩阵A和矩阵B,然后在寄存器中生成矩阵C,这个过程分布在一个warp中。其中,线程T0被突出显示,并对其进行了放大,以展示权重矩阵B经过数据类型转换,需要符合布局一致性才能映射到硬件支持的张量核心操作。

d2675bea9d4a3873ff079fdc73397696.png

将软件中的混合输入(F32=F16U8)操作映射到硬件中原生支持的warp级张量核心(F32=F16F16)。原图来源:《在NVIDIA A100上开发CUDA核心以充分发挥张量核心的性能极限》。

2
应对计算挑战的软件策略

典型的数据类型转换涉及对32位寄存器的一系列操作,如下图所示。每个矩形块代表一个寄存器,相邻文本则表示相应的操作。整个序列展示了从4个U8转换为2个(2个F16)的过程。该序列大约包含10个操作。

2d08926a5e344c05336f65d57c05141b.png

在32位寄存器中,将4个U8转换为2x(2个F16)的NumericArrayConvertor。

实现布局一致性的方法有很多,两种现有解决方案如下:

1.较窄位宽的共享内存加载:在这种方法中,线程发出较窄位宽的内存加载操作,将U8数据从共享内存移动到寄存器。这会导致两个32位寄存器,每个寄存器包含2个F16值(如上所示,对于矩阵B的线程T0)。较窄的共享内存加载直接实现了布局一致性,使其存入寄存器,而无需任何移动(shuffles)操作;然而,这种方法未充分利用共享内存带宽。

2.全局内存中的预处理:另一种策略是,在全局内存中重新排列数据(在内存层次结构中位于共享内存的上一级),允许更宽的共享内存加载。这种方法最大程度地利用了共享内存带宽,并确保数据以一致的布局直接加载到寄存器中。虽然重新排列过程可以在LLM部署之前离线执行,确保不影响应用程序的性能,但它引入了一个额外的、有意义的硬件特定的预处理步骤,需要额外的程序来重新排列数据。

NVIDIA/FasterTransformer采用这种方法有效地解决了布局一致性的挑战。

3
优化的软件策略

为进一步优化并减少数据类型转换和布局一致性的计算开销,我们分别实现了FastNumericArrayConvertor和FragmentShuffler。

FastNumericArrayConvertor在32位寄存器中直接处理4xU8,而无需拆解单个1xU8值。此外,它使用的算术操作成本较低,减少了指令数量,提高了转换速度。

U8到F16的转换序列如下图所示。这些运算使用打包的32位寄存器,避免了显式的解包和打包。FastNumericArrayConvertor使用置换字节来重新排列4xU8的字节,将其放入两个寄存器中。此外,FastNumericArrayConvertor不使用开销较大的整数到浮点数转换指令,并采用矢量化操作,在两个32位寄存器中获取包含2x(2xF16)值的打包结果。相对于上述方法,U8到F16的FastNumericArrayConvertor大约使用了六个操作,相对上文提到的方式,其性能有约1.6倍的提升。

6f64f3ed56241da1d12186d7f8c86c04.png

FastNumericArrayConvertor利用permute字节和packed计算,减少了数据类型转换中的指令数量。

FragmentShuffler通过对数据进行重新排列,可以使用更宽的位宽加载操作,实现了布局一致性,增加了共享内存带宽利用率,并减少了总操作数。

NVIDIA Ampere架构提供了一个加载矩阵指令(ldmatrix)。ldmatrix是一种warp级操作,其中一个warp的32个线程将数据从共享内存移动到寄存器中,而这些寄存器的形状和布局符合矩阵A和B进行矩阵乘法累积运算所需的要求。使用ldmatrix减少了加载指令的数量,提高了内存带宽利用率。由于ldmatrix指令将U8数据移动到寄存器中,加载后的布局符合U8U8的mma操作,不符合F16F16的mma操作。我们实现了FragmentShuffler,使用shuffle(shfl.sync)操作在寄存器内重新排列数据,以实现布局一致性。

这项工作最重要的贡献之一就是通过寄存器shuffles实现了布局一致性,避免了在全局内存中进行离线预处理或更窄的位宽共享内存加载。此外,我们提供了FastNumericArrayConvertor的实现,涵盖了从U8到F16、S8到F16、U8到BF16以及S8到BF16的数据类型转换。

4

性能表现

我们在NVIDIA A100 SXM芯片上测量了该方法的八种混合输入变体的性能(如下图中的蓝色和红色所示;根据矩阵A和B的数据类型不同而变化)以及两种混合精度数据类型(绿色显示)的性能。性能结果以FLOPS(数值越高表示性能越好))显示。


值得注意的是,相对于最后两个矩阵乘法,前八个需要额外的操作,因为混合精度变体直接针对硬件加速的张量核心操作,无需数据类型转换和布局一致性。即便如此,在混合输入矩阵乘法性能上,我们的方法仅略低于或与混合精度相当。

834b277803dbf68d50352e92a11718a0.png

在NVIDIA A100 40GB SMX4芯片上,针对一个计算受限的矩阵问题,测试混合输入矩阵乘法的性能,其矩阵大小为m=3456,n=4096,k=2048。

致谢

在此,我们要特别感谢一些同仁,他们通过技术头脑风暴和博客文章改进做出了杰出贡献,包括Quentin Colombet,Jacques Pienaar,Allie Culp,Calin Cascaval,Ashish Gondimalla,Matt Walsh,Marek Kolodziej和Aman Bhatia。此外,我们还要对NVIDIA的合作伙伴Rawn Henry,Pradeep Ramani,Vijay Thakkar,Haicheng Wu,Andrew Kerr,Matthew Nicely和Vartika Singh表示由衷的感谢。

beec9b9ff00f919cf2f90047d7379a57.png

试用图片/视频生成加速引擎OneDiff: github.com/siliconflow/onediff

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

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

相关文章

LeetCode148题:排序链表(python3)

在数组排序中,常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等。 而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠 next 指针从头…

Flip Clock(not good)

最近体验了一下iOS的翻页时钟app,很想自己做一个,但是效果不好 public class main {public static void main(String[] args) {//psvmnew MyFrame();} }import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; im…

Vue的HTML插入——v-html指令

有时我们希望将数据作为HTML代码插入到HTML模板中&#xff0c;而不是以纯文本的形式显示。在这种情况下&#xff0c;我们需要使用Vue.js的v-html指令&#xff1a; <template><div><p>纯文本: {{ rawText }}</p><p>属性: <span v-html"r…

influxdb2.0插入数据字段类型出现冲突问题解决

一、问题出现 一个学校换热站自控系统&#xff0c;会定时从换热站获取测点数据&#xff0c;并插入到influxdb数据库中。influxdb插入数据时&#xff0c;报错提示&#xff1a; com.influxdb.exceptions.UnprocessableEntityException: failure writing points to database: par…

AlexNet 网络结构详解

一、基本了解 什么是过拟合&#xff1f; 解决方法 AlexNet网络结构通过使用dropout方法&#xff0c;使一些神经元失活&#xff0c;变相的减少了网络训练的参数化&#xff0c;从而实现减少过拟合。 二、AlexNet网络结构的详细解释 他是由上下两组GPU进行运算的&#xff0c;所以…

13 OpenCv自定义线性滤波

文章目录 卷积算子示例 卷积 卷积是图像处理中一个操作&#xff0c;是kernel在图像的每个像素上的操作。Kernel本质上一个固定大小的矩阵数组&#xff0c;其中心点称为锚点(anchor point) 把kernel放到像素数组之上&#xff0c;求锚点周围覆盖的像素乘积之和&#xff08;包括锚…

灵魂指针,教给(二)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 目录 一、数组名的理解 二、使用指针访问数组 三、一维数组传参本质 四、冒泡排序 五、二级指针 六、指针数组 七、指针数组…

黑马点评-好友关注实现

关注和取关 针对用户的操作&#xff0c;可以对用户进行关注和取消关注功能&#xff1a; 需要实现两个接口&#xff1a; 关注和取关接口 判断是否关注的接口 接口&#xff1a; //关注和取关 PutMapping("/{id}/{isFollow}") public Result follow(PathVariable(&…

Jmeter查看结果树之查看响应的13种详解方法

Jmeter查看结果树查看响应有哪几种方法&#xff0c;可通过左侧面板底部的下拉框选择: 01 Text 查看结果树中请求的默认格式为Text&#xff0c;显示取样器结果、请求、响应数据3个部分内容。 取样器结果&#xff1a; 默认Raw展示&#xff0c;可以切换为Parsed视图&#xff0c…

Python爬虫——scrapy-4

免责声明 本文章仅用于学习交流&#xff0c;无任何商业用途 部分图片来自尚硅谷 meta简介 在Scrapy框架中&#xff0c;可以使用meta属性来传递额外的信息。meta属性可以在不同的组件之间传递数据&#xff0c;包括爬虫、中间件和管道等。 在爬虫中&#xff0c;可以使用meta属…

7-4 哲哲打游戏(Python)

哲哲是一位硬核游戏玩家。最近一款名叫《达诺达诺》的新游戏刚刚上市&#xff0c;哲哲自然要快速攻略游戏&#xff0c;守护硬核游戏玩家的一切&#xff01; 为简化模型&#xff0c;我们不妨假设游戏有 N 个剧情点&#xff0c;通过游戏里不同的操作或选择可以从某个剧情点去往另…

rabbitmq4

独占队列&#xff1a;我们的队列只能被当前通道所绑定&#xff0c;不能被其他的连接所绑定&#xff0c;如果有其他的通道或连接再使用此队列的话&#xff0c;会直接报错&#xff0c;一般设置为false&#xff1a; autoDelete&#xff1a;消费者在消费完队列&#xff0c;并且彻底…

20 卷积层里的填充和步幅【李沐动手学深度学习v2课程笔记】

1. 填充和步幅 在上下左右分别填充一些0 2. 代码实现 2.1 填充 我们创建一个高度和宽度为3的二维卷积层&#xff0c;并在所有侧边填充1个像素。给定高度和宽度为8的输入&#xff0c;则输出的高度和宽度也是8。 import torch from torch import nn# 为了方便起见&#xff0c;…

第三百八十九回

文章目录 1. 概念介绍2. 使用方法2.1 获取所有时区2.2 转换时区时间 3. 示例代码4. 内容总结 我们在上一章回中介绍了"分享一些好的Flutter站点"相关的内容&#xff0c;本章回中将介绍timezone包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在…

LeetCode 2482.行和列中一和零的差值

给你一个下标从 0 开始的 m x n 二进制矩阵 grid 。 我们按照如下过程&#xff0c;定义一个下标从 0 开始的 m x n 差值矩阵 diff &#xff1a; 令第 i 行一的数目为 onesRowi 。 令第 j 列一的数目为 onesColj 。 令第 i 行零的数目为 zerosRowi 。 令第 j 列零的数目为 zer…

el-form-item内的el-select如何自适应宽度

最近在使用element-ui做后台管理的时候&#xff0c;有个需求是在弹窗组件里面&#xff0c;添加一个el-select下拉框选项&#xff0c;但是给el-select设置的宽度无法自适应&#xff0c;原因很简单&#xff0c;我们不需要设置固定宽度&#xff0c;设置百分比就行了&#xff0c;让…

【框架设计】MVC、MVP、MVVM对比图

1. MVC&#xff08;Model-View-Controller&#xff09; 2. MVP&#xff08;Model-View-Presenter&#xff09; 3. MVVM&#xff08;Model-View-ViewModel&#xff09;

Golang基于Redis bitmap实现布隆过滤器(完结版)

Golang基于Redis bitmap实现布隆过滤器&#xff08;完结版&#xff09; 为了防止黑客恶意刷接口&#xff08;请求压根不存在的数据&#xff09;&#xff0c;目前通常有以下几种做法&#xff1a; 限制IP&#xff08;限流&#xff09;Redis缓存不存在的key布隆过滤器挡在Redis前 …

对simplex算法的时间复杂度进行分析

对于simplex算法,如果每进行一次pivot变换,目标函数所得到的结果都会有可能出现增加的情况,所以得到的结论中,可以肯定它的值是一定不会出现减少的情况的,每次从目标函数中找到一个系数大于0的变量,然后再在约束条件中选取能够让它的增值最少的那个来继续进行pivot变换。…

linux kernel物理内存概述(五)

目录 概述 一、快速路径分配 1、get_page_from_freelist 2、rmqueue()函数 二、慢速路径分配 1、分配流程 三、direct_compact 概述 物理内存分配步骤 1、初始化,参数初始化 2、内存充足&#xff0c;快速分配 get_page_from_freelist 3、内存压力大&#xff0c;慢速…