查看原文
其他

以贡献飞桨框架API为例,手把手教你从User进阶到Contributor

飞桨 百度AI 2023-03-16

2016年9月,飞桨框架正式开源。飞桨框架建设并非只靠百度工程师,也离不开热爱飞桨、热爱开源的开发者们,他们用自己的方式参与飞桨框架建设,与飞桨共同成长。作为大型开源项目,飞桨社区十分欢迎各位开发者积极提出需求或不足,并主动提交 PR 解决。本文将由开发者李其睿介绍贡献 API 的经验。


前段时间百度飞桨组织了飞桨黑客马拉松第三期活动,该活动发布了诸多有趣的开发任务。我参与了基础 API 方向的开发任务,并新增了 paddle.triu_indices API。这个 API 可以获取一个 Tensor 主对角线所有右上方元素的坐标,在不久前发布的飞桨2.4版本中已经上线(官方文档)。借此机会记录一下开发过程,供有兴趣为飞桨贡献的小伙伴入门参考。

▎感兴趣的同学可以直接参考 PR

  • 飞桨代码
    https://github.com/PaddlePaddle/Paddle/pull/45168
  • 文档
    https://github.com/PaddlePaddle/docs/pull/5161
  • 提案
    https://github.com/PaddlePaddle/community/pull/207

 一个 API 背后的组成 

由于飞桨核心是由 C++ 构建的,通过 Python 封装了 API 供用户使用,因此每个 API 均对应着 Python 部分的代码和 C++ 部分的代码,以下将分开介绍。

▎C++ 部分

现在飞桨的算子库核心代码都在 paddle/phi 这个目录下。

  • paddle/phi/kernels: 这个目录下是算子的实现部分,分别需要实现“.h”文件(头文件)、 “.cc”文件(CPU 算子)、“.cu”文件(GPU 算子) 这三个部分;
  • paddle/phi/infermeta: 这个目录下的文件是推理一个 API 的输出参数的 meta 数据,例如:数据维度、数据 type 等。该目录下有多个文件,根据算子输入 Tensor 的参数数量确定 InferMeta 函数放置位置,比如算子输入 Tensor 参数是0个,那推理函数需要放在 nullary.cc/h 中;如果是 Tensor 参数有2个,则需要放在 binary.cc/h 中;
  • paddle/phi/api/yaml:这个目录下是用以注册 API 属性的 yaml 文件,需要在 yaml 文件中添加你新增 API 的一些属性信息。

▎Python 部分

Python 部分相对而言内容比较少了,主要在 python/paddle 目录下。


  • python/paddle/tensor/creation.py: 这个文件主要是用来构造该 API 在 Python 端调用时的接口,其中包含了入参的校验、算子的调用过程。另外英文版的 API 文档也在这里进行编写;
  • python/paddle/_init_.py:初始化文件;
  • python/paddle/fluid/tests/unittests/test_xxxx.py:单测文件。

▎文档

一个 API 的完整构建,除了代码的开发外,其文档的撰写也是非常必要的。飞桨的中文文档位于 PaddlePaddle/docs 仓库中。


  • docs/api/paddle/Overview_cn.rst 中用一句话对 API 的功能进行简要概述;
  • docs/api/paddle/xxxxx_cn.rst 对于新增的 API,需要新增一个 rst 文件进行文档撰写,撰写内容包括 API 的输入、输出、代码示例等。

 撰写提案 

当着手开发一个新的 API 时,第一步不是写代码,而是进行分析和设计。

▎关于设计,有以下几点需要注意


  • 飞桨的现状。想要实现的 API 是否不存在、是否可以由其他 API 组合方式实现?
  • 业内是否有此 API?例如 PyTorch、NumPy、TensorFlow 等,如果有的话他们是怎么实现的?哪种实现方式更好?
  • 飞桨中新增的这个 API 的命名、参数设计。
  • 测试方案设计。

作为社区开发者,我们可以综合上述几点将设计写成文档,提交至 Coummunity 库,与飞桨的工程师沟通、调整并确定方案。

 正式开发 

▎拉代码并成功编译


  • 首先在 Paddle 仓库的 GitHub 页面上 fork 一份代码;
  • 最方便的搭建开发环境的办法就是使用飞桨官方提供的 Docker 镜像; 


  • 然后在 Docker 镜像内 clone,并切换到 develop 分支(最好在做每个任务时从 develop 分支 checkout 一个新分支,会更方便)。

顺利的话到此就编译成功。编译好的 whl 文件在/paddle/build/python/dist 目录下,进入目录使用 pip install 安装。

以后修改完代码就重复执行 make、pip3 install 的过程即可。

▎到此就可以正式开始写代码

第一步:首先需要实现最核心的 kernel 部分。

