S-LoRA:同时应用多个LoRA模块并行推理

写在前面:当base大模型在垂类任务上表现一般,需要少量参数微调时,流行的做法就是对其进行少量参数(LoRA)微调时。这样难免存在每个任务有个单独的adapter ,推理时需要把adapter与base model参数加到一起再推理。这样做的结果是,在推理时依然是一任务一模型,仿佛又回到了BERT时代,但是每个模型的参数却大得多,推理需要的显存更多。既然base模型一样,有没有什么方法能够节省推理的GPU显存,构造一个统一的推理模型,能够满足所有的业务场景呢?S-LoRA为我们提供了一种很好的工程上的解决方案。

0. 快速复习LoRA

论文:LORA : LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

LoRA:Low-Rank Adaptation,LoRA冻结预训练模型权重并将可训练的秩分解矩阵注入到 Transformer 架构的每一层中,大大减少了下游任务的可训练参数的数量(推理参数不变)。它基于的假设就是,下游任务需要修改的特征空间是低秩的,即只要针对优化少量参数即可。类似是传统的Adapter思想,但是它的设计比较巧妙,就是用d×r和r×d(r远小于d,r一般又被称为秩)的参数矩阵A×B来做自适应学习的那部分(如下图所示),推理时只要跟base模型的d×d参数矩阵加起来就可以了,不会有额外的推理需求增加。但是这样做有个隐患就是,依然是一任务一模型。之前的解决方案是通过对参数矩阵的加减实现一个base模型加载多个adapter,但是这样依然不能实现在一个batch里同时使用多个lora。

1. S-LoRA解决了什么问题?

论文:S-LoRA: Serving Thousands of Concurrent LoRA Adapters

S-LoRA实现了同时用多个LoRA的Adapters并行推理,即1 base model + n Adapters(A×B的部分),相比于n models,在推理时就可以减少显存占用。

2. S-LoRA为什么能做到的?

S-LoRA在具体实现上有很多细致的设计,能够真正支持多Adapters并行化推理,又能尽量减少推理显存消耗,并减少相比一任务一模型的结构的中间的性能损失。论文主要从3大方面来解释它的做法的,分别是:推理请求的并行化处理、内存管理和张量并行。

2.1 请求的并行化处理

为了减少base model的数量,本文将base模型和adapter分开计算(如下图所示),而不是直接把参数合在一起了,是在分别计算后再把两个模型输出的结果加起来。

道理看起来简单,实际操作却暗含很多需要优化的问题:lora的每个adapter的秩不一定一样,就导致每个批次里请求的adapter参数矩阵大小不一,如何并行化处理?每批次请求用到的adapter都不一样,如何调度?等等问题

2.1.1 token-level的批处理调度方法

为了实现显存的尽可能高的利用率,这里的batch request调度采用了orca(Yu@OSDI2022, ORCA: A Distributed Serving System for Transformer-Based Generative Models)调度方法,实现token-level的迭代调度。详细讲解可以看作者自己的报告视频(一般的批处理调度-视频第6分钟左右; orca批处理调度-第8分钟左右):Orca: A Distributed Serving System for Transformer-Based Generative Models | USENIX

简要来说,就是orca构造了一个请求池,每并行处理完所有序列的一个token长度就把没结束生成的requests放回池子里,新来的requests也按照来的顺序放在一起,等下次生成再从请求池里调度不超过最大batch-size的请求进行处理。

2.1.2 对请求按照Adapter聚在一起

为了尽量降低推理显存占用,就需要在每个batch的请求里尽量用最少的Adapters,即尽量把相同Adapter的请求放在一个batch里,这样每次需要从内存中取出的adapter的数量就会比较少,减少并行推理时的显存。

2.1.3 准入控制

S-LoRA还介绍了它在请求高并发状态的处理方式。对于每个请求,S-LoRA会预先衡量这个请求在当前状态下的处理时延用户是否能够接受,不能被接受的话,就会被直接抛弃掉。如果一下来的请求过多,它会选择只处理时间顺序相对靠后的满足时延的请求。相比于超时才显示请求失败这种硬性门槛,可以提高响应效率,对于完不成的请求就会直接显示失败,不需要用户等了指定时间超时了才显示。

2.1.4 新的GPU算子

