Morpheus 架构
Morpheus 的组织结构可以分为四个不同的层级。从上到下依次是
编排层
负责协调管道和促进通信。
即,监控管道、在管道之间传输消息、启动和停止管道以及为管道分配资源。
在多机管道中发挥重要作用,但开箱即用即可用于单机管道。
管道层
由一个或多个通过边连接的阶段组成。
数据使用缓冲通道在阶段之间移动,管道将通过监控每个边缓冲区中的数据量来自动处理背压。
阶段层
Morpheus 中的主要构建块。
负责对来自管道中前一阶段的传入数据执行特定功能。
彼此隔离,可以被认为是黑盒。
由一个或多个通过边连接的节点组成。
所有节点都保证在同一台机器的同一进程空间中运行。
模块层
与阶段类似,模块可以构建和连接多个节点。
节点层
Morpheus 中最小的构建块。
每个节点都在同一线程上运行。
由一个或多个反应式编程风格的算子组成。
管道是一个或多个通过边连接的阶段的集合。数据使用缓冲区在这些边上从一个阶段流向下一个阶段。我们利用这些缓冲区来允许阶段以不同的速率处理消息。一旦每个阶段完成对消息的处理,管道就会将其移动到下一个阶段的缓冲区以进行处理。此过程持续到消息通过整个管道。
管道的主要目标是通过阶段的并行执行来最大化吞吐量。这样我们就可以最佳地利用硬件并避免顺序处理单个消息。给定一个由阶段 1 和阶段 2 组成的多阶段管道。阶段 1 从其数据源收集其第一条消息并开始处理它。一旦阶段 1 完成其第一条消息,生成的输出消息将被转发到阶段 2。此时,阶段 1 立即开始处理管道的下一个输入,而阶段 2 开始处理阶段 1 的输出。这允许在管道中同时存在多条消息,从而增加并行化。
以这种方式在阶段之间使用缓冲区确实是有代价的。增加缓冲区的大小有助于通过确保所有阶段都有一些工作要做来提高并行化。但是,这也增加了延迟,因为消息可能会在缓冲区中等待处理。反之亦然。减小缓冲区大小可以改善延迟,但可能会使某些阶段缺乏工作,从而降低并行化。管道必须在保持所有阶段都以尽可能小的缓冲区供应数据之间走一条细线。
阶段是 Morpheus 中的基本构建块,负责执行管道中的所有工作。一个阶段可以封装任何功能,并且能够与任何服务或外部库集成。这种自由度允许阶段的范围从非常小的 Python map 函数到非常复杂的推理阶段,后者连接到服务并在多个线程中工作。例如,Morpheus 具有用于诸如读取和写入文件之类的简单操作的简单阶段,以及更复杂的阶段,例如 Triton 推理阶段,它可以发送许多使用共享设备内存的异步推理请求。
虽然阶段非常灵活,但它们都包含三个主要部分:标识、类型推断和节点创建。
标识
阶段标识符是在日志记录和从 CLI 创建阶段时使用的唯一字符串。
类型推断
为了执行工作,每个阶段都需要知道它将要操作的数据类型。Morpheus 可以将任何类型的数据从一个阶段传递到另一个阶段,管道必须确保阶段之间每个边连接处的类型兼容。此过程称为阶段类型推断,并在管道构建阶段执行。
阶段类型推断是必要的,因为某些阶段的输出类型可能取决于前一阶段的输出类型。例如,考虑一个简单的直通阶段,该阶段将输入消息未修改地传递到下一阶段。如果我们的直通阶段前面是一个生成字符串的阶段,则其输出类型将为字符串。相反,如果它前面是一个生成整数的阶段,则其输出类型将为整数。
由于阶段输出类型的动态性质,阶段必须指定一个类型推断函数,该函数接受输入类型并返回输出类型。从源阶段开始,管道将使用此函数来确定源阶段的输出类型。然后,此结果将传递给下一阶段的类型推断函数,直到确定管道中每个阶段的输入和输出类型。
在构建阶段之后,阶段的输出类型无法更改。返回与构建阶段期间指定的类型不同的类型将导致未定义的行为。
节点创建
阶段最重要的部分是节点创建。节点创建函数负责创建构成阶段的节点的实例。与管道一样,阶段可以由一个或多个通过边连接的较小节点构建而成。
阶段和节点之间的区别在于,阶段保证同一台机器将在同一进程空间中运行所有节点。这允许节点优化它们在彼此之间传递的信息,以确保最大性能。例如,两个节点可以在它们之间传递原始 GPU 设备指针,从而以最小的开销实现最大性能。如果没有保证两个节点都在同一进程空间中运行,则传递如此低级别的信息将是不安全的。
在 23.03 版本中引入的模块,引入了一种定义工作单元的新方法,这些工作单元紧凑、可组合、可嵌套且完全可重用。一旦定义并注册了模块,就可以在新的和现有的管道中将其用作新的 ModuleStage,或者使用 builder.load_module(...)
直接加载到现有阶段的上下文中。