TVM Python与Cpp绑定机制解析
本文以TVM v0.20.0
为例,分析TVM中Python前端与C++后端的绑定机制。绑定机制主要包括三个部分:类的注册、对象的初始化以及成员函数的绑定。本文将以DataflowBlockRewrite
类为例,逐步解析这一过程。
为行文方便,前端默认指TVM python前端,后端默认指TVM C++后端
一、前端注册类
在TVM前端中,需要对每一个类进行注册。
以DataflowBlockRewrite
为例,注册代码如下
1 |
|
为了行文流畅,本文中大多数的类、文件没有注明具体路径,读者可以使用VSCode
Ctrl + P
搜索文件,侧边栏搜索类名进行检索。
接下来,我们分析tvm._ffi.register_object
的执行流程,该函数定义如下:
1 | def register_object(type_key=None): |
当传入字符串参数"relax.DataflowBlockRewrite"
时, tvm._ffi.register_object("relax.DataflowBlockRewrite")
返回一个注册函数register
。
再看register(DataflowBlockRewrite)
1 | def register(cls): |
函数 _register_object
的定义在object.pxi
中,以_register_object(240, DataflowBlockRewrite)
的执行流程为例:
Cython编译流程:
python/tvm/_ffi/_cython
中base.pxi
,ndarray.pxi
,object.pxi
,packed_func.pxi
描述了与C++的接口,core.pyx
include
了这些*.pxi
文件。TVM项目编译后,
python/tvm/_ffi/_cython
中的core.pyx
被编译成python/tvm/_ffi/_cy3
中的.so
文件(例如core.cpython-312-x86_64-linux-gnu.so
)。(Cython具体编译配置见python/setup.py
L161)。完成编译后,便可以在python
文件中调用.so
中的C++函数/类。例如python/tvm/_ffi/registry.py
中from ._cy3.core import _register_object
1 | """Maps object type index to its constructor""" |
- 前端和后端中相对应的类的
type_index
相等,从而实现一一对应。
二、前端对象初始化
仍然以DataflowBlockRewrite
为例
1 |
|
DataflowBlockRewrite
前端初始化函数__init__
所需的参数与后端C++构造函数一致。 binding_rewrite.h
:
1 | class DataflowBlockRewrite : public ObjectRef { |
python对象初始化时,通过self.__init_handle_by_constructor__
创建了C++实例。
object.pxi
中定义了类ObjectBase
和方法__init_handle_by_constructor__
:
1 | cdef class ObjectBase |
类的初始化发生在函数ConstructorCall
。此处函数调用栈比较深,可以简单理解为:使用构造函数fconstructor
和参数args
,构造了一个C++
的DataflowBlockRewrite
类,并用临时指针chandle
指向这个C++
实例,再用self.chandle
指向该C++实例。
DataflowBlockRewrite
继承自ObjectBase
:
1 | classDiagram |
因此,python类DataflowBlockRewrite
有一个成员变量self.chandle
指向C++对象DataflowBlockRewrite
,即python DataflowBlockRewrite.chandle -> &(C++ DataflowBlockRewrite),从而实现了python对象与C++对象的绑定。
三、TVM前端对象的成员函数与后端对象的成员函数绑定
仍然以DataflowBlockRewrite
为例,它的replace_all_uses
方法与后端C++函数ReplaceAllUses
进行了绑定:
1 |
|
1 | /*! \brief Statement rewriter for relax.DataflowBlock. */ |
python类DataflowBlockRewrite
调用_ffi_api.dfb_rewrite_replace_all_uses
,实际在调用后端的注册函数relax.dfb_rewrite_replace_all_uses
:
1 | TVM_REGISTER_GLOBAL("relax.dfb_rewrite_replace_all_uses") |
函数体内再调用rwt->ReplaceAllUses(old_var, new_var);
所以,_ffi_api.dfb_rewrite_replace_all_uses
是如何与后端注册的函数relax.dfb_rewrite_replace_all_uses
进行绑定的呢?_ffi_api
是一个module,为什么会有一个dfb_rewrite_replace_all_uses
方法呢?这一功能的实现依赖于Python一个特性,运行时对象属性更新(Dynamic Attribute Assignment).
在binging_rewrite.py
中,通过from . import _ffi_api
导入_ffi_api
,这个导入操作会执行_ffi_api.py
中的tvm._ffi._init_api("relax", __name__)
,这行代码将后端注册的、relax namspace
中的函数写入了_ffi_api
这个module。
1 | tvm._ffi._init_api("relax", __name__) |
registry.py
1 | def _init_api(namespace, target_module_name=None): |
Tips
:
- 后端
relax.dfb_rewrite_replace_all_uses
的注册
1 | TVM_REGISTER_GLOBAL("relax.dfb_rewrite_replace_all_uses") |
list_global_func_names()
返回的列表中有一个字符串为relax.dfb_rewrite_replace_all_uses
- 经过以上步骤,
_ffi_api
具有属性dfb_rewrite_replace_all_uses
,_ffi_api.dfb_rewrite_replace_all_uses
是一个python函数dfb_rewrite_replace_all_uses
, 它里面包装了对应的名为relax.dfb_rewrite_replace_all_uses
的后端注册的函数。