代码自动生成(Code generation)是指根据指定规范或高级描述,自动生成源代码或其他软件部件的过程。代码自动生成的目标是提高生产力,减少错误,提高软件的可维护性。 代码自动生成有不同的技术,包括基于模板的代码生成、基于模型的代码生成等。代码自动生成在现代软件开发中被广泛应用,例如在神经网络、编译器、数据库等领域中都有应用。它可以大大提高软件开发的效率,同时减少人工出错的可能性。
Paddle作为国内第一的深度学习框架,功能已经十分丰富,代码量十分庞大。为了实现更多的硬件接入Paddle体系,也开放给第三方的硬件平台修改Paddle主框架的权限,与此同时也增加了框架的维护成本。鉴于此,引入CodeGen这套设计模式来解决这些问题。CodeGen能够给paddle带来以下收益:
-
减少框架代码量:相关代码通过简单的yaml文件和Python脚本在编译阶段(或构建阶段)自动生成,大大减少了Paddle的可见代码量;
-
降低开发成本:如果要进一步往Paddle中增加新的算子,只需要配置yaml文件、添加对应的Kernel文件和Python端接口代码,即可完成添加;
-
实现Op定义统一,降低维护成本:通过固定的机制来生成相关代码,规范了代码添加规则,降低框架维护升级成本。
厂内的CINN中也大量使用CodeGen的技术生成目标代码。
Paddle核心训练框架中,目前主要存在的是三套CodeGen设计体系,即动态图、静态图、旧动态图,但旧动态图在完全退场后,旧动态图的CodeGen体系也预计会被完全清除。
图1-1 代码自动生成总体框目前动态图和静态图均是依据"yaml配置文件+Python脚本"的设计自动生成相关代码,区别点在于静态图同时使用了Jinja作为生成模板。旧动态图API依据静态图注册的OpInfoMap,使用C++程序进行生成,所以旧动态图的生成的代码和静态图OP耦合度极高,这也是因为旧动态图的调用机制复用了静态图Op。这些模块内部还隐含了组合算子CodeGen、运算符重载CodeGen等模块。 首先介绍CodeGen相关的yaml配置文件,最主要的yaml文件位于paddle/phi/api/yaml/,(tensor_operators.yaml是运算符重载yaml文件),此外还有组合算子的一个单独yaml文件位于paddle/fluid/prim/api/api.yaml,如图1-2所示。
图1-2 代码自动生成相关yaml文件相关文件yaml文件的功能如下所示:
图1-3 代码自动生成相关yaml文件功能说明新动态图的codegen体系分为三层:c++api层、dygraph_function网络构建层、python-c映射层。C++API层是纯粹的调用kernel进行计算,dygraph_function层会根据需求建立起backward网络,将forward和backward联系起来,Python-C映射层则是方便Python端可以直接调用动态图算子接口,三者的详细介绍可以参见"动态图调用过程详解"。
图2-1 新动态图代码自动生成框架从图2-1可以看出,动态图三个层次的代码生成均是依据原始的yaml配置文件,只有组合算子网络构建层的API是依据parse_op.py处理后的yaml文件。
API的生成脚本文件(paddle/phi/api/yaml/generator/)如图2-2所示:
图2-2 动态图API生成脚本每个文件的功能如下:
图2-3 API脚本功能以api_gen.py为例,生成代码的流程如下所示,相应的CMakeLists文件为paddle/phi/api/lib/CMakeLists.txt
:
简单的生成流程如图2-4,输入yaml文件并解析后,构造相应的生成类,调用相应的接口即可生成相应的文本,最后将文本写入文件即可。细节可以阅读相关源码。图2-4仅仅列出了普通ops也fused_ops的生成过程,其他的sparse_ops,infermediate_ops,string_ops的调用流程类似。注意Python脚本直接生成的是xxx.cc.tmp文件,最后需要通过add_custom_command添加编译target(output)进行拷贝生成最终的xxx.cc文件。
动态图网络构建层分为两个部分,一部分是基础网络构建层,一部分是组合算子网络构建层。
普通算子网络构建层生成脚本文件(paddle/fluid/eager/auto_code_generator/generator/)如下:
图2-5 动态图网络构建层和Python-C交互层生成脚本由于网络构建层和Python-C交互层的脚本较为简单,二者放置在同一路径下。
图2-6 网络构建层和python-C交互层脚本功能执行流程如下,相关CMakeLIsts文件(paddle/fluid/eager/auto_code_generator/generator/CMakeLists.txt):
图2-7 动态图网络构建层生成流程这里生成的文件位于paddle/fluid/eager/api/generated/eager_generated/路径下。 注意Python脚本直接生成的是xxx.cc.tmp文件,最后需要通过add_custom_command添加编译target(output)进行拷贝生成最终的xxx.cc文件。
组合算子网络构建层生成脚本文件(paddle/fluid/prim/api/auto_code_generated/)如下:
图2-8 动静态图组合算子网络构建层组合算子网络构建层分为动态图和静态图,这里静态图代码功能类似于Python端调用append_op这个函数。每个文件的功能如下:
图2-9 组合算子网络构建层脚本调用流程可以参考paddle/fluid/prim/api/auto_code_generated/CMakeLists.txt。注意Python脚本直接生成的是xxx.cc.tmp文件,最后需要通过execute_process添加构建(cmake)期脚本命令进行拷贝生成最终的xxx.cc文件。
Python-C交互层相关文件和功能参见图2-5和图2-6中的python_c_gen.py文件.从CMakeLists文件paddle/fluid/eager/auto_code_generator/generator/CMakeLists.txt中看出,通过add_custom_target添加target调用python_c_gen.py脚本直接生成最终文件。
add_custom_target( eager_python_c_codegen
COMMAND "${PYTHON_EXECUTABLE}" "${PADDLE_SOURCE_DIR}/paddle/fluid/eager/auto_code_generator/generator/python_c_gen.py" "--api_yaml_path=${api_yaml_path},${fwd_api_yaml_path}" "--output_path=${tmp_python_c_output_path}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${tmp_python_c_output_path} ${python_c_output_path} VERBATIM)
这里的target--eager_python_c_codegen会被paddle/fluid/pybind/CMakeLists.txt下的add_custom_command依赖,保证add_custom_target会被执行:
if(NOT ((NOT WITH_PYTHON) AND ON_INFER)) add_custom_command( OUTPUT ${eager_impl_file}
COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=$ENV{LD_LIBRARY_PATH}:." "${CMAKE_CURRENT_BINARY_DIR}/eager_legacy_op_function_generator" "${tmp_eager_impl_file}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${tmp_eager_impl_file} ${eager_impl_file}
COMMENT "copy_if_different ${tmp_eager_impl_file} to ${eager_impl_file}" DEPENDS ${EAGER_OP_IMPL_DEPS} VERBATIM) endif()
由于旧动态图复用了静态图的Op(Trace模式),所以旧动态图的代码生成的只有两部分组成,也就是网络构建层和Python-C交互层。
图2-10 旧动态图代码自动生成框架旧动态图网络构建层使用的C++程序,之所以使用C++程序是方便从OpInfoMap::Instance()中获取Op的信息。文件生成器构成如下:
图2-11 旧动态图算子网络构建层生成器文件相关文件功能如下:
图2-12 旧动态图网络构建层生成器旧动态图的相关代码生成过程如下:
图2-13 旧动态图网络构建层生成流程通过观察生成的CMakLists.txt文件,如paddle/fluid/eager/api/generated/fluid_generated/forwards/CMakeLists.txt:
图2-14 相关CMakeLists.txt文件生成lib库时只用到了带有数字后缀的相关文件,dygraph_forward_functions.cc并没有用到。这里分割文件的意义是防止单个文件过大,导致编译时间急剧增加。
旧动态图的Python-C交互层生成器组成较为简单仅有一个文件paddle/fluid/pybind/eager_legacy_op_function_generator.cc,执行流程如下:
图2-15 旧动态图Python-C生成流程在Python层不会通过函数形式直接调用静态图op,并且静态图组网是通过Program、Block等模块实现的,因此静态图只需要自动生成Op相关的代码。
图3-1静态图Op代码自动生成框架如图3-1所示,yaml文件经过parse_op.py解析后输出对应的parsed文件,静态图生成的Python脚本借助Jinja模板生成对应的op文件和sig文件。相关文件如下:
图3-2 静态图Op代码生成器相关文件相关的文件功能如下:
图3-3 静态图Op代码生成器相关文件功能从图中可以看出,静态图Op的生成器主要包括三部分:jinja模板,Python脚本和C++文件。静态图通过这三者之间的配合生成相关的代码文件,执行流程如下:
图3-4 静态图Op代码生成流程从图3-4可以看到,yaml文件经过parse_op.py解析后生成静态图规范格式的yaml文件,这一步骤的主要原始是原始的yaml配置会缺省很多配置,格式不够规整,不利于后续的jinja模板读取。为保证yaml配置的正确性,这里通过cross_validate.py进行交叉检验,主要是通过同一个op的forward和backward的配置进行检验。经过检验正确的yaml文件会输入到generate_op.py中,并结合op_compat.yaml、op_version.yaml中的信息,利用Jinja模板生成兼容手写Op文件的代码。这里的op_compat.yaml的主要功能是参数名字映射和增加一些原始ops.yaml中没有的信息,确保生成的Op和原始手写的文件一致,新增的算子一般不需要在op_compat.yaml中增加配置。 由于sparse相关的算子是新增到Phi下的,没有历史兼容性问题,所以不需要输入op_compat.yaml、op_version.yaml。ops的extra attrs的信息全部配置在op_compat.yaml中,可以直接生成。
-
"快乐开源"--框架静态图算子自动生成一期
-
"快乐开源"--框架静态图算子自动生成二期
-
CMake关键命令(官方文档):
a. execute_process:直接算构建(cmake)期间执行脚本
b. add_custom_command:在编译(make)期间执行脚本,相当于构建一个target。通常有两个中用法(OUTPUT和绑定TARGET)
c. add_custom_target:在编译(make)期间执行脚本,相当于构建一个target,但是默认是不会构建该target的,当显示指明该target(cmake .. --build --target my_target)或这有其他target依赖该target时才会生成。
d. copy_if_different:如果是多个文件,确保文件路径已经存在。