IBM推出的张量计算库Ocean Tensor Library
矩阵和张量运算构成了广泛领域和应用的基础,并且在许多情况下构成了整体计算复杂性的重要部分。通用gpu能够加速其中许多操作并使其他操作成为可能,这导致了这些设备的广泛适应性。为了充分利用张量运算的计算能力,需要专门的软件,目前有几个包(主要是在深度学习领域)包含了CPU和GPU上的张量运算。然而,支持一般张量运算的独立框架仍然缺失。在本文中,我们填补了这一空白,并提出了海洋张量库:一个模块化的张量支持包,它被设计为在各种设备类型上需要密集张量操作的应用程序的基础层。API经过精心设计,功能强大,可扩展,同时易于使用。这个包是开源的。
1简介
在过去十年左右的时间里,通用GPU已成功应用于医学成像[14,17]、分子动力学模拟[9]、射电天文学[3]、数据挖掘[4]、图形处理[15]和许多其他领域[10]。最近,gpu已经广泛应用于深度学习,并在很大程度上实现了深度学习。与其他领域相比,深层次学习的软件包可能更多,如Caffe[8]、Torch[7]和Tensorflow[1]。其中一个原因是在深入学习中需要灵活性,以便尝试不同的网络架构和数据转换,以获得最佳可能的模型。考虑到数据、节点参数和中间结果可以方便地以多维数组或张量的形式表示,这些包已经逐渐向通用计算环境转变。尽管取得了这些进展,但仍有很大的改进空间:现有的软件包往往是单一的,需要大量的外部依赖,并且往往缺乏张量布局灵活性、支持的数据类型或对新设备类型的扩展。最重要的是,一个独立的张量支持包被设计为广泛的其他应用程序的基础仍然缺失。为了填补这一空白,解决现有封装的一些缺点,我们提出了海洋张量包(Ocean Tensor Library),一个模块化的开源基础库用于密集张量操作。
我们在第二节描述了海洋张量包(
简称海洋Ocean)的设计和实现,并在第三节强调了它的一些独特特性。在第四节中,我们将查看现有的包,并将它们与海洋进行对比。我们在第5节中提供了一个使用海洋的示例,并在第6节中进行了总结。
2设计和实现
海洋张量(Ocean Tensor Library)包被设计成一个基础层,用于需要在一个或多个设备类型和实例上进行密集张量操作的应用程序。考虑到广泛的潜在应用和领域,重要的是将张量运算分组到相干模块中,而不是通过一个巨大的单片封装来提供。这样,用户可以在需要时安装功能,这有助于减少依赖项的有效数量。另一个优点是,与外部库的接口和兼容性被本地化为独立的模块,从而使包更易于管理。海洋中使用的另一个设计原则(本节稍后将讨论)是使用定义良好的层。
2.1 模块化
Ocean中的模块化模块包括一个接口以及每个受支持设备类型的独立实现。模块接口负责与设备无关的参数检查,包括张量的有效性和张量维度的兼容性。然后,它确定要使用的数据类型和设备,并为与设备类型关联的模块查询函数查找表。如果可用,则在执行所有必需的类型转换、广播以及结果张量和中间张量的分配(例如,当张量在内存中重叠时)之后调用该函数。如果函数不可用,或者尚未加载设备类型的模块实现,则会引发错误。设备级的函数通常只需要检查对给定数据类型的张量的支持,或者实现张量操作,或者更典型地,调用提供所需操作的低级库函数。
如果需要,函数可以访问与每个设备实例关联的特定于模块的上下文信息。
模块接口和设备实现可以单独加载,但核心模块接口除外,核心模块接口包括CPU实现。接口和实现之间的分离使得可以用替代方案来替换模块实现,例如高度优化或专用的专有版本。使用函数表还可以替换单个函数进行性能比较或调试,或者插入记录运行时或累积调用统计信息的函数。模块接口和设备实现之间的分离也使得使用新的设备类型扩展海洋变得容易。
特别是,每个模块中的模块和功能可以一次添加和测试一个,从而避免了开始时的巨大开发工作。
核心模块构成了海洋张量包的基础。它提供所有基本的张量操作,并实例化和公开可用的设备实例。许多标准功能,例如不同设备类型之间的打印和张量复制,都需要CPU上的张量支持,因此核心模块接口与CPU实现(pyOcean_CPU)相结合。GPU实现可以通过导入pyOcean_GPU单独加载。
为了方便起见,这两个包裹都是通过海运进口的。允许模块之间的依赖关系,并且一个模块的实例化可以导致加载其他模块。仔细注册模块的实例化顺序,以确保只有当没有其他模块依赖于模块时,才能最终确定模块。
2.2分层实施
在Ocean Tensor包的实现中,注意保持不同抽象级别之间的干净分离,如图1所示。底层的库提供独立于张量表示的低级张量操作。这包括现有的库,如BLAS和CUBLAS,以及定制开发的坚实基础库,它为CPU和GPU提供基本张量函数。这个级别的库不是特定于海洋的,可以由需要低级别张量运算的其他应用程序独立使用。海洋张量API定义了一个统一的张量类型以及各种张量操作,它们被组织为模块。如上所述,模块本身可以分为两层,即接口和设备实现。海洋API是用C语言实现的,以最大限度地提高其他语言的可访问性。图1中的顶层显示了可能绑定到Ocean的语言(当前版本只实现对Python的支持)。
但是,API的使用不限于语言绑定。例如,使用张量运算的应用程序,或者支持符号张量计算图的库,也可以构建在海洋张量API之上。
3功能
Ocean Tensor软件包[2]为CPU和GPU提供了一套全面的张量操作。 这些函数可以直接作为C库使用,也可以通过易于使用的Python界面使用。 在本节中,我们将探索该软件包的某些功能,并以基于Python接口的代码摘录进行说明。
3.1对象类型
Ocean Tensor Package的用户界面公开了几种对象类型,如图2所示。在对象层次结构的顶部是Tensors和Scalars,它们每个都有给定的数据类型。 张量是存储为存储对象的连续内存块的视图。 与每个存储对象相关联的是一个流,该流用于调度异步操作以及维护流间依存关系。 流对象本身与特定设备类型(例如CPU或GPU)的设备实例(例如CPU或GPU#0)关联。
3.1.1设备
设备对象使实例化张量或存储对象时可以使用的设备规范。 此外,它们还提供给定设备的一般信息,例如支持字节交换数据或所有当前已加载模块的列表。 根据设备类型,可能会提供其他信息。 例如,在GPU设备上,可以查询许多属性,包括多处理器计数或当前可用的空闲内存。 高级功能包括实例化新流,指定设备的中间张量缓冲区的数量及其最大大小。 Ocean维护可用设备的列表,包括ocean.cpu设备以及GPU设备的ocean.gpu列表,可以对它们进行索引以获得所需的设备实例。
3.1.2存储
存储对象封装了一块连续的内存,该内存可以动态分配,也可以由外部源提供。 与存储关联的数据类型有两个主要目的:首先,当在不提供类型的情况下从存储实例化张量时,将其用作默认数据类型; 第二,格式化存储元件以供显示。 存储对象的数据类型可以自由更改,而不会影响使用它的张量。 可以在同一存储上叠加不同数据类型的张量。 发生这种情况的一个典型示例是查询复杂的双精度张量的虚部时,这会导致在double类型的存储中产生一个额外的张量视图。 可以共享同一存储的不同张量类型的数量没有限制。 张量操作使用存储流进行同步,以避免竞争情况和数据不一致。
可以将存储数据标记为只读,这可以防止直接或通过张量操作对数据进行任何更新(将存储标记为只读反映在所有派生的张量中)。
3.1.3张量
海洋张量易于在任何可用设备上实例化。 例如,在设备gpu [0]上创建具有单精度浮点格式的3×4张量只需编写:tensor = ocean.tensor([3,4],ocean.float,ocean.gpu [ 0])。 省略数据类型或设备时,将使用用户指定的默认值。 与稍后在第4节中介绍的某些其他程序包相比,没有当前活动设备的概念,也不需要进行显式设备更改即可实例化新张量或对其执行操作。 默认情况下,张量遵循列为主的内存布局,但支持常规跨度(以字节为单位),从而允许与大多数Numpy张量兼容。 Numpy [12]的两个区别是(1)支持Ocean中复杂的半精度数据类型,以及支持Numpy中的其他数据类型(例如字符串和日期时间),以及(2)张量维数的最大值。 目前,Ocean中最大张量维数设置为8,但是此限制很容易放松或消除。 Numpy的硬编码最大张量为32。 与Numpy相似,Ocean允许CPU上的张量具有小端字节顺序和大端字节顺序,并且张量操作可以按任一字节顺序进行。 如果需要,可以通过字节交换元素或在标志和实际字节顺序不匹配的情况下,通过简单地指定适当的字节顺序来轻松更改字节顺序。
3.2 Tensor操作
如第2节中所述,Ocean中的Tensor操作是通过模块提供的。Core模块构成了Ocean的基础,并且包括基本对象类以及设备实例的定义。 作为最基本的操作,Core模块支持张量创建。 带有或不带有初始化的数据,来自存储以及嵌套列表,序列和其他张量类型形式的数据的数据。 除了创建张量,Core模块还提供了广泛的基本功能集,包括用于形状和轴操作,索引,复制功能,类型和设备转换,基本算术运算,三角运算的功能(在所有实数和复数浮点上均受支持) 类型),以及沿一个或多个轴的张量缩减。 (可以在Ocean Tensor Package存储库[2]中找到功能的完整列表。)下面,我们重点介绍一些功能。
3.2.1类型转换
张量的类型可以看作是数据类型和与张量关联的设备的组合。 海洋中的张量具有关联的类型,因此有时可能需要类型转换。 显式类型转换可以使用ocean.cast函数(仅返回所请求类型的张量的副本)和ocean.ensure函数(仅当所请求的类型与输入的类型不同时才返回类型转换的张量)使用。 使用数据类型(ocean.float(T))或设备实例(ocean.gpu [1](T))进行类型转换等效于仅使用数据类型或设备更新来调用ensure函数。
Ocean中的隐式类型转换用于确保张量操作的输入参数具有适当的类型和字节顺序。 例如考虑张量加法:C = A + B。 为了避免对所有可能的类型组合实施加法运算,我们需要根据A和B的类型确定C的类型,并相应地对数据类型和设备进行标准化。 解决设备类型的一种方法是强加设备订购并选择优先级最高的设备。 这要求对命令进行规范,并且肯定会导致意外结果,当然,当可以从代码的其他部分更改设备命令时也是如此。 另一种方法是始终从操作员的左侧或参数列表中的第一个参数使用设备。
在A + = B中,很明显B应该被强制到A的设备,因此我们对A + B进行相同的操作。 如果希望使用B的设备(即B.device),则在这种情况下可以写B + A或使用显式强制转换:ocean.ensure(A,B.device)+ B或 只需B.device(A)+ B
对于隐式转换数据类型,我们遵循Numpy并使用可以保留两种数据类型的最小可用数据类型。 例如,将有符号和无符号8位整数相加会得到16位有符号整数。 由于没有标准数据类型可用于四精度浮点数,因此对于64位整数和浮点数会产生异常,这会导致双精度浮点数。 默认情况下,Ocean上的自动类型转换默认是打开的,但是如果需要严格的类型检查,则可以由用户禁用。 关闭时,只要遇到类型不匹配,就会引发异常。
例如,当取负实数的平方根或大小大于1的标量的反余弦时,基于张量的内容进行类型转换是理想的。
这样的运算是否应该导致非数字(NaN)值,返回复数值结果或产生错误? Ocean中采用的方法是向此类运算符添加指示计算模式的参数。 在标准模式下,无需对张量元素进行检查,并且在需要时会生成NaN值。 在警告和错误模式下进行检查,当遇到值超出操作员域的元素时,分别给出警告或错误。 最后,在复杂模式下,进行检查以确定是否结果数据类型应为实数或复数。 如果需要,可以始终使用显式类型转换。
3.2.2索引
Ocean支持沿一个或多个维度的多种索引模式,这些模式可以组合以对张量进行索引。 基本的一维分度模式是:(1)标量,沿轴分度单个元素; (2)范围,以索引规则排列的元素集; (3)冒号“:”运算符表示整个尺寸。 除了基本模式外,还可以使用一维或二维索引数组来选择特定元素,方法是沿一维指定索引,或者沿多个维指定索引元组。 按照Python的惯例,可以使用负索引来指示相对于维度末端的索引。 最后,布尔张量可以用作索引的掩码,其中非零元素表示要选择的元素。 索引中省略的维将使用冒号运算符进行隐式索引,并且省略号对象“ ...”可能会出现一次,以指示在该位置应用零个或多个冒号运算符来完成索引。 仅使用基本索引模式(显式或隐式)对张量进行索引时,该张量的视图以共享原始存储的新张量的形式返回。 在所有其他情况下,通过复制索引元素来创建新的张量
索引数组和布尔掩码需要特殊的预处理:对于索引数组,需要检查索引的有效性; 对于布尔型蒙版,必须计算所选元素的数量,以确定输出张量的大小; 并将所选择的索引转换为相对偏移量到被索引张量的数据缓冲区中。 当这样的索引被重复使用时,为每次使用应用相同的预处理步骤就浪费了计算工作。 为了避免这种情况,Ocean引入了索引对象,该索引对象是通过对ocean.index对象建立索引来构造的(在常规函数调用中,不允许将range和冒号参数作为参数使用)。 一旦创建了索引对象,就可以将其绑定到张量大小以转换负索引,检查索引的有效性,确定索引范围与给定维的重叠,以及将布尔掩码转换为显式索引。 索引对象可以随后(或直接)绑定到张量跨度,后者将所有索引数组和布尔掩码转换为张量数据内的相对偏移量。 绑定索引对象和未绑定索引对象都可以以与用于构造它的索引模式完全相同的方式使用。 这样,如果需要,可以使用索引对象来创建其他索引对象。 当索引对象绑定到大小时,相关的张量尺寸必须匹配,并且跨度也必须匹配。
3.2.3互操作性
Ocean的Python接口提供了插件模块,用于定义张量和标量的外部对象类型,以及在这些对象和相应的Ocean类型之间进行转换。 解析张量操作参数时,会将插件提供的所有扩展对象类型进行比较。 这使它们的使用方式与洋张量和标量基本相同。 例如,我们可以通过导入pyOceanNumpy来声明Numpy张量和标量类型。 一旦完成,就可以编写诸如A + np.asarray([4,5,6])的表达式,其中A是一个海洋张量。 可以使用A.convertTo('numpy')转换为Numpy,其中'numpy'字符串由插件注册。 当受外部张量类型支持时,将生成张量的浅表副本,除非用户另有要求。
3.2.4解除内存分配
Python中的自动垃圾收集可以延迟张量对象的删除,并导致设备耗尽可用内存,尽管用户进行了认真的管理。 为了强制删除张量,可以调用dealloc张量函数,该函数维护Python张量对象,但将内容替换为空张量。 这样可以释放任何动态分配的张量数据,同时避免在重新分配后意外使用张量的问题。
4 现有的软件包
我们现在将海洋中的一些功能与其他软件包中的功能进行比较。由于大多数软件包都处于活动开发阶段,因此我们只讨论编写时可用的功能。
Numpy[12]是用于密集张量操作的事实上的Python包。Numpy是为cpu上的张量编写的,不支持任何其他设备类型上的张量。最近的CuPy[11]包为GPU设备实现了tensors,它的接口与Numpy的接口非常相似,但在其他方面基本上是独立的。这两个包都是作为Python-C API编写的,并直接扩展Python类,这限制了它们作为独立包的使用。此外,这两个包中的每一个只支持一种设备类型。
ArrayFire[18]是一个支持多种设备类型并作为具有单独语言绑定的通用库编写的包。这同样适用于大多数深度学习包。正如在引言中提到的,张量操作是深度学习包的基础,因此我们考虑一些最流行的软件:CAFE(8)、PyTrof(6, 13)、TunSoFrase[1 ]和MxNET[5 ]。所有这些包都支持多种设备类型上的张量操作,并将至少一部分可用的张量操作公开给用户。尽管如此,考虑到对深度学习的关注,这些包不是编写的,也不是打算用作独立的张量支持包。特别是,许多包定义了具有高度特定于域的成员函数和变量的张量类。例如,类可以为每个张量提供梯度信息,或者包括对包含张量的符号计算图节点的引用。
转存失败重新上传取消转存失败重新上传取消
在表1中,我们列出了一些我们认为在通用张量包中很重要的性质,因此在海洋中实现。如第3.2.1节中详细讨论的,这些特性之一是支持自动类型铸造。Numpy、CuPy和ArrayFire都支持此功能,但正在考虑的所有深度学习包中都缺少此功能。
从开发人员的角度来看,有一个统一的张量类型或类是很方便的。除Caffe外的所有包都支持此功能,Caffe提供了一个模板类(其他几个包在后台使用模板类,但在API中提供统一类型)。对于tensor包2,支持一组全面的数据类型显然很重要。包之间的覆盖范围有很大的不同,因此我们将重点放在对复杂数据类型的支持上,这需要额外的功能,例如共轭和对张量的实部和虚部的访问。在所考虑的四个深度学习包中,只有TensorFlow支持复杂的张量类型(基于单精度和双精度浮点)。Numpy、CuPy和ArrayFire也支持这些类型。Ocean是唯一一个额外提供基于半精度浮点的复杂数据类型的包。
张量在内存中的布局由步长或沿每个维度的连续元素之间的距离给出。 张量跨度的灵活性可实现以下功能:沿尺寸进行广播,轻松操纵轴以及在定期索引的次张量上创建视图。 此外,它确保与张量和矩阵的各种现有数据类型兼容。 大多数深度学习包以及ArrayFire都遵循连续的行为主数据顺序,并具有隐式跨度,可以根据张量维和给定数据类型的元素大小来推断隐式跨度。 PyTorch默认情况下也使用此数据顺序,但是允许用户通过将张量步幅指定为元素大小的非负倍数来覆盖标准布局。 Numpy和CuPy支持任意步伐。 这些软件包中的每一个都与Ocean一起,在可能的情况下实现轴的排序和合并连续的轴,以增加内存局部性并减少遍历维度的开销,这两者都有助于提高跨步数据的张量运算的计算效率。 对于许多操作,例如一元和二进制元素级操作,使用连续张量布局的包可以将张量展平为一个维。 其他操作可能需要与上述类似的优化。 ArrayFire将张量维数限制为四个,并且经常使用显式嵌套的for循环,并在最内层的循环中使用索引计算来遍历数据。
提供任意步幅带来的一些困难是张量可能在内存中自动重叠,并且成对的张量之间的重叠检测变得不平凡。
为了获得一致的计算结果,例如A [[1,2]] = A [[2,1]],对重叠检测的良好支持至关重要。 Ocean检查自重叠张量,并在大多数操作中将它们视为只读(未正确定义将不同值写入相同内存地址的语义)。 还包括张量对之间的重叠检测以及中间张量的分配以解决重叠。 在PyTorch和Numpy中也存在类似的检查。 TensorFlow中的重叠检测仅限于张量视图。
除了Ocean之外,本节中考虑的所有程序包都没有定义张量类型与张量操作的底层实现之间的明确区分。 结果,除了现有库(例如BLAS和cuBLAS)已经提供的那些张量操作之外,其他任何张量操作都无法轻松转移以用于其他程序包。
5说明性示例
我们现在基于示例QR因式分解来说明Ocean Tensor Package的某些功能(例如,参见[16])。 当然,这仅仅是一个例子。 QR分解应该是软件包不可或缺的一部分,并计划在未来的线性代数模块中提供直接支持。 可以在Ocean Tensor Package存储库的文档中找到核心模块提供的功能的完整列表以及大量示例[2]。
6 结论
在本文中,我们介绍了Ocean Tensor软件包,这是一个通用的张量支持软件包,用于在不同设备类型上的密集张量。 该软件包以模块化的方式组织,将张量操作的连贯集合组合在模块中。 每个模块都包含一个与设备无关的接口,该接口公开了可用的功能,以及单独的设备专用模块,这些模块提供了这些设备上这些功能的实现。 在Ocean中,一个有意识的设计决定是提供清晰分离的抽象层。 底层提供了独立于张量表示的低级张量操作,因此也可以被其他软件包用作独立的基础库。 Ocean Tensor软件包的基础牢固地建立在核心模块中,因此未来的工作将主要集中在通过添加新模块和扩展现有模块来提高功能性上。
Available at https://github.com/ibm/ocean-tensor-package