一、背景
自从 OpenAI 发布了 ChatGPT,全球范围内对基于 Transformer 结构的大语言模型 (LLM) 的技术关注达到了前所未有的高度。这些模型凭借其强大的理解和生成能力,正在深刻地改变我们对人工智能的认知和应用。然而,大语言模型在推理应用方面的成本过高,极大地阻碍了技术的广泛应用。
因此,优化大语言模型的推理性能成为了业界研究的焦点。大语言模型推理不仅面临着计算资源的巨大需求,还面临着计算效率的挑战。通过优化推理性能,我们不仅可以降低硬件成本,还能提高模型的实时响应速度。这将使模型能够更快速地执行自然语言理解、翻译、文本生成等任务,从而改善用户体验,加速科学研究,推动各行业应用的发展。
二、优化技术
LLM 推理服务的关键在于两大核心指标:吞吐量和时延。
- 吞吐量:这是从系统角度观察的,代表了系统在单位时间内能够处理的 tokens 数量。计算方法是:系统处理完成的 tokens 个数除以对应耗时。这里的 tokens 通常指的是输入和输出序列的总长度。更高的吞吐量意味着 LLM 服务系统的资源利用率更高,系统成本也相应降低。
- 时延:这是从用户视角出发的考量,衡量了用户平均收到每个 token 所需的时间。计算方法为用户从发出请求到收到完整响应的时间除以生成的序列长度。通常,当每 token 的时延不超过 50ms 时,用户会感觉体验非常流畅。
吞吐量关注的是系统成本,高吞吐量代表系统能够高效处理更多请求,提高系统利用率。而时延则直接影响用户体验,快速响应是提升满意度的关键。然而,这两个指标往往相互影响,需要我们在实践中精心权衡。
例如,为了提升吞吐量,一个常见的策略是增大 batchsize,即将用户请求从串行转为并行处理。然而,这也会在一定程度上增加每个用户的等待时间,因为系统现在要同时处理多个请求。
为了优化 LLM 的推理性能,我们的目标是同时提高吞吐量和降低时延。这涉及到七个关键部分,下面将详细展开描述。
2.1 显存
2.1.1 KV Cache
大模型推理性能优化的利器之一就是 KV Cache 技术。这项技术在不牺牲任何计算精度的前提下,巧妙地运用空间换时间的策略,显著提升了推理性能。目前,业界主流的 LLM 推理框架都默认支持并启用了这一功能。
Transformer 模型因其自回归推理的特性而独树一帜,即每次推理仅预测输出一个 token。当前轮输出的 token 与历史输入的 tokens 拼接后,成为下一轮的输入 tokens,如此循环往复。在这个过程中,相邻两轮的输入仅相差一个 token,导致了大量的重复计算。而 KV Cache 技术正是为了解决这个问题而生,它通过将可复用的键值向量结果保存下来,巧妙避免了这些重复计算,从而大幅提升了推理效率。
具体来讲,KV Cache 技术在每次自回归推理过程中发挥了巨大作用。当 Transformer 模型的每一层 Attention 模块执行时,它会将 Q、K、V 的计算结果保存到一个预先分配好的数据结构(我们称之为 KV Cache)中。这样,当下一次自回归推理开始时,我们只需将新的 Q 与已有的 KV Cache 中的 K、V 拼接起来,即可供后续计算使用。
值得注意的是,由于 KV Cache 缓存了每一轮已计算完毕的键值向量,因此会额外增加显存开销。以 LLaMA-7B 模型为例,每个 token 对应的 KV Cache 空间大小可通过下面的公式来计算,这样我们就能更精准地掌握和预测显存使用情况。
cache_per_tokens = 2 * n_layer * n_head * d_head * dtype_byte_size
公式中的第一个因子 2 代表 Key/Value 两个向量,它们在每一层中都需要被存储。这里的 n_layer 表示 Transformer 层的数量,n_head 代表 KV head 的个数(在多头注意力模型中,这个值等于注意力头数;而在多查询注意力模型中,这个值为 1)。而 d_head 则代表每个 KV head 的维度,dtype_byte_size 则表示每存放一个数据所需的字节数。
模型推理所需的 KV Cache 总量计算公式如下:
其中 Context_length 表示输入和输出序列长度之和。因此,KV Cache 的大小与 batch size 和序列长度呈线性关系。
total_cache = batchSize * Context_length * cache_per_token
KV Cache 的引入巧妙地将推理过程划分为两个阶段,进而为后续的优化策略提供了更多可能性。
- 预填充阶段 (Prefill):这个阶段发生在计算第一个输出 token 的过程中。在这个阶段,计算机会为每个 Transformer layer 精心计算并保存 key cache 和 value cache。此时的 FLOPs(浮点运算次数)与未启用 KV Cache 时相同,其中包含大量的 GEMM(General Matrix-Matrix multiply,即通用矩阵乘法)操作,这种计算属于 Compute-bound 类型,即计算密集型。


