【vLLM】核心技术PagedAttention,调度原理

news2024/11/29 10:51:50

在这里插入图片描述

vLLM 简介

  • 来自加州大学伯克利分校、斯坦福大学、加州大学圣迭戈分校的研究人员基于操作系统中经典的虚拟(Virtual)内存和分页(Page)技术,提出了一个新的注意力算法 PagedAttention,并打造了一个LLM服务系统——vLLM,官网为:https://vllm.ai/
  • vLLM团队宣称 vLLM 能实现比 HuggingFace Transformers 高 24 倍的吞吐量,同时推理速度最少提高 8 倍

原理简介

  • vLLM通过借鉴虚拟(Virtual)内存的原理,采用固定大小的块和动态映射的方式,有效地管理了内存,减少了内存浪费
  • vLLM通过PagedAttention技术和“先来先服务(FCFS),后来先抢占,gpu不够就先swap到cpu上”的调度策略(Scheduler),在1个推理阶段处理尽可能多的请求,解决高并发场景下的推理吞吐问题。这就是整个vLLM运作的核心思想

vLLM 主要特性:

  • 先进的服务吞吐量
  • 通过 PagedAttention 对注意力 key 和 value 进行内存管理
  • 对传入请求的批处理
  • 针对 CUDA 内核的优化

vLLM 灵活易用:

  • 与 HuggingFace 模型无缝集成
  • 支持并行采样、beam search 等解码算法的高吞吐量服务
  • 支持分布式推理的张量并行
  • 支持流式输出
  • 兼容 OpenAI 的接口服务

VLLM服务吞吐对比

  • 单路输出在这里插入图片描述
  • 3路并行输出
    在这里插入图片描述
  • ollama vs vllm性能
    • vllm的优势是在多卡
    • ollama的优势是低门槛,容易上手
    • vllm 和 ollama 在单个请求时,时间性能差不多
    • 在提升并发时,发现ollama 性能遇到瓶颈,且推理时间明显翻倍,而vllm推理时间稳定,仍然保持单个推理时间的性能

LLM推理的两阶段

  • 一个常规的LLM推理过程通常分为两个阶段:prefill和decode。通常会使用KV cache技术加速推理
    在这里插入图片描述

Prefill

  • 预填充阶段。在这个阶段中,我们把整段prompt喂给模型做forward计算。如果采用KV cache技术,在这个阶段中我们会把prompt过后得到的保存在cache_k和cache_v中。这样在对后面的token计算attention时,我们就不需要对前面的token重复计算了,可以帮助我们节省推理时间。

  • 在上面的图例中,我们假设prompt中含有3个token,prefill阶段结束后,这三个token相关的KV值都被装进了cache

Decode

  • 生成response的阶段。在这个阶段中,我们根据prompt的prefill结果,一个token一个token地生成response。

  • 同样,如果采用了KV cache,则每走完一个decode过程,我们就把对应response token的KV值存入cache中,以便能加速计算。例如对于图中的t4,它与cache中t0~t3的KV值计算完attention后,就把自己的KV值也装进cache中。对t6也是同理。

  • 由于Decode阶段的是逐一生成token的,因此它不能像prefill阶段那样能做大段prompt的并行计算,所以在LLM推理过程中,Decode阶段的耗时一般是更大的。

  • 从上述过程中,我们可以发现使用KV cache做推理时的一些特点:

    • 随着prompt数量变多和序列变长,KV cache也变大,对gpu显存造成压力

    • 由于输出的序列长度无法预先知道,所以我们很难提前为KV cache量身定制存储空间

  • 下图展示了一个13B的模型在A100 40GB的gpu上做推理时的显存占用分配(others表示forward过程中产生的activation的大小,这些activation你可以认为是转瞬即逝的,即用完则废,因此它们占据的显存不大),从这张图中我们可以直观感受到推理中KV cache对显存的占用。因此,如何优化KV cache,节省显存,提高推理吞吐量,就成了LLM推理框架需要解决的重点问题。

在这里插入图片描述

