赞
踩
毫无疑问,AI是当下最热的话题之一,而大模型又是当前AI的主角。几年前,正当深度学习进入瓶颈时,以GPT为首的LLM的横空出世让之似乎又找到了“第二增长曲线”。当模型规模大到一定程度时,它所表现出来的涌现能力(Emergent ability)是之前在小模型中所不曾见过的。这种大模型所特有的推理、计算等能力给我们带来了无穷的想象空间。
但是,它的代价是模型和以往模型相比增大了成百上千倍。要玩大模型十亿参数基本是个入门级门槛,上百亿才算像点样。各个大公司为了争夺大模型的话语权,更是将大模型越“卷”越大。模型规模增大所带来的挑战是多方面的,这里我们主要关注它在计算,尤其是推理时带来的挑战。提高LLM推理性能,降低LLM推理成本是加速其相关应用产品化的必经之路。
具体来说,LLM推理计算的主要挑战来自几个方面:
内存用量大:模型参数多,导致模型本身及中间张量的内存使用量增大。就算以FP16存储,模型的大小基本上也是参数量的两倍。一个模型本身动辄占用几十、几百个G,就已经数倍于主流GPU的内存了。再加上推理时其它一些中间张量,及KV Cache等,所需内存往往需要参数本身的几倍。因此,内存就成为了非常严峻的问题,甚至决定了能否跑得起来的关键因素。
硬件利用率低:目前主流的LLM推理采用auto-regressive方式。它是一个迭代的过程,每次迭代会产生一个token。其工作范式分为两个阶段:第一个阶段称为Prefill或prompt Processing。它根据给定的prompt计算KV Cache,并产生第一个输出token。第二个阶段称为decoding或token generation。它迭代式地逐个生成token,同时更新KV Cache用于下一次迭代。两者的计算模式有很大不同,前者通常为compute-bound;后者由于arithmetic density较低,通常为memory-bound。它难以并行,导致硬件利用率低。
业界提出了很多LLM的推理优化技术。本文无法全部覆盖,主要涉及以下几个方面:
目前主流LLM模型几乎都是基于Transformer架构。该网络架构除了头尾,其它都是Transformer layer(或称为Transformer block)的重复堆叠。Transformer block包含三部分:dense layer projection,self-attention, feed-forward。它们之中,除了self-attention其它其实都是GEMM。GEMM的优化技术被研究了几十年,相对成熟,不用多说。而attention的结构相对更复杂,更难并行,而且naive的实现中内存占用与context长度是平方关系。这使它成为耗时与内存占用的瓶颈,因此是优化的重点。
其中一个优化角度是Sharing-based Attention优化,即修改Attention结构使多个头间共享部分数据。如论文《Fast Transformer Decoding: One Write-Head is All You Need》中提出的MQA(Multi-Query Attention)与论文《GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints》中提出的GQA(Group-Query Attention)。它们被用于在PaLM, StarCoder, LLaMA2等模型中。但由于涉及网络结构本身的改变,这里不作展开。
FlashAttention系列可能是这几年中关于Attention最重要的优化。论文《FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness》与《FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning》分别提出了FlashAttention(FA)和FlashAttention 2(FA2)。由于出色的性能表现,目前已成为业界的标配。
记Attention的输入为
Q
,
K
,
V
∈
R
N
×
d
\mathbf{Q,K,V} \in \mathbb{R}^{N \times d}
Q,K,V∈RN×d,其计算过程的数学描述为:
S
=
Q
K
T
∈
R
N
×
N
,
P
=
softmax
(
S
)
∈
R
N
×
N
,
O
=
P
V
∈
R
N
×
d
\mathbf{S = QK}^T \in \mathbb{R}^{N \times N}, \quad \mathbf{P} = \text{softmax}(\mathbf{S}) \in \mathbb{R}^{N \times N}, \quad \mathbf{O = PV} \in \mathbb{R}^{N \times d}
S=QKT∈RN×N,P=softmax(S)∈RN×N,O=PV∈RN×d
其中矩阵相乘
Q
K
T
\mathbf{QK}^T
QKT产生shape为[batch, head num, seq len, seq len]的中间张量,
P
\mathbf{P}
P也一样。而最终需要的
O
\mathbf{O}
O是shape为[batch, head num, seq len, hidden size of one head]的张量。由于seq len一般会很长(现在的模型中支持的seq len越来越长,如GPT-3为2K, LLaMA-2为4K到32K,GPT-4有32k,CodeLlama有100k,Gemini 1.5有1M),因此前者的中间张量会非常大。这也意味着如果按照上面的数学定义来计算,中间张量大概率会把内存撑爆。FlashAttention使用tiling解决了该问题,同时减少了HBM与SRAM的读写。谈到tiling,很多文献是针对GEMM或者Conv这样的算子,但要应用于softmax咋一看会有些棘手,因为它本身包含了reduction的语义。对于这个问题,FlashAttention使用论文《Online normalizer calculation for softmax》中的online softmax技术,从而避免了构建巨大的attention matrix,同时也使得softmax可以被并行(原文3.1节)。该算法使内存的复杂度降低到线性,极大降低了它的memory footprint。另外,它还通过kernel fusion,将attention所有操作融合到一个CUDA kernel中,从而减少了kernel launch与访存的开销。
FlashAttention可达理论FLOPS的25-40%,比标准的attention快2-4倍,节省10-20倍的内存。但是它在thread block与warp的任务切分上还不是最优的,会导致occupancy低下,以及不必要的shared memory读写。另外,近年来context长度越来越长,FA在这方面没有专门的优化。在这样的背景下,FlashAttention 2对它进行了优化,改进了计算任务的划分,提升了并行性,使之能达到理论FLOPS的50~73%,性能提升2倍。它采用的主要技术包括:
论文《A Case Study in CUDA Kernel Fusion: Implementing FlashAttention-2 on NVIDIA Hopper Architecture using the CUTLASS Library》针对NVIDIA Hopper架构改进了Flash-Attention-2实现。它基于CUTLASS实现,利用了Hopper架构新引入的特性-Tensor Memory Accelerator(TMA)和Warpgroup Matrix-Multiply-Accumulate(WGMMA)指令。通过将TMA copy与WGMMA指令进行overlap,同时选取最优的tile size以平衡寄存器压力与shred memory的使用,相比Ampere架构上的FA 2实现有20-50%的性能提升。
文章"Flash-Decoding for long-context inference"和论文《FlashDecoding++: Faster Large Language Model Inference on GPUs》提出了Flash-Decoding(FD)与FlashDecoding++(FD++),也称为FlashAttention-V3和V4。前面的FA是训练与推理通用的,而FD,就像它的名字一样,是针对推理场景中的decoding的。
Attention的性能瓶颈在于对中间张量 Q K T QK^T QKT的读和写。前面的FA在batch size与query length维度上做并行。训练阶段由于batch大,容易并行。但推理阶段query length一般是1,同时推理时batch一般还比较小。这意味着如果batch数小于SM数量,就打不满GPU,硬件资源会被浪费。这在长序列时尤为严重,因为序列长一般意味着batch较小。因此,FA的优化不太适用于推理。
FD在FA的基础上,对于长序列推理有8x的提速。它的主要思想将并行化维度扩展到key和value,使得它们可以并行地加载和计算。其代价是最后需要做reduction。其工作流程有三步:1) 将keys/values切成更小的chunk。这一步无需GPU操作。2) 使用FlashAttention并行地为每个split计算query的attention。3) 最后,对所有split的结果做reduction。
FlashDecoding++通过进一步实现了Attention计算的并行,并针对扁平矩阵的乘法进行了优化。它针对LLM推理中存在的三个问题,在FD的基础上做了如下改进:
挑战 | 方案 |
---|---|
Synchronized partial softmax update | Asynchronized softmax with unified max value |
Computation resources is under-utilized for the flat GEMM operation | Flat GEMM optimization with double buffering |
The performance of LLM inference suffers from the static dataflow | Heuristic dataflow with hardware resource adaption |
基于这些优化,FlashDecoding++与FlashDeocding相比提速1.37x。
Asynchronous Softmax
为了数值稳定性,避免溢出,在计算softmax一般会先将数值减去最大值。以往FA中采用partial softmax的方式,将数据切块,分别计算块内softmax。虽然达到了一定程度的并行,但由于依赖于其它的partial结果,因此最后仍需要以同步的方式更新。这种synchronized partial softmax update的方式占到Attention计算的相当比重(Llama2-7B中18.8%)。文中指出LLM中的softmax的输入值域99.99%都在特定区间中,因此可以使用一个统一的值来取代这个最大值,从而避免同步,让这部分计算完全并行起来。如果发生溢出,再用同步的方法进行重计算(recomputation)。
Flat GEMM
Decode阶段batch size与seq len一般较小,因此GEMM操作中的矩阵比较扁平(即flat GEMM)。当batch size为1时甚至退化成GEMV。另外当prefill阶段的seq len较小时也会产生flat GEMM。这些情况下会导致硬件利用率很低。LLM推理中广泛使用cuBLAS或CUTLASS这样的库利用Tensor Core进行加速。尽管Tensor Core支持GEMM中的M=8,但库中一般会在M维度上将tile size设为64以hide memory latency。但是,decode阶段中GEMM的M常常远小于64,这样会需要padding从而导致浪费。 FD++将矩阵padding到8(而非像之前设计中的64)的倍数以提升利用率。文中指出不同shape的flat GEMM会面临不同的瓶颈(当N较小时是parallelism-bound,N较大时是memory-bound)。FD++在N较大时通过引入double buffering来hide memory latency。即在shared memory中分配两块buffer:一个buffer中的tile执行GEMM操作,另一个块buffer为下一次GEMM操作加载tile。这样,计算与访存就可以overlap。
Heuristic Dataflow
LLM推理中的GEMM是多样的,可能是GEMV(decode阶段batch size=1的情况),可能是flat GEMM(decode阶段小batch size情况,或是prefill阶段小seq len情况),也可能是传统的GEMM(prefill阶段当batch size与seq len都较大时)。当前的框架(如FasterTransformer,DeeSpeed)使用cuBLAS中的高性能GEMM实现处理不同的workload。这种统一的处理方式并不能保证达到最好的性能。影响linear workload性能的因素有很多,有input dynamics(batch size, seq len),Model多样性(结构和size),GPU能力(memory bandwidth, cache size等),工程effort等。对不同的输入与硬件采用静态的方案会让不同shape下GEMM性能有50.25%的性能损失。因此,FD++考虑输入的多样性,用启发式的方式使用不同的硬件资源(Tensor Core或CUDA core)进行优化。经过分析可知,对于一个特定LLM模型,其GEMV/GEMM操作的[K,N]组合只有4种。对于这几种情况,考虑三种实现:FastGEMV,自定义的GEMM实现和CUTLASS的传统GEMM实现。M较小时考虑前两种,M较大时考虑后两种。具体用哪种通过profile的方式确定。
论文《Blockwise Parallel Transformer for Large Context Models》提出了Blockwise Parallel Transformer(BPT)方法对self-attention与FF模块都用blockwise的方式计算,并将它们融合。这样可以节省构建中间张量所需的内存。对于GPT2模型,能训练的seq长度比标准的attention长32倍,比FlashAttention长4倍。
为了降低计算复杂度,有一类方法研究用稀疏矩阵或者低轶矩阵近似Attention的中间矩阵。但这些方法普遍不支持causal masking。以往工作表明Attention在最坏情况下,需要quadratic time,除非attention matrix的元素有界或者矩阵rank较小。论文《HyperAttention: Long-context Attention in Near-Linear Time》中提出的HyperAttention是一种approximate attention机制,用来解决LLM中长上下文带来的计算挑战。该方法采用Locality Sensitive Hashing(Hamming sorted LSH)与Approximate Matrix Multiplication(AMM)对Attention的计算 A t t n = D − 1 A V \mathbf{Attn = D^{-1}AV} Attn=D−1AV进行近似。它可以达到接近线性复杂度,且支持casual mask。
由于attention在LLM中独特的重要性,业界也涌现出一些优秀的计算库,如FlashInfer。它是一个LLM推理的高性能GPU kernel(如FlashAttention, PageAttention和LoRA)实现库。它包含single-request和batching的prefill, decode和append kernel。KV-Cache支持Padded tensor, Ragged Tensor, Page Table。另外,它还支持shared-prefix batch decoding(详见"Cascade Inference: Memory Bandwidth Efficient Shared Prefix Batch Decoding"),优化GQA, Fused-RoPE Attention和Quantized Attention等特性。
Auto-regressive的token生成方式中,每一个token理论上需要基于前面的token。Naive的LLM推理会重复计算前面token的Keys与values(上面公式中K和V两个张量)。为了避免了这种重复计算,我们可以将前面token计算好的K和V缓存起来,该方法称为KV Cache。它也是目前LLM推理的主要优化方法之一。这本质上是一种“空间换时间”的优化,因此其代价是会占用很多内存。在实际场景中所占内存相当大,可达内存总用量的30%左右。因此,业界提出了不少关于KV Cache优化的工作。它们可大致分为几个方向:
Naive的KV Cache以最长sequence为粒度分配和管理,会带来巨大的浪费。以更小的粒度管理通常可以节省大量的内存。但过小的粒度又会因为物理不连续带来性能负面影响。因此,我们需要根据实际需求在两者之前平衡。
论文《Efficient Memory Management for Large Language Model Serving with PagedAttention》中提出的PagedAttention使用类似于操作系统中的虚拟内存机制对KV Cache进行管理。KV Cache的size会随着token产生的过程动态变化。一种简单的方式是提前按最大可能长度预分配连续的内存。但是,这会导致三种内存浪费:预留空间,内部碎片化,外部碎片化。其中有效利用的内存可能只有20%左右。另一方面,这种方式难以实现KV Cache的内存共享(Memory sharing)。在一些场景中,我们需要对于一个request生成多个序列,或者在做beam search时,其prompt或者不同beam candidate的序列的KV cache是可以共享的。
PagedAttention允许在不连续的内存空间中存储连续的K和V。它将KV Cache分成逻辑块(Logical block),每个逻辑块包含固定token数量的K和V。这些逻辑块,只有当真正用到时才分配物理块。这种方式使浪费率低于4%。这样做的另一好处是可以实现内存的共享,即将不同序列的逻辑块映射到同一物理块。它引入了reference count和copy-on-write机制来进行对这些内存块进行管理。
vLLM是基于PagedAttention的LLM推理系统。vLLM采用了centralized scheduler来协调分布式GPU worker的执行。KV Cache manager通过来自centralized scheduler的指令管理物理的KV Cache 内存。在调度方面,它采用FCFS调度策略,保证公平防止“饥饿”,同时利用fine-grained batching mechanism(即以iteration为粒度调度)实现了preemptive request scheduling。在cache的eviction策略上,它根据LLM的特点(一个序列的所有block同时使用)实现了all-or-nothing eviction policy。同时支持swapping和re-computation两种recovery机制。前者适用于block size大时,后者适用于block size小时。在kernel级别的优化上,它还实现了Fused reshape and block write,block read and attention, fused block copy等GPU kernel。与FasterTransformer与Orca相比,它在latency不变的情况下将throughput提升2-4x。
对于request间有相同的prefix的情况,将KV Cache缓存下来可以避免重复计算。这就是Prefix caching的基本思想。比如论文《Improving Large Language Model Throughput with Efficient Long-Term Memory Management》基于PagedAttention提出prefix cache,该prefix cache可被swap到CPU与disk。
PagedAttention以block(一个block可以存放多个token的KV Cache)为单位对KV Cache进行管理,仍然会有内存浪费。基于Python的LLM推理与服务框架LightLLM中采用了"TokenAttention"以更小的粒度,即token为粒度对KV Cache进行管理。它能最小化碎片化,并使得内存的分配与释放更加高效。其工作流程可分几步:1) 模型初始化时,基于用户设定的最大token数量分配KV Cache,同时创建token table用于记录物理内存。2) 当新request到来时,先检查在预分配的内存中是否有可用的连续空间。如果没有,它会分配非连续的GPU memory,并记录在token table中。3) 对于新产生的token,从预分配的token cache中找到空闲的空间,并加到token table中。4) 当一个request完成后,可以将token table中对应的记录删除。从而可以让内存被新的request所使用。
前面的PagedAttention与TokenAttention都是以减少内存浪费为目标,但以性能为代价。因为内存不物理连续时,其性能也受到一定程度的影响。在Continuous batching技术(后面会提到)中,同时处理的序列的长度可以是不同的,这样就形成了Ragged tensor。"PAI BladeLLM 推理引擎: 超长上下文、更高性能"中的RaggedAttention通过保证同一序列的KV是连续存储的,这样可以提升kernel的访存效率,从而提升性能。也就是说,它没有PagedAttention和TokenAttention那么极端,但其代价是会有一些内存浪费。
平台特性可以被用来优化KV Cache。
论文《vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention》指出PagedAttention为KV Cache使用了非连续的virtual memory,导致软件复杂、移植性、冗余和低效问题。vAttention为KV Cache使用连续的virtual memory,使用low-level系统中已有的demand paging支持(CUDA 10.2中引入的cuMemCreate, cuMemMap, cuMemAddressReserve等API用于分离virtual与physical memory的分配,详细请见https://developer.nvidia.com/blog/introducing-low-level-gpu-virtual-memory-management/)。另外,它引入两个优化:一方面,修改NVIDIA driver(开源部分)增加page size选择,以减少内部碎片,和hide内存分配延迟。另一方面,它使用三种技术hide内存分配的开销:1) 在background thread中分配新页,使其与计算可以overlap。2) Deferred reclamation使batch中的request的KV cache可以重用。3) Eager allocation在physical page在需要前分配好,比如新的prefill request到来前。
在prompt工程中,我们会为用户的prompt前加一些system prompt。它包含了任务描述、上下文信息、样例等信息。System prompt在多个request间是相同的,并且可能会很长。如果对于每个request,都去重新计算就会带来巨大的浪费,导致Time to first token (TTFT)非常长。
"Fast and Expressive LLM Inference with RadixAttention and SGLang"一文指出现有支持KV Cache共享的系统需要手工配置和调整,无法自动化。它提出的RadixAttention是一种运行时的自动高效的跨多LLM generation调用的KV Cache reuse机制。它在当request的处理完成后,不会丢弃其KV Cache,而是将token序列到KV Cache的映射用radix tree管理起来。这样prefix search,insertion和eviction操作会非常高效。另外它还实现了LRU eviction policy和cache-aware scheduling policy来提升cache hit rate。
论文《ChunkAttention: Efficient Self-Attention with Prefix-Aware KV Cache and Two-Phase Partition》也是用于多request的prompt有共同prefix的场景(如system prompt与task-specific input组合成最终prompt的情况,或者使用templated requests的情况)。它提出的ChunkAttention是一种新的self-attention module。一方面,它采用Prefix-Aware KV Cache(PAKV),将KV Cache切成小的chunk,将它们以prefix tree的方式组织。具有相同prompt prefix的序列的query张量会被batch到一起。另一方面,它引入two-phase partition(TPP)算法加速在PAKV上的计算。它的优点是可以在运行时自动与动态地检测相同的prefix并优化。
KV Cache和其它cache一样,当内存不够时也需要考虑eviction。但它与其它cache相比,又有自身的一些特点。因此,传统cache的eviction policy对它未必适用。
论文《Efficient Streaming Language Models with Attention Sinks》中它提出的StreamingLLM通过保留特定token的KV Cache改进了LLM在streaming应用中的表现。它指出将LLM用于streaming应用(如多轮对话)会面临两大挑战:1)在decoding阶段,KV Cache会消耗大量内存。2)流行的LLM无法应用到比训练时所用序列更长的场景。流行的Window attention方法只保留最近的固定长度的sliding window中的token的KV Cache。但这种方式在序列长度超过cache size会失效。业界涌现出不少方法进行改进。总得来说,将LLM扩展到lengthy text的方法有三个方向:
本文中指出在auto-regressive LLM中,大量的attention score会位于几个initial tokens上,即使它们在语义上并不重要。这些tokens称为attention sinks。直观上,在auto-regressive LLM中,初始的token会对后面所有的token产生影响。基于该观察,StreamingLLM除了保留最近的token对应的KV Cache,还保留initial tokens的KV Cache。它可以让LLM在训练时使用有限长的attention window,而在推理时无需fine-tuning便可以用于无限长的情况。实验证明在序列长度达到120K tokens时仍能保持合理的准确率。
KV Cache虽然也是cache,但由于它的特殊性,传统用于Cache的LRU/LFU策略可能并不是最适合的。论文《H2O: Heavy-Hitter Oracle for Efficient Generative Inference of Large Language Models》中提出的H2O是一种用于减少memory footprint的KV Cache eviction policy。该方法基本这样的现象:一小部分的token在计算attention score的过程中有大部分的贡献,去掉它们会对结果有很大影响。LLM中的attention矩阵在推理时稀疏度可达95%(也就是说5%的KV Cache其实就足够用于得到相同的输出)。这部分token称为Heavy Hitters(H2)。统计前置token的attention score的累加可以用于找出这些Heavy Hitters。根据这个观察,文中提出Heavy Hitter Oracle(H2O)框架。它是一种可以动态地在最近的token与H2 token间保持平衡的KV Cache eviction policy。该方法可以在不显著影响准确率的前提下将memory footprint降低5-10x。
前面提到,LLM推理中的decoding阶段相对来说难以打满GPU,因此往往是优化的重点。业界有几种优化思路:
网络模型对于不同的输入,需要的计算是不同的。因此,我们可以用adaptive compute来减少计算量。Adaptive compute主要依赖early-exit策略。Transformer网络是一个多层结构。意味着对于简单的输入,可能只要算前面几层就行。Early exiting可以根据中间层的表征直接输出新token,跳过网络中后面的部分,从而避免计算整个模型。那现在主要的问题就是如何确定exit point,也就是如何判断是否是“难”的。对于这个问题大致有几类方法:1) Heuristic metrics:如entropy, maximum softmax score, 最上两层softmax的差,或者cosine similarity等。但它们的泛化性较差,且threshold需要tuning。2) Learn-to-exit:泛化性好,且不需要threshold tuning。如论文《Confident adaptive language modeling》中就用了这两种方式。3) Hash-function:如论文《A simple hash-based early exiting approach for language understanding and generation》中提出Hash-based Early Exiting(HASHEE)方法用于替代learn-to-exit。这里的hash function用于构建token到exiting layer的映射。基本思想是如果训练中一个样本是容易的,那推理时与它相似的样本也应该是容易的。
论文《SkipDecode: Autoregressive Skip Decoding with Batching and Caching for Efficient LLM Inference》是一种token-level early exit方法,使之能用于batching inferencing和Key-Value Caching。Batching带来的难点是需要等batch中最后的token被处理。KV Cache带来的难点在于如果当前token比其它token退出更晚则需要更新前面token的KV Cache。SkipDecode为batch中的每个sequence position设立单独的exit point,从而克服了这些缺点。它利用了序列结尾更容易预测的特点,序列越到后面所需计算越少(可参见论文Figure 2)。这样做也使前面token的KV Cache不需重新计算。
Decoding阶段的主要挑战在于每个token依赖前面的token,它是迭代式地吐结果,因此整个过程是串行的。想要提高性能,并行化是一个重要手段。Decoding阶段并行化有几种常见手段:
LLM推理的性能瓶颈在于auto-regressive decoding。该过程对GPU不友好,因为auto-regressive这种逐个输出token的方式不易并行。Speculative decoding是近几年比较火的一类方法。它可以打破每次只输出一个token的限制。这类方法采用的是Guess + Verify的范式。它分为两步:第一步使用draft model(或称small speculative model,通常是一个计算量小的模型)产生一定长度的token,称为draft(可通过non-autoregressive方式,也可使用更快的autoregressive模型)。第二步对前面产生的draft用LLM模型(或称为target model,verifier model)进行验证或评分。它最大的好处就是这两个阶段都可以并行化,而且可以保证结果与LLM模型产生的一致。
其中draft model可以是target model的一部分(一起从头训练),也可是模型压缩方法得到,或是target model的部分activation作为draft model的输入,然后训练得到。可以看到,这种方法要想高效,很大程度上取决于draft model的能力。它的准确度越高,或者说与target model的输出越吻合,整个方法的提速就越明显。但是如果为了它的准确度过分增大其计算量,又得不偿失。因此,我们需要从其它方面去想办法,比如一次产生多个draft等。
在这种Guess + Verify范式下,衍生出来很多玩法,比如:
还有一些工作致力于将之应用于小型设备,比如:
另一类方法不需要额外的draft model,称为Model-free prediction strategies。比如:
既然auto-regressive的方式对性能不友好,那还有一个思路就是使用Non-autoregressive(NAT)的方式。NAT以迭代的方式迭代的方式将所有的token一起decode出来。NAR(Non-autoregressive)最开始在NMT(Neural machine translation)领域被提出。与auto-regressive方式相比,它可以并行产生token,因此耗时更少,但是代价是准确率更低。其主要原因是"failure of capturing the target side dependency"。它不像auto-regressive方法一样,产生第t个token时有前面t-1个上下文token信息。业界有一种折中的做法是iteration-based NAT模型。即每一次产生的token再给decoder做refinement,迭代数次。相关方法详细可参见论文《A Survey on Non-Autoregressive Generation for Neural Machine Translation and Beyond》。其中讨论了包括data manipulation, modeling method, traning critierion, decoding algorithm和pre-trained model等方面的工作。
众所周知,GPU是massively-parallel架构。通常batch越大,可并行的计算任务越多,计算任务更倾向于compute-bound而非memory-bound,也就越容易提升硬件利用率。另一方面,同一batch中的推理无需重新加载模型权重,省去了从HBM到on-chip SRAM的数据搬运开销。但是推理场景下,batch一般较小,因此一般硬件利用率也比训练场景下更低。因此,推理场景下,我们希望将更多任务batching在一些,从而提升吞吐。根据batching的方式不同,可分为static batching, dynamic batching和continuous batching三种。
最简单的方式是static batching。这种方式中,client端就将request进行batching,发到服务端。但作为服务端,假设client做batching太过理想,而且很多时候request来自于不同的client。另外,不同request产生的序列长度变化可能很大。因此,在LLM中实用性不大。
另一种常见的是dynamic batching。比如Triton Inference Server可以动态地将客户端的请示在服务端组成一个大的batch。为了控制对于latency的影响,它可以配置latency的约束(最长不超过多长时间做一次batching)。但是,对于input shape会变化的场景(比如语言类模型),需要对input tensor进行padding。而padding会带来资源的浪费与性能的损失。为了避免这个损耗,就需要对输入数据进行"压缩"。比如论文《Prepacking: A Simple Method for Fast Prefilling and Increased Throughput in Large Language Models》提出batch当中prompt长度差很多时,padding的方式浪费资源严重。Prepacking优化prefill计算。它避免了pad token的计算。它使用bin-packing算法,将不同长度的prompt打包进一个序列。然后修改attention mask和positional encoding来对单个序列中的多个prompt进行计算。
这种长度不规则的张量称为ragged tensor。Ragged batching让用户指定输入中的哪些是有效的数据,从而可以避免显式的padding。接下来的问题就是还需要有相应的kernel来高效处理这种ragged tensor。一种是手写,比如Effective Transformer中的Remove padding算法可用于减少padding部分的计算量。该算法后来也被集成到FasterTransformer中。另一种是自动生成,比如论文《The CoRa Tensor Compiler: Compilation for Ragged Tensors with Minimal Padding》中的CoRA是一个张量编译器,用于为CPU与GPU平台自动生成ragged tensor的高效代码。
如果batch中的request的序列长度(包括prompt与生成的序列)相差很大,那些早结束的request需要等最长的序列完成,导致计算资源被浪费。因此,如果我们将长度相差不多的request放在一起,对性能是更好的。对于一个request,在没有到达最长限制前,它会一直生成token直到得到遇到EOS为止。这里最大的难点在于它会生成多长的序列是不确定的,因此我们很难提前将生成差不多长度输出的request放在一块做。一种直观的思路就是预测其输出序列长度,业界提出的一些相关方法包括:
前面一些方法使用预测的方法来将输出序列长度差不多的request放在一块。但这样就依赖于预测的准确性。如果预测不准,不仅起不到预期的效果,还搭上了预测本身的计算开销。
回过头来思考,之所以输出序列长度差异会导致浪费,更本质的原因是因为传统采用的是request-level batching,即一个batch中的request在batch没全部完成前是不会变的。这样,就衍生出另一种策略。就是根据LLM中迭代式产生token的特征,每当有request完成,就把它移出batch,然后把新的request放入batch。它有很多名字,如continuous batching(vLLM中的叫法), iteration-level scheduling(Orca中的叫法), in-flight batching(TensorRT-LLM中的叫法)。很多框架中都有实现:
上面的工作主要想着怎么把prefill与decode两个阶段放在一起,从而提高计算密度。业界现在还有一种看似有些相反的思路是既然它们计算模式如此不同,何不把它们尽可能分开,以减少相互影响,方便调度。
多数分布式AI计算相关工作和是针对训练,感兴趣的可以看之前写的文章《LLM时代中的分布式AI》。但其中一些思路其实对推理也适用,如最为常见的TP,PP等。很多框架如FasterTransform,Megatron-LM,DeepSpeed, Petals等都对分布式推理有支持。
一些可用于AI推理的分布式方法如:
分布式计算意味着会有很多通信算子。这些算子容易成为性能瓶颈。因此,减少它们的开销是优化的重点。使用NCCL这样的三方库优点是方便,大部分情况下性能也不错。要进一步深入优化,就需要让通信与计算算子融合。一些工作比如:
对于内存不足的问题,除了优化内存占用外,另一个常用的方法是利用memory hierarchy中更下一级的存储,比如CPU memory,或者磁盘。这方面业界相关的可用于LLM的工作比如:
LLM的常见压缩方法和传统神经网络模型类似,有pruning, knowledge distillation, quantization, low-rank factorization这几大类。LLM会给这些技术带来一些新的挑战。
我们知道,模型的量化可分为PTQ(Post-training quantization)和QAT(Quantization-aware training)两种。LLM因其训练成本很大,因此QAT很多时候不实际,而PTQ要实用地多。但是,PTQ往往会导致准确率下降很多。因此,LLM量化中主要目标之一是减少PTQ的准确率损失。量化本质上是用更小的位数去表达原数据,需要在范围与精度间做取舍。当网络参数量大到一定程度,activation就容易出现outlier。它会使量化范围增大,从而使得量化误差变大,最终降低准确率。一些业界的一些量化方法包括:
Pruning通过裁剪网络进行模型压缩。对LLM进行pruning时,需要在减小模型规模的同时保证原模型的能力尽可能地保留。
Low-Rank Decomposition基本思想是将一个大型矩阵分成两个更小矩阵的积,从而减少参数量。它有好处是处理后仍是dense matrix,因此可以充分利用硬件与为之写的高效的dense matrix计算库。相关工作如:
LoRA还可以与量化方法相结合,如:
因为很多LLM模型是通用的,我们可以将LLM中对于特定任务的能力用更小的模型抽取出来。用小型模型来(称为student model)进行训练,学习原LLM模型(称为teacher model)的概率分布,这种方法称为知识蒸馏。在LLM之前,该方法就被用于LM,如DistillBERT。在LLM下的相关工作如:
业界还有一些从其它角度进行优化的工作,比如:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。