大模型推理优化

transforms模型分类

  • Decoder架构:适合生成任务,大模型llm的主流结构,典型模型有GPT,LLAMA等。只有一个Mask Attention层,没有Cross Attention层。
  • Encoder-Decoder:理论上结合了GPT和Bert的优点,典型模型是T5,训练成本很高,Google提出后并未过多发展,适用于需要输入的生成性任务,如翻译,摘要等等。
  • Encoder架构:不适合做生成,在理解任务上finetune性价比很高,如句子分类,命名实体识别等。典型模型为Bert。

大模型推理流程

LLM 将一系列tokens作为输入,并自回归生成后续tokens,直到满足停止条件(例如,生成tokens数量的限制或遇到停止词)或直到生成特殊的 <end> 标记生成结束的tokens。该过程涉及两个阶段:预填充阶段和解码阶段。

预填充阶段

在预填充阶段,LLM处理输入token以计算中间状态(keys和value),用于生成“第一个”token。每个新的token都依赖于所有先前的token,但由于输入的全部已知,因此在运算上,都是高度并行化矩阵运算,可以有效地使用GPU。

解码阶段

在解码阶段,LLM一次自回归生成一个输出token,直到满足停止条件。每个输出tokens都需要直到之前迭代的所有输出状态(keys和values)。这与预填充输入处理相比,就像矩阵向量运算未充分利用GPU计算能力。数据(weights, keys, values, activations) 从内存传输到GPU的速度决定了延迟,而不是计算实际时间消耗。即,这是一个内存限制操作。

预处理&向量化:Tokenizer&Embedding

预处理:可以将原始文本转换为由token组成的文本的初始数值表征,又可以进一步分成两步:

  1. 将字符串转换为token的序列
  2. 将token转换为token id

向量化:Embedding操作

后处理

基于模型的输出logits,从词表中选出一个token作为当前生成的结果。常见的后处理操作有top-k,top-p,temperature,greedy等:

  • top-k:模型从最可能的"k"个选项中随机选择一个,如果k=10,模型将从最可能的10个单词中选择一个
  • top-p:模型从累计概率大于或等于“p”的最小集合中随机选择一个,如果p=0.9,选择的单词集将是概率累计到0.9的那部分。
  • temperature:控制生成文本随机性的参数。较高的温度值会产生更随机的输出,而较低的温度值则会使模型更倾向于选择最可能的单词,较高的温度值,如1.0,会产生更随机的输出,而较低的温度值,如0.1,会使模型更倾向于选择最可能的单词(不可以为0)。
  • greedy:选择概率最高的那个,确定性后处理。

KV缓存

解码阶段的一种常见优化是 KV 缓存。解码阶段在每个时间步生成单个token,但每个token依赖于之前token的键和值张量(包括预填充时计算的输入tokens的 KV 张量,以及当前时间步之前计算的任何新 KV 张量) 。

为了避免在每个时间步重新计算所有tokens的这些张量,可以将它们缓存在 GPU 内存中。每次迭代,当需要计算新token时,它们都会被添加到正在运行的缓存中,以便在下一次迭代中使用。在一些实现中,模型的每一层都有一个KV缓存。

上图解释:Q、K、V是通过输入向量X经过线性输入W^Q、W^K、W^V得到,所以字符一定的情况下,QKV也是固定的,每次新来的token会重新生成一个Q、K、V,填充到原来的矩阵里面即可。

模型并行化

减少模型权重在每设备的显存占用的一种方法是将模型分布在多个 GPU 上。分散内存和计算可以运行更大的模型或更大批量的输入。模型并行化是训练或推理模型所必需的,模型并行化需要比单个设备更多的内存,用来训练和推理(延迟或吞吐量)。根据模型权重的划分方式,有多种方法可以并行化模型。

pipeline 并行

Pipeline并行化将模型(垂直)分片为块,其中每个块包含在单独设备上执行的层的子集(比如模型pipeline分为F1、F2、F3、F4)。图 a 说明了四路Pipeline,其中模型按顺序分区,并且所有层的四分之一子集在每个设备上执行。一个设备上的一组操作的输出被传递到下一个设备,后者继续执行后续块。F_nB_n分别表示设备上的前向传播和后向传播。每个设备上存储模型权重的内存需求被分成四份。

该方法的缺点是:由于处理的顺序性质,某些设备或层在等待前一层的输出(激活、梯度)时可能保持空闲状态。这会导致前向和后向传递效率低下或出现“Pipeline bubbles”。在图 b 中,白色空白区域是Pipeline并行性产生的Pipeline bubbles,其中设备闲置且未得到充分利用。

微批处理可以在一定程度上缓解这种情况,如图 c 所示。输入的全局批次大小被分成子批次,这些子批次被一一处理,最后累积梯度。请注意,F_{n,m}B_{n,m} 分别表示设备n上m批次的前向和后向传递。这种方法缩小了管道气泡的尺寸,但并没有完全消除它们。

Tensor 并行

Tensor并行化将模型的各个层(水平)分片为更小的、独立的计算块,这些计算块可以在不同的设备上执行。Transformer的主要组成部分,注意力块和多层感知器(MLP)层是可以利用Tensor并行化的。在多头注意力块中,每个头或一组头可以分配给不同的设备,以便它们可以独立且并行地计算。

  • 图 a 显示了两层 MLP Tensor并行的示例,每一层都由一个圆角框表示。在第一层中,权重矩阵A分为A_1A_2 。对于输入X,可以在同一批次不同设备上计算XA_1XA_2,其中,f是 identity 操作。这将每个设备上存储权重的内存需求减半。归约操作g组合了第二层的输出。
  • 图 b 是自注意力层中Tensor并行的示例。多个注意力头本质上是并行的,并且可以跨设备分割。

Sequence 并行

Tensor并行化是有局限性,它需要将层划分为独立的、可管理的块,不适用于 LayerNorm 和 Dropout 等操作,而是在tensor并行中复制。虽然 LayerNorm 和 Dropout 的计算成本较低,但它们确实需要大量内存来存储(冗余)激活。

transformer层的tensor并行化和sequence并行化如上图所示,这些操作在输入序列中是独立的,并且这些操作可以沿着“序列维度”进行分区,从而提高内存效率。这称为序列并行性。

注意力机制优化

MHA & MQA & GQA

  • MHA:多头注意力,当使用八个并行注意力头时,每个注意力头的维度都会减少(例如d_m / 8) 。这使得计算成本与单头注意力相似。
  • MQA :多查询注意力,在多个注意力头之间共享键和值。与以前一样,查询向量仍然被投影多次。虽然 MQA 中完成的计算量与 MHA 相同,但从内存读取的数据量(键、值)只是以前的一小部分。当受内存带宽限制时,这可以实现更好的计算利用率。它还减少了内存中 KV 缓存的大小,为更大的批量大小留出了空间。key头的减少会带来潜在的准确性下降。此外,需要在推理时利用这种优化的模型需要在启用 MQA 的情况下进行训练(或至少使用大约 5% 的训练量进行微调)。
  • GQA:分组查询注意力 (GQA) 通过将键和值投影到几组查询头,在 MHA 和 MQA 之间取得平衡。

MQA 和 GQA 等优化通过减少存储的key头和value头的数量来帮助减少 KV 缓存所需的内存。

FlashAttention

优化注意力机制的另一种方法是修改某些计算的顺序,以更好地利用 GPU 的内存层次结构。

在实际计算过程中将多个层融合在一起可以最大限度地减少 GPU 需要读取和写入内存的次数,并将需要相同数据的计算分组在一起,即使它们是神经网络中不同层的一部分。

KV缓存的分页高效管理

PagedAttention 算法能够将连续的键和值存储在内存中的不连续空间中。它将每个请求的 KV 缓存划分为代表固定数量token的块,这些块可以不连续存储。

模型服务技术

连续批处理

总结对比如下:

  • 静态批处理:客户端将多个Prompt打包进一个请求中,并在批次中所有序列完成后返回响应。通常,多数推理服务支持这种方法,但并不要求这样做。
  • 动态批处理:多个请求的Prompt在服务端内部动态打包进一个批次处理。通常,这种方法的表现不如静态批处理,但如果响应短或长度一致,可以接近最优。当请求具有不同参数时,这种方法效果不佳。
  • 连续批处理:将请求在到达时一起批量处理,它不是等待批次中所有序列完成,而是在迭代推理层级将序列组合在一起。它可以实现比静态批处理高10倍到20倍的吞吐量,目前是最先进的方法。

静态批处理

静态批处理指将多个Prompt打包进行一个批处理请求,并在批处理请求中所有Prompt完成后返回响应,批处理的大小在推理完成之前保持不变。

如上图所示,在第一遍迭代(左)中,每个序列从提示词(黄)中生成一个标记(蓝色)。经过几轮迭代(右)后,完成的序列具有不同的尺寸,因为每个序列在不同的迭代结束时产生不同的结束序列标记(红色)。尽管序列3在两次迭代后完成,但静态批处理意味着 GPU 将在批处理中的最后一个序列完成。

动态批处理

动态批处理是指允许将一个或多个推理请求组合成单个批次(必须动态创建)以最大化吞吐量的功能。就 Triton 推理服务框架而言,Triton 会对这些输入请求进行批处理,没有任何延迟,但用户可以选择为调度程序分配有限的延迟,以收集更多推理请求供动态批处理程序使用。

连续批处理

连续批处理:一旦批中的一个序列完成生成,就可以在其位置插入一个新的序列,从而实现比静态批处理更高的GPU利用率。

如上图所示,使用连续批处理完成七条序列。左图显示了单个迭代后的批,右图显示了多次迭代后的批。一旦一个序列产生结束序列标记,我们在其位置插入新的序列(即序列S5、S6和S7)。这实现了更高的 GPU 利用率,因为 GPU 不需要等待所有序列完成才开始新的一个。

现实情况比这个简化模型更复杂:因为预填充阶段需要计算,并且与生成阶段的计算模式不同,因此它不能很容易地与令牌的生成一起进行批量。连续批处理框架目前通过超参数来管理这个问题:等待已服务比和等待结束序列标记的请求比(waiting_served_ratio)。

参考文档

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容