WholeMemory#
WholeMemory 可以被视为 GPU 内存的整体视图。无论底层数据如何分布在多个 GPU 上,WholeMemory 都提供一个内存实例的句柄。WholeMemory 假设每个 GPU 由一个独立的进程控制。
WholeMemory 基础#
要定义 WholeMemory,我们需要指定以下内容
1. 指定处理内存的 GPU 集合#
由于 WholeMemory 由一组 GPU 拥有,因此需要指定这组 GPU。这可以通过创建 WholeMemory 通信器 并在创建 WholeMemory 时指定该 WholeMemory 通信器来完成。
2. 指定内存位置#
虽然 WholeMemory 由一组 GPU 拥有,但内存本身可以位于主机内存或设备内存上。因此需要指定内存的位置。可以指定两种类型的位置。
主机内存:将使用锁定(pinned)主机内存作为底层存储。
设备内存:将使用 GPU 设备内存作为底层存储。
3. 指定内存的地址映射模式#
由于 WholeMemory 由多个 GPU 拥有,每个 GPU 都将访问整个内存空间,因此我们需要地址映射。有三种类型的地址映射模式(也称为 WholeMemory 类型),它们是
连续模式 (Continuous):每个 GPU 的所有内存将被映射到每个 GPU 的一个连续内存地址空间中。在此模式下,每个 GPU 可以直接使用单个指针和偏移量访问整个内存,就像使用普通设备内存一样。软件不会看到差异。硬件点对点(peer-to-peer)访问将处理底层的通信。
分块模式 (Chunked):每个 GPU 的内存将被映射到不同的内存块中,每个 GPU 一个块。在此模式下,也支持直接访问,但不是使用单个指针。软件将看到分块的内存。然而,一个抽象层可以隐藏这一点。
分布式模式 (Distributed):其他 GPU 的内存不会映射到当前 GPU,因此不支持直接访问。要访问另一个 GPU 的内存,需要明确的通信。
如果您想了解 WholeMemory 位置和 WholeMemory 类型的更多详细信息,请参阅 WholeMemory 实现细节
WholeMemory 通信器#
WholeMemory 通信器有两个主要目的
定义一组在 WholeMemory 上协同工作的 GPU。 WholeMemory 通信器由所有希望协同工作的 GPU 创建。只要需要的 GPU 集合相同,WholeMemory 通信器就可以重复使用。
提供 WholeMemory 所需的底层通信通道。 WholeMemory 在创建 WholeMemory 和对某些类型的 WholeMemory 执行某些操作时可能需要 GPU 之间的通信器。
要创建 WholeMemory 通信器,首先需要创建一个 WholeMemory Unique ID,通常由 GPU 集合中的第一个 GPU 创建,然后广播给所有希望协同工作的 GPU。然后,此通信器中的所有 GPU 将使用此 WholeMemory Unique ID、当前 GPU 的 rank 和所有 GPU 的数量调用 WholeMemory 通信器创建函数。
WholeMemory 粒度#
由于底层存储可能物理上被分区到多个 GPU 上,通常不希望在一个单一的用户数据块内部发生分区。为了解决这个问题,在创建 WholeMemory 时可以指定数据的粒度。因此,WholeMemory 被视为多个具有相同粒度的数据块,并且在粒度内部不会被分割。
WholeMemory 映射#
由于 WholeMemory 提供 GPU 对内存的整体视图,通常需要映射来访问 WholeMemory。不同类型的 WholeMemory 支持不同的映射方法,其名称就说明了这一点。一些支持的映射包括
所有 WholeMemory 类型都支持映射本地 GPU 负责的内存范围。也就是说,每个 rank 都可以直接访问所有类型的 WholeMemory 中的“本地”内存。这里的“本地”内存不一定在当前 GPU 的内存中,它可能在主机内存甚至可能在其他 GPU 上,但保证当前 GPU 可以直接访问它。
分块模式和连续模式的 WholeMemory 也支持分块映射。也就是说,所有 GPU 的内存都可以映射到当前 GPU,每个 GPU 对应一个连续的块。当前 GPU 可以直接访问每个块。但不同块的内存不保证是连续的。
连续模式的 WholeMemory 可以映射到连续的内存空间中。也就是说,所有 GPU 的内存都映射到一个单一的虚拟内存范围,访问此内存的不同位置将物理上访问不同的 GPU。这种映射将由硬件(CPU 页表或 GPU 页表)处理。
WholeMemory 操作#
可以对 WholeMemory 执行一些操作。这些操作基于 WholeMemory 的映射。
本地操作#
由于所有 WholeMemory 都支持本地内存的映射,因此支持对本地内存的操作。操作可以是读或写。就像使用当前设备的 GPU 内存一样即可。
加载 / 存储#
为了方便文件操作,支持从文件加载 WholeMemory 和将 WholeMemory 存储到文件。WholeMemory 使用原始二进制文件格式进行磁盘操作。对于加载,输入文件可以是单个文件或文件列表,如果是列表,它们将被逻辑上连接起来然后加载。对于存储,每个 GPU 将其本地内存存储到文件,生成一个文件列表。
收集 / 分散#
WholeMemory 还支持收集 / 分散操作,这些操作通常在 WholeMemory 张量 上进行。
WholeMemory 张量#
与 PyTorch 相比,WholeMemory 类似于 PyTorch 存储 (Storage),而 WholeMemory 张量类似于 PyTorch 张量 (Tensor)。目前,WholeMemory 仅支持 1D 和 2D 张量,即数组和矩阵。只有第一维被分区。
WholeMemory 嵌入#
WholeMemory 嵌入就像 2D WholeMemory 张量,增加了缓存支持和稀疏优化器支持。
缓存支持#
WholeMemory 嵌入支持缓存。要创建带缓存的 WholeMemory 嵌入,首先需要创建 WholeMemory CachePolicy。WholeMemoryCachePolicy 可以通过以下字段创建
WholeMemory 通信器:WholeMemory CachePolicy 也需要 WholeMemory 通信器。此 WholeMemory 通信器定义了缓存所有嵌入的 GPU 集合。它可以与创建 WholeMemory 嵌入时使用的 WholeMemory 通信器相同。
WholeMemory 类型:WholeMemory CachePolicy 使用 WholeMemory 类型指定缓存的 WholeMemory 类型。
WholeMemory 位置:WholeMemory CachePolicy 使用 WholeMemory 位置指定缓存的位置。
访问类型:访问类型可以是只读或读写。
缓存比例:指定缓存将使用多少内存。此比例针对缓存整个嵌入的每个 GPU 集合计算。
有两种最常用的缓存。它们是
设备缓存的主机内存:当 WholeMemory CachePolicy 的 WholeMemory 通信器与创建 WholeMemory 嵌入时使用的 WholeMemory 通信器相同时,意味着缓存与 WholeMemory 嵌入具有相同的 GPU 集合。因此,每个 GPU 只缓存其自身部分的原始嵌入。通常,当原始 WholeMemory 嵌入位于主机内存上,而缓存位于设备内存上时,每个 GPU 只缓存其自身部分的主机内存。
本地缓存的全局内存:WholeMemory CachePolicy 的 WholeMemory 通信器也可以是 WholeMemory 嵌入的 WholeMemory 通信器的子集。在这种情况下,这组 GPU 共同缓存所有嵌入。通常,原始 WholeMemory 嵌入分布在不同的机器节点上,我们希望在本地机器或本地 GPU 中缓存一些嵌入,那么这组 GPU 可以是本地机器上的所有 GPU。本地缓存的全局内存仅支持只读。
WholeMemory 嵌入稀疏优化器#
WholeMemory 嵌入的另一个特性是它支持嵌入训练。为了高效地训练大型嵌入表,需要一个稀疏优化器。WholeMemory 嵌入稀疏优化器可以在缓存或非缓存的 WholeMemory 嵌入上运行。目前支持的优化器包括 SGD、Adam、RMSProp 和 AdaGrad。