MEP 3 – Tensor API 设计规范#
- 编号:
3
- 标题:
Tensor API 设计规范
- 作者:
高华佐,曹骁威
- 状态:
草稿
- 类型:
信息
- 创建时间:
2021-09-16
摘要#
我们预计通过该提案确定在 MegEngine 中所使用的 Tensor API 规范,以便符合一致的开发和使用理念; 并提倡现在开始为采纳《 数组 API 标准 》做好相应的准备,后者将允许用户写出更具移植性的代码。 在该提案中将阐明 Tensor API 和社区标准之间的差异点,以及补充说明社区标准中未明确的部分。
背景与动机#
Python 用户可以选择各种不同的用于数值计算、数据科学、机器学习和深度学习的库和框架。 新框架几乎每年都在出现,百家争鸣所导致的一个后果便是基本数据结构 —— 多维数组(又名张量)的碎片化, 几乎每一个 API 都非常相似,但是它们之间又存在着足够的差异,导致用户很难编写适用于多个库的代码。
考虑到 NumPy 中所使用的设计和约束未必是最理想的,比如没有考虑到非 CPU 设备、基于图形的库或 JIT 编译器, 以及一些已有设计存在着副作用(比如基于值的类型提升规则),在一些情况下反而对库本身的开发形成了限制, 因此一些库在必要情况下选择了不遵循 NumPy 的部分设计,包括 MegEngine 也是如此。
Python 数据 API 标准联盟 在 2020 年发布了《 数组 API 标准 》,目前正接受社区审查。 它不规定任何有关如何实现功能的内容——只关心语法(如函数签名)和语义(预期输出是什么——形状、数据类型、数值结果) , 而不是性能、算法选择或获得数值结果的精度。NumPy 在自己的 NEP 47 提案中表明了积极适配的倾向, 并提出了一些参考实现,但该提案在相当长的一段时间内仍将处于草案状态。 MegEngine 的 Tensor API 设计也应当考虑尽可能地与社区其它成员相互兼容,但也有额外需要关注的地方:
上述标准并不能覆盖到 MegEngine Tensor API 的全部使用情景,如一些神经网络编程情景;
必须尽力保障不向现有的 Tensor API 设计引入兼容性破坏(Breaking Change),这将被主要讨论。
基于以上背景,我们需要一份提案(或许在很长一段时间内也将以草案的形式存在),来确定当前的 Tensor API 规范。 而术语《数组 API 标准》更多用于指代目前正在处于发展阶段的社区标准,只是它当前还不够完整,无法被直接使用。 《数组 API 标准》某种意义上可以看作是 Tensor API 设计规范的子集, 我们将在本提案中讨论 MegEngine 将要如何支持社区标准,不遵循社区标准的部分,以及为了避免兼容性破坏可能采取的措施。
警告
由于《数组 API 标准》本身仍处于发展变化状态中,因此本 MEP 也有随时进行更新的可能性。
旧版 API 设计原则#
以下为 MegEngine Tensor API 在设计之初所采取的基本原则:
与数组科学计算相关的 API 设计,如果在 NumPy 中存在相应实现,优先以 NumPy 作为参考标准;
与神经网络编程相关的 API 设计,如果在 PyTorch 中存在相应实现,优先以 PyTorch 作为参考标准;
如果一些 API 在 NumPy 和 PyTorch 中都有实现,默认优先以 NumPy 作为参考标准;
所有的 API 变更操作(包括新增),最终都需要经过 MegEngine API 委员会的审核。
另外,在 NumPy 中被认为不科学的 API 命名没有被 MegEngine 采纳,如 concatenate
被改为 concat
.
上述原则已被模糊地实施了几个版本,但未形成详细的参考规范,因此在实施新的规范时,需要保证向后兼容。
另外,由于一些历史原因,Tensor API 的设计与 NumPy 之间也存在着不一致的地方:
比如 NumPy 习惯将函数接受的输入位置参数命名为
x
, 而在 MegEngine 中通常为inp
;MegEngine 中提供的接口并不保证与 NumPy 完全对应,比如
dot
的实现;
这让我们的 API 设计无法脱离开 API 委员会的审核,由于缺少一份参考规范,给后续的维护引入了昂贵的成本。
采纳数组 API 标准#
我们惊喜地发现,Python 数据 API 标准联盟发布的《数组 API 标准》与 MegEngine Tensor API 的设计哲学高度一致, 因此我们接下来将选择用其代替 NumPy 实现作为首要参考标准,并尽量避免引入兼容性破坏。
Tensor API 设计规范#
以下是 Tensor API 设计时采取的基本原则:
备注
默认情况下,采用《 数组 API 标准 》以及 数组 API 标准补充说明 作为基本规范;
对于《数组 API 标准》中尚待形成标准的主题内容(TODO),采取如下原则:
API 科学合理是首要追求,只有在几种参考选择仅仅是习惯上的区别时,优先级为 NumPy > PyTorch > 其它;
除 NumPy 和 PyTorch 外,应该总是全面地比较各框架/库的 API, 包括但不限于 TensorFlow, OpenCV 等…
一些还未被讨论的主题或许会在将来被纳入《数组 API 标准》,届时 MegEngine 中应当向标准靠拢,并保留该旧接口以免造成兼容性破坏。
例如由于目前尚未形成 random
的《标准》参考实现,而按照 Tensor API 设计规范的基本原则,目前应当参考 NumPy 中的 random
进行实现,
其中有:
numpy.random.permutation
numpy.random.shuffle
numpy.random.Generator.permuted
几个语义极为接近的 API. 如果将来的《标准》中提出了更加规范的定义,MegEngine 将实现新的接口,并保留旧接口调用。
另一种情况是,如果《标准》中对 NumPy 中的 API 进行了重命名,比如使用 concat
代替 concatenate
,
那么 MegEngine 中将实现 concat
, 并且在文档中仅向用户展示这种用法,旧的用法将不被推荐(但保证兼容历史用法)。
警告
任何 API Change 行为都需要在相关实现中给出参考标准,以便 API 委员会进行代码审核。
数组 API 标准补充说明#
本小节主要用于讨论:
一些《数组 API 标准》中尚未明确的主题,以及 MegEngine 目前打算如何做;
由于向后兼容等历史性问题,MegEngine 的 Tensor 实现与《标准》不一致的地方;
将来打算如何提供与《标准》中完全一致的实现。
现有 Tensor 实现的差异性说明#
- 副本和视图,以及可变性( Copy-view behaviour and mutability )
对于该情况《标准》中没有做硬性要求,MegEngine 中的 Tensor 没有视图(View)这样的概念。 与
view
有关的操作虽然能提升效率,但在语义上加大了用户的心智负担。警告
这不代表 MegEngine 中并不存在原地(Inplace)操作,以下行为依然是原地进行的:
原地运算符(例如
*=
)元素赋值(例如
x[0] = 1
)
但由于 MegEngine 没有视图的概念,一个 Tensor 上的原地操作一定不会影响另一个 Tensor.
- 数据类型与类型提升( Data Types / Type Promotion Rules )
目前 MegEngine 中支持的数据类型可在 Tensor 数据类型 中找到,并未完全实现《标准》中要求的数据类型,例如 64 位数据类型和复数数据类型;
混合类型提升在《标准》中是未定义行为,而在 MegEngine 用户指南中明确了相关行为。
- 函数与方法签名( Function and method signatures )
位置参数必须使用 PEP 570 中规定的仅位置(Positional-Only)参数,语法为
/
符。 由于该特性仅在 Python >= 3.8 版本可用,而 MegEngine 目前仍需要支持一些低于 3.8 的 Python 版本。 因此现在的做法是在文档中以示例代码的形式为用户添加指导,要求用户以仅位置参数的方式去使用它们。 另一种方式是在文档字符串的第一行重载签名内容,参考 autodoc_docstring_signature .关键字参数必须使用仅关键字(Keyword-Only)参数,且保证向后兼容性。
对于只有单个 Tensor 输入作为位置参数的函数,在标准中要求使用
x
作为输入位置参数的名字。 而在 MegEngine 中,部分函数签名中的输入已经被命名为inp
, 当前此类参数命名并不统一。 由于位置参数要求以仅位置参数的形式使用,因此位置参数的名称改动将不被看作是兼容性破坏, 后续会有相应的命名变更,使之符合《标准》的要求。为了可读性,《标准》要求类型注释被排除在签名本身之外,但是需要被添加到单独的参数描述中。 目前 MegEngine 中 API 的做法是签名中的参数都需要添加类型注释,而不要求写在参数描述中。
《标准》参考示例中使用了 NumPy 风格的文档字符串,而在 MegEngine 中要求使用 Google 风格。
向后兼容性处理#
NumPy 的主命名空间下已经实现了非常多的 API, 为了在采用《数组 API 标准》的同时不破坏兼容性,
NumPy 在 NEP 47 中提出了添加一个单独的命名空间 numpy.array_api
的做法,
类似的做法也出现在一些其它想要采用现有 NumPy 标准的库或框架中(比如 jax.numpy
)。
而 MegEngine 在接口设计之初,并没有要求主命名空间与 NumPy API 完全对应,因此不存在这样的历史负担。
我们可以继续保留现有设计, megengine.functional
中将负责提供所有与 Tensor 相关的原子操作。
如果将来用户想要找到 numpy.array_api
中的对应接口,应当确保能够在 megengine.functional
中找到。
可能导致的问题是:
MegEngine 现有的一些接口和设计(未在当前《标准》提及)可能会和后续版本《标准》中的要求起冲突, 比如同一个 API 实现功能不同,或者在后续标准中要求统一支持视图操作,届时无法保证用法完全兼容。
可能的解决方案:
如果将来《标准》中对于一些概念有了更加具体的规定,或存在着相应的需求,导致无法处理向后兼容问题,
MegEngine 中或许会提供 megengine.functional.numpy
与 megengine.functional.array
命名空间,
以寻求和 NumPy 以及 Array API 标准的高度统一。也有可能不采用新的《数组 API 标准》,锁定某一个版本。
关于 NN API 的讨论#
目前 MegEngine 有关于神经网络编程的接口实现都存放在 megengine.functional.nn
命名空间中,
大都以 torch.nn.functional
作为参考标准(包括命名/分类规则)。
相关 API 在《数组 API 标准》中被划分为 misc functions 一类,尚未进行具体讨论。
以下是 MegEngine 在处理此类 API 时的基本思路:
PyTorch 中的一些 API 命名规则是不合理的,因此最终命名需要经过 API 委员会的审核。
例如 Pytorch 中的
fold
和unfold
两个接口, 在 MegEngine 中对应为sliding_window
和sliding_window_transpose
. 这样的命名是经过研发人员和用户经过讨论后确定的,相关讨论应该被记录。由于 MegEngine 不存在类似
torchvision
,torchtext
这样的领域细分库, 因此规定所有的神经网络领域概念相关接口均统一放在functional.nn
命名空间 (具体实现的源代码文件如何组织不做要求,但提供给用户的 API 入口需要统一) 包括像l1_loss
这样的接口,也统一使用functional.nn
调用;相关 API 对比信息应当提供在 comparison 页面,方便用户查询。