当前位置: 首页 > 后端技术 > Python

OneFlow学习笔记:从Python到C++的调用流程分析作者

时间:2023-03-26 17:37:21 Python

|约塔在OneFlow中,我们可以从Python端使用各种Ops进行相关操作。下面是使用reluop最简单的例子:>>>importoneflowasof>>>x=of.tensor([-3,-2,-1,0,1,2,3],dtype=of.float)>>>of.relu(x)tensor([0.,0.,0.,0.,1.,2.,3.],dtype=oneflow.float32)虽然调用是在Python端,具体实现在C++这边,那么OneFlow是如何一步一步来的呢?一步步从Python到C++,本文以最简单的ReluOp为例,追溯OneFlow中从Python到C++的一般调用过程。具体过程大致概括为Pythonwrapper和C++gluefunctor有两部分,下面是两部分的具体细节。1PythonwrapperPython代码在python/oneflow文件夹下。在分析Pythonwrapper的过程中,也会涉及到很多C++代码,主要是pybind11绑定相关,也归类到Pythonwrapper部分。先看本文开头例子中relu接口的直接源码。在python/oneflow/__init__.py中可以找到如下一行:fromoneflow._Cimportrelu可以看到relu是从模块_C派生的,所以继续看文件oneflow/_C/__init__.py:fromoneflow._oneflow_internal._Cimport*可以看出relu接口来自模块_oneflow_internal。_oneflow_internal是pybind11定义的模块,位于oneflow/api/python/init.cpp:PYBIND11_MODULE(_oneflow_internal,m){...::oneflow::cfg::Pybind11ModuleRegistry().ImportAll(m);::oneflow::OneflowModuleRegistry().ImportAll(m);}继续看上面代码中的OneflowModuleRegistry,它是注册的Op,是暴露给Python层的key,位于oneflow/api/python/of_api_registry.h:classOneflowModuleRegistry{...voidRegister(std::stringmodule_path,std::functionBuildModule);voidImportAll(pybind11::module&m);};该类提供Register接口,封装成如下注册宏,代码位于oneflow/api/python/of_api_registry.h:\OfApiRegistryInit(){\::oneflow::OneflowModuleRegistry()\.Register(module_path,&OF_PP_CAT(OneflowApiPythonModule,__LINE__));\}\};\ofApiRegistryInitof_api_registry_init;module&m)知道了ONEFLOW_API_PYBIND11_MODULE这个宏,继续查找会用到的地方,在自动生成的文件build/oneflow/api/python/functional/functional_api.yaml.pybind.cpp中,可以找到用到的:ONEFLOW_API_PYBIND11_MODULE("_C",m){py::options选项;options.disable_function_signatures();...m.def("relu",&functional::PyFunction);...选项。启用_功能_signatures();}由此可以看出本节开头的代码fromoneflow._Cimportrelu中的模块_C和算子Relu是从哪里来的。这里Relu映射为functional::PyFunctionfunctional::ReluSchema_TTB是一个模板函数,先看模板参数ReluSchema_TTB的定义:structReluSchema_TTB{usingFType=Maybe(conststd::shared_ptr&x,boolinplace);使用R=Maybe;staticconstexprFType*func=&functional::Relu;staticconstexprsize_tmax_args=2;staticconstexprsize_tmax_pos_args=2;staticconstexprcharconst*signature="Tensor(Tensorx,Boolinplace=False)";静态FunctionDeffunction_def;};可以看出,与调用过程关系最大的是一个指向functional::Relu的函数指针成员。functional::Relu系列的功能非常重要。它是一个自动生成的全局C++接口,可以认为是Python和C++的分水岭。细节将在第二部分详细讨论。我们继续看模板函数functional::PyFunctionfunctional::ReluSchema_TTB,它决定了如何调用functional::ReluSchema_TTB中的func,它指向functional::Relu的函数指针,functional::PyFunction模板函数定义所在在oneflow/api/python/functional/py_function.h:templateinlinepy::objectPyFunction(constpy::args&args,constpy::kwargs&kwargs){staticPyFunctionDispatcher调度程序;returndispatcher.call(args,kwargs,std::make_index_sequence{});}这里又调用了PyFunctionDispatcher中的call函数:templateclassPyFunctionDispatcher{...templatepy::objectcall(constpy::args&args,constpy::kwargs&kwargs,std::index_sequence)const{std::cout<;std::vectorparsed_args(T::max_args);如果(ParseArgs(args,kwargs,&parsed_args,T::function_def,T::max_pos_args,schema_size_==1)){returndetail::unpack_call(*T::func,parsed_args);}返回调用(args,kwargs,std::index_sequence{});}...};把functional::ReluSchema_TTB中的func作为参数指向functional::Relu的函数指针,继续调用oneflow/api/python/functional/unpack_call.h中的unpack_call::nargs;usingR=typenamefunction_traits::return_type;returnCastToPyObject(unpack_call_dispatcher::apply(f,args,std::make_index_sequence{}));}这里又是函数式的funcin::ReluSchema_TTB是一个指向functional::Relu的函数指针作为参数,在同一个文件中继续调用unpack_call_dispatcher::apply:templatestructunpack_call_dispatcher{templatestaticRapply(constF&f,conststd::vector&args,std::index_sequence){返回f(args[I].As::args_type>::type>>()...);}};至此全局C++接口functional::Relu的调用已经完成,下一节会详细讲fun全局C++接口functional::Relu是如何生成的?2C++gluefunctor首先看oneflow/core/functional/impl/activation_functor.cpp中的一个类,这是Relu底层实现的大门。底层实现是OneFlow框架的精髓。我还没有调查过,到时候会继续总结,提供接口给上层调用。本文只关注接口部分:classReluFunctor{...Maybeoperator()(conststd::shared_ptr&x,boolinplace)const{...returnOpInterpUtil::Dispatch(*op_,{x});}};ReluFunctor提供了一个函数调用操作符的重载函数,所以它对应的对象是一个可调用对象,它会被下面的代码注册:ONEFLOW_FUNCTION_LIBRARY(m){m.add_functor("Relu");...};继续看ONEFLOW_FUNCTION_LIBRARY的定义,它定义了一个静态变量在OneFlow的初始化阶段,上面所有的像ReluFunctor这样的funtor都是通过add_functor接口注册到单例类FunctionLibrary中的:#defineONEFLOW_FUNCTION_LIBRARY(m)ONEFLOW_FUNCTION_LIBRARY_IMPL(m,__COUNTER__)#defineONEFLOW_FUNCTION_LIBRARY_IMPL(m,uint)O_staticuid_oneflow_function_library_dummy_,uuid)=[](){\FunctionLibrary*library=FunctionLibrary::Global();\OF_PP_CAT(_oneflow_function_library_,uuid)(*库);\返回0;\}();\voidOF_PP_CAT(_oneflow_function_library_,uuid)(FunctionLibrary&m)FunctionLibrary的主要数据结构和接口如下,其中PackedFuncMap是一个注册对象的数据结构,add_functor用于注册,find用于查找注册对象,Global是一个单例接口:=类型名std::function()>;staticHashMapfunctors;return&functors;}};templatevoidadd_functor(conststd::string&func_name){...}templateautofind(conststd::string&func_name)->Maybe::FType>>{...}staticFunctionLibrary*Global(){staticFunctionLibraryglobal_function_library;return&global_function_library;}};代码中数据结构部分使用的PackedFunctor位于oneflow/core/functional/packed_functor.h中,通过调用接口封装了functor的调用:templateclassPackedFunctor{public:PackedFunctor(conststd::string&func_name,conststd::function&impl):func_name_(func_name),impl_(impl){}R调用(Args&&...args)const{returnimpl_(std::forward(args)...);}private:std::stringfunc_name_;std::functionimpl_;};前面是functor的定义和注册部分。它们是提供全局C++接口的基石。我们继续看看全局的C++接口functional::Relu是怎么来的。在代码库中,有一个oneflow/core/functional/functional_api.yamlRelu相关的配置文件如下:-name:"relu"signature:"Tensor(Tensorx,boolinplace=False)=>Relu"bind_python:True这是一个yaml配置脚本。最终的functional::Relu全局C++接口是通过前面functor的定义、注册、yaml配置,最后通过tools/functional/generate_functional_api.py这个python脚本是自动生成的,简化代码如下:if__name__=="__main__":g=Generator("oneflow/core/functional/functional_api.yaml")g.generate_cpp_header_file(header_fmt,"oneflow/core/functional/functional_api.yaml.h")g.generate_cpp_source_file(source_fmt,"oneflow/core/functional/functional_api.yaml.h")...可以看出具体的接口生成到上面指定的文件中,而具体的生成过程在generator中,在py中,内容比较琐碎,主要是通过硬编码自动生成全局C++接口,下面是functional::Relu全局C++接口的例子:namespaceoneflow{namespaceone{namespacefunctional{...MaybeRelu(conststd::shared_ptr&x,boolinplace){staticthread_localconstauto&op=CHECK_JUST(FunctionLibrary::Global()->find,conststd::shared_ptr("Relu"));returnop->call(x,inplace);}...}//namespacefunctional}//namespaceone}//namespaceoneflow可以看上面的Relu接口找到通过注册类的find接口注册的ReluFunctor,然后使用PackedFunctor中的调用接口进行调用。至此,我们终于知道全局C++接口functional::Relu的前因后果了。欢迎下载体验新一代OneFlow开源深度学习框架:https://github.com/Oneflow-In...