OneFlow学习笔记从Python到C

撰文|月踏

在OneFlow中,从Python端我们可以使用各种Op进行相关操作,下面是一个最最简单的reluop的使用示例:

importoneflowasofx=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++中的呢,本文以最最简单的Relu这个Op作为例子,来追溯一下在OneFlow中从Python端到C++中的大致调用过程,具体过程大概总结为Pythonwrapper和C++gluefunctor两部分,下面是两部分的具体细节。

1

Pythonwrapper

Python的代码都在python/oneflow文件夹中,在分析Pythonwrapper的过程中,也会涉及很多C++代码,主要是和pybind11绑定相关的,也一并归类到Pythonwrapper这部分了。

先看本文开头示例中的relu接口的直接来源,在python/oneflow/__init__.py中可以找到下面这一行:

fromoneflow._Cimportrelu

可以看到relu是从_C这个module中导出来的,所以继续看oneflow/_C/__init__.py这个文件:

fromoneflow._oneflow_internal._Cimport*

可见relu接口来自_oneflow_internal这个module,_oneflow_internal是pybind11定义的一个module,位于oneflow/api/python/init.cpp:

PYBIND11_MODULE(_oneflow_internal,m){...::oneflow::cfg::Pybind11ModuleRegistry().ImportAll(m);::oneflow::OneflowModuleRegistry().ImportAll(m);}

继续看上面代码中的OneflowModuleRegistry,它是注册过的Op暴露到Python层的关键,它位于oneflow/api/python/of_api_registry.h:

classOneflowModuleRegistry{...voidRegister(std::stringmodule_path,std::functionvoid(pybind11::module)BuildModule);voidImportAll(pybind11::modulem);};

这个类提供了一个Register接口,被封装进了下面这个注册宏里,代码位于oneflow/api/python/of_api_registry.h:

#defineONEFLOW_API_PYBIND11_MODULE(module_path,m)\structOfApiRegistryInit{\OfApiRegistryInit(){\::oneflow::OneflowModuleRegistry()\.Register(module_path,OF_PP_CAT(OneflowApiPythonModule,__LINE__));\}\};\OfApiRegistryInitof_api_registry_init;\staticvoidOF_PP_CAT(OneflowApiPythonModule,__LINE__)(pybind11::modulem)

知道了ONEFLOW_API_PYBIND11_MODULE这个宏,继续搜哪里会用到它,在build/oneflow/api/python/functional/functional_api.yaml.pybind.cpp这个自动生成的文件中,可以搜到它被用到:

ONEFLOW_API_PYBIND11_MODULE("_C",m){py::optionsoptions;options.disable_function_signatures();...m.def("relu",functional::PyFunctionfunctional::ReluSchema_TTB);...options.enable_function_signatures();}

由此可知本节刚开头的fromoneflow._Cimportrelu这句代码中的_C这个module和Relu这个算子是从哪来的了,在这里Relu被映射到了functional::PyFunctionfunctional::ReluSchema_TTB这个函数,这是一个模板函数,先看其中的模板参数ReluSchema_TTB的定义:

structReluSchema_TTB{usingFType=Maybeone::Tensor(conststd::shared_ptrone::Tensorx,boolinplace);usingR=Maybeone::Tensor;staticconstexprFType*func=functional::Relu;staticconstexprsize_tmax_args=2;staticconstexprsize_tmax_pos_args=2;staticconstexprcharconst*signature="Tensor(Tensorx,Boolinplace=False)";staticFunctionDeffunction_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:

templatetypename...SchemaTinlinepy::objectPyFunction(constpy::argsargs,constpy::kwargskwargs){staticPyFunctionDispatcherSchemaT...dispatcher;returndispatcher.call(args,kwargs,std::make_index_sequencesizeof...(SchemaT){});}

这里又继续调用了PyFunctionDispatcher中的call函数:

templatetypename...SchemaTclassPyFunctionDispatcher{...templatesize_tI0,size_t...Ipy::objectcall(constpy::argsargs,constpy::kwargskwargs,std::index_sequenceI0,I...)const{std::coutI0std::endl;usingT=schema_tI0;std::vectorPythonArgparsed_args(T::max_args);if(ParseArgs(args,kwargs,parsed_args,T::function_def,T::max_pos_args,schema_size_==1)){returndetail::unpack_call(*T::func,parsed_args);}returncall(args,kwargs,std::index_sequenceI...{});}...};

这里把functional::ReluSchema_TTB中的func这个指向functional::Relu的函数指针作为参数,继续调用了oneflow/api/python/functional/unpack_call.h中的unpack_call:

templatetypenameFpy::objectunpack_call(constFf,conststd::vectorPythonArgargs){constexprsize_tnargs=function_traitsF::nargs;usingR=typenamefunction_traitsF::return_type;returnCastToPyObject(unpack_call_dispatcherF,R::apply(f,args,std::make_index_sequencenargs{}));}

这里又把functional::ReluSchema_TTB中的func这个指向functional::Relu的函数指针作为参数,继续调用了同一个文件中的unpack_call_dispatcherF,R::apply:

templatetypenameF,typenameRstructunpack_call_dispatcher{templatesize_t...IstaticRapply(constFf,conststd::vectorPythonArgargs,std::index_sequenceI...){returnf(args[I].Asoneflow::detail::remove_cvref_ttypenamestd::tuple_elementI,typenamefunction_traitsF::args_type::type()...);}};

至此完成了对全局C++接口functional::Relu的调用,下一节具体讲functional::Relu这个全局C++接口怎么生成的。

2

C++gluefunctor

先看oneflow/core/functional/impl/activation_functor.cpp中的一个类,它对下是通往Relu底层实现的大门,通往底层的实现是OneFlow框架的精髓,我还没有往里看,以后时机到了会继续总结出来,对上则提供了上层调用的接口,本文只


转载请注明:http://www.aierlanlan.com/tzrz/614.html