MuseAI 是由阿里集团爱橙科技研发的面向阿里内部的 AIGC 创作工作台,同时通过与阿里云旗下魔搭社区合作共建的形式,将主体能力通过魔搭社区的 AIGC 专区对公众开放。本文主要分析了平台由于频繁切换 Diffusion Pipeline 引起的用户体验与资源浪费问题,并从网络传输、内存管理、Host-to-Device、模型量化等方面着手优化。
考虑到 MuseAI 平台本身是公司内部服务,下文通过底层技术同源的魔搭社区 AIGC 专区来做说明与介绍,为避免混淆,下文如未特意提及,“MuseAI”和“魔搭社区 AIGC 专区”指代同一事物。
魔搭社区 AIGC 专区链接:https://modelscope.cn/aigc/home
MuseAI 是一款专为设计专业人士量身定制的先进 AI 绘图工具,旨在提供卓越的绘画体验,并为设计团队打造一个既稳定又易于管理的创作平台。基于尖端的扩散模型(Diffusion Model)技术,MuseAI 不仅提供了强大的推理与训练服务,还允许用户通过简化的文本输入和参数设置(包括选择模型版本及设定分辨率等),轻松实现心中构想的视觉作品。
在 MuseAI 的核心——Diffusion Pipeline 中,多个模型协同工作以生成高质量图像。这一流程包括以下关键组件:
MuseAI 集成了丰富的资源库,涵盖数百款 Checkpoint 模型、数千种 LoRA 模型以及数十种适用于不同场景的 ControlNet 方案,并支持用户上传自定义模型,既促进了个人创意表达,也鼓励了社区内的资源共享。然而,面对如此庞大的模型库,我们面临的主要挑战之一是如何有效地管理这些资源,在不影响用户体验的前提下最小化模型切换时间。
为了应对这一挑战,MuseAI 不断探索并实践创新方法,致力于减少请求间的流水线切换时间。尽管学术界和工业界已有诸多关于提升模型推理速度的研究成果,但在减少模型切换时间方面的工作仍然相对稀缺。因此,我们将分享一些 MuseAI 在过去一段时间内积累的经验,希望能为相关研究和技术发展提供有价值的参考。
在深入探讨之前,有必要对一些关键概念进行明确定义,以确保讨论的准确性和一致性。以下是与 MuseAI 平台性能优化相关的几个重要时间度量:
请注意,虽然“请求排队时间”(即从用户发起请求到请求进入推理集群的时间)是用户体验的一个重要因素,但它主要受用户请求数量和可用集群资源的影响,因此在本文后续讨论中不会被考虑。这使得我们可以集中精力研究其他影响端到端生成时间的因素,即:
上图仅形式化地展示了各个时间阶段对应的流程内容,并不反映实际的时间占比。在实际情况中,每个阶段的时间消耗会受到多种因素的影响,包括但不限于模型类型、模型大小、模型存储方式、缓存命中情况以及硬件性能等。
为了更好地理解这些因素如何共同作用于端到端生成时间,以下是一组基于 MuseAI 平台真实请求的数据,展示了各阶段时间消耗的分布情况:
表 1. MuseAI 真实请求下端到端生图时间的耗时分布
在上述简易测试中,我们通过清空 Linux 的 PageCache(使用命令 sudo sh -c "echo 1 > /proc/sys/vm/drop_caches"),模拟了首次推理的冷启动场景。此时,系统完全没有任何缓存支持,因此模型下载时间、模型读取时间和模型切换时间都显著增加。具体表现如下:
从上述简易测试的数据可以看出,在没有缓存命中的情况下,模型下载、模型加载和模型切换时间占据了端到端生成时间的绝大部分。这表明这些阶段的时间消耗是不可忽视的,尤其是在 MuseAI 这样的多模型环境下,缓存未命中的情况更为普遍。
MuseAI 平台集成了大量不同类型的模型,使得将所有模型都缓存到磁盘或内存中变得极为困难。因此,平台不得不频繁面对缓存未命中的挑战,这对用户体验和服务效率产生了负面影响。
鉴于此,我们的研究重点将集中在以下几个方面:
1. 增强硬件与软件协同优化:
2. 提升模型构建与加载效率:
3. 内存管理与复用:
4. 模型量化:
5. 模块拆解并行:
4.1 模型加载优化
模型加载 是指推理服务从存储介质加载模型数据到内存的时间,涵盖了前文所述的“模型下载时间”与“模型读取时间”。模型加载时间不仅依赖于存储介质的理论性能,还需要通过最佳实践来充分发挥其性能。因此,本节将围绕“基于业务特性选择存储介质”和“如何充分发挥存储介质性能”两点展开讨论。
4.1.1 业务特性分析
在 Diffusion 生图社区中,模型种类繁多且数量庞大,难以将所有模型都存储在服务端本地磁盘上。因此,选择适当的存储介质至关重要,以确保模型能够高效地保存并快速读取。
截至撰写本文时,最大的生图模型网站 Civitai AI 上常用的模型属性如下表所示。考虑到算法版本的迭代,SD1.5 的模型将逐渐被淘汰,同时高质量的生成效果模型相对较少。综合评估后,400 TB 的存储容量对于 MuseAI 来说是充足的。
表 2. 模型总量存储统计
根据第二章提供的“MuseAI 真实请求下端到端生图时间的耗时分布”表格,当前 MuseAI 第一次生图的实际推理时间约为 10 秒,但“模型下载时间”与“模型读取时间”的总和却接近其 10 倍。这不仅严重影响了用户体验,还造成了 GPU 资源的极大浪费。目前,模型下载速度仅为 100 MB/s,而服务器的网卡带宽为 2 GB/s,显然,提升带宽利用率以减少非推理时间占比是当务之急。
简而言之,MuseAI 的业务特性对存储方案提出了以下要求:
4.1.2 存储方案选型
在选择适合 MuseAI 平台的存储介质时,我们考虑了多种常见的文件存储方案,包括对象存储服务(OSS)、网络附加存储(NAS)以及阿里内部的分布式集群存储服务“盘古”,三者的特点简单归纳如下:
每种方案都有其独特的优势和适用场景,如下表所示。
表 3. 存储方案特点
三种存储方案都能满足 MuseAI 的容量需求,但在读取性能和扩展性方面存在显著差异:
综上所述,基于 MuseAI 的业务需求和技术特点,我们决定采用以下存储策略:
4.1.3 NAS 最佳实践
在 MuseAI 平台中,选择合适的 NAS 类型和优化其配置对于确保高效的模型加载至关重要。阿里云 NAS 提供了通用型和极速型两种主要类型,前者适合存储大量数据且对总吞吐量有需求的场景,后者则适用于处理大量的读写请求和对响应延迟要求较高的场景。鉴于 MuseAI 的业务特性——需要存储大量模型数据并保证高吞吐量,我们选择了通用型 NAS。
表 4. 通用型和极速型 NAS
为了验证 ECS 实例与 NAS 之间的连接性能,我们在 ecs.g7ne.12xlarge 规格(48 核 192G 内存 40 Gbit/s 带宽)的 ECS 上进行了性能测试。由于通用型 NAS 的读带宽与容量有关,我们首先创建了一个 33TB 的文件以确保 NAS 能够达到 20 GB/s 的读带宽。
truncate -s 33T /mnt/nas
通过阿里云控制台默认挂载参数进行测试,采用 nfsv3 协议并通过 TCP 访问,具体挂载参数如下。
vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport
使用 fio 测速工具测试 NAS 的随机读取吞吐量,命令如下。
fio -numjobs=2 -iodepth=128 -direct=1 -ioengine=libaio -sync=1 -rw=randread -bs=4M -size=1G -time_based -runtime=60 -name=Fio -directory=/mnt/muse
初次测试结果显示,读取速度仅为 500 MB/s,远低于机器网卡上限(40 Gbit/s)和 NAS 读带宽上限(160 Gbit/s),显然存在性能瓶颈。
经过排查,发现 NFS 客户端和服务器之间默认仅通过一个 TCP 连接通信,并且阿里云 NAS 前端机限制了每条连接的带宽上限。为了解决这一问题,我们调整了 Linux 内核参数,特别是 nconnect 参数,该参数控制 NAS 与客户端之间的连接数,默认值为 1,最大值为 16。
vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,nconnect=16
增加 TCP 连接数后,读取带宽显著提升至 4500 MB/s,达到了网卡带宽的大约 90%,效果明显。此外,我们还采用了多线程并发读取模型文件的方法,进一步充分利用网络带宽,确保了高性能的数据传输。
inline int read_file(int fd, void *buf, size_t nbytes, off_t offset) {
std::stringstream oss;
uint64_t total_read_bytes = 0;
while (total_read_bytes < nbytes) {
void *buf = (char*)buf + total_read_bytes;
ssize_t n = pread(fd, buf, nbytes - total_read_bytes, offset + total_read_bytes);
if (n == 0) {
oss << "cannot read expected " << nbytes << " bytes, total read bytes: " << total_read_bytes;
throw SafetensorsException(oss.str());
}
if (n < 0) {
oss << "pread failed. fd: " << fd << ", bytes: " << nbytes << ", errorno: " << errno;
throw SafetensorsException(oss.str());
}
total_read_bytes += n;
}
return 0;
}
void multi_thread_read_file(int fd, void *buf, size_t start_offset, size_t end_offset, int num_threads) {
uint64_t file_offset = start_offset;
uint64_t buf_offset = 0;
uint64_t data_size = end_offset - start_offset;
uint64_t block_size = (data_size + num_threads - 1) / num_threads;
ThreadPool pool(num_threads);
std::vector<std::future<int>> futures;
char *buf = reinterpret_cast<char*>(buf);
for (int i = 0; i < num_threads; ++i) {
size_t read_bytes = std::min(end_offset - file_offset, block_size);
void *store = reinterpret_cast<void*>(buf + buf_offset);
futures.emplace_back(
pool.enqueue(read_file, fd, store, read_bytes, file_offset));
file_offset += read_bytes;
buf_offset += read_bytes;
}
for (auto &future : futures) {
future.get();
}
}
4.1.4 PANGU + fsfuse 最佳实践
在 MuseAI 平台中,盘古结合 fsfuse 提供了一种高效、透明的数据访问方式,极大地提升了模型加载的性能。为了充分发挥这一组合的优势,我们总结了以下最佳实践:
在 MuseAI 平台中,模型切换时间是影响整体性能的关键因素之一。本章将详细介绍如何优化模型从内存加载到 GPU 的过程,包括 state dict 的传输、nn.Module 的构造和装载,以及进一步的性能提升策略。
4.2.1 内存中的模型切换时间组成
内存中的模型切换时间主要包括以下几个部分:
4.2.2 执行顺序优化
为了达成上述目标,一是可以让 nn.Module 先装载 state dict,再让 nn.Module 调用 to("cuda:0"),二是先让 state dict 中的 tensor 执行 to("cuda:0"),再让 nn.Module 装载 state dict。这两者之间是否有性能差异呢?
我们构造了一个 4.1 GB 的 safetensors 文件(忽略其读取时间),并通过实验对比了两种常见方案的性能差异。为了消除磁盘性能对实验的影响,我们在 profile 之前对 state dict 进行了 clone,确保每个 tensor 都已读取到内存中。
from safetensors.torch import load_file, save_file
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
self.linear1 = nn.Linear(10, 1024)
self.linear2 = nn.Linear(1024, 1024 * 1024)
self.linear3 = nn.Linear(1024 * 1024, 10)
def forward(self, x):
x = self.linear1(x)
x = self.linear2(x)
x = self.linear3(x)
return x
state_dict = load_file("test_model.safetensors")
def func1(state_dict):
model = MyModel()
model.load_state_dict(state_dict)
model = model.to("cuda:0")
torch.cuda.synchronize()
def func2(state_dict):
model = MyModel()
model = model.to("cuda:0")
model.load_state_dict(state_dict)
torch.cuda.synchronize()
if __name__ == "__main__":
state_dict = load_file("test_model.safetensors")
new_state_dict = {}
for key, value in state_dict.items():
new_state_dict[key] = value.clone()
func1(new_state_dict)
# func2(new_state_dict)
先 load_state_dict 再 to:
先 to 再 load_state_dict:
实验结果显示,先 load_state_dict 再 to("cuda:0") 的方法显著优于先 to("cuda:0") 再 load_state_dict。
问题关键点在于,MyModel 其实在构造完成后就已经持有了许多 tensor,而 load_state_dict 操作本质做的事情相当于:
model.load_state_dict(state_dict) 相当于
for key, param in model.name_parameters():
param.data.copy_(state_dict[key])
如果模型遵循 cpu-> gpu -> load_state_dict 的流程,并且 state_dict 本身的 tensor 在 cpu 内存上,那么就需要进行一批额外的 cpu to gpu 操作。因为 gpu tensor 只能拷贝 gpu tensor 的值,所以 state dict 被隐式的进行了 h2d 传输。
如果模型遵循 cpu -> load_state_dict -> gpu 的流程,那么就相当于把 gpu 上的 tensor copy 变成了 cpu 上的 tensor copy,节省了一大批的 h2d 操作,因此降低了整个流程的耗时。
简单总结两种执行顺序涉及的内存操作:
因此,推荐采用 cpu->load_state_dict->gpu 的流程,以节省大量 H2D 操作,从而降低整个流程的耗时。
为了进一步缩短时间,可以在 load_state_dict 中传入参数 assign=True。这会直接用 state dict 的 tensor 充当 parameter 的 data,省去了参数数据拷贝的过程,从而加速模型装载。
4.2.3 H2D 传输性能优化
Tensor 从 CPU 到 GPU 的传输较为缓慢,主要原因在于通常持有的 tensor 内存是由 Linux 分配的 pagable memory,在传输到 GPU 前 CUDA API 会开辟临时的 pinned memory 空间,将 pagable memory 中的内容复制到该空间后再通过 PCI-E 总线传输到 GPU。
这里有一个简易的传输时间的对比,tensor 的大小是 16GB:
我们可以采取措施减少内存分配和拷贝的次数:
这部分优化细节放在下一章展开叙述。
4.2.4 H2D 传输性能优化
实际场景中的 state dict 往往包含成百上千个 tensor,顺序进行 H2D 操作效率较低。参考 PyTorch 官方文档的建议,我们可以使用 tensordict 包装 state dict,并结合 pin_memory、non_blocking 和多线程操作,实现并发异步传输,从而大幅提升性能。
4.2.5 Skip init 技术
我们可以看到 2.2 章节实验中的 MyModel 的构造时间其实也占了很大一块时间(5s 以上),但 2.3 章节实验中分配 16GB 的 tensor 只需要 500ms,说明 MyMode l 构造的时间大头并不是构造 tensor,这并不符合我们的预期。
我们对 nn.Linear 的 __init__ 函数进行单独 profile 可以发现,在 nn.Linear 构造过程中,绝大多数时间都集中在 reset_parameters 上:
翻看 torch 的代码可以发现:
reset_parameters 本质上做的事情是在 nn.Parameters 构造后为 weight 和 bias 遵循 kaiming 初始化方法赋予初始值。这个步骤在模型训练中非常重要,但是在推理中显得很冗余,因为不管初始化 Parameter 的值是什么,我们都需要 load_state_dict 去覆盖这个值。PyTorch 提供了一种 skip init 技术,允许我们将模型构造在虚无的 meta 设备上,然后再迁移到 CPU 或 GPU,从而跳过初始化过程。
在 MuseAI 平台中,内存管理和复用是优化模型加载的关键环节。本章将详细介绍现有方案的性能问题,并提出相应的优化措施,以实现更高效的内存利用和更快的模型切换。
4.3.1 原方案性能分析
MuseAI 原有的模型加载和推理链路大致如下:
import torch
import torch.nn as nn
from safetensors.torch import load_file, save_file
## 定义模型 17GB
class MyModel(nn.Module):
def __init__(self, device):
super(MyModel, self).__init__()
self.linear1 = nn.Linear(10, 1024, device=device)
self.linear2 = nn.Linear(1024, 1024 * 1024, device=device)
self.linear3 = nn.Linear(1024 * 1024, 1024, device=device)
self.linear4 = nn.Linear(1024, 1024 * 1024, device=device)
self.linear5 = nn.Linear(1024 * 1024, 1024, device=device)
self.linear6 = nn.Linear(1024, 10, device=device)
def forward(self, x):
x = self.linear1(x)
x = self.linear2(x)
x = self.linear3(x)
x = self.linear4(x)
x = self.linear5(x)
x = self.linear6(x)
return x
@profile
def load_baseline(model_path):
state_dict = load_file(model_path)
model = torch.nn.utils.skip_init(MyModel, device="cuda:0")
model.load_state_dict(state_dict, assign=True)
model = model.to("cuda:0")
torch.cuda.synchronize()
return model
if __name__ == "__main__":
model_path = "test_model.safetensors"
load_baseline(model_path)
通过对 infer 函数进行 line profiler 分析,我们观察到了以下几个现象:
原方案存在以下性能问题:
4.3.2 性能优化
针对上述性能问题,我们采取了以下优化措施,并已集成至文首提及的代码仓中:
虽然 MuseAI 涉及的模型类型较多,但每种模型大小在 [10MB, 24GB] 范围内。我们提前分配并维护几个固定大小的内存块,形成两级内存池。当有需求时从中拿一个适当的空闲块给用户,示意图如下所示 。
举个例子,先加载一个 2.8G 的模型,再加载一个 8.5G 的模型,紧接着卸载最先加载的 2.8G 模型,最后加载一个 2.9G 的模型,内存池会进行以下行为:
PyTorch 中的 state_dict 是一个简单的字典对象,其中 key 类型为 str,代表 tensor 名称;value 类型为 tensor,记录了模型权重信息,是消耗内存的主要因素。为了控制在指定的内存区域构造 tensor,我们首先理解 tensor 的数据结构:
因此,我们可以首先用预分配好的 pinned_memory 构造 Storage,再设置 tensor 的 storageOffset 为适当的值。具体步骤如下:
1.解析 safetensors 文件格式:
2.构建 tensor:将文件中的 tensor 数据段拷贝到预分配的 pinned_memory 中,根据 safetensors header 中的 offsets 构建 tensor。需要注意的是,storage_offset 是指在 storage 中元素个数的偏移量,而非字节偏移量。若同个 storage 中不同 tensor 的 sizeof(dtype) 不一致,可能导致字节偏移量计算错误,这时需要将内存按每个 tensor 占用量切分为多块连续内存,在每块内存上单独构造 storage,并绑定到对应 tensor 上。
读取性能评估
通过比较以下三个函数的耗时,评价模型读取所带来的性能提升:
@profile
def load_baseline(model_path):
state_dict = load_file(model_path)
model = torch.nn.utils.skip_init(MyModel, device="cuda:0")
model.load_state_dict(state_dict, assign=True)
model = model.to("cuda:0")
torch.cuda.synchronize()
return model
@profile
def fast_safetensors_fuse(model_path):
# 顺序直读
state_dict = fast_safetensors.load_safetensors(model_path, num_threads=1, direct_io=True)
model = torch.nn.utils.skip_init(MyModel, device="cuda:0")
model.load_state_dict(state_dict, assign=True)
model = model.to("cuda:0")
torch.cuda.synchronize()
return model
@profile
def fast_safetensors_nas(model_path):
# 16 线程非直读
state_dict = fast_safetensors.load_safetensors(model_path, num_threads=16, direct_io=False)
model = torch.nn.utils.skip_init(MyModel, device="cuda:0")
model.load_state_dict(state_dict, assign=True)
model = model.to("cuda:0")
torch.cuda.synchronize()
return model
每次实验前清除 page cache 消除系统 cache 的影响。实验结果表明,优化后的方案显著提升了模型读取速度。
表 4. 读取性能比较表
内存复用性能评估
先后加载、卸载、再加载同一模型,观察第二次加载的时间差异,评估内存复用的性能收益。由于第二次加载模型时基本命中了 page cache,所以文件读取时间只有毫秒级差异,可以忽略。
def reload(model_path, load_func):
model = load_func(model_path)
del model
gc.collect()
torch.cuda.empty_cache()
tic = time.time()
model = load_func(model_path)
toc = time.time()
print(f"reload model cost {toc - tic}s")
if __name__ == "__main__":
reload(model_path, fast_safetensors_load)
reload(model_path, load_baseline)
实验结果显示,内存复用显著缩短了模型加载时间,提高了整体推理效率。
表 5. 内存复用性能结果表
随着模型复杂度和规模的不断增加,显存容量逐渐成为限制推理性能的关键瓶颈。为了应对这一挑战,社区中越来越多的用户开始采用低精度数据类型(如 FP8)来存储模型,以减少显存占用并提高加载速度。
主流的 Stable Diffusion 模型在推理时通常采用 Float16 或 BFloat16 精度计算。然而,随着模型大小的日益增加,社区用户的显卡显存容量已经难以支撑这两种精度下的推理生图需求。因此,大量社区模型开始转向使用 FP8 等低精度数据类型进行存储。这种做法的主要优势包括:
从 Ada 架构开始,英伟达引入了第四代 Tensor Core,支持 FP8 精度计算。以 Ada 架构的 L40S 显卡为例,其 FP8 精度的理论计算性能(733 TFLOPS)可以达到 FP16 的约两倍(362.05 TFLOPS)。这为基于 FP8 的推理提供了强大的硬件支持,使得我们可以在降低显存占用的前提下,进一步提升推理速度。
为了充分利用新一代硬件的优势,我们基于 PyTorch 和 cuBLAS 库提供的算子,适配了基于 FP8 的生图推理:
以下是 flux-dev 1.0 量化前后在 盘古 +fsfuse 环境,L40s 显卡上的耗时对比,其中 bf16 模型占 22.4GB,fp8 模型占 11GB。
表 6. 量化前后端到端耗时
在获得大幅加速的同时,量化前后的出图效果能基本对齐。
在 MuseAI 平台中,文本嵌入向量的生成是影响最终图像生成质量的关键步骤。传统模型如 SD1.5 和 SDXL 使用经典的 CLIP 模型来生成提示词对应的文本嵌入向量(Text Embedding),但由于 CLIP 模型本身的局限性,导致生成的图片对提示词的遵从性较差。2024 年中陆续开源的 SD3-Medium、FLUX 等模型引入了更强的 T5 模型来生成文本嵌入向量,显著提升了提示词的遵从性。
然而,T5 模型本身较大的体积也给推理效率带来了挑战。例如,Google 官方发布的 t5-v1_1-xxl 模型大小约为 45GB,即使量化版 T5 模型也有约 10GB 的显存占用。每次生图请求中,如果需要从本地文件加载 T5 模型或出于显存考虑卸载 T5 模型到内存,无疑会非常耗时。
为了解决上述问题,我们借鉴了大规模推荐系统中的架构设计,引入了专门的 T5 Embedding Server 来处理文本嵌入向量的生成。这种独立部署的方式使得获取提示词对应的文本嵌入向量的过程与核心去噪流程分离,从而将原本复杂的模型加载和卸载操作简化为一次轻量的 RPC 调用。
为了进一步降低 T5 Embedding Server 的响应时间,我们考虑引入了以下几种服务端优化技术:
在拆分出 T5 模型后,我们在同 VPC 内进行了简单的跨机器性能测试,评估了大量并发请求下 T5 Embedding Server 的耗时。
从下表结果中可以看到,T5 模型的推理并不足以让 GPU 利用率满载,整体耗时和并发请求数并不是一比一线性增长的关系。与之对比,在同一台机器上本地使用 T5 模型推理包含(1)调用 from_state_dict 读取模型参数并实例化 T5 对象到内存;(2)将 T5 对象从内存搬运到显存;(3)GPU 侧推理;(4)将 T5 对象 offload 到内存等 4 个主要流程,整体的耗时在 4.3 秒左右,远大于通过 RPC 调用的耗时。而这 4 个阶段的耗时热点,依然是从内存到显存的 H2D 传输阶段,而非实际推理。
想要独立化部署 T5 存在着诸多挑战,其中最关键的一点是工程实现和部署上的难度,独立的 T5 Embedding Server 会导致我们的推理 SDK 不再是一个独立的、本地可用的 SDK,而开始依赖分布式网络通信。此外针对 T5 Server 还需要引入一套新的健康检查、重启等流程。并且通过上述几节中的模型切换、内存复用技术,我们已经大幅减少了在本地使用 T5 模型进行推理的耗时,因此我们最终将该技术作为技术储备,并没有应用到生产环境。
为了评估本次优化对 MuseAI 业务带来的性能收益,我们使用了实际业务中最常使用的 Flux Pipeline 进行了一系列实验。本章将详细介绍实验设置、结果及分析。
Flux Pipeline 组成
Flux Pipeline 由以下三个模型组成:
为了比较 MuseAI、Diffusers 和 WebUI-forge 三者的性能,我们设置了以下对照实验组:
1. 冷启动性能测试:
2. 模型切换性能测试:
软件版本:
硬件环境:
5.2 实验结果与分析
表 7. 实验结果
盘古 +fsfuse 环境
1. 冷启动性能:
2. 模型切换性能:
NAS 环境
1. 冷启动性能:
2. 模型切换性能:
MuseAI 平台为提供丰富的绘画风格,集成了大量不同类型的模型,提供服务的过程中面临频繁切换 Diffusion Pipeline 的情况。基于用户真实请求分析,我们发现模型下载、模型加载和模型切换时间占据了端到端生图时间的绝大部分,严重影响用户体验,造成资源的极大浪费。
我们分析业务特性,在公司内外分别使用“盘古 +fsfuse”和 NAS 作为模型存储介质,并根据两者特性制定相应的访问策略——盘古 +fsfuse 采取 Direct I/O 与顺序读、NAS 多线程并发读,充分挖掘读取性能,大大降低模型下载和加载时间。
我们分析模型切换时间的组成,发现主要是 torch Module 构造和 Host-to-Device 较为耗时。我们使用了 skipInit 技术跳过 torch Module 构造时多余的初始化阶段。我们精心分析并调整代码执行顺序,避免不必要的内存拷贝,并使用多线程 H2D 技术最大化传输效率。
我们观察到 Diffusion Pipeline 切换时伴随着巨大的内存分配与释放开销,采用内存池复用内存,尽量减少了这些开销。
我们探索了模型量化和 T5 独立化部署。前者能大幅加快模型加载,但尚缺乏自动化能力,支持代价较高。后者也能加快模型加载,但对服务链路施加了较大的稳定性与运维风险,因此未实际应用。
最后,我们将 MuseAI 与 Diffusers、WebUI-Forge 进行性能比较。冷启动时,在 NAS 和“盘古 +fsfuse”上 MuseAI 均优于其他两者。热启动时,MuseAI 与 WebUI-Forge 相近,优于 Diffusers。
文章来自于“阿里技术”,作者“杜健聪”。
【开源免费】FASTGPT是基于LLM的知识库开源项目,提供开箱即用的数据处理、模型调用等能力。整体功能和“Dify”“RAGFlow”项目类似。很多接入微信,飞书的AI项目都基于该项目二次开发。
项目地址:https://github.com/labring/FastGPT
【开源免费】graphrag是微软推出的RAG项目,与传统的通过 RAG 方法使用向量相似性作为搜索技术不同,GraphRAG是使用知识图谱在推理复杂信息时大幅提高问答性能。
项目地址:https://github.com/microsoft/graphrag
【开源免费】Dify是最早一批实现RAG,Agent,模型管理等一站式AI开发的工具平台,并且项目方一直持续维护。其中在任务编排方面相对领先对手,可以帮助研发实现像字节扣子那样的功能。
项目地址:https://github.com/langgenius/dify
【开源免费】RAGFlow是和Dify类似的开源项目,该项目在大文件解析方面做的更出色,拓展编排方面相对弱一些。
项目地址:https://github.com/infiniflow/ragflow/tree/main
【开源免费】phidata是一个可以实现将数据转化成向量存储,并通过AI实现RAG功能的项目
项目地址:https://github.com/phidatahq/phidata
【开源免费】TaskingAI 是一个提供RAG,Agent,大模型管理等AI项目开发的工具平台,比LangChain更强大的中间件AI平台工具。
项目地址:https://github.com/TaskingAI/TaskingAI
【部分开源免费】FLUX是由Black Forest Labs开发的一个文生图和图生图的AI绘图项目,该团队为前SD成员构成。该项目是目前效果最好的文生图开源项目,效果堪比midjourney。
项目地址:https://github.com/black-forest-labs/flux
在线使用:https://fluximg.com/zh
【开源免费】XTuner 是一个高效、灵活、全能的轻量化大模型微调工具库。它帮助开发者提供一个简单易用的平台,可以对大语言模型(LLM)和多模态图文模型(VLM)进行预训练和轻量级微调。XTuner 支持多种微调算法,如 QLoRA、LoRA 和全量参数微调。
项目地址:https://github.com/InternLM/xtuner
【开源免费】LangGPT 是一个通过结构化和模板化的方法,编写高质量的AI提示词的开源项目。它可以让任何非专业的用户轻松创建高水平的提示词,进而高质量的帮助用户通过AI解决问题。
项目地址:https://github.com/langgptai/LangGPT/blob/main/README_zh.md
在线使用:https://kimi.moonshot.cn/kimiplus/conpg00t7lagbbsfqkq0