动手学深度学习框架(2)- python 端如何调用 c++ 的代码

  0 前言

  朋友们好,欢迎来到深度学习框架系列专题。在上一篇文章中,我抛出了一系列问题,算是本专题的序言了,今天我们将正式开启 paddle 学习之旅,去了解它的实现原理和技术细节。对于本文的选题,我想了许久,最后还是觉得讲讲『python 端如何调用 c++ 的代码』最为合适,阅读深度学习框架源码,不管是 pytorch 还是 tensorflow,这都是绕不开的,也是最基础的。试想:如果一个新手看着 python 端的一行行代码,却不知道它是在哪定义的,函数体里内容是什么,岂不是抓狂~

  1 什么是 pybind?

  pybind11 是一个轻量级的只包含头文件的 c++ 库,用于将你的 c++ 代码暴露给 python 调用(反之亦可,但主要还是前者)。pybind11 借鉴了 Boost::Python 库的设计,但使用了更为简洁的实现方式,使用了大量 c++11 的新特性,更易于使用。

  2 paddle 中 python 端是如何调用 c++ 代码的?

  关于 pybind 的原理,网上已经有不少文章已经介绍过了,不再赘述,这里主要讲下 paddle 框架中 python 和 c++ 端是如何绑定的。

  示例 1

  首先,我们来看一个最基础的,也是最常见的用法。Program 是 paddle 框架中用来描述神经网络的结构,如果要使用 paddle 分布式训练功能,首先得实例化一个 Program 对象。

  import paddle.fluid as fluid

  program=fluidgram()

  很明显,Program 是 python 中的一个类,它的定义是在文件

  python/paddle/fluid/framework.py 里,代码如下:

  class Program(object):

  def __init__(self):

  self.desc=coregramDesc()

  ...

  由上述代码可知,实例化 Python Program 对象时,实际上调用的是 core 里的 ProgramDesc. 接着,我们先来看看

  python/paddle/fluid/core.py 中干了啥:

  from ctypes import cdll

  cdll.LoadLibrary(core_avx.so)

  什么意思呢?paddle c++ 代码编译完之后生成 core_avx.so 文件,而当我们安装完 paddle wheel 包之后,python/paddle/fluid 目录下就会有 core_avx.so. 也就是说,core.py 文件的主要功能就是加载 c++ 端的 so. 那 python 端的 ProgramDesc 类型又是在哪定义的呢?答案是protobuf.

  void BindProgramDesc(pybind11::module *m) {

  pybind11::class_(*m, "ProgramDesc", "")

  .def(pybind11::init<>())

  .def("__init__",

  [](pd::ProgramDesc &self, const pd::ProgramDesc &other) {

  new (&self) pd::ProgramDesc(other);

  })

  //...

  到这儿,python 端终于能够访问到 c++ 端的类型了,即 pd::ProgramDesc,其中,namespace pd=paddle::framework.

  示例 2

  我们再来看看 executor.py 中的用法。

  from . import core

  g_scope=core.Scope()

  assert isinstance(scope, core._Scope)

  var =scope.find_var(_to_name_str(name))

  这个 Scope() 又是什么呢?pybind 文件中有如下定义:

  PYBIND11_MODULE(core_avx, m) {

  m.def("Scope",

  []() -> Scope * {

  auto *s=new Scope();

  ScopePool::Instance().Insert(std::unique_ptr(s));

  return s;

  },

  R"DOC(

  Create a new scope.

  Returns:

  out (core._Scope): the created scope.

  )DOC",

  py::return_value_policy::reference);

  //...

  }

  PYBIND11_MODULE 这个宏用来告诉 python: import 神马,"core_avx" 是这个 module 的名字,"m" 是一个 py::module 类型的变量,可以理解成代表 module 本身, "module::def()",也就是 "m.def",用来定义 python 端的接口函数。

  上述代码中绑定了 python 端 Scope() 和 c++ 中的函数(lambda 表达式)。进一步发现,调用 python 函数的返回值显然是 python 端的,也就是 _Scope,而它对应的也就是 c++ 端的 Scope 类,绑定代码如下:

  py::class_(m, "_Scope", R"DOC(

  Scope is an association of a name to Variable. All variables belong to Scope.

  Variables in a parent scope can be retrieved from local scope.

  You need to specify a scope to run a Net, i.e., `exe.Run(&scope)`.

  One net can run in different scopes and update different variable in the

  scope.

  You can create var in a scope and get it from the scope.

  Examples:

  .. code-block:: python

  import paddle.fluid as fluid

  # create tensor from a scope and set value to it.

  param=scope.var('Param').get_tensor()

  param_array=np.full((height, row_numel), 5.0).astype("float32")

  param.set(param_array, place)

  )DOC")

  .def("_remove_from_pool",

  [](Scope &self) { ScopePool::Instance().Remove(&self); })

  .def("var",

  [](Scope &self, const std::string &name) -> Variable * {

  return self.Var(name);

  },

  py::arg("name"),

  R"DOC(

  Find or create variable named :code:`name` in the current scope.

  If the variable named :code:`name` does not exist in the

  current scope, the variable would be created. Otherwise,

  return the existing variable.

  Args:

  name (str): the variable name.

  Returns:

  out (core.Variable): the found or created variable.

  )DOC",

  py::return_value_policy::reference)

  //...

  示例 3

  再来看一个嵌套的例子:

  ExecutionStrategy=core.ParallelExecutor.ExecutionStrategy

  BuildStrategy=core.ParallelExecutor.BuildStrategy

  if self._build_strategy is None:

  self._build_strategy=BuildStrategy()

  self._build_strategy.is_distribution=_is_pserver_mode(self._program)

  上述代码中的 BuildStrategy 类型绑定的又是 c++ 端哪个函数呢?在 pybind 文件中有如下定义:

  py::class_ pe(m, "ParallelExecutor");

  py::class_ exec_strategy(pe, "ExecutionStrategy", R"DOC(

  ...

  )

  记住:paddle 框架代码中,core、core_avx 都是 python 的 module. 除了 pybind、protobuf 之外,paddle 中类似地还有 data_set_py 等文件中也定义了 python 与 c++ 的绑定关系。

  另外要注意的是,paddle 中并不是每一个 python 类型、函数或变量都是调用 c++ 端的,paddle 框架中也有许多 python 代码,它们的作用要么调用后端 c++ 代码,要么封装一些 c++ 功能,或者用 python 实现部分功能,以便给用户提供简洁、便捷的 api.

posted @ 2022-02-15 17:22  ebuybay  阅读(353)  评论(0编辑  收藏  举报