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 将要如何支持社区标准,不遵循社区标准的部分,以及为了避免兼容性破坏可能采取的措施。

Warning

由于《数组 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 设计时采取的基本原则:

Note

  1. 默认情况下,采用《 数组 API 标准 》以及 数组 API 标准补充说明 作为基本规范;

  2. 对于《数组 API 标准》中尚待形成标准的主题内容(TODO),采取如下原则:

    1. API 科学合理是首要追求,只有在几种参考选择仅仅是习惯上的区别时,优先级为 NumPy > PyTorch > 其它;

    2. 除 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, 并且在文档中仅向用户展示这种用法,旧的用法将不被推荐(但保证兼容历史用法)。

Warning

任何 API Change 行为都需要在相关实现中给出参考标准,以便 API 委员会进行代码审核。

数组 API 标准补充说明#

本小节主要用于讨论:

  • 一些《数组 API 标准》中尚未明确的主题,以及 MegEngine 目前打算如何做;

  • 由于向后兼容等历史性问题,MegEngine 的 Tensor 实现与《标准》不一致的地方;

  • 将来打算如何提供与《标准》中完全一致的实现。

现有 Tensor 实现的差异性说明#

副本和视图,以及可变性( Copy-view behaviour and mutability

对于该情况《标准》中没有做硬性要求,MegEngine 中的 Tensor 没有视图(View)这样的概念。 与 view 有关的操作虽然能提升效率,但在语义上加大了用户的心智负担。

Warning

这不代表 MegEngine 中并不存在原地(Inplace)操作,以下行为依然是原地进行的:

  • 原地运算符(例如 *=

  • 元素赋值(例如 x[0] = 1

但由于 MegEngine 没有视图的概念,一个 Tensor 上的原地操作一定不会影响另一个 Tensor.

数据类型与类型提升( Data Types / Type Promotion Rules
  • 目前 MegEngine 中支持的数据类型可在 Tensor data type 中找到,并未完全实现《标准》中要求的数据类型,例如 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.numpymegengine.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 中的 foldunfold 两个接口, 在 MegEngine 中对应为 sliding_windowsliding_window_transpose. 这样的命名是经过研发人员和用户经过讨论后确定的,相关讨论应该被记录。

  • 由于 MegEngine 不存在类似 torchvision, torchtext 这样的领域细分库, 因此规定所有的神经网络领域概念相关接口均统一放在 functional.nn 命名空间 (具体实现的源代码文件如何组织不做要求,但提供给用户的 API 入口需要统一) 包括像 l1_loss 这样的接口,也统一使用 functional.nn 调用;

  • 相关 API 对比信息应当提供在 comparison 页面,方便用户查询。