.. _mep-0003: ============================ 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:: #. 默认情况下,采用《 `数组 API 标准`_ 》以及 :ref:`array-api-addon` 作为基本规范; * `Purpose and scope `_ * `Design topics & constraints `_ * `API specification `_ #. 对于《数组 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``, 并且在文档中仅向用户展示这种用法,旧的用法将不被推荐(但保证兼容历史用法)。 .. warning:: 任何 API Change 行为都需要在相关实现中给出参考标准,以便 API 委员会进行代码审核。 .. _array-api-addon: 数组 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 中支持的数据类型可在 :ref:`tensor-dtype` 中找到,并未完全实现《标准》中要求的数据类型,例如 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 对比信息应当提供在 :ref:`comparison` 页面,方便用户查询。 .. _Python 数据 API 标准联盟: https://data-apis.org/ .. _数组 API 标准: https://data-apis.org/array-api/latest/ .. _NEP 47: https://numpy.org/neps/nep-0047-array-api-standard.html .. _PEP 570: https://www.python.org/dev/peps/pep-0570/ .. _autodoc_docstring_signature: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html .. _PEP 3102: https://www.python.org/dev/peps/pep-3102/ .. _PEP 484: https://www.python.org/dev/peps/pep-0484/ .. _array_object: https://data-apis.org/array-api/latest/API_specification/array_object.html .. _DLPack: https://github.com/dmlc/dlpack .. _Copy-view behaviour and mutability: https://data-apis.org/array-api/latest/design_topics/copies_views_and_mutation.html .. _Data Types: https://data-apis.org/array-api/latest/API_specification/data_types.html .. _Type Promotion Rules: https://data-apis.org/array-api/latest/API_specification/type_promotion.html .. _Function and method signatures: https://data-apis.org/array-api/latest/API_specification/function_and_method_signatures.html