大模型推理优化实践:KV cache 复用与投机采样

作者:米基

一、背景

RTP-LLM 是阿里巴巴大模型预测团队开发的大模型推理加速引擎,作为一个高性能的大模型推理解决方案,它已被广泛应用于阿里内部。该引擎与当前广泛使用的多种主流模型兼容,并通过采用高性能的 CUDA 算子来实现了如 PagedAttention 和 Continuous Batching 等多项优化措施。RTP-LLM 还支持包括多模态、LoRA、P-Tuning、以及 WeightOnly 动态量化等先进功能。

随着大模型的广泛应用,如何降低推理延迟并优化成本已成为业界关注的焦点。我们不断地在这一领域内探索和挖掘新方法。在本文中,我们将详细介绍两种在业务中实践的优化策略:多轮对话间的 KV cache 复用技术投机采样方法。我们会细致探讨这些策略的应用场景、框架实现,并分享一些实现时的关键技巧。

二、多轮对话复用 KV cache

淘宝问问中,有两类多轮对话的场景:一是问答类的场景,它每次请求模型时会拼接之前的问答;二是 LangChain 应用,它在模型生成结束后会调用外部插件,拼接插件返回的结果再次请求模型。这两类场景共同的问题是:随着对话轮数的增加,请求长度变长,导致模型的 First Token Time(下称 FTT)不断变长。

模型的 FTT 变长,本质上是因为第一次进入模型时,越来越多的 token 需要生成 KV cache。考虑到这两种多轮对话场景存在一个共同点:前一轮对话的输出构成后一轮对话输入的一部分,或者存在较长的公共前缀。且大部分自回归模型(除了 chatglm-6b)的 Attention Mask 都是下三角矩阵:即某一位置 token 的注意力与后续 token 无关,因此两轮对话公共前缀部分的 KV cache 是一致的。进而能够想到的解决办法是:保存上一轮对话产生的 KV cache,供下一轮对话时复用,就能减少下一轮需要生成 KV cache 的 token 数,从而减少 FTT。根据这个思路改进前后的模型如下:

2.1 框架设计

用户请求对应的 KV cache 存放在机器显存中,因此不同轮次的对话需要请求同一台机器,才能复用 KV cache。但是在生产环境中,模型部署在由多台机器组成的机器集群,用户层的请求由统一域名服务转发到机器集群中某一台机器上,这样的架构设计导致不同轮对话命中同一台机器的概率微乎其微。

最直观的解决办法是让用户去记录首次请求的机器信息,并将后续请求同一台机器。这个方法可行但是不合理,用户不仅需要感知机器集群的具体信息,还需要对自己链路做大量改造;进而能想到的办法是增加一层转发层,用户将多轮请求携带同样的标识 id 并发送给转发层,转发层感知集群信息并匹配标识 id 和下游机器。这样不同轮对话就能打到同一台存有 KV cache 的下游机器。至于如何在转发机器间同步匹配信息,可以使用分布式数据库记录,我们采样的方法是使用统一的哈希算法,将相同 id 哈希到固定的机器。只要选择合适的哈希算法,就能在机器集群负载均衡的同时让多轮对话命中同一台机器。

在底层实现上,复用 KV cache 的逻辑和 P-Tuning v2 在实现上非常相似,通过复用参数,我们使用 PTuning 的算子支持了 KV cache 复用。

2.2 总结与反思

我们在 Qwen13B/int8 量化/A10 机器的条件下,对不同输入和前缀长度的请求进行了测试:

可以看到在复用 KV cache 功能极大程度的减少了 FTT,并且历史长度的变化对 FTT 的影响较小,FTT 更多的取决于本次请求的输入长度。并且除了多轮对话场景外,KV cache 复用功能也扩展到复用 Ptuning 前缀和长 System Prompt 的场景,降低 FTT 和显存占用。

虽然复用 KV cache 的功能能够显著减少多轮对话场景下的 FTT,但是在服务压力过大时,存放历史 KV cache 的显存可能被新请求占用,导致后续请求出现 cache miss 请求时间变长,加剧服务压力最后导致雪崩。目前我们已实现的解决方案是使用 LRU 算法优先移除较旧请求的 KV cache。未来进一步的策略是参照 vllm 的思路,将过期的 KV cache 转移到内存,必要时重新加载至显存。这种策略比重新计算快,有助于减轻极端情况下的请求延迟,防止服务雪崩。

三、投机采样

3.1 介绍

