文章目录
导言
本文介绍了大型语言模型推理的各个方面。它首先解释了推理的基本概念,如预填充和解码阶段、在线和离线推理、基础(Grounding)等。然后详细讨论了推理性能的关键指标,包括延迟、吞吐量、第一个Token的时间(TTFT)和每个输出Token的时间(TPOT)。后面还深入探讨了模型内存使用情况,特别是KV Cache的重要性和计算方法。
参考英文资料链接:https://github.com/stas00/ml-engineering
大模型推理
预填充和解码
由于提示的所有token都是已知的,那么一次处理完整的提示长度(类似于训练)并缓存中间状态(KV缓存),可以让即使是1k的提示也可以在足够的内存下非常快地处理,并且几乎不会增加延迟。代价是会增加显存。
预填充的概念
在讨论大模型推理过程中的预填充阶段之前,我们首先需要了解“预填充”的概念。预填充通常指的是在进行序列处理任务(如自然语言处理中的文本生成、机器翻译等)时,将输入序列的一部分或全部提前加载到模型中,作为后续计算的基础。这个步骤对于确保模型能够正确理解和处理输入数据至关重要。
预填充阶段发生了什么
在大模型推理的上下文中,预填充阶段主要涉及以下几个方面:
-
输入准备:将待处理的数据(如一段文本)转换成模型可以理解的形式(通常是数值向量Token)。这包括对输入文本进行分词、编码等操作。
-
状态初始化:为模型创建一个初始状态,该状态可能包含一些默认值或是根据特定算法生成的起始点。这一步骤对于那些依赖于先前信息(例如循环神经网络RNN及其变体或者是GPT)的模型尤为重要。
-
加载到模型:将准备好的输入数据加载到模型中,开始执行前向传播过程。在此过程中,模型会基于当前输入和其内部状态来更新其权重和激活函数的输出。
预填充的作用与意义
-
提高效率:通过预先准备好输入数据并将其加载到模型中,可以减少实际推理过程中的计算负担,从而加快整个处理流程。
-
增强准确性:预填充允许模型更好地理解输入数据的整体结构和上下文信息,这对于需要长距离依赖的任务特别重要,比如文档级别的文本分析。
-
支持复杂任务:在执行复杂的序列预测任务时,预填充提供了一个框架,使得模型能够在已知信息的基础上逐步构建出完整的响应或预测结果。
解码的概念
解码:新token的生成是基于所有先前的token(提示和迄今为止生成的任何新token)一次生成一个新token(回归方法)。因此,与预填充不同,这一阶段对生成的延迟贡献最大,因为解码无法并行化。
在线推理与离线推理
当用户实时发送查询时 - 这是在线推理,也称为部署。示例:聊天机器人、搜索引擎、通用REST API。在这种情况下,通常会运行一个推理服务器,并且可能有各种客户端连接到它。
当你有一个包含提示的文件需要进行推理时 - 这是离线推理。示例:基准评估、合成数据生成。在这种情况下,通常不需要推理服务器,推理直接在发送查询的同一程序中运行(客户端和服务器在一个应用程序中)。
基础Grounding
这是为预训练模型提供在训练期间不可用的额外信息的过程。例如,输入基础任务(input-grounded-tasks)在提示中为模型提供了大量额外信息。非零样本提示在示例中为模型提供基础,改变了默认的模型行为。提示工程的全部内容是使模型在推理期间以某种特定方式来推理。
检索增强生成(RAG)是为模型提供基础的主要技术之一,因为它为推理过程提供了与提示相关的额外数据。目的是使模型比其训练时的大量压缩信息更重视这些额外信息。
微调到不同的知识领域是另一种基础方法,我们更新模型,使其在一个新的数据集上有基础,这个数据集可能与基础模型训练的原始数据领域完全不同。
基础可以被认为是提供上下文。正如任何人都可以证明的那样,当一个人理解问题的上下文时,回答问题会更容易。模型生成也是如此。上下文越好,生成的输出就越相关。
在多模态使用情况下,图像或视频与文本提示一起提供可以作为基础或上下文。
任务(Tasks)
输入基础任务(Input-grounded tasks)
输入基础任务是那些生成响应主要来自提示的任务,即主要知识包含在提示中。这些包括:
- 翻译
- 摘要
- 文档问答
- 多轮对话
- 代码编辑
- 语音识别(音频转录)
批处理(Batching)
一次处理一个token的解码阶段对加速器来说是非常低效的。将多个查询一起批处理可以提高加速器的利用率,并使一次处理多个请求成为可能。
批处理的最大可能大小取决于在加载模型权重和填充KV缓存后剩余的内存量。
静态批处理(Static batching)
这是最简单直接的批处理方式,前N个查询一起批处理 - 问题在于,如果许多查询已经完成生成,它们将不得不等待最长的查询完成,然后才能返回给调用者 - 大大增加了延迟。
连续批处理或运行中的批处理(Continuous Batching or In-flight batching)
连续批处理或飞行中的批处理是一个过程,在这个过程中,生成引擎在生成完成后立即删除完成的查询,并用新查询替换它们,而不等待整个批处理完成。因此,批处理中位置0的序列可能正在生成其第10个token,而批处理中位置1的序列可能刚刚开始其第一个token生成,位置3的序列正在生成其最后一个token。
这提高了响应时间,当然,如果所有计算都忙于处理,并且没有新的空闲位置,那么一些请求将不得不等待计算开始处理它们。
分页注意力(Paged Attention)
分页注意力是推理服务器中非常流行的技术,因为它允许非常高效地利用加速器内存,通过接近加速器内存的方式使用分页,从而允许动态内存分配并防止内存碎片。
分页注意力的概念
分页注意力(Paged Attention) 是一种优化技术,主要用于处理大型模型中的长序列数据推理问题。在传统的Transformer架构中,自注意力机制需要对输入序列的所有元素进行配对比较以计算注意力分数,这对于非常长的序列来说,计算和内存开销都非常巨大。
作用
- 降低计算成本:通过将输入序列分割成多个较短的段落或“页”,可以显著减少每次计算所需的资源。
- 管理内存使用:对于超长序列,直接处理可能导致内存溢出错误。分页注意力允许模型按需加载和处理数据,有效控制内存占用。
分页注意力的过程
-
分割阶段:原始输入序列被分成若干个子序列或“页”。这一步骤有助于限制每次注意力计算所涉及的数据量。
-
局部计算:针对每个“页”单独执行标准的注意力机制计算。这意味着每一页内的元素之间会计算相互之间的注意力权重,但不同页间的直接交互在此步骤中是有限的或是不存在的。
-
跨页连接:为了补偿由于分割而丢失的全局信息,不同的实现可能会采用各种策略来维持或重建跨页的信息流。例如,某些方法可能包括相邻页之间的重叠区域,或者是利用额外的机制如缓存来存储关键信息以便后续页面使用。
-
整合输出:最后,所有单独计算的结果会被重新组合起来形成最终的输出。这个过程需要谨慎设计,确保从各个部分得到的信息能够无缝地集成在一起,同时尽可能保持原始未分割序列的特性。
通过这种方式,分页注意力能够在不过度牺牲性能的前提下,扩展模型处理更长序列的能力,使得大模型在实际应用中更加实用和高效。
解码方法(Decoding methods)
主要的解码方法有:贪心解码、束搜索和采样。
贪心解码(Greedy decoding)
贪心解码是模型总是选择概率最高的token。这是最快的解码方法,但它不一定生成最好的结果,因为它可能会选择一个不太理想的token路径,并错过一个很好的未来token序列。
贪心解码的主要问题是创建循环,即相同的句子被一遍又一遍地重复。
定义:
贪心解码是一种用于序列生成任务的简单策略,常见于自然语言处理(NLP)中的文本生成模型。它通过每一步选择当前时间步下概率最大的单词来逐步构建输出序列。
作用意义:
- 简单高效: 由于其简单的决策过程,贪心解码计算成本低且易于实现。
- 实时应用: 在需要快速响应的应用场景中,如即时翻译或对话系统,贪心解码因其效率而受到青睐。
贪心解码的过程
- 初始化: 从输入序列开始,通常是一个句子或段落,编码为向量表示。
- 第一步预测: 使用训练好的模型对输入进行第一次预测,得到第一个词的概率分布。
- 选择最大概率词: 从这一步的概率分布中选择具有最高概率的词作为输出序列的第一个词。
- 迭代更新: 将已选择的词加入到部分完成的输出序列中,并使用这个新的序列作为下一步的输入。
- 重复步骤2-4: 直到达到预定的最大长度或者生成了一个结束标记。
贪心解码的缺点
在每次迭代中,模型基于迄今为止生成的所有词来预测下一个词。然而,因为每次都只选择最可能的词,这种方法忽略了其他潜在的高质量序列。这意味着:
- 局部最优解: 贪心解码倾向于找到局部而非全局的最优解,可能导致最终生成的序列质量不如那些考虑了更多可能性的方法,如集束搜索(Beam Search)。
- 缺乏多样性: 输出往往较为单一,难以产生多样化的结果,限制了创造性内容的生成。
束搜索(Beam search)
束搜索通过同时生成多个输出克服了贪心解码的限制,因此,在每个新token上,它遵循概率最高的3个输出(束大小为3),然后丢弃所有其它路径(除了前三个路径,子路径就是有3*3条了),这些子路径在链中的所有token的总概率最高。最后,选择概率最高的所有token的路径。
这种方法比贪心解码慢,因为它必须生成n倍多的token,并且需要n倍多的内存。
采样(Sampling)
采样引入随机性。
但是,当然,选择随机词不会产生好的结果,所以我们仍然想要贪心解码的确定性,但通过向其添加受控的随机性使其更有趣/更生动。
最常见的采样方法是:Top-K 和 Top-P
在大模型推理中,尤其是生成式模型如语言模型的文本生成过程中,TOP-K采样和TOP-P(也称为核采样或Nucleus Sampling)是两种常用的技术,用来提高生成文本的质量和多样性。
(1)TOP-K 采样
概念:
- TOP-K采样是一种选择最有可能的前K个词作为候选集合,然后从这个集合中随机选取一个词进行输出的方法。这里的K是一个预先设定的数值。
- 这种方法通过限制候选词汇的数量来减少低概率词被选中的机会,从而增加生成文本的相关性和流畅度。
实现步骤:
- 计算每个可能词的概率分布。
- 选择概率最高的前K个词形成候选集。
- 根据这些词的概率重新归一化,使得它们的总和为1。
- 从这K个词中随机选择一个词作为输出。
(2)TOP-P (Nucleus Sampling)
概念:
- TOP-P采样不同于固定数量的候选词,它根据累积概率来动态确定候选集大小。具体来说,它是选择最小的一组词,使这些词的累积概率达到某个阈值P(例如0.9)。
- 相较于TOP-K,这种方法更加灵活,因为它不是基于固定的词数而是基于累积概率,这样可以在不同情况下自适应地调整候选集大小。
实现步骤:
- 同样,首先计算每个可能词的概率分布。
- 将所有词按概率降序排列,并计算累积概率。
- 找到最小的词集,使得这些词的累积概率大于或等于P。
- 对选定的词集进行概率重新归一化。
- 随机选择一个词作为输出。
这两种方法都旨在平衡生成文本的质量与多样性,避免了贪婪解码可能导致的过于保守的选择,同时也减少了完全随机选择带来的不可预测性。选择TOP-K还是TOP-P通常取决于特定应用的需求以及对结果的偏好。在实践中,可能需要通过实验来找到最适合的参数设置(K值或P值)。
(3)温度(Temperature)
在大模型推理中,“温度”(Temperature)是一个用于控制生成文本随机性的参数。它直接影响模型输出的概率分布,从而调整生成内容的多样性和确定性。
温度的概念
- 低温度(例如接近0):会使模型更加自信地选择概率最高的词,这会导致生成的内容更加确定和重复,但可能缺乏创造性。
- 高温度(例如大于1):会使模型更倾向于探索不太可能的选择,增加生成内容的多样性,但可能会导致语法错误或不连贯的输出。
- 标准温度(通常设置为1):表示不做任何调整,直接使用原始概率分布进行采样。
实现步骤
-
计算初始概率分布:
首先,模型会为下一个词生成一个概率分布。这个分布反映了每个可能的词被选中的概率。 -
应用温度变换:
对于每个词的概率 (p),通过以下公式进行调整:其中 (T) 是温度参数。可以看到,当 (T < 1) 时,较高的概率会被放大,而较低的概率会被缩小;相反,当 (T > 1) 时,差异被缩小,所有选项变得更加平等。
-
重新归一化:
调整后的概率需要重新归一化,确保所有概率加起来等于1,这样它们才能正确地代表一个概率分布。 -
根据调整后的概率分布采样:
使用调整后的概率分布进行随机采样,以决定下一个词是什么。不同的温度设置将影响到这个过程中每个词被选中的几率。
温度对贪心解码、束搜索和Top-K采样策略没有影响,因为它影响logit概率之间的距离,而所有这些策略都使用基于其顺序的top概率,温度不会改变概率的顺序。而Top-p采样允许更多或更少的竞争者进入基于其总概率的子集,因此,概率越接近(高温)随机性越大。
指导文本生成(Guided Text Generation)
指导文本生成(Guided Text Generation)是指在生成文本的过程中,通过某种方式引导模型生成特定风格、主题或符合特定条件的文本。这种方式可以提高生成文本的相关性、质量和多样性。
定义目标:
首先明确想要达到的目标是什么。例如,你可能希望生成与特定主题相关的文本,或者想要文本体现出某种情感色彩。
选择指导方法:
- 基于规则的方法:通过预设的规则来限制或引导文本的生成方向。例如,指定关键词必须出现在生成的文本中。
- 基于样例的方法:使用一组示例文本作为参考,使生成的文本尽可能模仿这些样例的风格或内容。
- 约束优化:在文本生成过程中加入特定的约束条件,如语法正确性、逻辑一致性等,以确保输出质量。
- 微调模型:针对特定任务对大模型进行微调,使其更擅长处理某一类问题或生成某一种类型的文本。
实施指导策略:
- 在基于规则或样例的方法中,可以通过调整输入提示(Prompt Engineering)来实现。比如,在输入中添加具体的指令或样例文本。
- 对于约束优化和微调模型的方法,则需要修改模型训练或推理的过程。例如,增加损失函数中的特定项来惩罚不满足约束条件的输出。
-
评估与迭代:对生成的文本进行评估,检查是否达到了预期的目标。根据评估结果,可能需要调整指导策略,重复上述步骤直到满意为止。
-
实际应用:将经过指导的文本生成应用于实际场景中,如自动写作、对话系统、内容创作辅助等。
指导文本生成的关键在于找到有效的指导方法,并将其恰当地融入到文本生成的过程中,从而提高生成文本的质量和适用性。
实现指导文本生成的方法之一是使用vLLM推理框架,也可以使用模式来加速推理。例如,考虑这个简单的“profile”模式:
{
"type": "object",
"properties": {
"name": { "type": "string"},
"age": { "type": "integer"}
},
"required": ["name", "age"]
}
由于模式具有特定的键name
和age
,一旦模型预测到:{"n
或{"a
,它就不需要进行自回归生成来得到{"name"
: 和{"age"
: ,因为这两者都必须导致一个特定的明确结果{"name"
: 和{"age"
。
关键性能推理指标
有两种方法可以查看性能指标,一种是系统指标的延迟和吞吐量,另一种是用户体验指标:Time To First Token (TTFT)
和Time Per Output Token (TPOT)
。让我们看看两者。
系统性能指标
(一)延迟
延迟是指从发送请求到接收到完整响应所花费的时间。
这包括以下时间:
- 接收请求
- 预处理提示(预填充阶段)
- 生成响应的新token(解码阶段)
- 将响应发送回客户端
接收请求和发送响应的时间大致相同,只有小的变化,因为提示和生成响应的长度不同。这些长度变化对总时间的影响应该可以忽略不计。
预填充阶段并行处理所有提示的token,因此在这里提示长度的变化不应该有太大影响,尽管较长的提示会消耗更多的加速器内存并影响总吞吐量。
解码阶段是受生成响应长度影响最大的阶段,因为每个新token都是作为一个单独的步骤生成的。这里响应越长,解码阶段就越长。
如果服务器没有足够的容量一次处理所有当前请求,并且必须排队一些请求,那么排队等待时间会延长延迟时间。
举例:如果你把道路上的汽车交通考虑在内,延迟是指从A点到B点(例如从家到办公室)所需的时间,包括由于交通信号灯、堵车和法律限制导致的速度限制。
(二)吞吐量
吞吐量衡量推理服务器并行处理多个请求和高效批处理请求的能力。
吞吐量的定义可以是同时可以处理多少请求,但由于有些请求比其他请求处理得快得多,因此在一个长请求期间可以处理多个短请求,所以计算整个系统生成的总token速率是有意义的。
因此,更常见的定义是:推理吞吐量是整个系统每秒生成的总token数。
举例:如果你把道路上的汽车交通考虑在内,吞吐量是指在任何给定时间内通过给定道路的汽车数量。道路车道越多,速度限制越高,吞吐量就越高。但显然有些车辆很短,有些很长,所以需要某种标准化。例如,渡轮计算可以容纳多少米或英尺的车辆,因此长车辆比短车辆支付更多费用。
用户体验指标
虽然可以通过许多特性来评估推理服务器的性能——如功耗、效率和成本,但有人可能会说,由于这些系统是与人类进行交互的,所以最重要的特征都集中在提供流畅用户体验的领域。如果用户体验缓慢且不流畅,用户将转向竞争对手。因此,关键需求是:
(一)第一个Token的时间
第一个Token的时间(TTFT)定义为用户点击提交按钮(或回车)到他们收到第一个单词或部分单词的时间。
希望第一个Token的时间(TTFT)非常短。如今,用户期望任何应用程序的响应时间理想情况下都要快于1秒。因此,用户等待开始接收第一个token的时间越短越好。这对于期望互动的聊天机器人尤为重要。TTFT的长度受许多因素影响,关键因素是预填充阶段的计算(提示的预处理)以及请求在用户请求接收后是否立即处理或是否需要在队列中等待。
重要的是要注意,在没有负载的服务器上,TTFT可能与在负载很重的服务器上非常不同。如果通常服务器在1秒内发送第一个token,如果服务器已经忙于处理所有请求并且有一个队列,除了前几个请求外,有效的TTFT可能会长得多。因此,通常应测量平均TTFT并与基准测试期间发送的并发请求数量一起报告。
这是一个复杂的指标,因为根据提示的大小,时间会有所不同,因此理想情况下你希望将其标准化为提示中的token数量。
(二)每个输出Token的时间
希望每个输出Token的时间(TPOT)相对较低,不能太高。这个时间理想情况下应该接近发送请求的人的阅读速度。例如,如果你服务的是一年级学生,TPOT可以相当低,但受教育程度越高的人,TPOT应该越快,以实现流畅的阅读体验。
根据维基百科,3种阅读类型(https://en.wikipedia.org/wiki/Speed_reading#Types_of_reading),阅读速度以每分钟单词数(WPM)来衡量。
每个单词的平均token数因分词器而异,主要取决于其词汇量和语言。在这里,我们考虑一个英语分词器,大约每个单词1.5个token。现在我们可以将每分钟单词数(WPM)转换为每分钟token数(TPM)。
现在我们只需要除以60得到每秒token数(TPS) 即 (WPM*1.5) / 60
,并取倒数得到每个输出token的时间(TPOT)。
所以 TPOT = 60 / (WPM*1.5)
以秒为单位
记得将1.5系数更改为你的分词器的实际单词到token的平均比率。例如,截至本文撰写时,OpenAI ChatGPT的50k词汇量大约为每个单词1.3个token,而许多其他LLM有30k词汇量,这导致更高的单词到token比率。
如你所见,TPOT是一个难以跟踪和在脑海中思考的值,因此一旦你知道了目标TPOT,最好将其转换为每秒token数(TPS)并跟踪它。
因此,在这个例子中,如果你的系统可以每个请求持续生成20个token每秒,你的客户将会满意,因为该系统将能够跟上每分钟700个单词的超级快速读者。
当然,也会有用户更喜欢在生成完成后再开始阅读响应。在这种情况下,越快越好。
根据生成的类型,可能适用以下情况:
- 图像 - 一次性生成
- 文本 - 与用户的阅读速度一样快,或者如果他们不喜欢在开始阅读前有移动部分,则一次性生成
- 音频 - 与用户的听力速度一样快
- 视频 - 与用户的观看速度一样快
如果这是一个不与个人用户接口的离线系统,并且只是批量处理请求,这些指标没有区别,但延迟和吞吐量是关键指标。
更多指标说明
(一)加速器利用率
加速器利用率 - 无论是百分比还是功率测量,都是判断您的设置是否高效使用加速器的良好指标。例如,如果您使用NVIDIA GPU,并通过 -n 0.5 nvidia-smi
命令观察,发现在大量请求轰炸推理服务器的情况下,"gpu util"仅为10%,这通常意味着以下两种情况之一:要么推理服务器效率非常低(例如,花费大量时间在来回复制数据上),要么可能是客户端在接收数据时效率低下(即存在过多的IO阻塞)。
注意:当我最初使用openai客户端编写一个简单的基准测试时,在低并发情况下运行良好,但在更高并发下,推理服务器的GPU利用率降至6-7%。在我用aiohttp API替换客户端后,利用率上升到了75%。因此,请注意,可能是您的基准测试导致了性能报告不佳,而不是服务器的问题。
在理想情况下,你希望加速器利用率尽可能高。要注意的是,至少对于NVIDIA GPU,gpug util 不是你认为的那样,但如果它报告一个非常低的百分比,足以知道确实存在效率问题。
(二)百分位数
在大模型领域,尤其是在讨论性能指标如响应时间、处理速度等时,百分位数(如p50、p75、p90、p95和p99)是非常重要的统计度量。这些百分位数帮助我们理解一组数据中不同部分的表现情况。
-
定义:
- 百分位数是一种衡量数据分布的方法。给定一个数据集,某个百分位数表示的是低于该值的数据所占的比例。
- 例如,p50(第50百分位数)指的是数据集中有一半的值小于或等于这个数值。
-
具体应用到大模型的性能评估:
- p50: 表示一半的请求处理时间短于这个值。这是一个中间值,通常用来代表“典型”或“平均”的性能表现。
- p75, p90, p95, p99: 这些较高的百分位数分别表示只有25%,10%,5%,和1%的请求比这些值更大。它们用于识别长尾延迟问题,即那些不常见但可能严重影响用户体验的情况。
当然,为了更好地理解百分位数的概念,我们可以通过具体的例子来说明。假设我们有一个包含100个请求处理时间的数据集(单位:毫秒),这些数据已经按从小到大的顺序排列。
示例数据
假设有如下一组数据表示这100个请求的处理时间:
1,2,3,10, 12, 13, 14, 15, ..., 98, 99,100
计算百分位数
-
p50 (第50百分位数):
- 这是中位数,即一半的值小于或等于这个数值。
- 在我们的数据集中,第50和第51个值分别是50和51(因为数据量为100个,所以中位数位于中间两个数之间)。
- 因此,p50 = (50 + 51) / 2 = 50.5 毫秒。
-
p75 (第75百分位数):
- 这意味着有75%的数据小于或等于这个数值。
- 第75百分位数对应的是第75个数据点的值。
- 假设第75个数据点的值是75毫秒,则 p75 = 75 毫秒。
-
p90 (第90百分位数):
- 90%的数据小于或等于这个数值。
- 对于100个数据点,第90百分位数对应的是第90个数据点的值。
- 假设第90个数据点的值是90毫秒,则 p90 = 90 毫秒。
-
p95 (第95百分位数):
- 95%的数据小于或等于这个数值。
- 对应的是第95个数据点的值。
- 假设第95个数据点的值是95毫秒,则 p95 = 95 毫秒。
-
p99 (第99百分位数):
- 99%的数据小于或等于这个数值。
- 对应的是第99个数据点的值。
- 假设第99个数据点的值是99毫秒,则 p99 = 99 毫秒。
实际应用举例
假设你正在评估一个API的响应时间性能:
- p50: 表示一半的请求在50.5毫秒内完成,这是一个典型的响应时间。
- p75: 75%的请求在75毫秒内完成,表明大多数请求都很快。
- p90: 90%的请求在90毫秒内完成,显示了绝大多数请求的表现。
- p95: 95%的请求在95毫秒内完成,帮助识别那些较慢但仍然可以接受的请求。
- p99: 99%的请求在99毫秒内完成,揭示了系统在最差情况下的表现。
例如,让我们再看看一个系统加载报告的部分输出,该报告由k6生成:
http_req_duration..: avg=13.74s min=12.54s med=13.81s max=13.83s p(90)=13.79s p(95)=13.83s
http_req_receiving.: avg=27.98µs min=15.16µs med=21.6µs max=98.13µs p(90)=44.98µs p(95)=59.2µs
http_req_sending...: avg=133.8µs min=20.47µs med=75.39µs max=598.04µs p(90)=327.73µs p(95)=449.65µs
如果我们看报告的第一行总生成时间,如果我们看记录的最小值12.54秒,我们就知道90%的响应时间在12.54到13.79秒之间,95%的响应时间在12.54到13.83秒之间 - 在这种情况下,中位数报告值在p90和p95值之间。
模型内存使用情况解析
推理时的内存使用情况与训练时有很大不同。分为三部分内容:
- 模型权重
- KV缓存 - 关键在于不需要为每个新生成的token重新计算过去的token
- 激活内存 - 这是处理的临时内存,取决于批量大小和序列长度
模型权重
模型显存计算方式如下:
- 4字节 * 参数数量(fp32)
- 2字节 * 参数数量(fp16/bf16)
- 1字节 * 参数数量(fp8/int8)
- 0.5字节 * 参数数量(int4)
示例:Meta-Llama-3.1-8B在bf16格式下需要2(bf16字节)* 8B(参数数量)= 16GB(大约)
但是加上推理时的预处理和KV缓存,不止需要16G
KV Caching
在每次生成新token之前重新计算所有之前的KV(Key Value)值会非常昂贵,因此它们被缓存到加速器的内存中。新计算的KV值被附加到现有缓存中。
KV缓存大小与输入序列长度和批量大小直接成正比。过去的查询值在注意力机制中不再使用,因此不需要缓存。
计算方式如下
一个token的KV缓存需要dtype_bytes * 2 * num_hidden_layers * hidden_size * num_key_value_heads / num_attention_heads字节
其中:
- dtype_bytes 是每种数据类型字节数:4字节fp32,2字节bf16/fp16等。
- 2 表示keys + values,因为它们有2个。
- num_hidden_layers 为模型架构隐藏层的层数
- hidden_size 为模型架构里面隐藏层的神经元数量
- num_attention_heads 当是多查询注意力MQA时,它表示注意力头的数量
- num_key_value_heads 当是多查询注意力MQA时,它表示KV头的数量
- 当是多头注意力MHA的时候,num_attention_heads等于num_key_value_heads
- 当是分组查询注意力GQA的时候,num_key_value_heads 等于1,
你可以在模型文件夹中的config.json
中获取这些维度,或者从等效文件中获取。例如meta-llama/Meta-Llama-3.1-8B的config.json
计算示例
1 token Meta-Llama-3.1-8B in bf16将需要:2 (bf16 bytes) * 2 (keys+values) * 32 (num_hidden_layers) * 4096 (hidden_size) * 8 (num_key_value_heads) / 32 (num_attention_heads) = 131072字节 = 0.131MB。这个模型使用GQA,所以它使用 MHA的1/4。如果Meta-Llama-3.1-8B使用MHA,每个token将需要4倍多的内存。
批量大小为1的1024个token将需要0.131*1024 = ~134MB。
批量大小为128的1024个token将需要0.1311024128 = ~17170MB = ~17.2GB。
一个更小的KV缓存将导致更快的生成和更高的GPU利用率。因此,各种技术,如gisting、上下文蒸馏、键值淘汰策略、内存压缩、多查询注意力、分组查询注意力、跨层注意力、基于锚点的自注意力、量化等,都是为了实现这一点。
在小批量大小的情况下,您应该检查禁用KV缓存是否能带来更好的整体性能。
推理框架
有许多推理框架,而且每周都有新的框架出现,所以很难将它们全部列出。因此,这里提供了一些可能适合您需求的推理框架的入门列表,但如果这里列出的框架不能满足您的需求,请查看其他框架。
本节力求保持中立,不推荐任何特定的框架,因为即使我能够全部尝试,也无法猜测哪个框架最适合哪个用户/公司。
-
DeepSpeed-FastGen from the DeepSpeed team(https://github.com/microsoft/DeepSpeed).
-
TensorRT-LLM (also integrated what used to be FasterTransformer)
下面是仅支持NVIDIA GPU: