Llumnix:大型语言模型服务的动态调度


Llumnix:大型语言模型服务的动态调度

背景

1.LLM服务多样化,有总结任务,有生成小说任务,有VIP任务。

  1. KV-cache会随着推理越来越多,对GPU内存的需求也在增加。
  2. 使用动态内存分配的连续批处理

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)

动态调度策略

设计虚拟使用量。便于统一调度规则-全部都是负载均衡。

虚拟使用量就是预先分配量。我在模型实例上预先分配,然后发现分配不了,超载了,我就要调度碎片化。

高优先级的我就给他更多的虚拟使用量,可以保证他能平稳无干扰运行。

我想空出一台机器,空出一个模型实例,我就把他的虚拟使用量拉满,这样上面的任务都要被调度走。

  1. 调度分配-分配给自由度更高的。F = (M − ∑V )/B,(总内存-∑虚拟使用量)/批大小;

因为不停的生成KV-cache,所以所占用的内存是不停的生长的,如果有多个服务,就大概率生长的更快。

  1. 迁移,定期,选择自由度最低的迁移到自由度最高的,负载均衡。
  2. 弹性伸缩;自由度极高考虑关掉此实例,自由度极低考虑多开一个实例。负载均衡

算法实现

用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


文章作者: 爱敲代码の鱼儿
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 爱敲代码の鱼儿 !
  目录