大语言模型推理加速技术:计算加速篇

news2024/9/29 17:26:27

原文:大语言模型推理加速技术:计算加速篇 - 知乎

目录

简介

Transformer和Attention

瓶颈

优化目标

计算加速

计算侧优化

KVCache

Kernel优化和算子融合

分布式推理

内存IO优化

Flash Attention

Flash Decoding

Continuous Batching

Paged Attention

SplitFuse

总结


本文是《大语言模型推理加速技术》系列的第一篇

《大语言模型推理加速技术:计算加速篇》

《大语言模型推理加速技术:模型压缩篇》

《大语言模型推理加速技术:推理框架篇》

自从去年ChatGPT横空出世之后,业界对于大语言模型的热情也愈发高涨,随着模型规模越来越大,它们的计算需求也水涨船高,大模型部署和所需的资源量也让很多团队望而却步:毕竟可以拿社区开源的预训练模型跳过训练的过程,但是部署大模型推理是无法避开的流程。本系列旨在简单讨论几个业界生产环境可用的大模型推理技术,并分析对比几个主流的推理框架。

由于各大公司和学术团队都在“卷”大模型,大模型新技术层出不穷,本系列只能保证当前的信息有效性(2023年11月初)。另外由于本文是从工程角度出发,只会介绍工业界可落地的技术,一些前沿的学术成果可能并不包含在内,敬请谅解。

简介

Transformer和Attention

当前主流的大模型都是基于2017年谷歌团队提出的Transformer架构,其核心是注意力(Attention)机制,简单来说就是计算softmax(qk^T)*v:

def attention(q_input, k_input, v_input):
    q = self.Q(q_input)
    k = self.K(k_input)
    v = self.V(v_input)
    return softmax(q * k.transpose()) * v

其中Q,K,V是模型的三个矩阵。对当前主流的Decoder-only模型来说,推理过程分为两个阶段:

  1. context phase也叫prefill phase:需要计算整个prompt的自注意力,q_input, k_input, v_input大小都为[seq_len, emb_dim],即整个prompt的embedding,context phase只需要进行一次,生成第一个token。
  2. generation phase或decoding phase:每生成一个token就要计算一次,其中q_input为[1, emb_dim],代表当前token的embedding,k_input, v_input为[n, emb_dim]代表所有前文的embedding,这个阶段计算的是当前token和所有前文的注意力。

Attention计算占据了模型推理阶段的绝大部分资源,要了解本文介绍的优化技巧,只需要了解Attention的计算过程就足够了。由于本文更多关注工程细节,对模型的设计只是一笔带过,有兴趣的读者可自行了解完整的Transformer相关信息。

Attention计算,来自Raimi Karim的Towards Data Science文章

瓶颈

想知道如何优化,我们首先要知道模型的计算慢在哪里。

  1. 大模型通常需要处理很长的输入和输出,由于当前token需要和前面所有的token进行attention计算,随着seq_len和n的增加,模型需要的计算的矩阵尺寸也越来越大。
  2. 在生成阶段,每个token的生成都依赖前面所有的计算,只能一个一个token生成,无法并发计算。

简单来说,推理瓶颈就在于Attention的计算是一个O(N^2)的操作且无法并发。本文所介绍的技术都是针对以上两个瓶颈进行优化。

优化目标

加速优化一般有两个目标,一个是更低的延迟(Latency),一个是更高的吞吐量(Throughput),其中延迟是指单个请求返回的时间,而吞吐量是一定时间内处理请求的总量。这两者有时是不可兼得的。举个例子,如果我们把一个模型切分成多个小模型进行分布式计算(张量并行),我们可以把单个请求的速度提升数倍(延迟下降),但是由于有通信和聚合的成本,系统处理单个请求的资源消耗量变多了,导致系统的吞吐量也会下降。有些加速技术只会针对其中一种目标,我们后面会详细介绍。