让我们从最简单的部分开始,头文件 triu_indices_kernel.h。头文件通常不需要写主要的代码逻辑,只要注册好方法就好。

第二步:接下来需要实现 CPU 端的代码逻辑 triu_indices_kernel.cc。

CPU 端的主体的计算逻辑其实并不复杂,这里对代码的几个注意点进行说明:


  • kernel 需要在 phi 这个命名空间下;
  • 受 Python 书写习惯影响,有时会输出 return。更好的方式是将输出需要用到的内存分配后作为参数传入 kernel;
  • 需要多关注边界条件,以及不要受惯性思维限制。比如 Tensor 不一定是正方形,对角线也不一定是主对角线,对于这类非常规情况的处理需要格外注意,这些情况可能也是需要代码量最多的地方(如下图所示);
  • 最后通过 PD_REGISTER_KERNEL 注册算子。 


第三步:接下来需要实现 GPU 端的 kernel:triu_indices_kernel.cu。

编写 GPU kernel 需要有一点 CUDA 的基础知识,这里需要稍加学习,理解基础的概念。如果零基础的话,指路比较好的入门资料如下:


  • 一个入门博客:

https://face2ai.com/program-blog/#GPU编程(CUDA)

  • 两本入门书:《CUDA By Example》《Professional CUDA C Programming》

在编写 GPU kernel 时,我们需要抛弃掉往常写代码线性执行代码的思想,将整个计算任务展开成一个计算组。有许许多多个 worker 帮我们,每个 worker 只需要负责很小一部分数据计算即可。当转变成并行计算的思想后,代码编写和理解就会稍微简单一些。

由于整体的 kernel 代码较长,这里不再另作展示,有需求的可自行前往代码库里查看。

第四步:写完两个 kernel,最难的部分就结束了,下面该写配套代码了。

首先是推理 meta 信息所在:nullary.cc 和 nullary.h。这一步很简单,在 nullary.h 里进行函数声明,在 nullary.cc 里实现具体推理 meta 信息的逻辑。cc 文件内较为复杂的是需要计算输出的 shape,当输入是正方形,对角线就是主对角线,其计算很简单。但当输入是矩形,对角线进行了向上或向下平移的话则需要考虑不同的情况。

第五步:最后一项工作就是注册算子。在 legacy_api.yaml 中添加信息(最新 develop 已经更新为 legacy_op.yaml)。

根据格式规范,写清楚 API 的名字、参数等重要信息。

第六步:下面就是轻松的 Python 代码环节。首先说核心的 creation.py。

这里定义的函数格式就是以后使用该 API 过程中交互的格式。

def triu_indices(row, col=None, offset=0, dtype='int64'):
    """
    英文文档部分
"""

# 校验各入参类型
# 根据计算图类型引用算子
# 返回计算结果
        )
    return out
其中主要做的是例如参数校验、与 op 交互等过程。另外,这里的文档编写是直接展示在英文版飞桨文档网页的,需要认真编写。编写完成后,在__init__.py中导入即可。

到这里 API 主体代码就全部完成了,可以执行 make、pip install 去进行测试了。这里可以先试验,体验一下自己的工作成果。

import paddle
data1 = paddle.triu_indices(4,4,0)
print(data1)
Tensor(shape=[210], dtype=int64, place=Place(cpu), stop_gradient=True,

Output:
       [[0, 0, 0, 0, 1, 1, 1, 2, 2, 3],
        [0, 1, 2, 3, 1, 2, 3, 2, 3, 3]]
)
▎单测的编写

简单的体验看起来是没问题的,下面需要编写详尽的单测案例来进行测试。主要测试以下几项:


  • 函数功能在各种参数配置下是否都正确,可以与 NumPy、PyTorch 进行比较;
  • 函数在动态图、静态图中是否都可以正常使用;
  • 函数是否能正常报错。

当所有的单测都通过,API 的代码开发就基本完成了。只剩最后一项,编写中文文档。

▎文档编写

文档主要内容包括 API 的功能、接口形式、入参/出参的具体含义和格式以及使用示例。这几部分内容和英文文档几乎可以一一对应,写完一个可再翻译另一个。


到此,整个 API 开发的流程就结束了。通常在开发编译时会反复报错、debug、修改代码、重试,这些过程是必不可少的,最终体验到的喜悦也是难以言表的。

如果你也想加入开源、体验贡献开源的乐趣,快来参加👇

飞桨快乐开源活动:快来提PR,领取新年礼物啦

为鼓励更多的开发者参与到飞桨社区的开源建设中,帮助社区修复 bug 或贡献 feature,加入开源、共建飞桨。飞桨快乐开源活动暖冬来袭,我们提供了三种类型的贡献任务,并为完成任务的贡献者准备了礼品表示感谢!

如果你对提 PR 仍有问题,欢迎扫描下方图片二维码沟通咨询~



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存