大模型推理--KV Cache

KV Cache是大模型推理中常用到的一个技巧,可以减少重复计算,加快推理速度。不少人只是从概念上知道它可以减少重复计算,详细的原理则知之甚少,此外为啥只有KV Cache而没有Q Cache呢,我们在本博客中给出详尽的解释。我想在博客的开头先阐明KV Cache可行的根本原因,大家可以带着这句话去看后面的内容:KV Cache之所以可行,就是因为在LLM decoding阶段的Attention计算需要mask,这就会导致前面已经生成的Token不需要与后面的Token重新计算Attention,从而每个token的生成只和KV相关,与之前的Q无关,进而可以让我们通过缓存KV的方式避免重复计算。不明所以也不要紧,你只需要耐着性子往下看即可。

1. LLM推理流程

当我们给大模型提供一段prompt的时候,大模型是如何转化成我们想要的答案的呢?这里面会经历很多步骤:

  1. 输入Tokenize化。原始的输入都是文本,也有可能是多模态类型的音频、图像,我们需要将其转化成大模型能识别的token。Token可以翻译为词元,在自然语言处理中,它通常是指一个词、一个字符或者一个子词等语言单位。比如我们针对本章开头的第一句话进行tokenize会得到如下输出:“当 我们 给 大模型 提供 一段 prompt 的 时候 , 大模型 是 如何 转化 成 我们 想要 的 答案 的呢 ?”(一个可行的切分,真实场景中不一定如此)。可以看出,tokenize就相当于传统NLP任务中的分词。大模型有一个词汇表,其中包含了模型能够识别的所有token。在将文本转化为token序列时,会根据词汇表中的token进行匹配和划分。这一步的主要目的是将原始的prompt转化为token的id序列。

  2. 获取token的embedding+位置编码。这一步的操作是将token的id序列转化成embedding序列,embedding就是每个token的向量化表示。此外,在真正开始推理之前embedding中还需要加入位置编码(positional encoding)信息,这是由于transformer结构不再使用基于循环的方式建模文本输入,序列中不再有任何信息能够提示token之间的相对位置关系。具体来说,每个token在整个序列中所处的位置都会有一个位置向量,这一向量会与token的embedding向量相加。步骤1和2相当于大模型推理的前处理,将文本或者多模态输入变成一个N x d的矩阵正式进入后续大模型的推理阶段。
    在这里插入图片描述
    图1 大模型推理的两个阶段prefilling和decoding

  3. 大模型推理一般包括两个阶段prefilling和decoding,如图1所示(from paper:A Survey on Efficient Inference for Large Language Models)。Prefilling阶段就是将步骤2中生成的N x d矩阵送到大模型中做一遍推理。N就是prompt分词之后的长度,也即上下文的长度。N通常情况下会比较大,当前的大模型普遍可以支持128K的上下文。因为要一次性完成整个prompt对应token的计算,所以prefilling阶段的计算量很大,但它是一次性的过程,所以在整个推理中只占不到 10% 的时间。更核心的是要解决Attention计算过程中显存占用过大的问题,业界的主流的方案就是FlashAttention,可以参考我之前写的博客《大模型推理–FlashAttention》。大模型的最后一层往往是softmax,用来输出每个token的概率,从中选择概率最大的作为大模型的第一个输出token。事实上,如果不考虑速度因素,单纯利用prefilling也可以完成整个大模型的推理过程。每完成一次大模型推理我们就可以获得一个token,将其与prompt和之前输出的所有token组合重新开始新一轮的大模型推理,直到输出停止符为止。不过我们很容易看出这种推理模式包含非常多的重复计算,每增加一个token,prompt部分的推理就会重复一次。但是这些重复计算是没有意义的,所以才会将大模型推理拆分成了prefilling+decoding两阶段,目的就是要在prefilling阶段计算KV Cache供decoding阶段使用,当然prefilling也会生成第一个输出token。

  4. decoding阶段就是自回归输出每个token的过程。这里首先要强调一点,prefilling和decoding用的都是相同的大模型,只是推理方式不同:prefilling阶段一次性灌入整个prompt,而decoding阶段则是一次灌入一个token。所以prefilling阶段主要的操作是gemm(矩阵乘),到了decoding阶段就变成了gemv(矩阵向量乘)。正是由于 decoding阶段是逐个token生成的,每一次回答都会生成很多token,所以decoding阶段的数量非常多,占到整个推理过程的90%以上。decoding阶段就是要利用prefilling阶段生成的KV Cache来避免重复计算,这里的核心点在于mask机制保证了transformer每一个block中的Attention计算可以实现逐行运算。