在本系列中,推理优化技术分为两大类:计算加速模型压缩。计算加速,通过改进算法和硬件利用率来提高效率,而不影响模型的输出质量,本质上是让模型“算得更快”。而模型压缩则是改变模型结构,减少部分计算(比如稀疏Attention)或降低计算精度(比如量化),换来更快的推理速度和更低的资源消耗,但可能会影响模型的输出质量,本质上是让模型“算得更少”。本文作为系列的第一篇,只介绍计算加速技术,我们将在下一篇文章中介绍模型压缩技术。

计算加速

任何计算的本质都是CPU/GPU执行一系列的指令,在模型结构固定的情况下,我们能做的优化无非就是以下三种:

  1. 减少需要执行的指令数量,即减少不必要的或重复的运算。
  2. 充分利用硬件的并发度,要么是让单条指令可以一次处理多条数据(SIMD),要么是利用CPU和GPU的多核心机制,同时执行多条指令。
  3. 加速内存IO速度。利用缓存局部性加速内存读取指令的执行速度,或者减少不必要的内存读写。

前两种我们称之为计算侧优化,后一种我们称之为内存IO优化

计算侧优化

KVCache

在每一个decoding phase中,我们需要计算当前token和之前所有已生成token的attention,因此需要计算所有token的k和v向量,但是前面的token的kv值在每轮decoding中都被重复计算了,因此我们可以把它们存下来,存成两个[seq_len-1, inner_dim]的Tensor,在每轮计算中只需要计算当前token的kv值即可。

KVCache是最简单直接的优化手段,一般模型的默认实现都会自带KVCache,因此并不需要我们额外实现,以Huggingface Transformers库为例,我们只需要在配置中设置use_cache=True即可。

这里还想吐槽一下,一般我们在工程中要到的cache也都是基于hash_map这种kv map的,最开始我还以为KVCache也是基于map的复杂数据结构,没想到只是简单的两个Tensor就实现了。

KVCache图解,来自Joao Lages的Medium文章

Kernel优化和算子融合

在NVIDIA GPU环境上,我们通过CUDA Kernel来执行大部分运算,矩阵乘法(GEMM),激活函数,softmax等,一般老说每个操作都对应一次kernel调用。但是每次kernel调用都有一些额外开销,比如gpu和cpu之间的通信,内存拷贝等,因此我们可以将整个Attention的计算放进同一个kernel实现,省略这些开销,在kernel中也可以实现一些Attention专用的优化。比如Facebook的xformers库就为各种Attention变种提供了高效的CUDA实现。主流的推理库也基本都自带了高效的Kernel实现。

除了手写的Kernel外,模型编译器也可以提供类似的优化机制,编译器将模型结构转换为其中间格式然后进行一系列的优化,比如我们提到的算子融合。虽然在CUDA这种主流平台上编译器的优化效果不一定比得上专用的Kernel实现,但是多平台的通用性是编译器的一大优势,比如TVM团队的MLC-LLM和微软主导的ONNX常被用来在手机等边缘设备上运行大模型。

分布式推理

在大模型的训练和推理过程中,我们有以下几种主流的分布式并行方式:

  • 数据并行(DataParallel):将模型放在多个GPU上,每个GPU都包含完整的模型,将数据集切分成多份,每个GPU负责推理一部分数据。
  • 流水线并行(PipelineParallel):将模型纵向拆分,每个GPU只包含模型的一部分层,数据在一个GPU完成运算后,将输出传给下一个GPU继续计算。
  • 张量并行(TensorParallel):将模型横向拆分,将模型的每一层拆分开,放到不同的GPU上,每一层的计算都需要多个GPU合作完成。

流水线并行一般是一台GPU内存无法放下整个模型的妥协之举,各层之间仍然是顺序运行的,并不能加速模型的计算。而另外两种并行则可以加速模型的推理。推理阶段的数据并行非常简单,因为不需要像训练一样聚合推理结果更新参数,因此我们只需要将模型单独部署在多个GPU上推理即可。而张量并行一般使用NVIDIA的Megatron库,模型内部结构改为使用Megatron的ColumnParallelLinear、RowParallelLinear、ParallelMLP和ParallelAttention等结构实现。

流水线并行和张量并行,来自Sequence Parallelism的paper

内存IO优化