为KV cache分配存储空间的常规方式

  • 对于训练好的模型,一种常用的部署方式是将其打包成一个推理服务(server),它接收客户端发送来的请求(request),读取请求中的数据(prompt)来做推理。一个请求中可以只有1个prompt,也可以包含多个prompt

  • 在常规的推理框架中,当我们的服务接收到一条请求时,它会为这条请求中的prompts分配gpu显存空间,其中就包括对KV cache的分配。由于推理所生成的序列长度大小是无法事先预知的,所以大部分框架会按照(batch_size, max_seq_len)这样的固定尺寸,在gpu显存上预先为一条请求开辟一块连续的矩形存储空间。然而,这样的分配方法很容易引起“gpu显存利用不足”的问题,进而影响模型推理时的吞吐量。你可能觉得这个描述有点抽象,别着急,我们来具体看一个例子
    在这里插入图片描述

  • 我们假设max_seq_len = 8,所以当第1条请求(prompt1)过来时,我们的推理框架为它安排了(1, 8)大小的连续存储空间。

  • 当第2条请求(prompt2)过来时,同样也需要1块(1, 8)大小的存储空间。但此时prompt1所在的位置上,只剩3个空格子了,所以它只能另起一行做存储。对prompt3也是同理

  • 仔细观察这3条prompt的KV cache排布,你是不是隐约觉得这种排布似乎没有充分利用起gpu的显存?

  • 浅色块:观察图中的浅色块,它是prefill阶段prompt的KV cache,是无论如何都会被使用的空间,它不存在浪费。

  • 中色块:观察图中的中色块,它是decode阶段的KV cache,其中表示序列生成的截止符。虽然这些中色块最终都会被我们用上,但是在decode阶段一个个token生成时,我们并不能预知哪些块会被最终用上。例如对于prompt2,当你生成when的时候,你无法知道下一个会生成,还是会生成别的词。所以这些中色块都是一种“潜在的浪费”,我们称中色块的部分为预留碎片(reservation fragment)。

  • 深色块:观察图中的深色块,它也是decode阶段的KV cache,但直到序列生成完毕,它都没有被用上。由于这些深色块是预留的KV cache的一部分,所以我们称其为内部碎片(internal fragment)。

  • 灰色块:观察图中的灰色块,它不是我们预留的KV cache的一部分,且最终也没有被用上,我们称这些灰色块为外部碎片(external fragment)。想象一下,此时新来了一条prompt4,它也要求显存中的8个格子作为KV cache。此时你的显存上明明有9个空格子,但因为它们是不连续的碎片,所以无法被prompt4所使用。这时prompt4的这条请求只好在队列中等待,直到gpu上有足够显存资源时再进行推理,这不就对模型推理的吞吐量造成显著影响了吗

观察整个KV cache排布,你会发现它们的毛病在于太过“静态化”。当你无法预知序列大小时,你为什么一定要死板地为每个序列预留KV cache空间呢?为什么不能做得更动态化一些,即“用多少占多少”呢?这样我们就能减少上述这些存储碎片,使得每一时刻推理服务能处理的请求更多,提高吞吐量,这就是vLLM在做的核心事情,我们先通过一张实验图来感受下vLLM在显存利用上的改进效果(VS 其它推理框架)

在这里插入图片描述
不难发现,相比于别的推理框架,vLLM几乎能做到将显存完全打满
只能说牛逼
此时不免会想两个问题

  • 问题1: vLLM是通过什么技术,动态地为请求分配KV cache显存,提升显存利用率的?
  • 问题2: 当采用动态分配显存的办法时,虽然明面上同一时刻能处理更多的prompt了,但因为没有为每个prompt预留充足的显存空间,如果在某一时刻整个显存被打满了,而此时所有的prompt都没做完推理,那该怎么办?

PagedAttention原理

  • 我们先来回答问题1:vLLM通过一种名为PagedAttention的技术,动态地为请求分配KV cache显存,提升显存利用率
  • 整体上来说,PagedAttention的设计灵感来自操作系统中虚拟内存的分页管理技术

操作系统的虚拟内存

不使用虚拟内存

我们知道程序运行时,会将代码、数据等内容存放在物理内存上。在最原始的做法中(没有操作系统,例如单片机),程序直接对物理内存进行操作,决定使用它的哪些存储地址。

如果你只跑一个进程,那还好说。但如果需要运行多个进程时,麻烦就来了:由于我直接操作了物理内存地址,所以我在为自己的进程分配物理内存时,还要考虑别的进程是如何分配物理内存的(别人已经占用的我不能用)。这样不同进程间的耦合性太高了,给开发带来难度。

有没有一种办法,让各个进程间的开发能够相互独立呢?一种直觉的做法是:

  • 给每个进程分配一个虚拟内存。每个进程在开发和运行时,可以假设这个虚拟内存上只有自己在跑,这样它就能大胆操作。

  • 虚拟内存负责统一规划代码、数据等如何在物理内存上最终落盘。这个过程对所有进程来说都是透明的,进程无需操心
    在这里插入图片描述

