
从flask到vllm:探索2020年后语言模型服务的演变与创新解决方案
- Rifx.Online
- Large Language Models , MLOps , Industry Insights
- 26 Feb, 2025
大型语言模型服务的演变
想要与世界分享你的发明的古老故事在大型语言模型的世界中终于加速发展,因为我们正在迈向一个消费者可以托管自己的语言模型而不必依赖大量计算能力的时代。在这里,我们将讨论大型语言模型服务是如何从通用模型服务演变而来的,面临的挑战是什么,以及我们是如何克服这些挑战的。
让我们先谈谈模型服务是如何随着时间的推移而演变的。
2020年前,模型服务的环境相对干燥,大多数人通过不同的通用框架来提供他们的机器学习模型,其中最著名的是Flask,因为大多数机器学习代码都是用Python编写的。
第一个真正专注于提供机器学习模型服务的框架是Google推出的TensorFlow Serving,其首次提交是在2016年8月底。这为后来的大多数框架奠定了基础,包括但不限于Clipper (加州大学伯克利分校)、BentoML、TorchServe (Facebook)和HuggingFace的初始推理API。
所有这些框架都致力于为用户提供为其机器学习模型创建REST API的能力。然而,由于大型语言模型尚未面向消费者,因此这些框架都不是专门针对大型语言模型的。
改变一切的一年 — 2020
在2020年6月,OpenAI宣布了GPT-3的私有测试版API。当时该模型仅作为“文本输入,文本输出”模型工作,但它很快在行业内引起了轰动,因为这是语言模型首次在如此大规模上生成语法无误且连贯的文本。
到了11月,OpenAI向公众开放了其API,这被广泛认为是人工智能历史上的一个里程碑事件。人类历史上第一次,人们与机器对话,机器的回复就像是有人在操作它并撰写所有回复一样。
2020年后的模型服务
突然间,传统的机器学习模型服务已经不足够,因此 Cohere 和 AI21 Labs 也推出了自己的商业大型语言模型 API,以挑战 OpenAI。
直到 2021 年,分布式服务才进入了视野,Anyscale 引入了 Ray Serve,它提供了一种以 Python 为中心的构建和部署服务应用程序的方法。
在这一切之后,NVIDIA 也进入了视野,推出了 Triton推理服务器。虽然并非专门针对大型语言模型,Triton 因其高度优化并支持多个框架(包括 TensorFlow、PyTorch 和 ONNX)而获得了巨大的受欢迎程度。他们的独特卖点在于允许同时服务来自不同框架的多个模型,同时提供动态批处理和模型组合等功能。与 Triton 一起,NVIDIA 还在 2021 年推出了 FasterTransformer,这实际上是一个为 NVIDIA GPU 提供高度优化的变换器模型实现的库。
为什么要谈论FasterTransformer (FT)?
FasterTransformer的概述(图片来自NVIDIA的技术博客 — 链接可在参考部分找到)
在发布时,FT的独特之处在于它支持以分布式方式推理大型变换器模型(如上图所示)。当时,仅使用张量并行和流水线并行将模型分布到多个GPU和节点上。
张量并行 — 不在单个GPU/节点上执行所有计算,而是将张量(按列或按行)切分成更小的块,并分配到多个设备上。所有设备的输出最终会被连接在一起。在注意力计算中,不同块的张量(Q、K和V矩阵)的计算定义了多个头并促进了模型的学习。
流水线并行 — 将模型拆分为不同阶段,并在单独的设备上执行每个阶段,以最小化任何特定GPU闲置的时间。
以下是FT提供的其他优化,以减少延迟并增加吞吐量:
- 为键和值矩阵添加缓存机制,以及神经网络的多个部分。
FasterTransformer库中的缓存机制(图片来自NVIDIA的技术博客 — 链接可在参考部分找到)
- 在不同解码器层中重用激活/输出的内存缓冲区。
- 使用低精度输入数据进行推理(fp16和int8)。
TensorRT 一直是NVIDIA的首选推理引擎,针对NVIDIA GPU上的深度学习模型进行了高度优化。随着FT的发展,它用于对变换器模型进行优化的技巧开始很好地通用化,NVIDIA很快意识到这个库必须与TensorRT推理引擎紧密集成,以便在NVIDIA GPU上提供更高的吞吐量。
大型语言模型如何进行推理?
在进入框架之前,让我们花一点时间来看看任何大型语言模型是如何进行推理的(我们只讨论解码器模型)。
大型语言模型推理过程的两个阶段(图片摘自“关于大型语言模型高效推理的调查” — 参考部分有链接)
让我们逐一看看这两个阶段,
1 — 预填充阶段
在这个阶段,完整的输入文本通过所有的变换器层进行处理,模型同时捕捉输入序列中所有标记之间的关系。 这里还要注意的一点是,KV缓存也在这个阶段被创建。
2 — 解码阶段
解码阶段以阶段1中的编码上下文和KV缓存作为输入。此外,还提供了一个_开始标记_用于生成。这一次,注意力机制仅关注之前生成的标记。生成的输出基本上是对模型提供的词汇表的分布,具体取决于使用的采样类型(贪婪或随机),生成一个输出标记(这个标记也用于更新KV缓存)。
这种方法有助于解释解码器模型的自回归特性,同时支持批量计算和逐个流式生成大型语言模型产生的标记。
大型语言模型服务框架的年份 — 2023
到2022年,NVIDIA已将FasterTransformer与他们的Triton推理服务器集成,但仍然浪费了大量内存。
然后是2023年,出现了我们今天大多数著名的LLM模型服务框架,包括vLLM、DeepSeed-Inference、TensorRT-LLM、HuggingFace TGI、llama.cpp等。让我们谈谈vLLM,看看它_为什么_ 成为服务大型语言模型最成功的框架之一。
vLLM — PagedAttention的杰作
在我们深入探讨vLLM的瑰宝——PagedAttention之前,首先让我们了解在vLLM之前围绕LLM服务的问题。
在2023年6月之前,LLM服务的主要问题是**KV缓存的内存管理效率低下。**正如我们在FasterTransformer概述中看到的,KV缓存用于避免在自回归生成的每一步中重新计算每个先前标记的注意力分数。
在步骤_t_中,我们有_x(t)_作为最新标记的输入嵌入。
然后,_x(t)_被附加到生成序列的末尾,
X = X(1), X(2), …, X(t-1), X(t)
让_K(i)_和_V(i)_表示标记_i_的所有头部的矩阵。我们得到,
K_cache = [K(1), K(2), …, K(t-1)]
V_cache = [V(1), V(2), …, V(t-1)]
现在,在注意力计算中,只需要计算新标记的_q, k, 和 v_,然后再计算注意力权重,
这里,我们只需要计算_q(t), k(t),_ 和_v(t)_ 同时更新缓存。
问题 — 在PagedAttention之前,KV缓存通常被分配为连续的内存块,即为每个请求保留一大块连续的GPU内存。
因此,对于模型中的每个请求以及每个头部,您需要,
memory_per_head_per_layer = max_seq_len * d_h * sizeof(float)
其中_d_h_是每个头的维度,我们假设是单精度(float)。
total_cache_memory = num_layers * num_heads * memory_per_head_per_layer * num_requests
这导致了以下问题,
1 — 内部碎片: 任何短于_max_seq_的序列都会导致大量连续块未被使用,即每次分配内部浪费了内存。
2 — 外部碎片: 如果GPU内存被分割成较小的块,则需要足够大连续块的请求将被拒绝,即使较小块的总和大于所需空间。
3 — 批量大小限制: 可用的连续内存严重限制了同时发出的请求总数。
PagedAttention之前的KV缓存内存管理(图片来自_“Efficient Memory Management for Large Language Model Serving with PagedAttention” — 链接在参考部分可用)_
vLLM对大问题的解决方案 — 引入非连续的KV缓存分页内存分配。
vLLM通过借鉴操作系统的理念,改变了LLM服务的整体格局,基本上是虚拟内存和分页的概念。这使得操作系统能够在几十年内使用比物理上可用的更多内存。
在2023年,UCB的一组研究人员发表了一篇题为_“Efficient Memory Management for Large Language Model Serving with PagedAttention”_的论文,其中描述了一种有效减少KV缓存内存浪费至接近零的方法。在论文中,作者将PagedAttention描述为,
PagedAttention将每个序列的KV缓存分成KV块。每个块包含固定数量标记的键和值向量。
键和值向量存储在非连续块中(图片来自_“Efficient Memory Management for Large Language Model Serving with PagedAttention” — 链接在参考部分可用)_
这个变化也要求我们对注意力计算进行小的调整。我们现在有以下块级计算,而不是一次性完成完整计算,
其中,
B -> 单个块中的标记数量(固定)
然而,更好的想法是执行以下操作,
创建两个独立的函数,从内存中不同块收集相关的键和值向量,然后使用它们以传统方式执行注意力计算。
vLLM的概述(图片来自_“Efficient Memory Management for Large Language Model Serving with PagedAttention” — 链接在参考部分可用)_
现在让我们看一下vLLM的完整概述。操作系统将内存划分为固定大小的页面,并通过页表将用户程序的逻辑页面映射到物理页面。以类似的方式,vLLM为每个请求创建不同的逻辑KV块,并在生成新缓存时从左到右填充它们。 如上图所示,KV缓存管理器维护块表,以映射每个请求的KV块的逻辑和物理地址。
vLLM使用的技术还有另一个巧妙的好处,那就是**块共享。由于批处理中的许多请求共享一个公共前缀(可能是初始提示),PagedAttention允许这些请求共享与公共前缀相对应的块。另一方面,如果共享前缀后跟不同的标记,则使用一种称为写时复制 (COW)**的机制。
这基本上意味着共享块保持不变;创建一个新块,并更新该特定请求的块表以指向新块。
通过查看下面的图,可以更好地理解这一点,
展示了使用共享公共前缀的并行采样的COW(图片来自_“Efficient Memory Management for Large Language Model Serving with PagedAttention” — 链接在参考部分可用)_
vLLM — V1进一步改进了此设计,但我们将不在此文中讨论。
参考文献
-
vLLM 博客 — https://blog.vllm.ai/
-
PagedAttention 论文 — https://arxiv.org/pdf/2309.06180
-
FasterTransformer 博客 — https://developer.nvidia.com/blog/accelerated-inference-for-large-transformer-models-using-nvidia-fastertransformer-and-nvidia-triton-inference-server/
-
操作系统中的分页 — https://www.geeksforgeeks.org/paging-in-operating-system/
-
多 GPU 训练 — https://huggingface.co/docs/transformers/main/en/perf_train_gpu_many#tensor-parallelism
-
LLM 推理的调查论文 — https://arxiv.org/abs/2404.14294