在做性能优化时,我们除了要考虑单纯计算的速度,也要考虑内存访问的速度。一方面,和CPU缓存一样,GPU也有类似L1、L2这样的分级缓存(SRAM和HBM),级数越低的缓存大小越小,访问速度越快,因此我们在优化模型推理时也要考虑内存访问的局部性(Cache Locality)。另一方面,KVCache随着batch size和seq len的增加而扩张,在推理过程中会占据超过30%的内存,可能会出现因为内存不够用而限制最大并发度的问题。在并发度较高或者输入输出长度较大时,内存访问反而可能成为计算的瓶颈,而非CPU/GPU的计算量。

GPU的分级缓存,来自Flash Attention的paper

推理时的内存占用,来自vllm的paper

Flash Attention

在进行Attention计算时,QKV都是非常大的矩阵,直接进行矩阵乘法运算是非常缓存不友好的。因此我们可以考虑对矩阵进行分块乘法,每次只计算一个小的block,保证block可以放进SRAM而非HBM中。实际上这是一个很经典的思路,大部分的矩阵乘法kernel也是这样实现的。

而FlashAttention则更进一步,我们观察到Attention计算分为三步:

  1. 从HBM读取QK,计算S = QK^T,将S写回HBM
  2. 从HBM读出S,计算P = softmax(S),将P写回HBM
  3. 从HBM读出P和V,计算O=PV,将O写回HBM

我们然需要在整个计算过程中HBM读三次写三次,有没有办法只读写一次呢?如果Attention只是简单的矩阵乘法,可以通过分块计算的方法避免写回HBM,但是由于softmax的存在,我们无法直接这样做。因为softmax需要计算矩阵中每一行元素的最大值,所以我们必须等待所有分块遍历完成后才能计算下一步。

FlashAttention巧妙地利用了类似于动态规划的技巧,实现了online softmax,可以在一个循环中计算出一个分块的最终结果。FlashAttention算法在遍历过程中需要不断地对中间结果进行重新计算,但是得益于整个过程不需要读HBM,再增大了计算量的情况下仍然可以提升运算速度。

关于Flash Attention的完整介绍和数学推导,我推荐华盛顿大学的这个课件,里面非常直观地解释了Flash Attention背后的想法和推导过程。用户可以通过使用Flash Attention的库调用它的kernel,PyTorch官方也对Flash Attention提供了官方支持。

Flash Decoding

由于Flash Attention优化的是大矩阵乘法,矩阵越大优化效果应当越好。但是在在线推理的场景中,输入的batch size为1,Q矩阵实际上是一个向量而非矩阵,在这种场景下,Flash Attention无法充分地利用GPU的并发能力。而Flash Decoding通过以seq_len为维度并发,即将K和V分成多个部分,并发地与Q相乘而解决了这个问题。

与Flash Attention适合离线训练和批量推理不同,Flash Decoding在在线单次推理且上下文长度较长时效果更好。用户可以通过FlashAttention库或者xFormers的attention kernel来使用Flash Decoding。

动图封面

Flash decoding示例

Continuous Batching

在批量推理过程中我们一般使用固定的Batch Size,将多个请求Batch起来一起推理。在分配KVCache时,我们需要分配两个shape为[batch_size, seq_len, inner_dim]的tensor,但是不同的请求可能有不同输入和输出长度,而且我们无法预知最终的输出长度,无法固定seq_len,因此我们通常分配[batch_size, max_seq_len, inner_dim]这样的shape,保证所有请求的cache都放得下。

但是这样的分配策略有两个问题:

  1. 不是每个请求都可以达到max_seq_len,因此KVCache中很多的内存都被浪费掉了
  2. 即使一些请求输出长度很短,它们仍然需要等待输出较长的请求结束后才能返回

这种固定的Batch策略叫做静态Batching(Static Batching),为了解决这个问题,Orca提出了Continuous Batching策略,也叫Dynamic Batching或Inflight Batching。Continuous Batching允许输出较短的请求提前结束,并由新请求占用已结束请求的KVCache空间。

Continuous Batching示例,来自Anyscale官网,黄色为prompt,蓝色为生成的token

在批量推理场景中,Continuous Batching可以将模型的吞吐量提升两到三倍。当前主流的推理框架比如Huggingface TGI, Ray serve, vllm, TensorRT-LLM等都支持Continuous Batching策略。

Paged Attention

vLLM团队分析了推理时的内存浪费问题,认为推理中存在三种内存浪费

  1. Reservation:由于不确定每个请求的输出长度,我们需要给每个请求预留max_seq_len的空间。
  2. Internal Fragmentation:在Static Batching策略下,一个请求结束时,其剩余的空间旧被浪费掉了。
  3. External Fragmentation:由于KVCache是一个巨大的矩阵,且必须占用连续内存,操作系统如果只分配大的连续内存,势必有很多小的内存空间被浪费掉。

请求中的内存浪费,来自vLLM paper

vLLM团队认为,Continuous Batching可以部分解决Internal Fragmentation问题,但是Reservation和External Fragmentation的浪费仍然存在。因此他们提出了Paged Attention,其借鉴了操作系统中通过Page管理虚拟内存的思想:将KVCache分割为固定大小的Block,这些block不需要存储在连续内存中,由一个统一的内存分配器管理。请求按需申请内存,不需要预先留好max_seq_len大小的内存,解决了Reservation的浪费,请求结束后释放掉自己的blocks,解决了Internal Fragmentation,而系统只需要分配小的block,解决了External Fragmentation的问题。

vLLM团队的benchmark显示,使用PagedAttention可以将模型批量推理的吞吐量再提升3倍以上,达到Static Batching的6倍,而Paged Attention的另一个好处是不同的请求可以共享cache block,比如在beam search场景中,我们需要对同一个prompt生成多个结果,这些子请求就可以共享同一批prompt cache,PageAttention可以将beam search的吞吐量提升10倍以上。

用户可以通过官方的vLLM库使用Paged Attention,英伟达的TensorRT-LLM库和微软的Deepspeed-MII库也对部分模型提供了支持。

SplitFuse

微软的DeepSpeed团队观察到:如果我们要在F个前向推理中处理P个token,最高效的分配策略是将它们均分,即每个前向推理处理P/F个Token。但是在模型推理过程中,我们需要先在context phase一次性处理整个prompt,然后在genration phase一个一个生成token,是不符合最优策略的,因此deepspeed提出了SplitFuse:

  1. 将长prompt分割成多个短的输入,分在多个前向推理处理,只有最后一次推理会生成新token。
  2. 短的prompt会被合并在一起进行前向推理。
  3. 保证每次前向推理的输入token数是固定的。

在vLLM中,不同请求的prompt和generation需要分开处理,而Deepspeed SplitFuse可以把它们混合处理,图片来自deepseed blog

SplitFuse在输入长度越长时效果越明显,在Deepspeed自己的benchmark中,输入长度为2600时吞吐量可以达到vLLM的2.3倍,不过vLLM团队不久前推出了PagedAttentionV2,专门对长prompt进行了优化,不知道deepspeed的benchmark有没有对比最新的vLLM。

由于SplitFuse刚出不久,我对它的理解还不是很透彻,它声称在generation阶段也是每次输入一个>1的固定长度n,但是模型generation阶段一次只能生成一个token,是怎么做到输入n个token的,难道是直接pad成n个token,这样真的不会导致generation很浪费计算资源吗?我看到Deepspeed的benchmark里都是prompt length=2600,generation length=60,我怀疑它只适合prompt很长generation很短的情况,欢迎了解Deepspeed的读者在评论区解读。

SplitFuse目前被包含在DeepSpeed MII框架里,暂时只支持LLAMA,Mistral,OPT三种模型架构。

总结

本文是《大语言模型推理加速技术》系列的第一篇,简单介绍了大模型的计算过程和一些主流的推理加速技术。本篇所介绍的技术都是不改变模型结构和精度的前提下,以目标为最大化硬件利用率的优化技术。在后续的两篇文章中,我们将继续探讨模型压缩技术和当前主流的推理框架。

由于我并不是大模型方面的专家,本篇文章中可能出现了很多不准确的解读,也欢迎各位读者赐教~

