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

在这里插入图片描述

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真得是一个巨大的工程,它耦合了调度工程和模型本身的改造,代码中的嵌套非常复杂

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Anthropic AI模型Claude 3.5 Sonnet在Amazon Bedrock上正式可用

Claude 3.5 Sonnet是Anthropic最先进的Claude系列AI模型的新成员,比Claude 3 Opus更智能且价格只有其五分之一 北京——2024年6月21日 亚马逊云科技宣布,Anthropic最新、最强大的模型Claude 3.5 Sonnet现已在Amazon Bedrock上正式可用,该模型…

在智星云租用算力时,如何选择适合的GPU?

智星云平台分配GPU、CPU、内存的机制为:按租用的GPU数量成比例分配CPU和内存,算力市场显示的CPU和内存均为每GPU分配的CPU和内存,如果租用两块GPU,那么CPU和内存就x2。此外GPU非共享,每个实例对GPU是独占的。 一. CPU…

推动产业数字化转型,六个方面引领变革

从工业经济时代走向数字经济时代,世界经济发生着全方位、革命性的变化,产业数字化便是最显著的表现之一。当前,产业数字化不断深入发展,平台经济、工业互联网、智能制造等新业态、新模式不断涌现,成为了数字经济的重要…

API低代码平台介绍5-数据库记录修改功能

数据库记录修改功能 在上篇文章中我们介绍了如何插入数据库记录,本篇文章会沿用上篇文章的测试数据,介绍如何使用ADI平台定义一个修改目标数据库记录的接口,包括 单主键单表修改、复合主键单表修改、多表修改(整合前两者&#xff…

1.接口测试-postman学习

目录 1.接口相关概念2.接口测试流程3.postman基本使用-创建请求(1)环境(2)新建项目集合Collections(3)新建collection(4)新建模块(5)构建请求请求URLheader设…

pretender:一款功能强大的红队MitM安全测试工具

关于pretender pretender是一款功能强大的红队MitM安全测试工具,该工具专为红队研究人员设计,该工具不仅能够进行MitM和中继攻击,而且还支持执行DHCPv6 DNS接管以及mDNS、LLMNR和NetBIOS-NS欺骗攻击。在该工具的帮助下,广大研究人…

学生护眼大路灯应该怎么选?五款护眼大路灯对比推荐

我们都知道光线无处不在,想要减少近视隐患,就不得不提一下护眼灯了,特别是经常坐在电脑前码字的上班族以及深夜还在学习的学生党这一类人群,经常用眼光线不好不仅影响视力健康,还会影响效率。而一款护眼灯能够提供柔和…

Vue的学习之安装Vue

目录 一、Vue的特点 二、Vue的学习 一、Vue的特点 1.采用组件化模式(xxx.vue包含htmlcssjs) 2.声明式编码,编码人员无需直接操作DOM,提高开发效率 3.使用虚拟DOM优秀的DIFF算法(DIFF是用于新旧虚拟DOM的比较&#…

Adobe Dimension(Dn)下载:附安装包+详细教程

Adobe Dimension常常被简称为Adobe DN,它是一款三维建模设计软件,能让图形设计工作者在平面基础上构建高质量、高逼真的3D图像,将2D/3D构建成可视化场景。除此之外,它还能与 Photoshop和Illustrator等软件紧密的结合起来&#xff…

autodeauth:一款功能强大的自动化Deauth渗透测试工具

关于autodeauth autodeauth是一款功能强大的自动化Deauth渗透测试工具,该工具可以帮助广大研究人员以自动化的形式针对本地网络执行Deauth渗透测试,或者枚举公共网络。当前版本的autodeauth已在树莓派OS和Kali Linux平台上进行过测试,之后的…

易宝OA downloadfile 任意文件读取

【产品&&漏洞简述】 易宝OA系统是一种专门为企业和机构的日常办公工作提供服务的综合性软件平台,具有信息管理、 流程管理 、知识管理(档案和业务管理)、协同办公等多种功能 易宝OA downloadfile 文件读取,攻击者可通过…

Spring Boot 集成 MinIO 实现文件上传

Spring Boot 集成 MinIO 实现文件上传 一、 Minio 服务准备 MinIO的搭建过程参考 Docker 搭建 MinIO 对象存储。 登录MinIO控制台&#xff0c;新建一个 Bucket&#xff0c;修改 Bucket 权限为公开。 二、MinIO 集成 添加 MinIO 依赖 <!-- https://mvnrepository.com/ar…

难辨真假的Midjourney案例(附提示词):适合练手

人物 时尚女孩 Street style fashion photo, full-body shot of a young Chinese woman with long curly black hair, walking confidently with a crowd of people down a sidewalk in Hong Kong, wearing a emerald green Gucci maxi dress & gold jewelry, sunset lig…

英伟达和IBM搞事情!主攻“量子计算+AI”

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨娴睿/慕一 排版丨沛贤 深度好文&#xff1a;2000字丨8分钟阅读 Ismael Faro是一位计算机工程师&#xff0c;自2015年以来&#xff0c;他就成为开发IBM量子软件生态系统的重要人物。从2016…

2748. 美丽下标对的数目(Rust暴力枚举)

题目 给你一个下标从 0 开始的整数数组 nums 。如果下标对 i、j 满足 0 ≤ i < j < nums.length &#xff0c;如果 nums[i] 的 第一个数字 和 nums[j] 的 最后一个数字 互质 &#xff0c;则认为 nums[i] 和 nums[j] 是一组 美丽下标对 。 返回 nums 中 美丽下标对 的总…

DualSPHysics运行报错ERROR: Some boundary particle was excluded.

如下查看输出&#xff0c;看到报错ERROR: Some boundary particle was excluded.某些边界粒子超出了模拟域的X限制&#xff08;右限制&#xff09;&#xff0c;具体错误的边界溢出粒子储存在Error_BoundaryOut.vtk里边。 用paraview打开Error_BoundaryOut.vtk还有边界的stl&am…

React路由笔记(函数组件,自用)

配置 npm i react-router-dom基本使用 目录结构 在src中创建page文件夹放置各页面组件&#xff0c;router中放置路由 1、router中配置路由 在/router/index.js中&#xff0c;使用createBrowserRouter配置路由。 import { createBrowserRouter } from "react-router…

mybatis框架相关问题总结(本地笔记搬运)

1、背景 2、运行启动问题 问题一 运行spring boot项目时报错&#xff1a;‘factoryBeanObjectType‘: java.lang.String 解决一 版本问题&#xff0c;springframework版本和mybatis/mybatis-plus版本不兼容。现spring-boot使用3.3.0版本&#xff0c;mybatis-plus使用3.5.7…

js处理数据(过滤)

复选框的值这里为true或false 选中为true&#xff0c;未选中为false 看看数据&#xff1a; type中的前面那些字母是固定的不会变 括号里面的不固定&#xff0c;那就把固定的作为前缀去过滤&#xff0c;后面怎么变都无所谓&#xff0c;当checkbox三个值中的某个或某些值为false时…

如何使用LiveTargetsFinder生成实时活动主机URL列表

关于LiveTargetsFinder LiveTargetsFinder是一款功能强大的实时活动主机生成工具&#xff0c;该工具可以为广大研究人员以自动化的形式生成可供分析和测试的实时活动主机URL列表&#xff0c;并通过MassDNS、Masscan和Nmap自动过滤出无法访问的主机。 我们只需要提供一个域名作…