0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

LLMEngine下一层级的模块内如何实现各自功能接口

jf_pmFSk4VX 来源:GiantPandaCV 2023-08-21 09:45 次阅读

最近业余时间在看新番vLLM,在读源码过程中,对其显存管理原理有了清晰的认识。vLLM系统主要是基于python+cuda实现的,很多其他python项目实现都很混乱(各种重复代码、语意不明/模糊的抽象设计),但vLLM的系统设计却特别工整,为怕遗忘,特别开启本篇,top down的记录一下vLLM框架结构。

回到vLLM这个项目,vLLM针对GPT类模型推理过程中KVCache这个显存占用大头专门设计了block_table,将KVCache分段成多个block存储在GPU中。一方面,这种设计可以共用beam search多batch之间share prompt sequence(的KVCache),减少显存占用。另一方面,在gpu显存和cpu内存间调度这些block,可以在有限的gpu显存空间下同时推理更大batch的sequence,换句话说,就是尽可能拉满gpu显存使用率,提高吞吐。

本篇文章将会按top down的方式介绍整个系统。先总览整个框架包含的基本类型,包括类型之间的关系、各类职责。然后,针对系统入口LLMEngine,介绍各个类之间如何通过接口互相组织完成推理过程,加深各个类功能的抽象理解。更进一步的,分析LLMEngine下一层级的模块内如何实现各自功能接口。(后续也会抽时间专门开一篇介绍vLLM中用到的cuda ops源码,特别是PageAttention部分,敬请期待)

框架概览

271dae0c-3f2c-11ee-ac96-dac502259ad0.jpg

vLLM类关系图

整个框架核心的模块关系如上:

LLMEngine:是整个系统的入口,add_request负责输入prompt请求,step迭代推理,最终返回LLM生成的结果。其内部组合了一个Scheduler和一组Worker。

Scheduler:在每个推理步调度可处理的Sequence输入信息,其组合包含了一个BlockSpaceManager

BlockSpaceManager:维护gpu显存和cpu内存的使用情况,以及Sequence对应Cache的BlockTable信息。

Worker:在每个推理步执行LlamaForCausalLM推理,并返回采样后结果。除一个LLM模型外,其另一个核心组件是CacheEngine。

CacheEngine:负责执行相关gpu、cpu空间的换入、换出、拷贝等操作。

LLMEngine

275bb2ce-3f2c-11ee-ac96-dac502259ad0.jpg

LLMEngine实现细节

从图中可以看到,从上到下按先后顺序LLMEngine分别进行了__init__、add_request、step。

在构造LLMEngine时,LLMEngine就会调用Worker中的CacheEngine,初始化gpu、cpu空间,计算能容纳多少个block。每个block包含block_size个token对应的各层KVCache大小。在后续的组织中都会将Sequence对应的KVCache分成block_size大小的cache block,以方便管理对应block的空间。

add_request接口执行多次,接收多个待处理的prompt,将prompt处理成对应token的Sequence。每个输入prompt构造一个SequenceGroup, 其中包含了多个重复的Sequence为后续beam search做准备。SequenceGroup会最终保存到Scheduler中,以进行后续的调度。

step执行一个推理步。首先Scheduler会调度一组SequenceGroup和相关信息作为当前推理步的执行输入,除了输入外,还会包含当前SequenceGroup所需KVCache的换入换出信息。然后,Worker会将执行一次LLM推理(当然会先执行CacheEngine先准备KVCache)。Worker采样的输出结果会再次更新到Scheduler中的SequenceGroup内,以更新其内部的状态。最后,多次step循环,直到所有prompt对应的SequenceGroup都生成结束。

Scheduler & BlockSpaceManager

Scheduler

Scheduler中包含了三个队列:waitting、running、swapped。每当新增一个SequenceGroup时,添加至waitting队列中。

276dd332-3f2c-11ee-ac96-dac502259ad0.jpg

这三个队列之间的关系如下:

279b5c58-3f2c-11ee-ac96-dac502259ad0.jpg

waitting:等待计算KVCache的SequenceGroup(也就是prompt序列)

running:执行推理的SequenceGroup,会在当前step中作为输入,一共包含两类:

prompt:来自waitting,未计算KVCache的SequenceGroup

generate token:计算过KVCache的SequenceGroup,准备生成下一个token

swapped:KVCache暂时换出到cpu内存的SequenceGroup

在每次schedule执行时,会调度几个队列之间的SequenceGroup,维护队列间的状态,使得当前执行推理尽可能占满显存空间。详细逻辑如上图中的数字标号顺序所示,值得注意的是,通过调度能实现两种解决显存不足的策略,一个是换出到cpu中,另一个就是重计算了(只有在SequenceGroup内只有一个Sequence的情况才能使用)。

当SequenceGroup推理新增了token时,update接口会更新一遍SequenceGroup内的状态。如下图所示,SequenceGroup内包含一组beam search的seq,每次执行推理的时候,每个seq采样s次,那么久会生成n*s个生成的token,根据这里面token保留置信度topn个生成结果。下图所示的结果就是n=4的情况,可以看到topn保留的output里seq1和seq3都来自原始输入seq1(parent_seq=1),此时需要BlockSpaceManager将原始的seq3释放(因为保留的output里没有seq3的输出),然后从seq1拷贝/fork到seq3,再讲新token添加到各个seq中。

27d4af76-3f2c-11ee-ac96-dac502259ad0.jpg

BlockSpaceManager

BlockSpaceManager的功能是管理各个SequenceGroup对应KVCache存储信息。回顾LLMEngine提到过的,每个Sequence的KVCache序列会分成多个block_size长度的cache block,每个cache block的位置信息记录在BlocKspaceManager。如下图所示,BlockSpaceManager包含一个block_tables,其记录cache block到gpu显存或cpu内存物理地址的映射。

27e7fe00-3f2c-11ee-ac96-dac502259ad0.jpg

SequenceGroup刚加入Scheduler的时候并没有分配cache block空间,第一次进入running的时候需要向BlockSpaceManager申请可用的block空间。如下图所示,BlockSpaceManager分配block空间是以一个SequenceGroup作为一组输入,而且默认分配空间的时候,所有SequenceGroup内的token都是一样的(即是相同的prompt),因此会为所有Sequence都指向同一片cache block区域,该区域被引用数为Sequence数量。

当需要为一个Sequence新增token时,如下图所示,有两种情况:

当前cache block空间不足添加新token,则新增cache block。

当前空间足以添加新token,但last block与其他Sequence共用时(ref_count>1),如果新token还是添加到last block,那么会与共用last block的其他Sequence冲突,则需要释放掉last block(free不是真的释放,只是ref_count-1),分配一个新的last block。最后,返回信息标记原本last block内容需要拷贝到这个新的last block,即所谓的“copy-on-write”。

28209062-3f2c-11ee-ac96-dac502259ad0.jpg

最后就是BlockSpaceManager其他接口的实现图解了,详细可参加下图:

28547210-3f2c-11ee-ac96-dac502259ad0.jpg

实际上,BlockSpaceManager只负责维护cache block到gpu/cpu空间的索引,真正进行换入、换出、拷贝操作都需要通过Worker中CacheEngine进行。因此在Scheduler调度的时候,也会收集BlockSpaceManager返回结果,得到当前step所需KVCache的block_to_swap_in、block_to_swap_out、block_to_copy,以供后续CacheEngine操作内存空间。

Worker

Worker负责缓存更新执行和LLM推理执行。关于Worker的这个图比较长,因此这里截断成两张图来看。

2875684e-3f2c-11ee-ac96-dac502259ad0.jpg

如上图所示,Worker在执行时首先进行两个操作。

缓存更新:SchedulerOutputs包含了前面提到的当前所需swap in/swap out/copy的cache block信息,然后通过CacheEngine自定义的ops去执行缓存搬运操作,得到cuda stream的event,后续会在推理LLM各层的时候用到。

准备输入token序列__prepare_input:上图右侧的框内是预处理的过程,将SequenceGroupMetadata包含Scehduler调度得到running的所有SequenceGroup组合一个flatten的token序列,作为LLM的初始输入。Scheduler那节中提到过,running队列中当前执行的SequenceGroup有两类:一类未计算prompt(前缀)的KVCache,这部分需要完整的prompt token输入去计算各层的KVCache(全量推理)。另一类已经计算并缓存前缀的KVCache,因此只需要last token作为输入计算下一个generation token的分布(增量推理)。如上图所示,输入token序列的前半部分是多个prompt的token全量推理序列,后半部分是各个增量推理序列的last token。此外,全量推理的SequenceGroup中多个Sequence共享prompt,因此只需要任意一个Sequence的prompt作用输入就行。

289d68d0-3f2c-11ee-ac96-dac502259ad0.jpg

上图是Worker执行LLM模型的过程。由__prepare_input组装的flatten token在各层映射成flatten hidden state。除了线性层、激活层等token独立计算的层以外,attention层的计算涉及不同token的hidden state依赖。上图主要展开了Attention层的四个主要步骤:

prompt全量推理:prompt序列通过xformers的attention算子计算得到下个layer的hidden state。由于这里attention计算的输入是完整的tensor,不是KVCache中分散的cache block,所以可以用第三方的attention算子完成计算。

等待缓存事件:CacheEngine中发送了异步缓存操作,因此只有当前层序列的cache block完成缓存更新,才能进一步获取KVCache或者记录KVCache,这种异步的实现能通过overlap计算和缓存搬运,节省一部分缓存搬运时间。

记录当前KVCache:当前层输入的hidden state作为KVCache通过自定义算子记录到对应cache block内,这里记录所有有效token的hidden state,包括prompt和last token(last token是前几次step中新增的,所以也没有缓存hidden state到KVCache)。

generation token增量推理:vLLM的核心PageAttention即在此实现,这里作者通过一个自定义算子(好像是参考Faster Transformer实现),实现了基于BlockTable分散KVCache的增量attention计算。

最后LLM内的采样器进行采样,将beam_search结果(新token)返回给Worker输出。

碎碎念

