Llumnix:大型语言模型服务的动态调度
背景
1.LLM服务多样化,有总结任务,有生成小说任务,有VIP任务。
- KV-cache会随着推理越来越多,对GPU内存的需求也在增加。
- 使用动态内存分配的连续批处理
GPU满载容易爆炸,所以要空出一部分内存来,将蓝色的部分赶回请求队列。
解决的问题/问题特点
挑战和动机1:不可预测的内存需求和抢占
优先级-》尾部延迟很差
内存拉到100%,就会有一些request被抢占,扔到请求队列里。抢占率8%。
P1就是响应时间最小的请求,P10就是排名第十的请求,P100就是响应时间最长的请求。
在真正使用过程中,最常用的主要有P50(中位数)、P95、P99。
挑战和动机2:请求之间的性能干扰。
批处理的数量越多,处理的每条请求越长。他们之间的干扰就越明显。因为越多和越长肯定会占用更多的GPU内存资源。
挑战和动机3:内存碎片
考虑到挑战1和挑战2,应该将请求分散到不同的GPU,但这样容易造成碎片化。导致外部请求(尤其是长请求)的延迟很高。
挑战和动机4:满足VIP的更高优先级
现在的系统一般都是平等对待所有的请求
将请求分配到模型实例
由于执行不可预测,通过一次性调度进行负载平衡可能不是最佳选择。所以要重调度(迁移)
解决方案/创新点
Llumnix能干什么。负载平衡,减少碎片化,满足高优先级,弹性伸缩(更快地耗尽要终止的实例 或 使新实例饱和)。
继承了Orca: A distributed serving system for Transformer-Based generative models.的连续批处理(Continuous Batching)和Efficient memory management for large language model serving with pagedattention.的动态内存调用(PagedAttention)。
实时迁移
多阶段迁移;比如我复制一个KV-cache是0.5ms,计算一个KV-cache是1ms;我现在已经有了100个KV-cache,然后我一直不停的算,不停的复制迁移;当我计算到第200个KV-cache的时候,目标实例上也差不多有了199个KV-cache了。然后我要真正复制迁移的kv-cache就只有第200个这一个了,等待第200个计算完毕,复制迁移即可。
这里面还有很多细节:他这个迁移过程是比较长的,如果迁移过程中计算完了,比如就需要150个KV-cache就任务结束了。比如目标机器无法提供这么多的GPU内存给你用。
分布式调度架构
设计了一种集群的,分布式的架构,起名字llumlets。全局调度器知道且根据每个实例的负载状态调度。本地调度器要计算该实例的内存负载(注意这里是virtual usages)
动态调度策略
设计虚拟使用量。便于统一调度规则-全部都是负载均衡。
虚拟使用量就是预先分配量。我在模型实例上预先分配,然后发现分配不了,超载了,我就要调度碎片化。
高优先级的我就给他更多的虚拟使用量,可以保证他能平稳无干扰运行。
我想空出一台机器,空出一个模型实例,我就把他的虚拟使用量拉满,这样上面的任务都要被调度走。
- 调度分配-分配给自由度更高的。F = (M − ∑V )/B,(总内存-∑虚拟使用量)/批大小;
因为不停的生成KV-cache,所以所占用的内存是不停的生长的,如果有多个服务,就大概率生长的更快。
- 迁移,定期,选择自由度最低的迁移到自由度最高的,负载均衡。
- 弹性伸缩;自由度极高考虑关掉此实例,自由度极低考虑多开一个实例。负载均衡
算法实现
用3300行Python代码实现Llumnix,Llumnix目前支持vLLM作为后端,这是一个开源的最先进的推理引擎,具有连续批处理、PagedAttention和张量并行分布式推理的特点。
使用Gloo collective communication library进行KV-cache传输,而不是NCCL,因为后者并发调用不安全。
很多小的KV-cache在GPU中,复制到CPU中,融合为一个大的,统一发送到目标实例。
实验
实验设置
4个GPU机器,每个机器上4块A10的GPU。共计16块GPU。
参考ShareGPT和BurstGPT的轨迹,自己生成的各种短的,中等的,长的请求。
和什么调度器比较? RR(均匀轮转),INFaaS(2021ATC,无迁移),无优先级判断功能的Llumnix;
实验结果
迁移的效率:
图10左:我们的迁移很快,比全部复制KV-cache和在新的机器上重新计算kv-cache要快。而且不随着序列长度变大。
图10右:正常的decode和(一边迁移一边decode),效果差不多,1%,基本无影响。
调度器服务性能比较:
红色的最好,蓝色的还行,绿色最差。
迁移导致碎片率低。
Gamma分布的方差系数-(coefficients of variance)CV,我们改变CV参数,以显示突发负载的影响。
根据经验,高优先级给1600token的内存大小。
Llumnix-base 无优先级概念。
泊松分布和gamma分布下的延迟,橙色都比蓝色要好。
通过迁移,我可以在实现相同的P99延迟时,使用更少的实例。
右边是llumnix分布式调度,左边是集中式调度。集中式调度需要通信来同步请求状态和调度决策。在高请求速率的情况下,分布式和集中式调度的区别更为明显。
读后感
核心是使用虚拟使用量的方式,将所有方式(迁移-碎片化,优先级,弹性伸缩)都统一为负载均衡的方式。
最后提到的分布式调度架构和集中式调度架构不是很清楚?你难道没全局调度器?
在github中提到可以做PD分离,但还在试验中
目前,P-D分解是一个实验性特征,主要是为了证明使用Llumnix的抽象实现它的可行性。然而,我们还没有添加高级功能或性能优化,包括但不限于:
每层KV缓存传输(目前我们使用简单的阻塞传输);
P/D实例的显式或自动分配(目前我们只允许用户指定实例编号,分配规则简单);
更智能的容错(目前,由于简单的P/D分配,如果其中一种实例类型的所有实例都消失了,服务将挂起;我们将实施更好的P/D指派和容错策略,以确保高可用性);
异构实例,例如不同的设备类型、大小或并行性;
对调度策略进行微调。
我们正在积极处理这些项目。敬请关注:)
Llumnix在当前版本中使用两个简单的参数来启用预填充解码分解。
--enable-pd-disagg-True用于启用预填充解码分解。
--num可用调度实例用于配置预填充实例的初始数量。
请注意,应该确保num可用调度实例小于initial_instances(特别是在未设置--enable scaling的情况下),否则将没有解码实例。
参考:
OSDI’24 官方PPT