动手点关注
干货不迷路
本文旨在让无大模型开发背景的工程师或者技术爱好者无痛理解大语言模型应用开发的理论和主流工具,因此会先从与LLM应用开发相关的基础概念谈起,并不刻意追求极致的严谨和完备,而是从直觉和本质入手,结合笔者调研整理及消化理解,帮助大家能够更容易的理解LLM技术全貌,大家可以基于本文衍生展开,结合自己感兴趣的领域深入研究。若有不准确或者错误的地方也希望大家能够留言指正。
本文体系完整,内容丰富,由于内容比较多,分多次连载 。
第一部分 基础概念
1.机器学习场景类别
2.机器学习类型(LLM相关)
3.深度学习的兴起
4.基础模型
第二部分 应用挑战
1.问题定义与基本思路
2.基本流程与相关技术
1)Tokenization与Embbeding
2)向量数据库
3)finetune(微调)
4)模型部署与推理
5)prompt
6)编排与集成
7)预训练
第三部分 场景案例
常用参考
2.基本流程与相关技术
4)模型部署与推理
模型推理优化理论
前面的内容我们介绍了大模型的结构及相关参数,知道了影响大模型训练 推理阶段需要关注的相关指标,如模型的显存占用量,计算量,访存量等指标。同时,我们也发现随着模型参数量越来越大,要想将一个大模型运行起来,动辄需要高额的计算资源支持,进一步地提高吞吐量和降低访问延是一个非常大挑战,例如,GPT-175B(GPT-3)仅用于存储模型权重就需要 325GB 的内存。要让此模型进行推理,至少需要五块英伟达 A100(80GB)和复杂的并行策略。在 本节里,我们将先从理论角度分析,分析总结当前关于大模型推理优化层面的常见思路。
现在,我们来回顾一下上一节的内容,对于一个transformer结构大模型,参数量,计算量和访存量对于模型的性能影响巨大。其中参数量会直接影响到模型参数权重和KVCache(非必需)的大小,它决定了模型需要多大的显存才能正常运行,计算量和访存量联合构成了计算强度的概念,它受到了roofLine模型的影响,通过它与实际的计算平台一起,能够确定其最大的计算速度,从而影响到模型的吞吐和延迟。
因此,我们想要改善模型推理的性能表现,应该从两个角度切入:
1)降低显存成本,即尽可能地降低模型推理所需显存总量,尽可能充分利用GPU,尽可能降低GPU的规格要求,如显存大小。
2)降低计算成本,即尽可能地减少计算量,尽可能提高计算速度,重点提升模型推理性能。
下面,我们将 介绍当前业内的常见优化思路。 需要说明的是这两个方向并不是完全割裂的,可能同时对这两方面产生影响,比如前面提到的kvcache就是空间换时间的一种做法。 因此,文章阐述结构会从技术方案反向说明他们在这两方面产生如何的影响。
模型压缩
显而易见,为了降低模型的显存占用,提升速度,将原本大模型通过一定方法压缩成为相对较小的模型,这样就能够降低模型对资源和算力的要求。
https://arxiv.org/pdf/2308.07633.pdf
模型压缩,早期主要应用场景在边缘AI领域,旨在让模型能够运行在资源有限的嵌入式设备中,然而随着大模型AI对资源的高要求,模型压缩也变成了大模型推理优化的重要方向。模型压缩的方向大致分为剪枝(Pruning),知识蒸馏(Knowledge Distillation,KD),量化(Quantization),低秩分解(Low-Rank Factorization),权重共享(Weight sharing),神经网络架构搜索(NAS)等。下面将介绍几个常见的方向。
剪枝(Pruning)
对于一个大模型来讲,结构比较复杂,参数也非常多,那么是不是都发挥着重要的作用呢?研究表明,存在着大量冗余的参数,这些参数对于模型性能来讲并没有什么作用,因此,便可以去除这些不重要的模型权重或连接从而达到减少模型大小,加速推理速度的目的,这个过程可以类比于人类“减肥”,那么,具体怎么给模型“减肥“呢?
基本过程为正常训练模型->判断重要性->剪枝->再训练这样一个迭代的过程。
从修剪的单位粒度来看,常见的可以分为:
- 细粒度剪枝(fine-grained):对连接或者神经元进行剪枝,它是粒度最小的剪枝。
- 向量剪枝(vector-level):它相对于细粒度剪枝粒度更大,属于对卷积核内部(intra-kernel)的剪枝。
- 核剪枝(kernel-level):去除某个卷积核,它将丢弃对输入通道中对应计算通道的响应。
- 滤波器剪枝(Filter-level):对整个卷积核组进行剪枝,会造成推理过程中输出特征通道数的改变。
剪枝可以分为非结构化剪枝和结构化剪枝,非结构化剪枝方法中,以参数剪枝为例,只是简单的将参数置零,并没有考虑最终模型结构,会导致不规则的稀疏结构,这样的结构既不好实现,也不利于GPU计算加速。因此,为了避免这样的问题,结构化剪枝剪掉基于特定规则的连接或分层结构,同时保留整体网络结构,避免非结构化剪枝的问题。
对于transformer结构的模型来讲,剪枝过程遵循上面的模式。大致分为三个步骤:
https://arxiv.org/pdf/2305.11627.pdf
1)发现阶段:发现 LLM 中复杂的相互依赖关系,并找到最小可删除的单元、组,如 Attention head 。
2)评估阶段:估算每个分组对模型整体性能 的贡献,并决定修剪哪个分组。
- 再训练阶段:快速后训练,恢复模型性能。
以 LLM-Pruner结构化剪枝 为例,对比不同 剪枝后模型的生成文本质量,可以发现微调后的模型依旧维持了较好的生成能力。
知识蒸馏(Knowledge Distillation,KD)
知识蒸馏的理论基础和剪枝相同,都是考虑到神经网络中存在着很多的结构和参数权重实际起到的作用不大可以进行某种方式上的“瘦身”,不同于在原有模型上剪除作用不大的结构和参数,它旨在将原有复杂模型学到的知识转移到空间容量小得多的模型中,即“老师教学生“。因此,知识蒸馏是由教师网络和学生网络组成。教师网络通常是一个更复杂的模型,使用整个训练数据集进行训练。网络中的大量参数使得收敛相对简单。然后,应用知识转移模块将教师网络学到的知识提炼到学生网络中。
根据需要转移的知识类型不同,知识转移的方式也多种多样。我们将介绍两种较为常见的知识及其相应的传递方法。
1)基于响应的知识(Response-based knowledge):基于响应的知识侧重于网络的最终输出。换句话说,目标是让学生学会输出与教师网络类似的预测结果,并非直接从样本学习。因此,进行这种知识转移的典型方法是根据教师和学生的实际真实值的损失,而不是学生和实际真实值之间的比较。这种优化鼓励学生权重更新到与教师权重相似的空间,从而实现知识转移的目标。教师的这种软标签往往比实际真实值更能让学生学得更好,因为这样的软标签本身就包含着有用的信息,而直接使用真实值就丢失了这样的信息,比如,跟着老师在学习分辨数字时,由于老师见过的样本很多,它知道7和1是有相似性的,而学生模型哪怕没有见过真实的7的样子,但它也能通过这样的相似性,知道从1和3中分辨出1。
2)基于特征的知识:与输出相似性不同,基于特征的知识强调教师和学生之间中间表征的差异。因此,转移特征要求在中间表征相似性期间而不是在最终输出时计算损失。这就确保了学生的处理/提取方法也与教师的相似。这种方式显然也是希望尽可能地将老师模型在大量训练数据中学到的真正规律传递给学生模型,从而提升模型本身的泛化性。
对于大模型(LLM)来讲,根据方法是否侧重于将 LLM 的涌现能力(EA)蒸馏到小模型(SLM)进行分类。分为:标准 KD 和基于 EA 的 KD。其中标准蒸馏即前面提到的模式,区别仅在于区别在于教师模型是大模型。
而EA-based KD在原来传递知识到学生模型的同时,还蒸馏了涌现能力。因此,EA-based KD又分为了上下文学习(ICL)、思维链(CoT)和指令跟随(IF)。
知识蒸馏不仅可以用于模型压缩,它还可以将模型能力迁移,香港科技大学提出了一个针对闭源大语言模型的对抗蒸馏框架,成功将 ChatGPT 的知识转移到了参数量 7B 的 LLaMA 模型,在只有 70k 训练数据的情况下,实现了近 95% 的 ChatGPT 能力近似。也就是说,对于一些未开源的模型,通过蒸馏的方式来复刻一个新模型也不失为一个巧妙的思路。
量化(Quantization)
我们前面曾有简单介绍,通过参数精度的降低,可以节约显存的占用。比如pytorch默认的模型是采用全精度(fp32)计算存储的,如果我们将其量化成半精度(fp16),显然,其内存存储相较于降精度前减少了一半,又由于精度降低了,相应的计算速度就会变快。
而量化就特指浮点算法转换为定点运算(定点,即小数点的位置是固定的,通常为纯小数或整数)或离散运算,如将fp16存储的模型量化为INT8或者INT4, 因此,量化是一个对降低显存资源要求,提升推理速度非常有效的手段。当然,需要注意的是,这里计算的速度要结合GPU的计算能力综合判断,比如英伟达GPU在不同精度的矩阵计算能力上就有差异,不见得精度越低,计算就越快。
应用量化策略有三种常见的方法:
1)量化感知训练 ( Quantization Aware Training,QAT):在预训练或进一步微调 期间应用量化。QAT 能够获得更好的性能,但需要额外的计算资源,还需要使用具有代表性的训练数据。
2)动态训练后量化(Post-Training Quantization Dynamic, PTQ-Dynamic):即精度降低仅发生在模型训练之后,所有模型权重都是提前量化的,只有bias和激活函数 是在推理过程中动态量化;。但是对于不同的输入值来说,其缩放因子是动态计算而来。
3)静态训练后量化(Post-Training Quantization Static, PTQ-Static),同样发生在模型训练之后,它需要使用少量无标签校准数据,采用 KL 散度等方法计算量化比例因子。
静态量化(Static quantization)与动态量化的区别在于其输入的缩放因子计算方法不同,静态量化的模型在使用前有fine-tuning的过程(校准缩放因子),如果在模型推理过程中,加载权重比矩阵乘法本身花费更多的时间,那么 PTQ-动态推理就是最合适的选择。 另一方面,PTQ-static 使用校准数据集提前计算量化参数,计算量比Dynamic quantization明显更小。
此外,将相关工作根据LLM权重中的位数(精度)进行分类,又可以分为8位量化和低位量化。
那么,模型量化对于模型性能有什么样的影响呢?根据论文显示,在使用4位和2位量化模型进行实验,4位量化模型仍然保留了比较好的智能涌现能力,而2位量化模型性能严重退化。但通过模型微调进行性能补偿,可以一定程度提升低位模型的性能。
https://arxiv.org/pdf/2307.08072.pdf
模型压缩除了上面介绍的三种方法外,还有其它方法,但总的来讲,通过它可以有效地减少模型的大小及计算复杂度,在尽可能降低效果损失的情况下,获得运行内存和推理速度的双双改善。
Offloading
所谓Offloading就是将GPU中的计算放置到CPU中,这样就能够减少GPU的占用。这里有一个代表性的项目Flexgen,作者专注于高吞吐量生成推理的有效 offloading 策略。当 GPU 显存不够用时,我们需要将其卸载到二级存储,通过部分加载的方式,逐段进行计算。
在研究中,作者展示了就单位算力成本而言,单块消费级 GPU 吞吐量优化的 T4 GPU 效率要比云上延迟优化的 8 块 A100 GPU 的效率高 4 倍。该项目宣传称一块 RTX 3090 跑 ChatGPT 体量模型,可以看出offloading技术可以很好地降低对GPU的要求。
这种方法显而易见有一个问题,就是由于GPU与CPU之间的频繁卸载加载导致了比较大overhead,从而使得其在推理延迟上的巨大劣势,因此它,比较适合对延迟不敏感的批量推理场景。
多GPU并行化(Parallelism)
为了提高模型推理的速度及降低单GPU的内存要求,可以使用通过并行化技术来解决。常见的并行策略有三种:数据并行,张量并行,流水线并行。
1)数据并行(data parallelism, DP),即每张显卡上都完整保存一个模型,将数据分割成多个batch,然后独立计算梯度,最终再将梯度值平均化,作为最终梯度。在推理过程中,本质就是独立并行推理,可增加设备数来增加系统整体吞吐。
2)张量并行,也叫模型并行(model parallelism/tensor parallelism, MP/TP):模型张量很大,一张卡放不下,将tensor分割成多块,一张卡存一块,让每个卡分别计算,最后拼接(all gather)在一起。在推理过程中,可以将计算任务拆分到多个卡中并行计算,可横向增加设备数,从而提升并行度,以加少延迟。
- 流水线并行,将网络按层切分,划分成多组,一张卡存一组。下一张显卡拿到上一张显卡计算的输出进行计算,通过纵向增加设备数量可以提高流水线的并行性,减少显卡的等待时间,从而提升设备的利用率。
通过将模型并行和流水线并行联合使用,可以支持更大的模型,并获得最佳的效果。
但需要注意的是,使用多个 GPU 并行推理并不会加快推理速度,而是可以将单个显卡无法运行的模型分片到多个显卡上运行。
高效的模型结构
顾名思义,就是通过优化模型结构的方式取得推理性能的提升,曾经google做过一个调研《Efficient Transformers: A Survey》,关于transformer结构上的改进非常繁多,在这里不一一介绍,选择几个常见具有代表性的简单说明。
如前面分析,输入序列长度越长,模型需要的内存和处理能力就越大,并且增长速度呈平方级,这也被称作 二次复杂性(quadratic complexity)。因此,优化的方向都聚焦在自注意力机制的二次复杂性和内存复杂度上,旨在提高计算和内存效率。
Flash Attention
该方法由斯坦福大学在论文提出,其核心思路就是在传统的transformer pyTorch 计算Attention时,内存(HBM) 用于存储张量(如特征映射/激活),而 SRAM 用于对这些张量执行计算操作,并没有考虑到GPU SRAM与HBM的读写速度上的巨大差距,导致对HBM及其重复读写,从而形成IO瓶颈,无法充分利用计算能力。而Flash attention提出了IO感知(IO-Awareness)的概念,会关注到这样的差异,在计算精确注意力的同时,通过实现了一个cuda kernel,将所有注意力操作(matmul、mask、softmax 等)融合到一个 GPU 内核中,在计算 softmax 时,既不计算也不存储注意力矩阵,这样就减少内存读写操作的次数。
Flash Attention实现了两个目标:(1) 加快训练推理速度,(2) 支持更长的序列(上下文)。Flash Attention在没有损失准确性的情况下,相较于Huggingface 和 Megatron-LM , 在GPT -2模型上 端到端速度分别提高了 3 倍和 1.7 倍。 由于闪存注意力计算的是精确注意力,因此这些速度提升并没有牺牲任何准确性。
Flash Attention已经被集成到 PyTorch 2.0 中,可以方便地使用。但需要关注一下GPU型号和CUDA版本。
最近 FlashAttention v2发布,对v1进行了彻底的重构,模型 FLOP 利用率高达 72%,速度是上一代的 2 倍,相较于PyTorch的标准注意力,其运行速度最高可达9倍。
Page Attention
在前面我们提到过,为了提升推理性能,避免每次采样token时重新计算键值(KV)向量,可以利用预先计算好的k值和v值,将其存储在cache中,这叫做KV cache。 但 随着 输入序列 长度越来越长, 这些 向量 所消耗的内存就会变得非常大,在 在 LLaMA-13B 中,缓存单个序列最多需要 1.7GB 显存。 另一方面, 这些KV 存储大小 取决于序列长度,这是高度可变和不可预测的 ,为了避免内存爆掉, 这就会 导 致内存过度预留 和碎片化。 这种碎片化会使内存访问变得非常低效,尤 其是对于长标记序列。 至于过度预留,系统这样做是为了确保为张量分配了足够的内存,即使并没有耗尽所有内存 , 现有系统浪费了 6 0% - 80% 的显存 。
而PagedAttention的目标就是如何在 GPU VRAM 的非连续空间中更高效地存储键值张量。算法借鉴操作系统中的虚拟内存和分页概念。与传统的注意力算法不同,PagedAttention 允许在非连续的内存空间中存储连续的键和值。它将每个序列的 KV 缓存划分为多个区块,每个区块包含固定数量标记的键和值。在注意力计算过程中,PagedAttention 内核能有效识别并获取这些块。
由于区块不再需要连续的内存,PagedAttention在管理键和值方面获得了更大的灵活性,类似于操作系统中的虚拟内存。把区块看作页面,标记看作字节,序列看作进程。序列中逻辑上连续的块通过块表映射到物理上不连续的块。随着新标记的生成,物理块会按需分配。
使用 PagedAttention 时,内存浪费只发生在序列的最后一个块。在实践中,这使得内存利用率接近最优,浪费率仅为 4%。这种内存效率的提高带来了显著的好处:如上述性能结果所示,它可以将更多的序列批处理在一起,提高 GPU 的利用率,从而提高吞吐量。 它还有一个优势 : 高效的内存共享。 例如,在并行采样过程中,会从同一Prompt生成多个输出序列。 在这种情况下,基于Prompt的计算和存储可以在输出序列之间共享。
PagedAttention 在推理过程中采样时可以共享虚拟块。该功能大大降低了并行采样和集束搜索(beam search)等复杂采样算法的内存开销,最多可减少 55% 的内存使用量。在并行任务下,实现了PagedAttention的vLLM要比HF和TGI要快得多,特别是在多输出完成的情况下,并且TGI 和 vLLM 之间的差异随着模型的增大而增大。这是因为更大的模型需要更多内存,因此受内存碎片的影响更大。vLLM 比 Hugging Face Transformers 库快 24 倍。
Continuous batch
就如上面提到的,大模型的的显存占用高是对推理性能的一个非常大的瓶颈。自然而然,如何更充分的利用已经加载到显存中的参数权重和Kvcache,让它们不用频繁换入换出。借鉴批处理的思路,让模型一次性的处理多个输入序列,这样就能有效地共享内存,提高计算的利用率。
在此基础上,进一步地降低延迟,提出了一种新的优化方法叫做连续批处理(Continuous batch),也叫做动态批处理(Dynamic Batch) 。 它 不需要等到批次中的每 个序列都完成生成,而是实施迭代级调度,每次迭代决定批次大小。 这样做的结果是,一旦批 次中的一个序列完成生成,就可以插入一个新序列,从而比静态批次产生更高的 GPU 利用率。
使用连续批处理完成七个序列。左侧显示单次迭代后的批次,右侧显示多次迭代后的批。
可以看到,上面提到的vLLM就利用了这一思路,通过连续批处理和Page Attention使得推理性能得到了很大的提升。
小结:
本节主要介绍了当前主流的推理优化方面的理论,它们从降低计算成本和显存成本的角度入手,提升推理的吞吐量和降低延迟,给出的一些系列改进尝试。
在接下来一节将会介绍当下基于这些理论方案的主流的工具和推荐使用方法,供大家参考。
未完待续...
参考:
https://lilianweng.github.io/posts/2023-01-10-inference-optimization/
https://neptune.ai/blog/optimizing-models-for-deployment-and-inference
https://zhuanlan.zhihu.com/p/557859725
https://zhuanlan.zhihu.com/p/603908668
https://betterprogramming.pub/speed-up-llm-inference-83653aa24c47
合集目录:
3)微调
4)模型部署与推理
一文探秘LLM应用开发(8)-模型部署与推理(DEMO验证)
一文探秘LLM应用开发(9)-模型部署与推理(模型适配优化之GPU面面观-1)
一文探秘LLM应用开发(10)-模型部署与推理(模型适配优化之GPU面面观-2)