-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
【PIR】 PIR算子推全和单测修复 #59382
Comments
【报名】:2 |
【报名】:6 |
【报名】:7-10 |
【报名】:38 |
This comment was marked as resolved.
This comment was marked as resolved.
取决于前向算子定义在哪里吧?如果前向定义在 |
【报名】:14-17 |
【报名】:26 |
请教一下,我按照文档修改test_fused_token_prune_op代码后通过了编译,但在单侧时报错。我按照提示打印了前后的program,但似乎并没有错误,想麻烦看一下 @xingmingyyj @kangguangli 可能是哪里出现了问题,万分感谢!
|
这个算子是通过 |
@xingmingyyj 可以把这个问题加入到Q&A中 |
非常感谢!问题已经解决,pr已经提交在 #62644 ,请查看 |
【报名】:27,28,29 |
@MayYouBeProsperous 请问32/33两个任务需要释放么 |
@luotao1 |
【报名】:12 |
@MayYouBeProsperous 这里33在#59431 被重命名了,现在的Op名是fused_conv2d_add_act,相应的单测是test/legacy_test/test_fused_conv2d_add_act_op.py 。因为这个PR是在issue发布后合入的,一直也没发现,感谢反馈。 |
【报名】:13 |
【报名】:21 |
【报名】:22,30 |
【报名】:1 |
在打开FLAGS_PIR_OPTEST和FLAGS_PIR_OPTEST_WHITE_LIST的情况下no.29 test_coalesce_tensor_op不用修改就可以通过单侧,请问这个还需要修改吗? |
可以直接把它放到白名单里试一下,如果CI上通过了,就不用修复了。 |
好嘞了解了 |
如果可以通过,也需要提交PR并合入。这个应该是因为有同事之前修复了这部分内容,但是并没有添加单测,把单测放到白名单里也是本issue的一项重要内容。 |
【PIR】 PIR算子推全和单测修复 阶段性完成,未完成的子任务将被 【Paddle 框架旧动态图机制退场】 完全覆盖,感谢参与的小伙伴们!
欢迎继续参与快乐开源的其他任务! |
一、需求背景
飞桨正在构建一套新的IR体系.在新IR下飞桨基于动态图的更规范的算子定义(ops.yaml、legacy_ops.yaml)生成了新IR体系下的算子.在新的IR体系下仍然需要保证旧IR的兼容性.为此飞桨提供了
ProgramTranslator
(相关代码位于paddle/fluid/ir_adaptor/translator/
),它可以将旧IR表示下的计算图翻译为新IR下的计算图.目前,ProgramTranslator
的核心工作是完成单个OP
的翻译.也就是将旧IR下定义的OP
(一般定义在paddle/fluid/operators
文件夹下)翻译为新IR下定义的算子.但是,ProgramTranslator
在翻译单个OP
时会遇到下述两个问题:ProgramTranslator
翻译该OP
时无法得到新IR下对应的OP
定义.ProgramTranslator
在翻译这部分算子时通用的翻译方案并不适合这些算子的翻译,我们需要单独定义它们的转换工作.例如,对于
dpsgd
算子,它对应得单测文件是test/legacy_test/test_dpsgd_op.py
,我们在新IR下执行这个单测可以看到报错信息如下:这条错误提示主要是在说得不到
dpsgd
这个算子在新IR定义的OpInfo
.这是由于新IR下没有定义dpsgd
这个算子造成的.我们需要补充dpsgd
算子在新IR下的定义.修复以下Op单测在PIR下测试成功:
test_sequence_mask(已修复)@Dmovic #62668
PR提交模板
【PIR OpTest Fix No.1】 fix test_fake_quantize_op
认领方式
请大家以 comment 的形式认领任务,如:
多个任务之间需要使用中文顿号分隔,报名多个连续任务可用横线表示,如 2-5
PR 提交格式:在 PR 的标题中以 【PIR OpTest Fix No.xxx】 开头,注明任务编号
看板信息
统计信息
二、Tutorial
以
dpsgd
为例,下面是在新IR下补充定义dpsgd
的流程.2.1 为待修复OP在新Ir下补充定义
首先,可以看到
dpsgd
这个算子在旧IR下的定义.可以看到这里它有名为
Param
,Grad
,LearningRate
三个输入,一个名为ParamOut
的输出,四个名为clip
batch_size
,sigma
,seed
的参数.以及注册的
kernel
名称和GetExpectedKernelType
信息.我们可以在
paddle/fluid/pir/dialect/operator/ir/ops.yaml
文件中补充定义yaml
中前向算子的配置规则如下,下表引自文档开发C++算子的3.1 算子 Yaml 文件配置
注:Tensor[]表示 Tensor 数组;IntArray 为 int 类型数组,主要用于表示 shape,index 和 axes 等类型数据,可以直接使用 Tensor 或者普通整型数组构造,目前仍在测试阶段,如非必要暂不建议使用;Scalar 表示标量,可以支持不同的普通数据类型
注:当返回类型为 Tensor[]时,由于数组的 size 要在 kernel 执行前推导完成,所以需要在 Tensor[]后的'{}'内通过表达式指定返回数组的 size,如:Tensor[](out){input.size()}
特殊规则:如果 api 中算子名称有'_'后缀则只生成支持 inplace 功能的接口,如果算子名称没有'_'后缀,则会同时生成支持 inplace 操作的接口(自动添加'_'后缀)和不支持 inplace 的普通接口共两套接口
a. 如果是复用已有算子,需要被复用的算子为前向算子且两者的返回值类型相同,可参考 zeros_like 算子
b. 如果是实现自定义的 C++ API,需要在'paddle/phi/api/lib/api_custom_impl.h'声明自定义实现函数并在'paddle/phi/api/lib/api_custom_impl.cc'中进行实现,具体可参考 embedding 算子
需要说明的是因为
dpsgd
属于优化型算子,没有对应的backwardop
所以这里不用配置,对于backwardop
的配置后面会补充说明。另外,飞桨根据算子签名(ops.yaml
、legacy_ops.yaml
)自动生成了新IR体系下的算子定义。具体逻辑可实现在paddle/fluid/pir/dialect/CMakeLists.txt
及其调用的相关脚本,生成的 OP 定义文件在build/paddle/fluid/pir/dialect/operator/ir/pd_op.cc
2.2 为
dpsgd
配置op_compat.yaml
文件ProgramTranslator
需要确定应该将旧IR的哪个参数对应到新IR的哪个参数.这种映射定义在 paddle/phi/api/yaml/op_compat.yaml中.一般地我们只需要将旧IR下对应驼峰命名转为新IR下的下划线命名即可.
由该yaml文件生成的cpp文件是
paddle/fluid/ir_adaptor/translator/op_compat_info.cc
该文件指导ProgramTranslator
的翻译.2.3 为dpsgd配置InferMeta
InferMeta
和InferShape
有什么区别?为什么不继续叫InferShape
?修复Op单测时,并不需要我们真正去实现
InferMeta
,我们只需要根据需要修复Op的InferShape
函数稍加修改即可,但是dtype
信息需要我们单独设置一下,因为InferShape
,不包含dtype
信息.一般地,outputs的dtype
信息要inputs的dtype
一致即可.这里以dpsgd
为例,介绍注册InferMeta
的流程.我们可以看到
InferShape
主要完成两个工作,首先是对InferShapeContext
中是否具有某些参数进行检查,对与InferMeta
可以直接忽略这部分逻辑,这里是因InferMeta
的参数并不涉及InferShapeContext
,它直接接收对应的变量,而不用去InferShapeContext
中去查找.后面对某些变量进行维度推导的逻辑才是我们需要重点关注的,我们需要将这些逻辑照搬到InferMeta
里面.这里给出了
DpsgdInferMeta
的实现,可以发现和InferShape
的实现基本一致,仅更换了一些API.另外,根据dtype
的设置原则,将param_out(输出)的dtype
设置为param(输入)的dtype
即可.最后,关于
InferMeta
函数的实现以及声明位置,可以参考文档开发C++算子的说明.另外,函数在单个文件的中的排序方式是按照函数名称的字典序进行放置.
2.4 本地测试通过后提交Pr
在本地开启
FLAGS_PIR_OPTEST
和FLAGS_PIR_OPTEST_WHITE_LIST
单测通过之后可以提交PR.另外,可以打开FLAGS_enable_pir_in_executor
进行进一步测试,并在PR提交时说明执行情况,如果失败标明原因即可.在新IR下对应单测跑通之后,可以提交PR,需要将单测名称加入到
test/white_list/pir_op_test_white_list
中,这样可以开启PR-CI-PY3
,PR-CI-MAC-Python3
和PR-CI-Coverage
两条流水线上在新IR下执行该单测.2.5 补充说明需要注册BackwardOp的情况
事实上大部分Op是具有反向算子的.一个算子是否具有反向算子,我们大致可以根据在旧Ir下定义该算子时有没有定义他的反向Op.这里以
repeat_interleave
为例.可以发现
repeat_interleave
是有反向op的,我们要补充他的反向算子的定义.首先,我们定义正向算子
因为该算子具有反向算子所以我们需要配置
backward
.然后,我们根据
RepeatInterleaveGradMaker
为其在对应的backward.yaml
中注册反向算子.反向算子也是一种算子,所以其配置和正向算子大同小异,具体字段的含义同样需要参考文档开发C++算子的说明.
backward.yaml
中反向算子的配置规则如下:约束条件 1:所有参数需要在 forward 配置项的参数中(输入、输出以及输出对应的反向梯度)找到对应(根据变量名匹配)
约束条件 2:反向输入参数需要以:a.前向输入 Tensor b.前向输出 Tensor c.前向输出 Tensor 的反向梯度 d.前向非 Tensor 类型属性变量(Attribute) 的顺序排列,反向计算中不需要使用的前向变量无须添加
注意:由于 Tensor 内存被释放后会影响 dtype 接口的使用,所以需要在 kernel 的 data_type 配置项中指定其他的 Tensor 来推导 kernel 的 data_type
反向算子的
op_compat.yaml
不用特殊配置,只需要在为其对应的正向算子配置的同时,加入backward
字段即可.具体地另外,反向算子作为算子它也需要对应的
InferMeta
,配置方法和正向算子相同.三、验收标准
分别打开如下两组
flag
:FLAGS_PIR_OPTEST
和FLAGS_PIR_OPTEST_WHITE_LIST
FLAGS_enable_pir_in_executor
执行对应的单测,打开第一组
falg
单测通过即可将单测名称加入test/white_list/pir_op_test_white_list
中,然后提交PR.但是需要在PR中补充说明在FLAGS_enable_pir_in_executor
打开时的单测执行情况.它是否可以执行通过,如果不通过输出了什么样的报错信息.四、单测执行的整体流程
Pir下执行单测大致可以分为
计算图转换
,StandaloneExecutor初始化
和StandalneExecutor执行
三个阶段.大体流程如下图所示:下面展开对这些流程展开描述:
4.1 Python侧测试的流程
单测一般会调用在
test/legacy_test/op_test.py
中的OpTest
的成员函数check_out
.在OpTest
中需要重点关注_check_ir_output
和_check_ir_grad_output
两个函数,这里会涉及到几个flag
的使用.FLAGS_PIR_OPTEST
和FLAGS_PIR_OPTEST_WHITE_LIST
这两个FLAG同时打开时
_check_ir_output
才会执行,该函数会暂存FLAGS_enable_pir_in_executor
和FLAGS_pir_apply_inplace_pass
两个flag
,然后构建执行器,执行计算图,拿到结果进行比较.FLAGS_enable_pir_in_executor
该
flag
会在python/paddle/base/executor.py
中控制是否将旧Ir下的计算图翻译为新Ir下的计算图,并执行.FLAGS_PIR_OPTEST_RELAX_CHECK
该
flag
表示可以对于新Ir下的计算图输出结果,允许一定的误差.FLAGS_PIR_NO_CHECK
该
flag
开启表示不关心当前计算图在新Ir下的执行结果,只要执行成功即可.一般使用在涉及到具有随机性的Op,比如Seed
.所以,新Ir下测试Op的流程主要逻辑为在旧Ir下执行一次计算图拿到预期结果,然后开启相应的
flag
在新Ir下再执行一次计算图将两次所得到的结果进行比较.同样以
dpsgd
为例,下面展开说明python侧的执行流程它的单测继承自
OpTest
会直接执行OpTest
的check_output
方法.在OpTest
的check_output
中会调用check_output_with_place
.可以发现check_output_with_place
定义了内部类Checker
,以及它的子类StaticChecker
,DygraphChecker
和PirChecker
.check_output_with_place
后续逻辑大体上也是围绕这三个测试类展开的,如下述代码基本逻辑是初始化一个测试类然后执行类里的check
方法.check
方法的基本逻辑是这里可以看出
check
方法首先去执行计算图得到输出,然后再将得到的输出和预期的结果进行比较.在calculate_output
中会调用op_test._calc_output
函数,在该函数中可以发现如下逻辑这里会初始化一个执行器,然后执行该计算图.之后会执行
_check_ir_output
,通过阅读
_check_ir_output
源码,对于新IR下的单测执行就很清晰了,如果我们打开了之前提到的FLAGS_PIR_OPTEST
和FLAGS_PIR_OPTEST_WHITE_LIST
那么这个测试逻辑就会生效.它会提前保存FLAGS_enable_pir_in_executor
和FLAGS_pir_apply_inplace_pass
然后打开FLAGS_enable_pir_in_executor
,再执行一次计算图,因为FLAGS_enable_pir_in_executor
被打开这次测试会再新IR下执行,然后再对执行结果进行比较即可.4.2 C++侧执行新Ir计算图流程
4.2.1 计算图的翻译
首先,新Ir表示下的计算图是通过翻译旧Ir下的计算图得到的,所以首先需要考虑的是计算图的翻译.在
executor.py
中使用函数translate_to_pir
完成计算图的翻译.该函数被绑定到C++侧的paddle::TranslateLegacyProgramToProgram
上.关于paddle::TranslateLegacyProgramToProgram
的相关代码在paddle/fluid/ir_adaptor/translator
文件夹下.ProgramTranslator
的核心部分是OpTranslator
.对于该部分的工作原理文档program_translator有详细介绍,为某个Op制定特殊的转换规则我们可以为该Op指定OpTranscriber
,以下内容引用自该文档在修复某个Op的单测时可能会需要涉及到为该Op设计特殊的转换规则,此时需要考虑重写上述函数.
4.2.2 StandaloneExecutor执行计算图
翻译成新Ir表示的计算图最终会提交给
StandaloneExecutor
执行器执行.下面展开StandaloneExecutor
执行器执行计算图的流程.4.2.2.1 StandaloneExecutor初始化
其中,
place
是指定的运算设备,plan_
管理着需要执行器执行的计算图.scope
用于管理变量,op在运行时从scope中查找输入和输出变量.StandaloneExecutor
的初始化过程分为下述步骤:scope
加入mirco_batch_scopes_
中.pir_progam
中的每一个Op
,统计fetch_var_name
将其加入到fetch_var_names_
中.PdOpLowerToKernelPass
得到更加底层的KernelDialect
计算图描述.InterpreterCore
.下面展开第3步的过程:
第3步主要由
paddle/fluid/pir/transforms/pd_op_to_kernel_pass.cc
中的ProcessBlock
函数完成,该函数将由OperationDialect
下的计算图转化为KernelDialect
下的计算图表示.首先,会通过
GetOpYamlInfoParser
函数得到op_info_parser
,拿到op_info_parser
之后就可以访问对应Op的OpInfoTupe
,通过解析ops.yaml
自动生成的对应Op的GetOpInfo
函数定义在build/fluid/pir/dialect/operator/ir/pd_op.cc
中.在GetOpInfo
中保存了OpRunTimeInfo
信息,定义如下:这里保存了对应Op的
Kernel
信息.ProcessBlock
中的kernel_fn_str
对应OpRuntimeInfo
里面的kernel_func
字段.GetKernelKey
的处理逻辑十分复杂,它主要返回Kernel
的backend
,layout
以及dtype
.这里可能最需要关注的是dtype
因为配置的ops.yaml
信息会直接影响到OpRunTimeInfo
的kernel_key_dtype
,而最终得到的KernelKey
和kernel_key_dtype
直接相关.在得到
KernelKey
之后,会调用BuildOpInputList
和BuildOutputType
完成对inputs
和op_output_types
的build.最后调用
BuildPhiKernelOp
完成KernelDialect
下的计算图构建.下面展开第4步过程:
在构建
InterpreterCore
时会构建InterpreterBaseImpl
的实现PirInterpreter
.在PirInterpreter
的构造函数中会执行BuildScope
创建inner_scpoe
.inner_scope
被ValueExecutionInfo
管理.在BuildScope
中为每个普通Op的Value
实例化对应的Varibale
.4.2.2.2 StandaloneExecutor执行
执行
StandaloneExecutor::Run
在新Ir下会调用PirInterpreter
中的Run函数.主要执行下述三个操作:下面展开介绍这3个操作:
BuildInstruction
不考虑控制流Op,
BuildInstruction
主要涉及构建LegacyKernelInstruction
和PhiKernelInstruction
.这里以构建PhiKernelInstruction
为例.首先,执行
SetKernelType
主要是为该Op设置OpFuncType
,OpFuncType
描述Kernel
的运行硬件环境信息.然后,通过
op_info
中的GetInterfaceImpl
得到InferMetaInterface::Concept
,其中记录了该Op的InferMeta
函数的函数指针.如果该Op存在InferMeta
函数,则需要为其准备运行环境infer_meta_context
.该步骤主要由BuildPhiContext
函数实现.然后,根据Op里保存的kernel_name
和kernel_key
在Kernelfactory
中选择合适的phi_kernel
,使用BuildPhiContext
函数构造出kernel_context
.PreAnalysis
该部分主要完成对
instructions
之间的依赖分析,帮助后续对Op执行调度,方便并发执行.为jit program
的变量注册等待事件等.TraceRunImle
惰性初始化GC,然后调用
TraceRunInstructionList
.TraceRunInstructionList
会调用RunInstructionBase
等待同步时机到来后,调度当前PhiKernelInstruction
重写的Run
函数执行.在Run
函数中依次执行InferMeta
和Kernel
.五、Q&A
1.对于randint这类随机性的Op两次执行的结果不相同怎么验证正确性?
A: 此类Op需要将单测名称加入test/white_list/pir_op_test_no_check_list,在执行CI测试时FLAGS_PIR_NO_CHECK会自动打开.参考PR#57826
2.报错The kernel with key (CPU, Undefined(AnyLayout), int64) of kernel seed is not registered是什么原因,如何修复?
A: 该错误主要是由新旧ir下的GetExpectedKernelType不一致造成的,旧Ir下kerneltype为INT32,而新ir下的GetExpectedKernelType返回的是Out的dtype,修改新ir下的GetExpectedKernelType问题解决.参考PR#58552
3.FatalError: Segmentation fault is detected by the operating system.调用栈最后执行的是paddle::operators::DataOp::GetExpectedKernelType(paddle::framework::ExecutionContext const&) const,这是什么原因造成的?
A: 主要是单测机制导致的测试在开启FLAGS_enable_new_ir_in_executor时执行错误,开启FLAGS_PIR_OPTEST, FLAGS_PIR_OPTEST_WHITE_LIST单测成功,可以暂时不做处理.
4.OpYamlInfoParser在解析runtime_info.kernel_param时会将可变属性放入kernel_fn_attr_params这样对于新Ir下定义的sparse_momentum_op(定义了Scalar axis)会造成AttributeMap中不存在axis属性的问题,如何解决?
A: 对于此类legacy op暂时将可变属性统一放入kernel_fn_tensor_params中.解决方案是需要给OpYamlInfoParser多增加一个属性,用来判断当前翻译的Op是非为legacy op.
5.旧Ir下的Op往往需要根据参数的不同翻译为新Ir的多个Op,如何实现?
A: 需要重写LoopkUpOpInfo函数,甚至根据输入的不同需要重写其他函数.可以参考PR#58379
6.报错error: 'eager_api_XXX' was not decalred in this scope如何解决?
A: 需要将op名称添加到paddle/fluid/pir/dialect/op_generator/ops_api_gen.py中的NO_NEED_GEN_STATIC_ONLY_APIS这里同样需要保证字典序.最后,再次编译,问题消失.
7.其他错误如何快速定位发生错误的位置?
A: 首先,一个单测文件中可以有多个测试,这里建议只保留一个出错的单测.然后,可以在padde/base/executor.py中直接print(program)打印出计算图,可以比较新旧Ir下计算图的异同.最后,可以通过export GLOG_v=10打开全量日志,观察日志可以大体确定出错的位置.确定错误位置不确定修改方案可以联系 @xingmingyyj和@kangguangli.
8.RuntimeError: (PreconditionNotMet) op [pd_op.xxx] kernel output args defs should equal op outputs此类问题的原因是什么?怎么解决?
A:此类问题是Legacy op的kernel和phi kernel的推导机制不一致造成的。如果kernel是通过
PD_REGISTER_STRUCT_KERNEL
注册的,需要把他加在LegacyOpList
中,单独处理。The text was updated successfully, but these errors were encountered: