-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Description
任务划分🧾
Paddle 算子单测规范化任务分为两个阶段。
- 第一阶段是单测状态统计阶段,需要通过搜索查看算子的单测状态,在任务表格中填写单测状态,无需提交PR。
- 第二阶段是单测补充阶段,会根据一阶段统计结果发布需要添加单测的任务列表。
一阶段任务列表见:算子单测规范化统计表
认领方式: 请大家直接在👆的excel表中认领任务:
- 任务认领要求:一次最多同时认领 10 个统计任务
- PR提交格式:无需提交PR,直接在表格中更新状态
- 认领后,超过2周没有提交PR,将重新释放
二阶段任务列表::待一阶段完成后补充
认领方式: 请大家直接在👆的excel表中认领任务:
- 任务认领要求:为了方便排查 Bug,一次最多同时认领5个任务,一个算子至少需要添加 3 个 OpTest 单测
- PR提交格式:【Correctness TestCase No.xxx】开头,注明任务编号
- 认领后,超过2周没有提交PR,将重新释放
1. 前置知识介绍
1.1 算子体系介绍
用户使用深度学习框架搭建的各类模型,本质上是在描述一个计算过程。在框架和深度学习编译器的内部,会将用户构建的模型转换成一个由 Tensor 和 Operator 构成的计算。其中 Tensor 是深度学习中的基本张量表示,Operator 是用于执行各类运算的函数或类。
用户使用飞桨开发神经网络模型时使用的 Python 接口(如 paddle.add(), paddle.relu()等) 我们一般称都为飞桨的 Python API,每个运算类的 Python API 在框架内部都会对应到一个或者多个 C++ 端算子,每个算子在不同硬件设备上(CPU, GPU 等)实现的运算逻辑代码又被称为 Kernel, 这里主要是由于不同硬件设备提供的编程接口不同,所以虽然同一个算子的不同硬件设备 Kernel 都实现了相同的数学运算逻辑,但在代码实现上却有所差异。算子 InferMeta 函数是在算子 kernel 执行前先将输出结果的维度、数据类型等信息进行处理,由于计算量较小所以可以直接在 CPU 上计算,因此每个算子只需要实现一个 InferMeta 函数,而不必像 Kernel 一样在不同硬件上实现多个。
Python API、算子 Yaml 配置、算子 InferMeta 函数 和算子 Kernel 之间的关系如上图所示,最上层为用户使用的飞桨 Python API 接口,Python API 执行时会进入到 C++ 端由框架进行调度并执行相应的算子逻辑,算子的执行主要包括两个过程:
(1)执行算子 InferMeta 函数完成输出结果的维度、数据类型等静态信息的推导。
(2)根据输入变量的设备信息选择对应的硬件设备来执行算子 Kernel,完成输出结果的数值计算。
Python API 到算子 InferMeta 函数和 Kernel 调用之间的框架调度部分的逻辑代码主要通过算子 Yaml 配置中的信息自动生成,也可以理解为算子 Yaml 配置的作用是通过自动代码生成将上层 Python API 与底层算子的 Kernel 建立连接。
以 paddle.trace 算子(计算输入 Tensor 在指定平面上的对角线元素之和)为例,下表展示了其算子相关的定义在代码仓库中的位置
内容 | trace 示例代码仓库链接 | 查找方式 |
---|---|---|
算子描述及定义 | paddle/phi/ops/yaml/ops.yaml paddle/phi/ops/yaml/inconsistent/static_ops.yaml |
文件内搜索Op名(OpName),yaml定义总包含了除下方Python API和单测文件的所有信息 |
算子 InferMeta | paddle/phi/infermeta/unary.cc | infermeta 函数以OpName+InferMeta 命名,可以直接搜索拼接后的函数名 |
算子 Kernel | paddle/phi/kernels目录下的如下文件: /trace_kernel.h /cpu/trace_kernel.cc /gpu/trace_kernel.cu |
kernel 函数以 OpName+Kernel 命名,可以直接搜索 |
Python API | python/paddle/tensor/math.py | 算子的Python接口定义,可直接搜索 'def OpName' |
单元测试 | test/legacy_test/test_trace_op.py | Op单测文件一般以 test_OpName_op.py 命名,vscode环境下可直接用 command+p 搜索 |
推荐详细阅读 Paddle 官方文档中的算子添加过程加深对算子体系的理解:如何新增一个算子描述及定义
1.2 PIR&单测体系介绍
在深度学习模型构建上,有动态图和静态图两种模式,二者各有优势。动态图采用 Python 的编程风格,解析式地执行每一行网络代码,并同时返回计算结果,具备很强的灵活性。静态图需先在代码中预定义完整的神经网络结构,采用先编译后执行的方式,可优化的空间更多,性能更佳。飞桨框架采用了『动静统一』的设计方案,支持动态图编程和静态图编程两种方式,能同时兼顾动态图的高易用性和静态图的高性能优势:
- 在模型开发和训练时,推荐采用动态图编程。 可获得更好的编程体验、更易用的接口、更友好的调试交互机制。
- 在模型推理部署时,推荐采用动态图转静态图(以下简称:动转静)。平滑衔接将训好的动态图模型自动保存为静态图模型,可获得更好的模型运行性能。
在某些对模型训练性能有更高要求的场景,也可以使用动转静训练,即在动态图组网代码中添加一行装饰器 @to_static ,便可在底层转为性能更优的静态图模式下训练。
计算图中间表示(Intermediate Representation,即 IR)的概念起源于传统编译器,是介于源代码与目标代码之间的中间表示。在深度学习领域,它是从高层模型定义转换为底层可执行计算图的中间形式,是深度学习框架性能优化、推理部署、编译器等方向的重要基石。飞桨静态图核心架构分为Python前端和C++后端两个部分。在静态图模式中,Paddle框架会先根据用户的Python代码构造出模型的Program,C++后端会将Python端的Program转换为统一的中间表达(IR),以便于进行相应的编译优化(算子融合和存储优化)。
飞桨历史上在架构层面并存着多套不同的中间表示体系,其表达能力各不相同、Pass 开发维护成本较高,代码复用性较差,缺乏统一规范,存在严重的框架稳定性问题。在 3.0 版本下,飞桨研发了基于 MLIR 范式的新一代中间表示技术,即 Paddle IR(下简称 PIR)。PIR是Paddle的新版静态图IR表达,替代了旧版静态图的IR。PIR是Paddle的一次重大基础机制升级,目前进入推全、收尾阶段。
当前PIR核心机制基本健全, 在PIR的提供的灵活的基础组件的基础上Paddle框架和CINN编译器实现了服务于不同功能组件开发,如“组合算子”、“符号推导”等组件。算子单测是保障Paddle代码质量、正确性、计算精度的重要机制。在继承OpTest类实现的单测中,用户可以通过控制 Flag 灵活的开启或关闭算子的各类功能检查,是保证Paddle框架和编译器正确性的第一道防线。
2. 任务划分
2.1 任务背景
目前算子的单测有两种实现方式,分别是继承 OpTest 类和继承 unittest.TestCase 类的实现。
//test/legacy_test/test_eye_op.py
import numpy as np
from op_test import OpTest
import unittest
class TestEyeOp(OpTest)
class API_TestTensorEye(unittest.TestCase)
unittest 是更底层的类,OpTest是基于unittest开发的。相比于unittest,OpTest 有更强的扩展性,除了能做基本的算子功能检查外,还封装了用于各类检查的Checker,例如用于老静态图检查的StaticChecker、用于动态图检查的DygraphChecker、PIR模式下的PirChecker、用于检查算子符号推导功能的SymbolInferChecker。因此在Paddle功能不断扩展的过程中,算子的OpTest单测对我们来说具有十分重要的意义。下面以eye op为例,展示了该算子的一个OpTest单测实现。
class TestEyeOp(OpTest):
def setUp(self): // 执行单测的配置函数
'''
Test eye op with default shape
'''
self.python_api = paddle.eye // 指明该算子 Python 层的api
self.op_type = "eye" // 指明算子的 'name', 与yaml文件中对应
self.init_dtype() // 调用初始化数据类型函数
self.init_attrs() // 调用初始化属性参数函数
self.inputs = {} // 指定算子的运行时输入
self.attrs = { // 指定算子的属性列表
'num_rows': self.num_columns,
'num_columns': self.num_columns,
'dtype': framework.convert_np_dtype_to_proto_type(self.dtype),
}
self.outputs = { // 给出使用 numpy 实现的该算子同功能的输出,用于校验Paddle算子执行结果是否正确
'Out': np.eye(self.num_rows, self.num_columns, dtype=self.dtype)
}
def test_check_output(self): // 执行各类 Checker 的检查入口,各类检查可以通过 Flag 控制
self.check_output(check_pir=True)
def init_dtype(self): // 初始化tensor type
self.dtype = np.int32
def init_attrs(self): // 初始化 op 的属性值
self.num_rows = 319
self.num_columns = 319
3. 开发流程和示例
3.1 贡献 Paddle 代码的基本流程
Paddle 代码基本贡献流程:https://www.paddlepaddle.org.cn/documentation/docs/zh/dev_guides/api_contributing_guides/new_cpp_op_cn.html#liutianjiadanyuanceshi
3.2 本任务的开发流程
-
找到 yaml 文件中的该算子定义,根据文档、Kernel了解该算子的功能
-
搜索该 Op 单测文件以及定义
a. 根据 Op 单测文件命名搜索,参考1.1
b. 根据 self.op_type = "op_name" 搜索,参考2.1 -
查看是否有该 Op 的 OpTest 单测实现
-
补充 OpTest 单测
a. 编写 OpTest 单测的各项执行配置
b. 使用 Numpy 实现该配置下算子的预期输出
c. 配置 test_check_output() 中的各项Flag -
本地测试
-
整理代码、执行pre-commit、提交PR
-
申请代码 review、根据 review 意见讨论/修改
-
合入代码
4. 常见问题
待补充
Metadata
Metadata
Assignees
Labels
Type
Projects
Status