投机采样最早在 2022 年的 Fast Inference from Transformers via Speculative Decoding 提出,因为不久前的 gpt4 泄密而被更多人知道。投机采样的设计基于两点认知:在模型推理中,token 生成的难度有差别,有部分 token 生成难度低,用小参数草稿模型(下简称小模型)也能够比较好的生成;在小批次情况下,原始模型(下简称大模型)在前向推理的主要时间在加载模型权重而非计算,因此批次数量对推理时间的影响非常小。

基于以上两点认知,投机推理的每一轮的推理变成如下步骤: 1. 使用小模型自回归的生成 N 个 token 2. 使用大模型并行验证 N 个 token 出现的概率,接受一部分或者全部 token。由于小模型推理时间远小于大模型,因此投机采样在理想的情况下能够实现数倍的推理速度提升。同时,投机采样使用了特殊的采样方法,来保证投机采样获得的 token 分布符合原模型的分布,即使用投机采样对效果是无损的。

上图是投机采样的运行过程,每一行的绿色 token 代表小模型生成并被大模型接受的部分,红色 token 是小模型生成但被大模型拒绝的部分,蓝色 token 是大模型根据最后接受 token 的 logits 重新采样出来的部分。由上可以看到使用投机采样,在合适的场景下能够大幅提高每轮生成的 token 数,降低平均单个 token 生成时间。

3.2 设计思路

我们在 RTP-LLM 中基于论文的思路,使用大小模型进行了投机采样的实践。在代码设计上我们一方面考虑系统的可维护性,希望这部分能够和原始流程解耦;其次投机采样优化需要与其他优化正交,使投机采样时两个模型都能够用上 FT 的其他优化。最后我们的设计是为投机采样封装了一层编排层,对外提供统一的 API,在内部组织参数顺序调用正常流程。

3.3 性能评估

在实现过程中,我们着重关注投机采样引入的额外负担。我们希望做到在系统每轮接受 token 数较少的情况下,也能有与原始模型相近的表现。在实践中,我们测得额外时间消耗主要有两块:小模型顺序生成 token 引入时间和采样。

首先最直观的额外消耗,就是小模型推理所占用的时间。在小模型顺序生成 N 个 token 时,会有 N*T_{small\_model}的时间,在 N 较大且接受 token 数少的情况下,这部分开销会非常大。值得一提的时最初我们假设模型消耗时间和参数规模成正比,而实际上这个猜测是错误的。我们测试得到在参数规模减少的情况下, lm_head 在模型调用的占比会显著增加。以下是 Qwen1.8B 和 Qwen13B 在 A10/half 条件下,单个 token 在 Transformer 网络(transformer_layer)和输出层(lm_head)的时间对比:

造成上述比例不一致原因在于:模型参数规模从 13B 变成 1.8B 时, Transformer 网络在层数(40 -> 24)和权重大小(5120 -> 2048)两个维度减少,而输出层的参数仅从[5120, 152064]变成[2048, 152064] 。同时因为模型词表通常很大,因此输出层的时间通常也比较长。除了输出层的影响以外,在一些情况下小模型矩阵乘对硬件的利用率并没有大模型这么高,因此在选择小模型时需要对这部分开销进行更谨慎的估计。

其次重复多次的采样也引入了巨大的开销。从上面流程图可以看到,在一轮投机采样流程中,需要进行 N 次小 batch 采样和 1 次大 batch 采样。我们以 a10/half/vocab_size=152064/top_k=0.5/top_p=0.95 的情况下用 huggingface 采样逻辑进行了测试:

假设在原始请求 batch 为 2, 投机采样每次出 5 个 token 的条件下,需要 1.15*5+1.47=7.221.15∗5+1.47=7.22 毫秒的时间,这接近上述 1.8B 小模型一次推理的总时长。

好在 FT 的采样流程针对存在 top_k 参数的情况,通过融合算子对原版(Huggingface 流程)进行了优化,改进后的流程分成两步:对维度是[batch, vocab]的输入进行 TopK 采样后,使用输出维度是[batch, k]的 tensor 进行后续流程;省略 TopP 步骤,直接在采样过程中对 TopP 进行判断。改进前后的流程对比如下:

改进后的流程不影响结果分布,且大幅度减少了计算量和 kernel 数量,极大程度减少了采样需要的时间。我们测试优化后的采样流程需要的时间是原来的 1/10。

3.4 总结

我们在店铺起名文案生成两类任务,对原模型和投机采样模型进行了性能对比。其中原模型是 int8 量化的 Qwen13B 模型,投机采样使用量化后的 Qwen13B 和 Qwen1.8B 模型,在 A10 机器测试结果如下:

在两类任务下投机采样对模型均有加速,效果随接受 token 数和输入 token 长度变化。我们测得在使用上述条件每轮生成 5 个 token 的情况下,短序列跑一轮需要 60ms,长序列跑一轮需要 70ms。而原模型跑一轮需要 30ms,因此长/短序列只有在拒绝全部 token 的情况下会劣于原模型,其他情况则是与原模型平均 token 时间相近或者优于原模型。由于测试条件限制,相比原论文的大小模型比例(70B:6B),我们大模型和小模型的规模(7B:1.8B)更接近,并且 Qwen 中文模型的词表大小是 152064,相比其他模型(如 Llama 词表大小是 32000)大了数倍,这也额外开销时间变大,因此在其他测试场景下应该还能有更好的表现。

四、存在的问题

上文我们着重介绍了两个优化对推理速度的影响,但除了推理速度外,并行度也是影响大模型吞吐的重要因素。影响并行度的主要因素是显存,大模型的显存占用分三块:模型权重占用显存、运行时显存和 KV cache 显存,KV cache 显存越多,模型能够同时承载的请求数越多,并行度越大。

序列长度较长时,对运行时显存运行最大的是 Softmax Buffer,它的大小和序列的平方成正比,知名优化 FlashAttention 除了能降低模型第一次运行时间外,更重要的作用是消除了 Softmax Buffer 对显存的占用。但比较遗憾的是,FlashAttention 优化的开源实现要求 Attention 计算的 QKV 维度一致,而 KV csache 复用和投机采样都未满足这个条件,导致对显存有额外的占用。除此之外,投机采样因为要额外加载小模型的权重,且运行时需要多保存一份小模型的 KV cache,还需要额外的显存。

五、总结与致谢

以上是我们在大模型推理上做的一些优化尝试,有根据业务场景和实际问题的,也有参考论文实现的,并且都取得了一定的加速效果。但是从极致性能的角度,我们做的还远远不算完美,这些功能在算子层和框架层都还有优化空间,这些是我们后续需要改进的。

除了上述介绍的功能外,RTP-LLM 还支持了非常多的功能,和上文相关的对 System Prompt 进行缓存的 Multi Task Prompt 复用 Medusa 投机采样,以及动态 LoRA 和不规则剪枝模型支持。未来我们也会持续的添加新功能,优化底层算子性能,打造更好的大模型推理框架。

我们的项目主要基于 FasterTransformer,并在此基础上集成了 TensorRT-LLM 的部分 kernel 实现。FasterTransformer 和 TensorRT-LLM 为我们提供了可靠的性能保障。Flash-Attention2 和 cutlass 也在我们持续的性能优化过程中提供了大量帮助。我们的 continuous batching 和 increment decoding 参考了 vllm 的实现;采样参考了 hf transformers,投机采样部分集成了 Medusa 的实现,多模态部分集成了 llava 和 qwen-vl 的实现。感谢这些项目对我们的启发和帮助。

相关资料

[01] FasterTransformer

https://github.com/NVIDIA/FasterTransformer

[02] TensorRT-LLM

https://github.com/NVIDIA/TensorRT-LLM

[03] Flash-Attention2

https://github.com/Dao-AILab/flash-attention

[04] cutlass

https://github.com/NVIDIA/cutlass

[05] vllm

https://github.com/vllm-project/vllm

[06] hf transformers

https://github.com/huggingface/transformers

[07] Medusa

https://github.com/FasterDecoding/Medusa

[08] llava

https://github.com/haotian-liu/LLaVA

[09] qwen-vl

https://github.com/QwenLM/Qwen-VL

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

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

相关文章

出租车费 C语言xdoj697

问题描述 某城市普通出租车计费标准如下: 起步里程为 3 公里,起步费 10 元; 超起步里程后 10 公里内,每公里 2 元; 超过 10 公里以上的部分,每公里加收 50%的回空补贴费; 营运过程中&#xff0c…

大数据技术之Hudi

第1章 Hudi概述 1.1 Hudi简介 Apache Hudi(Hadoop Upserts Delete and Incremental)是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取服务、数据集群/压缩优化和…

【每日小bug】mybatis plus id注解错误导致的问题

插入数据 id不为自增 指定了主键,没有指定自增。会导致出现 修改如上 报错 Data truncation: Out of range value for column ‘id’ at row 1 数据库是bigint,java中是Integer。 修改如上

Day28 17电话号码的字母组合 39组合求和 40组合求和II

17 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 因为输入的数字的数量是不确定的,所以for循环的次数也是不确定的&…

重学Java 3 变量 数据类型转换 运算符

路上难免会有许多挫折,你要学会应对,要坚不可摧 ——24.1.12 一、常量 1.概述:在代码的运行过程中,值都不会发生改变的数据 2.分类: 整数常量:所有整数,包含正负 小数常量:所有带小数…

通过 Elastic Stack 充分利用电信领域生成式 AI 的力量