至此,笔者基本完成想要表达的的vLLM top down系统架构,相关的框架drawio已上库(图画的都有点挫,文章里可能不方便看。。),希望这篇文章能帮助有意愿在vLLM上做贡献的小伙伴。针对vLLM作者设计的cache_ops、attention_ops的自定义实现,笔者也会利用业余时间学习,补一篇文章进行介绍。

审核编辑:彭菁

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 模块
    +关注

    关注

    7

    文章

    2664

    浏览量

    47318
  • 接口
    +关注

    关注

    33

    文章

    8475

    浏览量

    150781
  • 模型
    +关注

    关注

    1

    文章

    3143

    浏览量

    48680
  • python
    +关注

    关注

    55

    文章

    4777

    浏览量

    84423
  • GPT
    GPT
    +关注

    关注

    0

    文章

    351

    浏览量

    15304

原文标题:vLLM框架top down概览

文章出处:【微信号:GiantPandaCV,微信公众号:GiantPandaCV】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    簇嵌套簇的中控件属性如何操作

    请问各位大大,我想控制簇嵌套簇中的某个控件的可见属性应该怎样做呢?发现可以控制簇的下一层控件的属性,但是再下一层簇中的控件就不知怎控制了。如图所示,我想控制让其中个布尔控件不可见。怎做到?
    发表于 07-06 23:59

    单片机程序设计的十功力,你练到那一层了?

    简单模块化,如何合理的利用CPU的时间。我曾经写过这一层点简单教程。对付这一层应该是绰绰有余了。第三 并肩作战,时间,说爱你不容易。
    发表于 11-01 08:50

    单片机程序设计的十功力,你练到那一层了?

    一层 : 我来了处在这一层的典型是可以用C语言写简单的逻辑控制,如闪烁LED,简单数码管显示,简单外围模块驱动实验。般对单片机感兴趣,经常动手实践的人,半年左右,可以练到此地步(针
    发表于 09-07 10:13

    EPON系统三接口设计

    软硬件平台驱动之上,三功能协议及管理之下,是三功能
    发表于 06-06 05:00

    如何添加一层机械

    请问如何添加一层机械?谢谢
    发表于 09-17 02:56

    用Verilog/SystemVerilog快速实现个加法树

    reduceBalancedTree提供了两个方法调用:Vec类型和Seq类型均可以调用该方法,该方法实现为:从源代码里可以看出,该方法最终的实现依赖两个参数:op: (T, T) => T 树的每一层级的操作,上面
    发表于 08-01 14:29

    STM32F1 LWIP开发手册

    了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,
    发表于 09-27 08:23

    嵌入式多功能接口转换器的设计与实现

    本文研究如何在嵌入式开发平台上构建台多功能接口转换器。使得不同接口的智能设备能通过该接口转换器实现数据统
    发表于 07-01 17:04 24次下载

    由Python算法编程来实现神经网络设计理论

    、多层前向神经网络 多层前向神经网络由三部分组成:输出、隐藏、输出,每层由单元组成; 输入由训练集的实例特征向量传入,经过连接结
    发表于 11-16 12:34 1159次阅读
    由Python算法编程来<b class='flag-5'>实现</b>神经网络设计理论

    基于运动外观多通道层级ICA编码模型

    ,进行三学习拓展,采用ICA统计方法提取视觉感知编码模式,利用HMAX机制实现层级信息
    发表于 11-30 17:03 0次下载
    基于运动外观多通道<b class='flag-5'>层级</b>ICA编码模型

    文全方位了解深度学习的诞生及未来

    对于深度学习来说,其思想就是对堆叠多个,也就是说这一层的输出作为下一层的输入。通过这种方式,就可以实现对输入信息进行分级表达了。
    发表于 10-07 15:16 4713次阅读

    AUTOSAR 基础软件

    基础软件主要是用于提供基础软件服务,包括标准化的系统功能以及功能接口,并且由系列的基础服务软件组成,包括系统服务、内存服务、通信服务等。
    发表于 12-22 19:03 26次下载
    AUTOSAR 基础软件<b class='flag-5'>层</b>

    Kepware如何实现不同层级的冗余

    在工业通信领域中,为了确保系统的正常运行,减少故障时间,提高可靠性,通常都需要对系统进行冗余设置。在冗余配置的过程中经常会遇到些这样那样的问题,下面就起来看看,Kepware如何实现不同
    发表于 04-22 10:38 1139次阅读
    Kepware如何<b class='flag-5'>实现</b>不同<b class='flag-5'>层级</b>的冗余

    什么是type-c全功能接口 Type-C充电接口和type-c全功能接口有什么不同

    Type-C全功能接口,也被称为USB-C全功能接口,是种多功能的连接接口标准。它是由USB Implementers Forum(USB
    的头像 发表于 08-03 14:32 4.5w次阅读

    克服设计难题-实现高性能接口

    电子发烧友网站提供《克服设计难题-实现高性能接口.pdf》资料免费下载
    发表于 08-28 09:41 0次下载
    克服设计难题-<b class='flag-5'>实现</b>高性<b class='flag-5'>能接口</b>