Reference:

[1] Attention介绍:https://towardsdatascience.com/illustrated-self-attention-2d627e33b20a

[2] KVCache介绍:https://medium.com/@joaolages/kv-caching-explained-276520203249

[3] SequenceParallel: https://arxiv.org/pdf/2105.13120.pdf

[4] From Online Softmax to FlashAttention: https://courses.cs.washington.edu/courses/cse599m/23sp/notes/flashattn.pdf

[5] Flash Decoding: https://pytorch.org/blog/flash-decoding/

[6] How continuous batching enables 23x throughput in LLM inference while reducing p50 latency: Achieve 23x LLM Inference Throughput & Reduce p50 Latency

[7] vLLM: Efficient Memory Management for Large Language Model Serving with PagedAttention

[8] SplitFuse: https://github.com/microsoft/DeepSpeed/tree/master/blogs/deepspeed-fastgen

[9] xFormers: GitHub - facebookresearch/xformers: Hackable and optimized Transformers building blocks, supporting a composable construction.

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

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

相关文章

STM32--低功耗模式详解

一、PWR简介 正常模式与睡眠模式耗电是mA级,停机模式与待机模式是uA级。 二、电源框图 供电区域有三处,分别是模拟部分供电(VDDA),数字部分供电,包括VDD供电区域和1.8V供电区域,后备供电&…

WinForms中的Timer探究:Form Timer与Thread Timer的差异

WinForms中的Timer探究:Form Timer与Thread Timer的差异 在Windows Forms(WinForms)应用程序开发中,定时器(Timer)是一个常用的组件,它允许我们执行定时任务,如界面更新、周期性数据…

喜报|迪捷软件入选工信部“2023年信息技术应用创新解决方案”

为进一步推进信创生态建设,激发产业自主创新活力,高效促进供需协同发展,加强区域联动和资源整合,国家工业和信息化部网络安全产业发展中心(工业和信息化部信息中心)联合相关单位,遴选了一批可复…

【电子书】研发管理

资料 wx:1945423050 整理了一些互联网电子书,推荐给大家 研发管理 ABAQUS 6.14中文版有限元分析与实例详解.epubAkka入门与实践.epubAltium Designer 16电路设计与仿真从入门到精通.epubAltium Designer17电子设计速成实战宝典.epubApache Kafka源码剖…

git push 总是需要输入密码或者个人访问令牌personal access token解决方案

文章目录 遇到问题解决方法 遇到问题 git push的时候总是需要输入密码或者个人访问令牌personal access token 解决方法 ChatGPT给出的解决方案,解决了我的问题。 如果在使用 git push 命令时总是需要输入个人访问令牌,这可能是因为您的 GitHub 账号…

Git 突破 文件尺寸限制

前言 当Git本地存储里右超过50MB,却又确实需要上传的时候,就需要用到了不是 解决 本代码就是把大文件进行拆解成小文件,然后上传。 等到拉取下来的时候,可以直接再进行合并,合并成原文件 代码如下,仅供…

MySQL集群 双主架构(配置命令)

CSDN 成就一亿技术人&#xff01; 今天刚开学第一天给大家分享一期&#xff1a;MySQL集群双主的配置需求和命令 CSDN 成就一亿技术人&#xff01; 神秘泣男子主页&#xff1a;作者首页 <———— MySQL专栏 &#xff1a;MySQL数据库专栏<———— MySQL双主是一…

AutoSAR(基础入门篇)11.5-服务映射(自顶向下)

目录 一、配置Service Needs 二、配置Cfg同步 我们在下一节的实验课中讲解这里的具体配置流程,本节主要讲一下这些配置的大致流程和配置项的作用。NvBlockSwComponents是一个可选项, 我们这里开始不使用NvBlockSwComponents,将我们的Application SWC直接和NvM通过C/S连接起…

RC4算法