将prefilling阶段输出的第一个token加decoding阶段所有的输出token组合起来即可以转换为最终的输出。这里不少人可能会有疑问,按照上面的步骤完成推理之后貌似生成的结果是固定的,但是为什么我们实际在使用大模型的时候同样的输入每次输出也不同呢?这里面涉及到了大模型采样参数的问题。在prefilling阶段我们提到,可以选择softmax层中概率最大的token作为输出,这可以看做是一种贪婪解码,我们可以完全不选择top1作为输出,而是引入更多采样策略让生成的结果更加多样化,比如从topk中随机选择一个,又或者引入beam-search之类的解码策略,更完整的介绍大家可以参考:《优化采样参数提升大语言模型响应质量:深入分析温度、top_p、top_k和min_p的随机解码策略》。

2. 单层transformer推理过程

给定一个输入 X X X,维度为N x d(忽略batch维),我们将其送入图1左侧所示的大模型一个block里面,看看具体的执行过程。如前所述,N表示prompt上下文长度,可以很长,目前大模型普遍可以支持到128K甚至更长。d表示embedding的维度,通常情况根据大模型的尺寸不同从4K到12K不等。输入X会先送入三个projection矩阵 W Q , W K , W V W_Q,W_K,W_V WQWKWV进行线性变换:
Q = X ∗ W Q \begin{equation} Q=X*W_Q \end{equation} Q=XWQ K = X ∗ W K \begin{equation} K=X*W_K \end{equation} K=XWK V = X ∗ W V \begin{equation} V=X*W_V \end{equation} V=XWV这三个矩阵的维度一般相等,通常都是d x d,所以输出维度还是N x d。上述三个公式都是 X X X乘以 W W W,所以 X X X增加一行对应的结果 Q 、 K 、 V Q、K、V QKV也会增加一行。变换完成之后生成的 K 、 V K、V KV即是我们要详细介绍的KV Cache。从这三个公式也可以看出,如果单纯采用prefilling完成大模型推理,虽然可以获得正确结果,但是 X X X在prompt部分的推理一直在重复。单单看这一步,我们确实可以分成prefilling和decoding两阶段,prefilling对完整的prompt进行推理,后续的自回归解码就可以逐行推理,因为每一行的计算都不会对之前的行产生影响。
上面的三个矩阵乘只是block的第一步,还没有用到完整的 Q 、 K 、 V Q、K、V QKV,更关键的是下一步Attention的计算。Attention的计算按照公式: O = A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d ) V \begin{equation} O=Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt d})V \end{equation} O=Attention(Q,K,V)=softmax(d QKT)V完成,在这一步中完整的 Q 、 K 、 V Q、K、V QKV都会参与运算,如图2所示。
在这里插入图片描述
从上图可以看出,Attention在计算过程中会生成一个N x N的临时矩阵,这个矩阵在N很大时会占用大量空间,FlashAttention就是为了避免生成这个临时矩阵而设计的。Attention计算的输入是三个N x d的矩阵 Q 、 K 、 V Q、K、V QKV,输出还是N x d的矩阵 O O O,到这里我们就会迫切想知道如果我们给输入增加一行变为(N+1) x d,输出的变化是怎样的。如果输出只是在旧的 O O O基础上新增一行,那就有机会实现逐行计算;如果会导致 O O O每一行都发生变化,则逐行计算就不可能实现。幸运的是,由于Mask机制的存在,大模型decoding阶段Attention计算的输出 O O O确实是逐行更新的,不会对之前行的结果进行修改。这也就确保了transformer一个block内Attention部分逐行计算的可行性。
Attention之后还包括Add、LayerNorm、Activation、FeedForward等运算,这些运算从原理上来说都非常容易实现逐行计算。所以放眼transformer整个block,只要解决了Attention部分的逐行计算,整个block就可以逐行计算,进而整个transformer大模型就都可以实现逐行计算,如此大模型的decoding阶段就可以按照自回归的方式逐个token逐个token的生成了。

3. Attention逐行计算的原理

图2展示的算是prefilling阶段的Attention计算,输入就是所有的token,我们将decoding阶段包含进来,在图2的基础上增加一行,看看会出现什么变化。
在这里插入图片描述
图 3 展示的相当于把第一次做 decoding 的 Attention 运算利用 prefilling 来实现的例子。Prefilling 阶段已经对 N x d 的输入完成了推理,并生成了第一个 token,我们将这一个 token和之前的 N 个 token 组合在一起继续进行推理。图 3 中我添加了很多问号,用来表示如果不添加限制,就会导致中间临时矩阵每一行之前计算的 softmax 不再正确,需要重新计算,进而导致后续乘以 V V V 时得到最终的输出 O O O 每一个位置的值都发生了变化。这种情况下就说明,新增一个 token 就会导致 Attention 计算输出 O O O 的完全改变,完全无法实现逐行计算。
但是我们真实使用的Attention是带mask的Attention,所以就不会出现上面所述的可能。具体来说,前面已经生成的token不需要与后面的token产生Attention,也即 Q Q Q前N行无需和 K T K^T KT的第N+1列进行计算,这样就会导致中间临时矩阵最后一列的前N行的值其实为0(mask操作一般在softmax之前,会将最后一列的前N行变为负无穷,等价于softmax之后的0),从而也就不会影响之前计算的softmax。中间临时矩阵前N行的值没有改变,在与 V V V相乘时也就不会导致 O O O的前N行发生变化,这就相当于 O O O只是在末尾增加了一行,如图4所示。当然不止第N+1行的计算如此,在Mask的影响下,后续N+2,N+3等等都是如此。
在这里插入图片描述
所以我们可以得到一个结论:在完成prefilling阶段之后,对N+1个输入再次进行prefilling只会产生第N+1行一个新的输出,旧的输出都不会改变。这就启发我们只对第N+1行的输入进行计算,这样很自然地就引入了decoding。我们将图4进行简化,只灌入一行 Q Q Q得到图5。
在这里插入图片描述
从图5我们又很清晰地看到,为了计算 Q Q Q的Attention值,我们需要完整的 K K K V V V,这也就是我们本博客要介绍的KV Cache!我们做一个总结:大模型自回归的机制使得单纯的prefilling推理存在大量的重复计算,带mask的Attention又使我们可以将重复计算去掉,为了实现逐行的Attention计算我们又必须要保留完整的 K K K V V V,这也就是KV Cache的基本原理。也正是因为Mask的原因,我们无需保留完整的 Q Q Q,只需要每来一个 Q Q Q进行逐行计算即可,这也是为啥存在KV Cache但是没有Q Cache的原因。希望通过本博客的介绍大家可以真正搞懂KV Cache。

4. 参考

  1. 大模型推理优化技术-KV Cache

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

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

相关文章

一些硬件知识(十八)

两个信号PIN之间串接电阻的作用: 1.阻抗匹配 2.吸收反射 3.防止程序异常导致两个IO都是输出的时候短路 尤其针对下图中的信号: 清理穿越机电机中的灰尘,可以用密封胶泥的办法: 一定要小心垫片的掉落: 20块左右的快充充…

游泳馆押金管理+手牌管理+刷手牌 开通方法

一、游泳馆手牌押金管理 1. 减少手牌丢失:收取押金可以让顾客更加谨慎地保管手牌,降低手牌丢失的概率。 2. 保障设施安全:有助于防止顾客对手牌的不当使用或故意破坏,保护游泳馆的设施和资源。 3. 规范顾客行为:促使…

SLM561A​​系列 60V 10mA到50mA线性恒流LED驱动芯片 为智能家居照明注入新活力

SLM561A系列选型参考: SLM561A10ae-7G SOD123 SLM561A15ae-7G SOD123 SLM561A20ae-7G SOD123 SLM561A25ae-7G SOD123 SLM561A30ae-7G SOD123 SLM561A35ae-7G SOD123 SLM561A40ae-7G SOD123 SLM561A45ae-7G SOD123 SLM561A50ae-7G SOD123 …

Pr下载安装教程2024(Adobe Premiere 2024)最新版分享百度网盘链接地址

提示:主要讲述了软件安装及初步使用流程。Pr下载安装教程2024最新版分享百度网盘链接地址首先,解压文件夹后,双击安装包进行安装,选择简体中文并确认安装位置,可按需更改。随后,点击继续等待安装完成并启动…

jmeter依赖jar包找不到类路径

这两天我在纠结这个问题,为啥我maven打包放在jmeter路径下后,jmeter的bean Shell 就是找不到这个类。 纠结很久解开了。我记录下,留给后来的朋友。 Error invoking bsh method: eval Sourced file: inline evaluation of: import org.apache…

python、C++、rust速度比较

TIobe指数依据向主要搜索引擎提交编程语言名称时返回的网页数量来衡量编程语言的流行程度。该指数每月更新一次,并提供了自2002年以来的历史数据。 其官网是https://www.tiobe.com/tiobe-index/ 有意思的事情来了,看下图。 这是编程语言排名的tiobe网站…

两数之和--力扣1

两数之和 题目思路C代码 题目 思路 根据题目要求,元素不能重复且不需要排序,我们这里使用哈希表unordered_map。注意题目说了只对应一种答案。 所以我们在循环中,使用目标值减去当前循环的nums[i],得到差值,如果我们…

LabVIEW如何确保采集卡稳定运行

