本次分享主题为:使用 OpenLLM 快速构建和部署大语言模型的应用。OpenLLM 是一个开源的大语言模型(LLM)开发框架。它支持多种开源的 LLM 模型,并且具有内建的两个关键的 LLM 优化,能够优化内存使用。此外,它与 LangChain 和 BentoML 都有很好的集成,可以快速地构建和部署大语言模型的应用。
一、BentoML 公司简介
BentoML专注于提供机器学习基础设施。核心产品是与公司同名的开源框架BentoML。公司总部位于旧金山湾区,在国内也有众多远程工作的同事。通过不断努力,公司获得了众多客户的认可。
二、OpenLLM 产品背景
首先来分享一下我们开发 OpenLLM 的背景。
1、LLM 爆发
相信即将过去的 2023 年对大家来说都是非常奇妙的一年。2022 年 11 月的时候 ChatGPT 刚刚发布,过了一年到现在,我们已经见证了各种各样的大语言模型横空出世:有商用的大语言模型,比如 Claude 和 GPT;也有很多开源的小模型,其参数量可能从几个 B 到几百个 B 不等。可以说这是一个大语言模型繁荣发展的时代,新型语言模型的层出不穷。
BentoML 在与一些客户互动时发现,他们在开发过程中可能会利用 OpenAI 的能力进行开发,将数据输入到 prompt 中,然后测试推理结果。在将模型部署到生产环境上线时,他们希望在自己的服务器或云服务器上进行部署。
2、属于自己的 LLM
大部分客户有以下诉求:
- 高可控性/灵活性:如果将所有数据作为一个 prompt 输入到 OpenAI 的模型中进行推理,这种方法可能并不太切实际。最终,他们期望能够通过自行训练或微调,利用多年来积累的用户数据得到一个属于自己的模型,实现更加灵活的推理需求。
- 数据安全性:如果将自己的数据用于模型推理,就需要关注数据安全性的问题。因为如果使用的是公共网络上的模型,就不可避免地需要将自己的私有数据输入到这个公共网络的模型中。这可能导致数据泄露的问题,在一些注重数据安全性的企业中,这是无法接受的。
- 成本效益:正如之前提到的,如果使用通用模型并采用经过优化的 prompt,可能需要一个庞大的 prompt。举例来说,如果要进行文档的推理,可能需要将整个文档或一本书输入到 prompt 中。目前大多数商业模型是按 Token 计费的,如果提示非常庞大,成本就会相应增加。然而,如果你能够拥有自己的大模型应用,那么成本可能只涉及硬件费用。并且,其精确度可能更高,这意味着你可能只需使用更小的模型就能达到相同的推理准确度,从而进一步降低成本。
3、生产环境中部署 LLMs 的挑战
如果我们自己去部署大语言模型,可能会遇到以下挑战:
- 可操作性:主要涉及到硬件成本,即一个大型模型是否能够有效地部署在特定硬件上。我们都知道,一些优秀的显卡可能不太容易获取。举例来说,一个拥有 13B 参数的模型,其参数总量可能需要 26GB,这就使得单卡 A10G 显卡难以应对。因此,可能需要考虑使用多卡或多机部署来解决硬件上的限制。
- 可扩展性:包含硬件可扩展性和软件可扩展性两个方面。首先是硬件的可扩展性,指的是在请求量和并发数较低时,可以只需一颗显卡或一台机器来满足需求,而在用户量增大时,希望系统能够自动启用多颗显卡或多台机器进行并发推理。第二方面是软件的可扩展性。考虑到现在有许多大型语言模型,你可能在开发或测试阶段希望方便地在不同模型之间切换,以观察它们的效果并进行比较。这就需要软件方面的可扩展性。
- 吞吐量:大型语言模型的应用与传统的机器学习应用有一些区别。传统的机器学习应用,比如图像分类任务,通常可以进行批处理,即一次性处理多个输入,同时返回结果。但是大型语言模型在推理时,其输入不是同时到达,输出也不是同时产生,这给吞吐量带来了更大的挑战。
- 延迟:我们自然希望延迟越低越好,但在机器学习应用中,延迟有时与吞吐量是一对相互制衡的指标。换句话说,如果要达到较大的吞吐量,可能需要在一定程度上牺牲延迟。
三、OpenLLM 介绍、应用举例与使用优势
1、OpenLLM 介绍
下面介绍我们的开源产品 OpenLLM 是如何解决上述问题的。
OpenLLM 于 2023 年 6 月开源,是一个用于部署大语言模型的框架。目前,该项目在 GitHub 上已经获得了 6800 多个星标。其最初的口号是通过一行代码或相对轻松地在不同的大语言模型之间切换,为个人用户提供方便。无论是想要尝试不同模型的个人用户,还是想要部署自己的 AI 应用的用户,OpenLLM 都可以提供便捷的解决方案。
OpenLLM 目前支持几乎所有常用的开源大语言模型。对于每个模型,框架都提供了多个不同的参数量和不同部署模型 ID 可供选择。目前包括 ChatGLM、百川、Dolly-V2 等模型。
2、OpenLLM 应用举例
接下来通过演示来介绍 OpenLLM 是如何加速大语言模型应用的开发和部署的。
首先,启动一个 LLM 服务。
启动 LLM 服务非常简单,只需在安装 LLM 工具后运行以下命令:"start dolly-v2" 即可启动应用。如果你的计算机上之前未下载过该模型,它将自动帮你下载。这可能需要一些时间,应用启动后,可以通过命令行直接与该服务交互,提出一个问题,比如"地球的重量是多少"。我们使用的是 Dolly-V2 模型,它给出了一个回答。由于这个模型相对较弱,因此回答可能不太准确。
我们尝试使用另一个模型,LlaMA,来看看是否可以提升准确度。在这里,我们选择使用 LlaMA2 模型,但由于 LlaMA2 不是 LlaMA 的默认模型,我们需要指定一下模型 ID。和之前一样,使用方法完全相同,只是切换了模型。
因此,使用我们的框架,用户可以轻松在不同的模型之间切换。尽管使用方法一样,但不同模型的回答可能会有所不同。这次使用 LlaMA2 模型,可以看到给出的答案相对更准确。
我们使用命令行与服务进行交互,这在测试阶段非常方便和实用。然而,在实际部署应用时,我们可能希望应用提供一个 HTTP 或 gRPC 接口,以便前端或外部系统能够调用。
OpenLLM 框架支持内置的开箱即用的 HTTP API。在服务的首页,显示一个 Swagger 页面,列出了所有服务支持的 HTTP 端点。你甚至可以在这里直接与服务进行交互和测试,输入提示并获取 JSON 格式的响应体,就像我们之前在命令行中演示的一样。
由于我们暴露了 HTTP 接口,因此无论使用 Python、Node 还是 Curl 等任何能够访问 HTTP 的客户端,都可以调用这个接口。这为应用的灵活性和可访问性提供了更多的选择。
我们还提供了一个 Python SDK,使用户能够在 Python 代码中直接与服务进行交互。只需导入 Client,指定服务的地址,就可以直接向服务提出问题,它将正确地给出答案。这样就可以在 Python 环境中更加便捷地集成和使用 OpenLLM 的服务。
3、OpenLLM 使用优势
使用 OpenLLM 有以下优势:
- 模型切换。用户能够方便地在不同的模型之间进行切换。
- 内置多种优化技术。首先是量化,通过减少参数精度来降低整体内存使用,特别适用于大型语言模型。OpenLLM 支持几乎所有模型的量化。其次是 Token Streaming 技术,在典型的聊天机器人场景中表现出色,允许边产生边接收答案,词汇逐个生成。还有 continuous batching(持续批跑)和 paged attention 等重要优化。
- 多卡支持。用户可以指定多个 GPU,并根据需要选择运行方式,甚至可以指定特定的GPU。
- 模型表现监测。基于公司产品 BentoML,支持 Metrics 测量和性能指标监控。
- 支持模型微调推理。OpenLLM 支持 Fine-tune,同时也支持与 LoRA Layer 进行合作的推理。
- 与其他 AI 工具如 BentoML、LangChain 和 Hugging Face 集成良好,提供了更全面的功能和应用场景。
下面重点讨论一下 Continuous Batching(持续批跑),因为我们使用了 VLM 的后端,它为我们带来了很好的优化。
在大型语言模型的推理中,输入的强度并不相同,有些输入可能更快产生结果,而有些可能需要更长时间。如果不进行优化,当其中一个输入(比如 S2)先完成推理时,整个过程就会等待其他输入完成才能进入下一轮。这会导致 GPU 时间的浪费,因为有一段时间没有推理任务在进行。
Continuous Batching 的优化可以在一个推理结束时立即引入下一个推理任务。以一个例子来说明,假设 S3 先完成了推理,那么 S5 就会立即加入,接着 S1 完成了推理,S6 就会立即加入。这样可以确保在每个时间片上都有一个模型推理任务在运行,从而最大程度地利用 GPU 资源。这是一个有效的优化策略,减少了空闲时间,提高了整体推理效率。
介绍了时间优化之后,再来看一下空间优化,尤其是关于 KVcache 的优化。首先简要介绍一下大语言模型的工作原理。它基于 Transformer 框架,模型通过一系列输入的 token,返回下一个 token 的最大概率。然而,由于输入可能存在重复计算,例如在输入"我是一只"后,模型返回"我是一只猫",在输入"我是一只猫"时,又返回"我是一只猫,",这导致了 Transformer 框架中有许多重复的计算。为了解决这个问题,我们使用 KVcache 缓存计算结果。
KVcache 带来了一个新问题,就是在计算机内存分配时,无法准确知道需要分配多少内存。我们只能先估算一个大致的范围。通过图表演示可以看到,当一个任务完成后,本来可以释放的内存却可能由于下一个任务的加入而无法充分利用。这样就会产生很多内存碎片,影响内存的使用效率。
为了优化这个问题,引入了 Paged Attention。Paged Attention 的优化思想是将所有内存拆分成一个个 block,然后在内存分配时按 block 分配。如果一个任务完成后,发现内存不够,需要再增加时,框架会在另一个地方(可能不是连续的地方)分配另一个 block。任务完成后,这个 block 可能会被释放,供下一个任务使用。通过这种方式,可以有效减少内存碎片。需要注意的是,内存不是连续的,因此需要一个 block table 来存储内存的物理位置和实际序号的映射关系,使得在大模型中看起来是获得了一个逻辑上连续的内存。
四、BentoML 介绍与应用举例
1、BentoML 介绍
完成了以上优化后,开始部署大语言模型。然而,大多数 AI 应用不仅仅是将一个模型部署上去,否则没有哪个公司能够与 OpenAI 竞争。因此,除了模型推理之外,我们的 AI 应用还可能包含许多其他方面的工作。例如,需要考虑如何收集数据、验证数据、配置部署指标以及如何进行模型推理,包括服务指标的观测。这些问题在研究阶段,可能不需要考虑,但在部署和工程化之后,必须考虑这些杂项问题。
BentoML 与 OpenLLM 集成带来了许多优势。BentoML 是一个专注于解决部署后事项的开源 AI 部署框架,具体如下:
- 开源 AI 部署框架:BentoML 提供了一整套的开发、部署和打包解决方案。
- 模型与代码的版本控制:BentoML 引入了模型和版本的控制概念,使得模型可以像 GitHub 的提交一样进行版本控制。由此可以轻松追踪和回退之前的指定版本,特别是在模型出错时非常有用。
- 通过 Metrics、Traces 和 Logs 提供开箱即用的可观察性:BentoML 内置了开箱即用的可观测性功能,包括测量指标、追踪和日志功能,帮助用户更好地监测和理解模型的性能。
- 容器化为标准的 OCI 镜像:BentoML 支持将整个机器学习应用打包为标准的容器镜像。
- 多种部署目标:可以将应用轻松部署在各种云服务器上,例如 AWS。
- 分布式部署与扩容:BentoML 支持分布式的部署与扩容,为处理高负载提供了便利。
- Serverless BentoCloud:这是 BentoML 最新支持的功能之一,允许以 Serverless 方式的部署。这意味着应用在没有任何请求时不占用任何资源,只有在第一个请求到达时才进行冷启动。
BentoML 模型是对机器学习模型的一种抽象。在机器学习领域,有许多不同的框架,每个框架都有自己的模型概念。BentoML 允许将这些不同框架的模型导入为 BentoML 模型。这种抽象化的设计有几个优点:
- 版本管理:BentoML 模型具有版本管理功能,这意味着可以轻松地追踪、管理和回滚模型的不同版本。这对于模型的迭代和更新非常有用。
- 团队协作:在开发 AI 应用时,通常会有研究团队负责训练模型。一旦模型被训练并保存为 BentoML 模型,就可以被推送到远程平台,供工程团队下载和使用。这种分工和协作的方式使得团队可以更加高效地合作,研究和工程团队可以专注于各自的任务。通过这种方式,团队成员不需要担心模型的具体来源,只需通过 BentoML 平台下载并使用相应的模型。这种团队协作的概念有助于提高整个开发过程的效率和协同工作的流畅性。
BentoML 的 Runner 具有以下特点:
- Remote Worker:Runner 将模型封装成一个远程对象,使其可以对外提供服务。这种分离的设计允许模型独立于应用部署,提高了系统的模块化性和可维护性。
- 独立扩容:由于 AI 应用的模型部分通常需要大量 GPU 资源,而其他任务如 API 接口、数据预处理和 IO 操作等则可能只需要 CPU。通过独立部署,它们可以相互独立地扩容。CPU 资源相对便宜,可以自由扩展,而 GPU 资源可能有限,例如只有 2 到 3 个。这种方式可以确保将 GPU 资源专注于模型推理,而将 CPU 资源用于其他任务,提高了整体的资源利用率。
- 支持主流 ML 框架直接转化为 Runner:BentoML 支持几乎所有主流的机器学习框架,包括 PyTorch 和 TensorFlow。模型可以直接转换为 Remote Runner,并在独立的进程中进行部署。
上图中展示了 BentoML API Server 的工作流程:应用从外部接收 HTTP 请求,可能是通过 POST 方法发送的 JSON 数据。接收到的 HTTP 请求数据可能需要进行预处理,以便转换为模型能够接受的格式。这个步骤通常涉及数据格式转换、验证等操作,确保输入数据符合模型的要求。IO Descriptors 定义了输入和输出数据的结构和类型,起到了数据格式转换桥梁的作用。经过 IO Descriptors 的转换后,数据被传递给模型进行推理。模型利用输入数据生成相应的预测或输出。模型的输出可能需要进行一些后置处理,以便将其转换为 HTTP 响应的格式。这个步骤包括将 Tensor 或 Numpy 数组转换为 JSON 格式等。处理完后的数据以 HTTP 响应的形式返回给请求方,完成了整个推理过程。这是应用与外部系统通信的出口。
BentoML 的请求处理流程经过了负载均衡层、API 服务器和 Runner 的多个层次:
- 负载均衡层:负载均衡层接收到所有的请求,并负责将它们分发给 API 服务器。这一层可以进行请求的智能分发,确保每个 API 服务器负载均衡。
- API 服务器:API 服务器是 BentoML 应用的入口,负责接收来自负载均衡层的请求。API 服务器可能会横向扩展,即创建多个实例以处理大量的并发请求。一旦 API 服务器接收到请求,它会进行一些预处理,然后将请求转发给 Runner。
- Runner:Runner 是对模型的封装,负责实际的模型推理。在这一层,模型推理支持批处理功能。当 Runner 接收到一个请求时,它会等待一段时间,看是否有其他请求到达。只有当等待时间达到最大值或者达到了规定的批处理容量时,Runner 才会一次性提交这批请求给模型进行推理。这个过程对用户是无感知的,而且可以提高推理的效率。
这里介绍一下 BentoML 中 Bento 的概念,Bento 是打包好的最小的部署的单元,包括机器学习的依赖、环境的 Dockerfile、源代码、需要的模型等各种部署的配置。
2、BentoML 应用举例
接下来通过一个实例来演示如何使用 BentoML 开发和部署 AI 应用。我们将创建一个应用,利用 LangChain 和 OpenLLM 实现广告词的生成功能,并展示 LangChain 和 OpenLLM 的集成。然后,我们将使用 BentoML 启动一个 HTTP 服务,通过一行代码切换 LLM 模型,并最终生成一个 Bento 包,以演示如何使用 BentoML 进行后续部署。
在前面的代码中,我们导入了一些 LangChain 的模块。如果你对 LangChain 比较熟悉的话,应该对这些模块有所了解,这里不做详细讲解。接下来是 BentoML 的部分。BentoML 主要导入了一些 IO Descriptor,这些描述符用于说明输入输出的类型。在这里,我们导入了两个类型,一个是 JSON,另一个是 Text。现在我们将利用这些导入的模块和描述符,因为我们的输入应该是 JSON 格式,而输出则是一段文字。
接下来,我们将使用 pydantic 的 Model 定义一个结构体,该结构体用于验证输入的有效性。这个 Query 对象包含以下几个参数:industry 表示所在行业,productName 表示产品名称,keywords 表示需要的关键词。这些参数是生成广告词所需的输入。
第二部分是一个 helper 函数,用于快速切换模型 ID。该函数返回一个 LangChain 对象。因此,我们首先使用 opt 模型,如果注释掉上面的一行并使用下面一行,就可以直接切换到使用百川模型。
这是 Prompt 的生成部分,利用 LangChain 的功能,指定一个 Prompt 的模板,然后将刚才输入的参数填充到这个模板中,生成一个完整的 Prompt。
接下来的步骤是定义要公开的接口。通过指定输入和输出的类型,将这个函数作为服务的一个接口。在第 48 行,将 LLM 的 Runner 传递给了这个服务,因为在启动服务时,需要知道有哪些 Runner 需要在分布式环境中远程启动。因此,这里的启动会生成两个独立的服务:一个是 API 接口层(`Service`),另一个是 LLM Runner。它们是相互独立的。
启动之后,可以看到与之前相似的 Swagger 页面,我们可以直接在上面进行测试。在这里,我们将行业设为"SAAS",产品名称设为"BentoML",执行后,它可以为我们生成一段广告词。这个广告词符合我们的需求,看起来像一条推文或者微信的内容。如果我们想生成一个房地产行业的广告,只需切换行业为"房地产",就可以得到一条看起来更符合房地产行业的广告,其中还包含一个房子的表情图标。
开发以及本地的测试完成后,将整个代码和模型打包成一个 Bento。通过使用 BentoML 的 Build 命令,可以轻松制作一个 Bento 包。制作完成后,还有两个后续的可选步骤。
第一个是将其容器化 containerize,打包成一个镜像,为后续的部署使用。
第二种选择是可以直接把这个 Bento push 到我们的 Cloud 平台上,Cloud 平台会自动帮你执行后续的部署操作。
Cloud 平台是一个能够部署你的 Bento 的平台。它会将 Bento 自动转化为一个运行在 Kubernetes 上的容器,并在其上运行。这里的例子包括一个 API 服务器以及多个不同的 runner,包括 LLM 的 runner 和 Text2Image 的 runner 等。此外,在 BentoCloud 上,可以指定扩容的方式,可以分别为 API 指定需要扩容的节点数,或者为每个 runner 指定需要的 GPU 数量或实例数。
五、OpenLLM 总结
OpenLLM 是一个开源的大语言模型(LLM)开发框架。它支持多种开源的 LLM 模型,并且具有内建的两个关键的 LLM 优化,能够优化内存使用。此外,它与 LangChain 和 BentoML 都有很好的集成。