虚拟内存的分段管理

  • 在分段式内存管理中,虚拟内存会尽量为每个进程在物理内存上找到一块连续的存储空间,让进程加载自己的全部代码、数据等内容
    在这里插入图片描述
  • 虽然物理内存上有640M的空间剩余,但因为是碎片化的,我的进程4无法加载进去
  • 在这个情况下,如果我硬要运行进程4,也是有办法的:我可以先把进程3从物理内存上交换(swap)到磁盘上,然后把进程4装进来,然后再把进程3从磁盘上加载回来。通过这种方法我重新整合了碎片,让进程4能够运行
  • 这种办法的显著缺点是:如果进程3过大,同时内存到磁盘的带宽又不够,整个交换的过程就会非常卡顿。这就是分段式内存管理的缺陷

虚拟内存的分页管理

在这里插入图片描述

我们可以将进程1、进程2想成是两本书。代码分布在书的不同page上。我们希望想读哪一页,就加载哪一页,而不是一下把两本书都加载进来。同时,当我们不想读某些页的时候,我们也能根据页码将其清空

现在,我们希望读进程1和进程2的page1,我们就将其加载到物理内存上。虚拟内存会帮我们做好映射,把来自不同进程的这两页分别加载到物理内存对应位置

虚拟内存的分业管理技术总结起来就是

  • 将物理内存划分为固定大小的块,我们称每一块为页(page)。从物理内存中模拟出来的虚拟内存也按相同的方式做划分

  • 对于1个进程,我们不需要静态加载它的全部代码、数据等内容。我们想用哪部分,或者它当前跑到哪部分,我们就动态加载这部分到虚拟内存上,然后由虚拟内存帮我们做物理内存的映射。

  • 对于1个进程,虽然它在物理内存上的存储不连续(可能分布在不同的page中),但它在自己的虚拟内存上是连续的。通过模拟连续内存的方式,既解决了物理内存上的碎片问题,也方便了进程的开发和运行

PagedAttention

处理单个请求

现在,你已经知道虚拟内存分页管理的基本原理和优势,趁热打铁,我们马上来看以其为灵感的PagedAttention技术是如何操作的。我们还是从具体的例子讲起

假设现在你向模型server发送一条请求,prompt为Four score and seven years ago our,你希望模型能做续写。PagedAttention的运作流程如下图
在这里插入图片描述

  • 请求(request)可理解为操作系统中的一个进程

  • 逻辑内存(logical KV blocks)可理解为操作系统中的虚拟内存,每个block类比于虚拟内存中的一个page。每个block的大小是固定的,在vLLM中默认大小为16,即可装16个token的K/V值

  • 块表(block table)可理解为操作系统中的虚拟内存到物理内存的映射表

  • 物理内存(physical KV blocks)可理解为操作系统中的物理内存,物理块在gpu显存上,每个block类比于虚拟内存中的一个page\

Prefill阶段
  • 划分逻辑块:vLLM拿到这条prompt,先按照设定好的block大小B(本例中B=4),为prompt划分逻辑块(Logical KV blocks)。由于prompt中有7个token,所以vLLM用2个逻辑块(block 0, block 1)来装它们的KV值。其中,在逻辑块1中目前只装了"years", “ago”, "hour"这3个token的KV值,有1个位置是空余的。这个位置就被称为保留位(reservation)

  • 划分物理块:划分好逻辑块后,我们就可以将其映射到物理块中去了。物理块是实际存放KV值的地方。我们通过一张block table来记录逻辑块和物理块的映射关系,block table的主要内容包括:

  • 逻辑块和物理块的映射关系(physical block number):例如逻辑块0对应物理块7

  • 每个物理块上被填满的槽位(# filled):例如在prefill阶段,对物理块7,其4个槽位都被填满;对物理块1,其3个槽位被填满

Decode阶段-生成第1个词
  • 使用KV cache计算attention,生成第1个词fathers。不难发现,当我们计算时,我们使用的是逻辑块,即形式上这些token都是连续的。与此同时,vLLM后台会通过block table这个映射关系,帮我们从物理块上获取数据做实际计算。通过这种方式,每个request都会认为自己在一个连续且充足的存储空间上操作,尽管物理上这些数据的存储并不是连续的。

  • 基于新生成的词,更新逻辑块、物理块和block table。对于block table,vLLM将它filled字段由3更新至4。

  • 分配新的逻辑块和物理块。当fathers更新进去后,逻辑块已装满。所以vLLM将开辟新的逻辑块2,并同时更新对应的block table和物理块。

  • 类比步骤来进行生成第2 3 4个词。

处理多个请求

在这里插入图片描述

PagedAttention在不同解码场景下的例子

根据实际需求,大模型的解码方式也比较复杂,例如:

  • Parallel Sampling:我给模型发送一个请求,希望它对prompt做续写,并给出三种不同的回答。我们管这个场景叫parallel sampling。在这个场景中,我们可以将prompt复制3次后拼接成1个batch喂给模型,让它做推理。但我们也需注意到,这种方式会产生prompt部分KV cache的重复存储。

  • Beam Search:束搜索,这是LLM常用的deocde策略之一,即在每个decode阶段,我不是只产生1个token,而是产生top k个token(这里k也被称为束宽)。top k个token必然对应着此刻的top k个序列。我把这top k个序列喂给模型,假设词表的大小为|V|,那么在下一时刻,我就要在k*|V|个候选者中再选出top k,以此类推。不难想象每一时刻我把top k序列喂给模型时,它们的前置token中有大量的KV cache是重复的。

  • Shared prefix:在某些大模型中,所有请求可能都会共享一个前置信息(比如system message: “假设你是一个有帮助的AI助手…"),这些前置信息没有必要重复存储KV cache

  • 其余一般场景:在一些更通用的场景中,虽然两个prompt可能完全没有关系,但它们中某些KV cache却是可以共用的。例如两个prompt的相同位置(position)恰好出现了完全一样的序列,比如它们的结尾都是好想下班。假设这个相同序列已经存在于KV cache中,那也没有必要重复计算和存储了

Parallel Sampling

问题:parallel sampling的场景下,vLLM(PagedAttention)是怎么做到节省显存的
传统KV cache怎么做:假设模型的max_seq_len = 2048。传统KV cache可能在显存中分配两块长度是2048的空间。由于prompt一致,这两块2048的空间中存在大量重复的KV cache

在这里插入图片描述

假定我们发给模型1个request,这个request中包含2个prompt/sample,记为Sample A1和Sample A2,这两个prompt完全一致,都为Four score and seven years ago our,我们希望模型对这两个prompt分别做续写任务。

  • 首先,Prefill阶段,vLLM拿到Sample A1和Sample A2,根据其中的文字内容,为其分配逻辑块和物理块。

    • 分配逻辑块:对于A1,vLLM为其分配逻辑块block0和block1;对于A2,vLLM为其分配逻辑块block0和block1。需要注意的是,A1的逻辑块和A2的逻辑块是独立的(尽管它们都叫block0和block1),你可以将A1和A2视作操作系统中两个独立运行的进程。

    • 分配物理块:对于A1和A2,虽然逻辑块独立,但因为它们的文字完全相同,所以可以在物理内存上共享相同的空间。所以A1的逻辑块block0/1分别指向物理块block7/1;A2的逻辑块block0/1分别指向物理块block7/1。我们设每个物理块下映射的逻辑块数量为ref count,所以对物理块block7/1来说,它们的ref count都为2。

  • 然后,进入decode阶段,A1和A2各自做推理,得到第一个token,分别为fathers和mothers。

    • 将生成的token装入逻辑块:对于A1和A2来说,将其生成的token装入各自的逻辑块block1。

    • 触发物理块copy-on-write机制:由于fathers/mothers是两个完全不同的token,因此对物理块block1触发复制机制,即在物理内存上新开辟一块空间。此时物理块block1只和A2的逻辑块block1映射,将其ref count减去1;物理块block3只和A1的逻辑块block1映射,将其ref count设为1。

总结起来,vLLM节省KV cache显存的核心思想是,对于相同数据对应的KV cache,能复用则尽量复用;无法复用时,再考虑开辟新的物理空间

Beam Search

在这里插入图片描述
我们从右往左来看这张图。虚线位置表示“当前decoding时刻”,beam width = 4。图中所有的block皆为逻辑块。

因为beam width = 4,这意味着根据beam search算法,在当前阶段我们生成了top 4个概率最大的token(我们记这4个token为beam candidate 0/1/2/3),它们分别装在block5,block6,block7和block8中。

现在我们继续使用beam search算法做decoding,继续找出top 4个最可能的next token。经过我们的计算,这top 4 next token,有2个来自beam candidate 1,有2个来自beam candidate 2。因此我们在block6中引出block9和block10,用于装其中两个top 2 next token;对block7也是同理。

现在,block9/10/11/12中装的top 4 next token,就成为新的beam candidates,可以按照和上述一样的方式继续做beam search算法。而对于block5和block8,它们已经在beam search的搜索算法中被淘汰了,后续生成的token也不会和它们产生关系,所以可以清除掉这两个逻辑块,并释放它们对应的物理块的内存空间。

好,我们继续往左边来看这幅图。block3引出block5/6/7,block4引出block8,这意味着当前这4个top4 token,是上一个timestep下candidate1和candidate3相关序列生成的(candidate0和2的block没有画出,是因为它们所在的序列被beam search算法淘汰了,因此没有画出的必要)。由于block8已经被淘汰,所以block4也相继被淘汰,并释放对应的物理内存空间。

由此往左一路推,直到block0为止(block0代表着prompt,因此被beam seach中所有的序列共享)。这一路上,我们都根据最新时刻的beam search decoding结果,释放掉不再被需要的逻辑块和对应的物理内存空间,达到节省显存的目的

调度和抢占

到目前为止,我们已经回答了“vLLM是如何优化KV cache显存分配”的问题,现在我们来回答另一个重要的问题:

当采用动态分配显存的办法时,虽然明面上同一时刻能处理更多的prompt了,但因为没有为每个prompt预留充足的显存空间,如果在某一时刻整个显存被打满了,而此时所有的prompt都没做完推理,那该怎么办?

总原则

当有一堆请求来到vLLM服务器上时,vLLM需要一个调度原则来安排如何执行这些请求,这个调度原则概括如下:

  • 先来的请求先被服务(First-Come-First-Serve, FCFS)

  • 如有抢占的需要,后来的请求先被抢占(preemption)

(1)先来的请求先被服务这个很好理解,当有一堆请求到达vLLM服务器时,vLLM肯定优先处理来得早的请求

(2)后来的请求先被抢占想象一下,当一堆请求来到vLLM服务器做推理,导致gpu显存不足时,vLLM会怎么做呢?

最直接的办法,就是暂停这堆请求中最后到达的那些请求的推理,同时将它们相关的KV cache从gpu上释放掉,以便为更早到达的请求留出足够的gpu空间,让它们完成推理任务。如果不这样做的话,各个请求间相互争夺gpu资源,最终将导致没有任何一个请求能完成推理任务。等到先来的请求做完了推理,vLLM调度器认为gpu上有足够的空间了,就能恢复那些被中断的请求的执行了。

在资源不足的情况下,暂时中断一些任务的执行,这样的举动就被称为“抢占(preemption)”

终止和恢复被抢占的请求

对于这些因gpu资源不足而被抢占的任务,vLLM要完成两件事:

  • 暂停它们的执行,同时将与之相关的KV cache从gpu上释放掉

  • 等gpu资源充足时,重新恢复它们的执行

针对这两件事,vLLM分别设计了Swapping(交换策略)和Recomputation(重计算策略)来解决。我们来细看这两个策略

Swapping

对于被抢占的请求,vLLM要将其KV cache从gpu上释放掉,那么:

  • 问题1:该释放哪些KV cache?

    • 一个请求可能对应多个block。我们既可以选择释放掉部分block,也可以选择释放掉全部block,或者更科学地,我们可以预测一下哪些block被使用的频率最低,然后释放掉这些低频block(但这种方式实现起来难度较大,性价比不是很高)。在vLLM中,采取的是all-or-nothing策略,即释放被抢占请求的所有block
  • 问题2:要把这些KV cache释放到哪里去?

    • 对于这些被选中要释放的KV block,如果将它们直接丢掉,那未免过于浪费。vLLM采用的做法是将其从gpu上交换(Swap)到cpu上。这样等到gpu显存充份时,再把这些block从cpu上重载回来

recomputation

当vLLM调度器任务gpu资源充足时,对于那些被抢占的请求,它会将其卸载到cpu上的KV block重新加载进gpu中,继续完成推理任务

总结

  • 当一堆请求来到vLLM服务器上时,按照First-Come-First-Serve(FCFS)原则,优先处理那些最早到来的请求。

  • 当gpu资源不足时,为了让先来的请求能尽快做完推理,vLLM会对那些后到来的请求执行“抢占”,即暂时终止它们的执行。

  • 一旦vLLM决定执行抢占操作,它会暂停处理新到来的请求。在此期间,它会将被抢占的请求相关的KV block全部交换(swap)至cpu上。等交换完成后,vLLM才会继续处理新到来的请求。

  • 当vLLM认为gpu有足够资源时,它会将cpu上的KV block重新加载回gpu,恢复被抢占请求的执行(recomputation)

加载模型与预分配显存

大家有想过没有,vllm怎么知道到底应该分配多少显存的呢

加载模型

预分配显存

在模型部署的初始化阶段(推理正式开始前),vLLM会通过模拟实验的方式,来决定gpu/cpu上到底有多少个KV cache物理块可以分配给后续的请求们做推理。vLLM管这个步骤叫profile_num_available_blocks

杜撰假数据

  • 用户在初始化LLMEngine引擎时,会提供两个重要参数

    • max_num_seqs:在1个推理阶段中,LLMEngine最多能处理的seq数量(1条seq就是指我们待推理的1条数据)。默认是256
    • max_num_batched_tokens:在1个推理阶段中,LLMEngine最多能处理的token数量。默认是2048
  • 据这两个参数,我们可以假设在模型推理中,平均一个seq要处理max_num_batched_tokens // max_num_seqs个token,余数部分我们默认放在第一个seq中。例如,假设max_num_batched_tokens=10,max_num_seqs = 3,那么我们就能杜撰出3条seq,每个seq的长度分别为4,3,3

用假数据模拟一次前向推理

  • 我们现在想知道在1次推理过程中,可以分配多少的显存给KV cache。我们可以使用如下公式计算:

  • 分配给KV cache显存 = gpu总显存 - 不使用KV cache情况下做1次FWD时的显存占用(包括模型本身和FWD过程中的中间数据)

  • 对于“不使用KV cache做1次FWD时的显存占用”,我们就可以用杜撰出来的假数据模拟一次FWD来计算得出。在前向推理之后,我们把gpu上的缓存清一次,让它不要影响后续模型的正常推理

计算可分配的KV cache物理块总数

  • 我们已经预估了一块卡上“分配给KV Cache的总显存”。现在,我们可以来计算总的物理块数量了。

  • 我们易知:总物理块数量 = 分配给KV Cache的显存大小/ 物理块大小,其中“大小”的单位是bytes。

  • 物理块大小(block_size)也是可以由用户自定义的,vLLM推荐的默认值是block_size = 16

  • CPU上物理块总数也是同理,但与GPU不同的是,它不需要做模拟实验。CPU上可用的内存总数是用户通过参数传进来的(默认是4G)。也就是我们认为只能在这4G的空间上做swap。将上面公式中“分配给KV Cache的显存大小”替换成4G,就能得到CPU上物理块的数量

预分配的KV Cache加载到gpu上

当我们确定好KV Cache block的大小后,我们就可以创建empty tensor,将其先放置到gpu上,实现显存的预分配。以后这块显存就是专门用来做KV Cache的了.也正是因为这种预分配,你可能会发现在vLLM初始化后,显存的占用比你预想地要多(高过模型大小),这就是预分配起的作用

应用

  • 示例代码块
from vllm import LLM, SamplingParams

# ===========================================================================
# batch prompts
# ===========================================================================
prompts = ["Hello, my name is",
           "The president of the United States is",
           "The capital of France is",
           "The future of AI is",]

# ===========================================================================
# 采样参数
# ===========================================================================
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)

# ===========================================================================
# 初始化vLLM offline batched inference实例,并加载指定模型
# ===========================================================================
llm = LLM(model="facebook/opt-125m")

# ===========================================================================
# 推理
# ===========================================================================
outputs = llm.generate(prompts, sampling_params)

# ===========================================================================
# 对每一条prompt,打印其推理结果
# ===========================================================================
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

在传统离线批处理中,我们每次给模型发送推理请求时,都要:

等一个batch的数据齐全后,一起发送
整个batch的数据一起做推理
等一个batch的数据全部推理完毕后,一起返回推理结果
这种“团体间等成员到齐,再一起行动”的行为,就被称为“同步”

# ===========================================================================
# Server:起服务
# ===========================================================================
$ python -m vllm.entrypoints.openai.api_server --model meta-llama/Llama-2-7b-hf

# ===========================================================================
# Client:发请求(OpenAI API)
# ===========================================================================
$ curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "meta-llama/Llama-2-7b-hf",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }'
  • 如果应用层想使用vllm, openai_api_server才是官方推荐的使用方式
  • 在这个过程中,vLLM通过PagedAttention技术和“先来先服务(FCFS),后来先抢占,gpu不够就先swap到cpu上”的调度策略,在1个推理阶段处理尽可能多的请求,解决高并发场景下的推理吞吐问题。这就是整个vLLM运作的核心思想
  • vLLM在实现在线服务时,采用uvicorn部署fastapi app实例,以此实现异步的请求处理。而核心处理逻辑封装在AsyncLLMEngine类中(它继承自LLMEngine)

LLMEngine

  • add_request():该方法将每一个请求包装成vLLM能处理的数据类型(SequenceGroup,后面我们会详细解释),并将其加入调度器(Scheduler)的waiting队列中。在LLMEngine中,这个函数是按照“同步”的方式设计的,也就是它被设计为“遍历batch中的每条数据,然后做相应处理”。所以这个函数本身只适合批处理场景。在异步的online serving中将会把它重写成异步的形式。

  • abort_request:在推理过程中,并不是所有的请求都能有返回结果。比如客户端断开连接时,这个请求的推理就可以终止了(abort),这个函数就被用来做这个操作。

  • step():负责执行1次推理过程(1个prefill算1个次推理,每个decode各算1次推理)。在这个函数中,vLLM的调度器会决定要送那些数据去执行本次推理,并负责给这些数据分配好物理块(这些信息都被作为metadata放在要送给模型做推理的数据中)。模型会根据这些信息,采用PagedAttention方法,实际完成推理

一句话总结:vLLM真得是一个巨大的工程,它耦合了调度工程和模型本身的改造,代码中的嵌套非常复杂

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

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

相关文章

OS复习笔记ch11-4

磁盘调度 磁盘的物理结构 经典的温彻斯特盘 其中的几个概念: 盘面:可以看成是一个操场的平面,不同的盘面通过中间的轴串在一起磁道:磁道可以看成是操场的跑道,我们知道操场上有外道和内道,最内道中间往…

【FlowShop流水线作业排班问题【数学规划的应用(含代码)】阿里达摩院MindOpt】

本文主要讲述使用MindOpt工具优化FlowShop流水线作业排班的数学规划问题。 一、案例场景 FlowShop流水线作业排班也有称为生产下料问题,它涉及到多台机器、多个工序以及多个作业调度安排。在这个问题中,我们需要对多个作业在一组流水线上的处理顺序进行…

矩阵中严格递增的单元格数

题目链接:leetcode:矩阵中严格递增的单元格数 描述 给你一个下标从 1 开始、大小为 m x n 的整数矩阵 mat,你可以选择任一单元格作为 起始单元格 。 从起始单元格出发,你可以移动到 同一行或同一列 中的任何其他单元格,但前提是目…

修复kazam意外中断的视频文件

0. Problem 在用kazam录视频的过程中,PC意外重启了,然后kazam没有把文件自动转换成MP4,而是存为以下两个文件: kazam_xxxxx.movie kazam-xxxxx.movie.mux这两个文件一个0k,另一个是有size的,但是没办法直…

[SAP ABAP] 变量与常量

1.变量 定义变量的基本方式 DATA <name> TYPE <type> [VALUE <val>]. <name>&#xff1a;指定变量的名称 <type>&#xff1a;指定变量的数据类型 <val>&#xff1a;指定<name>的初始值 示例1 定义变量lv_data1和lv_data3 输出结果…

【CMake】CMake从入门到实战系列(十七)—— CMake添加环境检查

&#x1f525;博客简介&#xff1a;开了几个专栏&#xff0c;针对 Linux 和 rtos 系统&#xff0c;嵌入式开发和音视频开发&#xff0c;结合多年工作经验&#xff0c;跟大家分享交流嵌入式软硬件技术、音视频技术的干货。   ✍️系列专栏&#xff1a;C/C、Linux、rtos、嵌入式…

【C语言】16.动态内存管理

文章目录 1.为什么要有动态内存分配2.malloc和free2.1 malloc2.2 free 3.calloc和realloc3.1 calloc3.2 realloc 4.常见的动态内存的错误4.1 对NULL指针的解引⽤操作4.2 对动态开辟空间的越界访问4.3 对⾮动态开辟内存使⽤free释放4.4 使⽤free释放⼀块动态开辟内存的⼀部分4.5…

redis高可用-集群部署

一&#xff1a;背景 前面我们实现了redis的主从同步和哨兵模式&#xff0c;解决了单机redis的故障转移和流量分担&#xff0c;但是不管是主从或者哨兵模式都是一个主服务对应一个或者多个从服务&#xff0c;并且主服务和从服务的数据是一样的&#xff0c;这样就实现不了redis大…

JSP基础知识概述

目录 JSP一、什么是JSP1.1 概念1.2 创建JSP1.3 JSP编写Java代码1.4 JSP实现原理 二、JSP与HTML集成2.1 普通脚本2.2 声明脚本2.3 输出脚本2.4 JSP指令2.5 动作标签 三、内置对象3.1 四大域对象 JSP 一、什么是JSP 1.1 概念 简化的Servlet设计&#xff0c;在HTMl标签中嵌套Jav…

Python学习打卡:day12

day12 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day1292、全国疫情地图构建数据整理获取数据数据整体结构&#xff08;全国&#xff09;省数据结构获取每个省份的确诊数据上述代码执行后输出&…

Homebrew使用

官网&#xff1a;https://brew.sh/ 安装&#xff1a; 简介&#xff1a;https://www.jianshu.com/p/f4c9cf0733ea 比如&#xff0c;安装maven: 1、brew install maven 2、查看安装路径&#xff1a;brew list maven 具体参考&#xff1a;https://blog.csdn.net/m0_67402970/arti…

创业众筹网

摘 要 创业是社会经济发展的重要动力&#xff0c;其在任何经济发展时期任何国家都最具活力与桃战性。然而创业的资金却是90%创业者面临的首要问题。包括积蓄不足、无不动产、负债、不知如何向银行申贷,及无法预估所创行业之总资金、成本。部分创业者虽然有心创业&#xff0c;但…

Python学习笔记16:进阶篇(五)异常处理

异常 在编程中&#xff0c;异常是指程序运行过程中发生的意外事件&#xff0c;这些事件通常中断了正常的指令流程。它们可能是由于错误的输入数据、资源不足、非法操作或其他未预料到的情况引起的。Python中&#xff0c;当遇到这类情况时&#xff0c;会抛出一个异常对象&#…

抖音开放平台代开发小程序,上传模板代码

大家好&#xff0c;我是小悟 抖音小程序第三方平台开发着力于解决抖音生态体系内的小程序管理问题&#xff0c;一套模板&#xff0c;随处部署。能尽可能地减少服务商的开发成本&#xff0c;服务商只用开发一套小程序代码作为模板就可以快速批量的孵化出大量的商家小程序。 第…

旋转式滚珠花键在自动装载机中的作用!

自动装载机是一种广泛用于公路、铁路、建筑、水电、港口、矿山等建设工程的土石方施工机械&#xff0c;是工程建设中土石方施工的主要机种之一。而旋转式滚珠花键作为自动装载机中重要的传动元件&#xff0c;在自动装载机的运用起着重要的作用。 自动装载机主要用于铲装土壤、砂…

Windows环境下使用VisualGDB进行Linux项目开发

1.新建项目-打开文件下的新建项目菜单 2.工程项目类型配置 3.Linux机器选择设置 4.设置代码位置 5.编译选项设置 6.调试环境设置

数据库精选题(二)(引言+关系代数)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;数据库 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 常见概念 一、什么是数据库&#xf…

QT4-QT5升级(4)GBK-UTF-8-乱码-QT工程构建的区别

文件编码为UTF-8 QString 正常 char* 中文乱码解决方法&#xff1a; #include <iostream> #include <QString> #include <QTextCodec>int main() {QString qstr "你好&#xff0c;世界&#xff01;"; // 中文字符串// 获取 GBK 编码的 QTex…

异步开发的终极答案—协程

我们在之前的文章中讲过,在并发场景下,传统的基于多线程的命令式开发模型虽然比较简单,但并发数高了之后资源占用较高,大量线程会阻塞;而响应式编程模式我们可以通过异步化处理提升系统资源的利用效率,但异步开发有违人的直觉,门槛比较高。作为成年人,我们肯定希望全都…

Echarts 社区分享

​ 首页先确定你要使用的Echarts版本&#xff0c;因为现在Echarts5以上版本引入地图与之前版本有所不同。但是大多数地图不太容易搞分享一些比较好用的ECharts社区网站。 1、makeapie 网址&#xff1a;makeapie 2、isqqw 网址&#xff1a;echarts图表集 3、PPchart 网址…