RC4 RC4是Ron Rivest为RSA设计的序列密码,RC4算法简单、速度快、容易用软硬件实现,因此应用广泛。比如WEP、WPA、SSL/TLS应用了RC4;Windows、Lotus notes、Apple APCE等软件系统也应用了RC4。 1. RC4算法 RC4具体算法如下: 第一步:密钥调度算法(The Key-Scheduling Alg…

数据结构与算法|线性结构

数据结构与算法|线性结构 第二章 线性结构2.1 多项式表示2.2 什么是线性表2.3 线性表的实现方式2.3.1 线性表的顺序存储实现2.3.2 线性表的链式存储实现1. 单链表实现2. 双链表实现 上篇&#xff1a;第一章、绪论 第二章 线性结构 线性结构是数据结构中最基础的&#xff0c;也…

【HarmonyOS】鸿蒙开发之Stage模型-基本概念——第4.1章

Stage模型-基本概念 名词解释 AbilityStage:应用组件的“舞台“ UIAbility:包含UI界面的应用组件&#xff0c;是系统调度的基本单元 WindowStage:组件内窗口的“舞台“ Window&#xff1a;用来绘制UI页面的窗口 HAP:Harmony Ability Package(鸿蒙能力类型的包) HSP:Harmony Sh…

ZYNQ:串口-CAN协议转换

前言 目前已经实现zynq的PS-CAN和PL-CAN功能。串口-CAN协议转换是实现以太网-CAN功能的过渡&#xff0c;通过这个流程能够减少后期以太网工程出现问题的频率。阶段性功能目标如下&#xff1a; 实现数据在CAN调试助手和串口调试助手之间的来回转换&#xff0c;从而了解中断机制…

B站项目-基于Pytorch的ResNet垃圾图片分类

基于Pytorch的ResNet垃圾图片分类 数据集预处理 画图片的宽高分布散点图 import osimport matplotlib.pyplot as plt import PIL.Image as Imagedef plot_resolution(dataset_root_path):image_size_list []#存放图片尺寸for root, dirs, files in os.walk(dataset_root_pa…

【深入理解设计模式】装饰者设计模式

装饰者设计模式 装饰者设计模式&#xff08;Decorator Design Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向现有对象添加新功能而不改变其结构。这种模式通常用于需要动态地为对象添加功能或行为的情况&#xff0c;而且这些功能可以独立于对象本身来进行扩展…

用C#开发Excel插件的强大开源工具

推荐一个开源项目&#xff0c;方便我们使用C#为Excel开发插件。 01 项目简介 Excel-DNA是一个.Net开源项目&#xff0c;为开发者提供了一种便利的方法&#xff0c;可以将.Net代码与Excel集成&#xff0c;能够轻松的为Excel创建自定义函数、图表、表单等&#xff0c;一方面不仅…

html5盒子模型

1.边框的常用属性 border-color 属性 说明 示例 border-top-color 上边框颜色 border-top-color:#369; border-right-color 右边框颜色 border-right-color:#369; border-bottom-color 下边框颜色 border-bottom-color:#fae45b; border-left-color 左边框颜色…

请求包的大小会影响Redis每秒处理请求数量

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容压测规划客户端长连接数量对性能的影响请求包大小的影响Pipleline模式对Redis的影响 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领…

pyspark分布式部署随机森林算法

前言 分布式算法的文章我早就想写了&#xff0c;但是一直比较忙&#xff0c;没有写&#xff0c;最近一个项目又用到了&#xff0c;就记录一下运用Spark部署机器学习分类算法-随机森林的记录过程&#xff0c;写了一个demo。 基于pyspark的随机森林算法预测客户 本次实验采用的…

springboot-基础-eclipse配置+helloword示例

备份笔记。所有代码都是2019年测试通过的&#xff0c;如有问题请自行搜索解决&#xff01; 目录 配置helloword示例新建项目创建文件 配置 spring boot官方有定制版eclipse&#xff0c;也就是STS&#xff0c;因为不想再装&#xff0c;所以考虑eclipse插件安装jdk和eclipse安装…

适配器模式(Adapter Pattern) C++

上一节&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09; C 文章目录 0.理论1.组件2.类型3.什么时候使用 1.实践1.基础接口和类2.类适配器实现3.对象适配器实现 0.理论 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允…