在LabVIEW开发中,如何确保硬件采集卡稳定运行,特别是长期采集电压信号,是系统稳定性的重要考虑因素。用户在使用采集卡时,可能需要频繁进行开始、停止和重新采集的操作,这对硬件和软件提出了高要求。下面介绍实现长期稳…

第十五届蓝桥杯图形化省赛题目及解析

第十五届蓝桥杯图形化省赛题目及解析 一. 单选题 1. 运行以下程序&#xff0c;角色会说( )? A、29 B、31 C、33 D、35 正确答案&#xff1a;C 答案解析&#xff1a; 重复执行直到m>n不成立&#xff0c;即重复执行直到m<n。所有当m小于或者 等于n时&…

vsftpd配置用户和密码让其他客户端连接

一、第一个主机:vsftpd下载及配置 前置准备: #卸载防火墙 yum -y remove firewalld #为了不让防火墙有影响&#xff0c;iptables配置也清空 iptables -F vim /etc/selinux/conf SELINUXdisabled #主要是把它改为disabled或者permissive SELINUXTYPEtargeted #重启linux让seli…

C语言深入理解指针4

1.回调函数 回调函数是通过函数指针调用的函数 将函数指针作为参数传递给另一个函数&#xff0c;当这个函数指针被用来调用其所指向的函数时&#xff0c;被调用的函数就是回调函数&#xff0c;回调函数不是应该由该函数的实现方直接调用&#xff0c;而是在特定的事件或条件发生…

MATLAB-基于高斯过程回归GPR的数据回归预测

目录 目录 1 介绍 1. 1 高斯过程的基本概念 1.2 核函数&#xff08;协方差函数&#xff09; 1.3 GPR 的优点 1.4. GPR 的局限 2 运行结果 3 核心代码 1 介绍 高斯过程回归&#xff08;Gaussian Process Regression, GPR&#xff09;是一种强大的非参数贝叶斯方法&…

CAN总线的位同步详细讲解

接收方数据采样 &#xff08;1&#xff09;CAN总线没有时钟线&#xff0c;总线上的所有设备通过约定波特率的方式确定每一个数据位的时长 &#xff08;2&#xff09;发送方以约定的位时长每隔固定时间输出一个数据位 &#xff08;3&#xff09;接收方以约定的位时长每隔固定…

C++入门基础篇

引言 说到编程语言常常听到的就是C语言C Java 。C语言是面向过程的&#xff0c;C是和Java是面向对象的&#xff0c;那么什么是面向对象呢&#xff1f;什么又是面向过程呢&#xff1f;C是什么&#xff1f;封装、继承、多态是什么&#xff1f;且听我絮絮叨叨。 C入门基础 1.命名…

24-9-8-读书笔记(十七)-《契诃夫文集》(三)([俄] 契诃夫 [译] 汝龙 )

文章目录 《契诃夫文集》&#xff08;三&#xff09;&#xff08;[俄] 契诃夫 [译] 汝龙 &#xff09;目录阅读笔记记录总结 《契诃夫文集》&#xff08;三&#xff09;&#xff08;[俄] 契诃夫 [译] 汝龙 &#xff09; 9月&#xff0c;好月份&#xff0c;秋高气爽&#xff0c;…

maven 编译构建可以执行的jar包

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

SQL语句中in条件超过1000怎么办?

博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 引言 当遇到SQL语句中IN条件超过1000个的情况时&#xff0c;可以采取以下几种策略来有效处理这一问题&#xff1a; 使用临时表&#xff1a;将IN列表中的值存储在临时表中&#xff0c;并将该临时表与查询表进行J…

初识redis(String,Hash,List,Set,SortedSet)

认识NoSql sql关系型数据库 nosql非关系型数据库 nosql具有非结构化&#xff0c;Key/Value&#xff0c;Document&#xff0c;Draph 无关联的&#xff0c;非sql&#xff0c;BASE&#xff08;原子性&#xff0c;持久性&#xff0c;一致性&#xff0c;隔离性&#xff09; 认识r…

数组与贪心算法——179、56、57、228(2简2中)

179. 最大数&#xff08;简单&#xff09; 给定一组非负整数 nums&#xff0c;重新排列每个数的顺序&#xff08;每个数不可拆分&#xff09;使之组成一个最大的整数。 注意&#xff1a;输出结果可能非常大&#xff0c;所以你需要返回一个字符串而不是整数。 解法一、自定义比较…

Linux-RPM与YUM

目录 前言&#xff1a; rpm包的管理 rpm包的简单查询指令 ​编辑 rpm包名的基本格式 rpm包名基本格式 ​编辑 卸载rpm包 细节问题 安装rpm包 yum yum的基本指令 安装指定的yum包 yum报错 问题描述&#xff1a; 解决方法&#xff1a; 前言&#xff1a; Linux操…