这一小节主要介绍的是S-LoRA采用的CUDA算子。论文中是放在了内存管理里介绍的,笔者认为这部分对于实现并行推理也很重要,就放在前面了。

为什么要用新的CUDA算子?原来我们在GPU中一般用到的矩阵计算算子是GEMM,GEMM是并行化处理矩阵的,我们在推理时,通过paddding把一个batch的所有序列填充到一样长度之后,每批次的输入,模型的各部分计算的矩阵大小是固定的,GEMM就可以实现各部分的并行计算。但是这样GPU的本身利用率就不高,再加上LoRA的Adapter的异构性(秩r的大小不一致),就导致原来的矩阵算子(GEMM)不能实现并行计算。于是,S-LoRA采用的是MBGMM和MBGMV算子,具体介绍读者可以参考下一段。

GEMM(通用矩阵乘法,动态计算图)->MBGMM(输入编码部分,sequence-level,triton)+MBGMV(解码部分,token-level,punica

上图是Punica论文中的SGMV算子(即这里的MBGMV算子),相比于GEMM的固定矩阵相乘,这里是先把矩阵Gather到一起后,再相乘,即外层的Y+=X@W是一样的逻辑,但是内部的具体参数会随着请求对应的Adapter发生变化。

2.2 内存管理

S-LoRA的内存管理是延续了vLLM的Paged-Attention的页管理思想,用每个block table将逻辑地址和物理地址联系起来。S-LoRA就在Paged-Attention的基础上,在cache中除了Key和Value之外,还增加了对Adapter的参数的管理,如下图所示。

之所以可以这么设计,作者主要是认为LoRA的Adapters和Key&Value向量有着两点相似之处:

  • 两者都是动态的,KV-cache是序列根据请求动态输入,请求结束就被销毁;adapter是每个请求如果用到了某个adapter就加载,下个batch没用到就移除;
  • 两者的矩阵有一维是一样的,KV的矩阵形状是sequence-length×hidden-size,adapter的矩阵形状是rank-size×hidden-size,所以就算合在一起,也相对规整,可以减少显存碎片。

于是,增加Adapter weights之后的cache如下图所示。

此外,为了减少adapter weight从内存到显存的load时间,s-lora有个prefetch机制,就是会根据下一个batch的request中用到adapter,提前取出到cache里,减少loading的时间。

2.3 张量并行

为了在显存不够的情况下,实现能够在多GPU上并行推理,又尽量减少通信损失,作者提出了张量并行的方案(类Megatron-LM)如下图所示。

3. S-LoRA怎么用?

调用代码如下:github.com/vllm-projec... 通过代码可以看出,S-LoRA的调用很简单,处理base模型的常用参数温度等,只要在每个request的时候加上lora地址即可。目前仅支持LLaMa和mistral模型,在vLLM项目上是实验性集成,等待支持更多的大模型以及正式发布~

相关推荐
掘金安东尼1 分钟前
字节-Trae、阿里-通义灵码、腾讯-CodeBuddy,为什么都在“卷”AI编码?
面试·llm·github
深科文库3 小时前
构建 MCP 服务器:第 4 部分 — 创建工具
python·chatgpt·prompt·aigc·agi·ai-native
幼稚园的山代王4 小时前
Prompt Enginering(提示工程)先进技术
java·人工智能·ai·chatgpt·langchain·prompt
土豆12505 小时前
告别“专属”编辑器:为什么 GitHub Copilot 是比 Cursor 更优的 AI 编程选择
llm·cursor·github copilot
知其然亦知其所以然5 小时前
RAG 结果太水?用 RRF + Reranker 重排,效果翻倍提升!
java·后端·llm
磊叔的技术博客5 小时前
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
spring·llm·openai
憨憨睡不醒啊20 小时前
如何让LLM智能体开发助力求职之路——构建属于你的智能体开发知识体系📚📚📚
面试·程序员·llm
柯南二号21 小时前
深入理解 Agent 与 LLM 的区别:从智能体到语言模型
人工智能·机器学习·llm·agent
Q同学1 天前
TORL:工具集成强化学习,让大语言模型学会用代码解题
深度学习·神经网络·llm
人肉推土机1 天前
AI Agent 架构设计:ReAct 与 Self-Ask 模式对比与分析
人工智能·大模型·llm·agent