作者:Elastic Piotr Kobziakowski, Jrgen Obermann 在瞬息万变的电信领域,Elastic Stack 与生成式 AI 的集成正在开创运营效率和创新的新时代。 这些技术不仅增强了网络运营,而且还彻底改变了各个部门的内部流程。 下面,我们将深入…

嵌入式必备的WEB知识

写在前面 嵌入式要学习Wed前端吗?答案是要的,不需要深入学习,只需要简单了解即可。为什么要学习? 原因如下: 可以远程控制和管理设备:通过简单的Web知识,嵌入式系统可以建立Web界面&#xff0c…

【Effective Objective - C】—— 熟悉Objective-C

【Effective Objective - C】—— 熟悉Objective-C 熟悉Objective-C1.oc的起源消息和函数的区别运行期组件和内存管理要点: 2.在类的头文件中尽量少引入其他头文件向前声明要点: 3.多使用字面量语法,少用与之等价的方法字符串字面量字面数值字…

Java--业务场景:SpringBoot 通过Redis进行IP封禁实现接口防刷

文章目录 前言具体实现步骤1. 定义自定义注解2. 编写拦截器类IpUrlLimitInterceptor3. 在WebConfig类中添加IpUrlLimitInterceptor4. 添加注解到接口上 测试效果参考文章 前言 在实际项目中,有些攻击者会使用自动化工具来频繁刷新接口,造成系统的瞬时吞…

单因素方差分析--R

任务说明 三个剂量水平的药物处理受试者,每个剂量水平十个受试者,现在收集到数据后,问: 药物剂量水平显著影响受试者的response? 或者不同剂量药物处理受试者有显著效果的差异吗? 数据 library(tidyvers…

css选择器在python中如何使用

css选择器整理:https://blog.csdn.net/qq_40910788/article/details/84842951 目标:爬取某文章网站列表: 基础代码如下: import random import time import urllib.request import redef reptileTest(url):try:my_headers [&q…

Self-Attention

前置知识:RNN,Attention机制 在一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attent…

Danswer部署指南

Quickstart How to deploy Danswer on your local machine ​ Requirements gitdocker with compose (docker version > 1.13.0) ​ Setup This quickstart guide covers setting up Danswer for local execution Clone the Danswer repo: git clone https://github.com…

Mysql 数据库ERROR 1820 (HY000): You must reset your password using ALTER USER 解决办法

Mysql 5.7数据库原来一直都能正常访问,突然访问不了,查看日志提示数据库需要修改密码, 具体解决办法如下操作: Windows 下: mysql的bin目录下, mysql>use mysql; mysql>mysql -uroot -p密码; 判…

gem5学习(14):将gem5扩展到ARM——Extending gem5 for ARM

目录 一、Downloading ARM Binaries 二、Building gem5 to run ARM Binaries 三、Modifying simple.py to run ARM Binaries 四、Running gem5 五、ARM Full System Simulation An aside on FS simulations 这个是gem5-learning中Getting Started的最后一篇文章&#xff…

imgaug库指南(20):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里,数据是模型训练的基石,其质量与数量直接影响着模型的性能。然而,获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此,数据增强技术应运而生,成为了解决这一问题的…

各版本 操作系统 对 .NET Framework 与 .NET Core 支持

有两种类型的受支持版本:长期支持 (LTS) 版本和标准期限支持 (STS) 版本。 所有版本的质量都是一样的。 唯一的区别是支持的时间长短。 LTS 版本可获得为期三年的免费支持和补丁。 STS 版本可获得 18 个月的免费支持和修补程序。 有关详细信息,请参阅 .N…

Java重修第五天—面向对象2

通过学习本篇文章可以掌握如下知识 static;设计单例;继承。 之前文章我们已经对面向对象进行了入门学习,这篇文章我们就开始深入了解面向对象设计。 static 我们定义了一个 Student类,增加姓名属性:name &#xff1…

用通俗易懂的方式讲解:内容讲解+代码案例,轻松掌握大模型应用框架 LangChain

本文介绍了 LangChain 框架,它能够将大型语言模型与其他计算或知识来源相结合,从而实现功能更加强大的应用。 接着,对LangChain的关键概念进行了详细说明,并基于该框架进行了一些案例尝试,旨在帮助读者更轻松地理解 L…

最好的 8 个解锁 Android 手机的应用程序分析

如何解锁我的 Android 手机是一个困扰全球数百万人的问题。有多种Android解锁器可用于解锁手机。用户应确保选择最好的应用程序以轻松满意地完成工作。必须注意的是,数据在解锁手机的整个过程中都是安全可靠的。此类应用程序还应该能够在所有情况下检索数据。 锁屏移…