Author: ninehills
Labels: blog
Created: 2023-12-19T03:10:54Z
Link and comments: https://github.com/ninehills/blog/issues/107
原文及评测脚本发布于 repo
参考 illustrated-gpt2 这篇文章,自回归的大语言模型的推理分为两个步骤:
重复这两个步骤直到生成 EOS token 或达到用户设定的停止条件(stop token 或最大 token 数)。
让我们以 Llama2-7B(4096 序列长度,float16精度)为例,计算一下 batch_size = 1的理想推理速度。
想要优化 LLM 推理,首先要了解 LLM 推理的核心指标。
我们将 LLM 应用分为两种:
而实际在某种应用(如在线流式应用),我们也应该在Latency 和 Throughput 之间进行权衡,提高 Throughtput 可以提高单个 GPU 承担的并发数,从而降低推理成本。
LLM推理的过程是一个自回归的过程,也就是说前i次的token会作为第i+1次的预测数据送入模型,拿到第i+1次的推理token。
在这个过程中Transformer会执行自注意力操作,为此需要给当前序列中的每个项目(无论是prompt/context还是生成的token)提取键值(kv)向量。这些向量存储在一个矩阵中,通常被称为kv cache。kv cache是为了避免每次采样token时重新计算键值向量。
对最大长度是 4096 的 LLaMa2-7B fp16 模型,服务端每创建 1 个并发,都需要大约 2GB 显存保存 kv_cache,即便是 A100 80G,能并发服务的用户也非常有限。
计算公式:
KV 缓存的总大小(以字节为单位)= (batch_size) * (sequence_length) * 2 * (num_layers) * (hidden_size) * sizeof(FP16)
所以KV-Cache 的减少以及优化是提高 LLM 推理性能的关键。
将 LLM 托管到现代 GPU 时,计算能力一般不是瓶颈,显存带宽才是瓶颈。一般的衡量指标是 MBU(模型带宽利用率,Model Bandwidth Utilization)。
MBU 定义为(实际内存带宽)/(峰值内存带宽),而(实际内存带宽) = (总模型参数大小 + KV 缓存大小) / TPOT。举个例子,假如 7B fp16 模型的 TPOT 是 14ms,那么它就需要在 14ms 内把 14GB 参数从显存加载到计算单元,也就是 1TB/s 的带宽使用量。假设显卡的峰值带宽为 2TB/s,那么 MBU = 0.5,即显存带宽利用率是 50%。
下文介绍目前业界在生产环境下常用的优化技术。(学术上还有更多的优化技术,但是可能不够成熟,或者不适合生产环境)
通过转换工具,将神经网络中的部分层进行融合,从而降低计算量和数据量,提高推理性能。
常见的两个框架是 FasterTransformer(TensorRT) 和 Deepseed Inference,都可以通过转换的方式,融合 Transformer 的特定层。
图:Deepspeed 层融合示意图
量化除了降低模型需要的显存外,最直接的收益就是降低了带宽使用率,所以从理论上来说,量化后的模型性能应该是成比例提升的,这个提升不仅体现在吞吐量上,也会体现在 Latency 上。量化分类:
以目前比较流行的 AWQ 量化为例,其原理如下:
相比于传统的直接量化方法,通过激活感知,如果保留了 1% 的显著权重不量化,可以大幅降低量化带来的模型性能损失。但是硬件对混合精度支持不好,AWQ 使用了逐通道缩放因子来控制不同通道的缩放因子,对显著权重进行保护。
目前生产上量化精度主要有 8bit 和 4bit 两种,也有 3-6bit 不等的量化精度。一般来说,8bit 对模型效果的影响很小,4bit 则需要进行效果和性能的权衡。
至于其他两种模型压缩方法(稀疏和蒸馏),有相关文章。但在实际的大模型推理中,因为对模型的通用能力影响较大,所以一般不会使用。
生产上可以应用稀疏微调,即在特定的任务上,对模型进行稀疏微调。稀疏后的模型虽然通用能力下降,但是在特定的任务上,能力可以不变甚至提升。就是将通用模型又转换为特定任务模型。paper
具体参考 FlashAttention介绍。
FlashAttention 就是通过利用 GPU 硬件中的特殊设计,针对全局内存和共享存储的 I/O 速 度的不同,尽可能的避免 HBM 中读取或写入注意力矩阵。FlashAttention 目标是尽可能高效地使 用 SRAM 来加快计算速度,避免从全局内存中读取和写入注意力矩阵。
随着序列长度和并发数的增加,KV-Cache 的大小等比例提升,成为核心瓶颈。目前有如下几种优化方法:
KV Cache 的大小取决于序列长度,这是高度可变和不可预测的。实际上因为显存碎片和过度预留,浪费了 60% - 80% 的显存。PageAttention 参考虚拟内存和内存分页的思想,解决显存碎片化的问题,提高显存利用率,从而提升推理性能。同时 PageAttention 具备 Copy-on-Write 的特性,可以对相同的 Prompt 共享 KV Cache,从而节省显存。
PagedAttention 是 vLLM 的核心技术,在生产环境中一般都有 4x 以上的性能提升。具体技术请参见 vLLM Blog。
与传统的注意力算法不同,PagedAttention 允许在不连续的内存空间中存储连续的键和值。具体来说,PagedAttention 将每个序列的 KV 缓存划分为多个块,每个块包含固定数量的令牌的键和值。在注意力计算过程中,PagedAttention 内核会有效地识别和获取这些块。由于块不需要在内存中是连续的,因此我们可以像在操作系统的虚拟内存中一样以更灵活的方式管理键和值:可以将块视为页面,将 token 视为字节,将序列视为进程。序列的连续逻辑块通过块表映射到非连续的物理块。随着新 token 的生成,物理区块会按需分配。
在 lmdeploy 中,提出对 KV—Cache 进行 Int8量化,从而将 KV-Cache 的大小缩小30%-40%。
MQA,GQA等。一般都需要在模型架构中采用,或者通过训练后增加。此处不做讨论。
并行化可以有效利用多个设备进行推理,从而提升吞吐,降低延迟。
可以看到随着设备的增加,首 Token 延迟是降低的,但是并不是线性降低,这是因为引入了显卡通信开销。
本文所说的推理后端指的是具体的推理实现,比如 Transformers 等。决定了模型的性能。而推理框架一般兼容多种推理后端,决定了模型的部署方式。
具体评测结果参见(持续更新):https://github.com/ninehills/llm-inference-benchmark。
摘要内容(2023.12.19):
Framework | Producibility** | Docker Image | API Server | OpenAI API Server | WebUI | Multi Models** | Multi-node | Backends | Embedding Model |
---|---|---|---|---|---|---|---|---|---|
text-generation-webui | Low | Yes | Yes | Yes | Yes | No | No | Transformers/llama.cpp/ExLlama/ExLlamaV2/AutoGPTQ/AutoAWQ/GPTQ-for-LLaMa/CTransformers | No |
OpenLLM | High | Yes | Yes | Yes | No | With BentoML | With BentoML | Transformers(int8,int4,gptq), vLLM(awq/squeezellm), TensorRT | No |
vLLM* | High | Yes | Yes | Yes | No | No | Yes(With Ray) | vLLM | No |
Xinference | High | Yes | Yes | Yes | Yes | Yes | Yes | Transformers/vLLM/TensorRT/GGML | Yes |
TGI*** | Medium | Yes | Yes | No | No | No | No | Transformers/AutoGPTQ/AWQ/EETP/vLLM/ExLlama/ExLlamaV2 | No |
ScaleLLM | Medium | Yes | Yes | Yes | Yes | No | No | Transformers/AutoGPTQ/AWQ/vLLM/ExLlama/ExLlamaV2 | No |
FastChat | High | Yes | Yes | Yes | Yes | Yes | Yes | Transformers/AutoGPTQ/AWQ/vLLM/ExLlama/ExLlamaV2 | Yes |
Backend | Device | Compatibility** | PEFT Adapters* | Quatisation | Batching | Distributed Inference | Streaming |
---|---|---|---|---|---|---|---|
Transformers | GPU | High | Yes | bitsandbytes(int8/int4), AutoGPTQ(gptq), AutoAWQ(awq) | Yes | accelerate | Yes |
vLLM | GPU | High | No | awq/squeezellm | Yes | Yes | Yes |
ExLlamaV2 | GPU/CPU | Low | No | GPTQ | Yes | Yes | Yes |
TensorRT | GPU | Medium | No | some models | Yes | Yes | Yes |
Candle | GPU/CPU | Low | No | No | Yes | Yes | Yes |
CTranslate2 | GPU | Low | No | Yes | Yes | Yes | Yes |
TGI | GPU | Medium | Yes | awq/eetq/gptq/bitsandbytes | Yes | Yes | Yes |
llama-cpp*** | GPU/CPU | High | No | GGUF/GPTQ | Yes | No | Yes |
lmdeploy | GPU | Medium | No | AWQ | Yes | Yes | Yes |
Deepspeed-FastGen | GPU | Low | No | No | Yes | Yes | Yes |
Backend | TPS@4 | QPS@4 | TPS@1 | QPS@1 | FTL@1 |
---|---|---|---|---|---|
text-generation-webui Transformer | 40.39 | 0.15 | 41.47 | 0.21 | 344.61 |
text-generation-webui Transformer with flash-attention-2 | 58.30 | 0.21 | 43.52 | 0.21 | 341.39 |
text-generation-webui ExllamaV2 | 69.09 | 0.26 | 50.71 | 0.27 | 564.80 |
OpenLLM PyTorch | 60.79 | 0.22 | 44.73 | 0.21 | 514.55 |
TGI | 192.58 | 0.90 | 59.68 | 0.28 | 82.72 |
vLLM | 222.63 | 1.08 | 62.69 | 0.30 | 95.43 |
lmdeploy | 236.03 | 1.15 | 67.86 | 0.33 | 76.81 |
bs=4
indicates the batch size is 4.References: