C-API 模型推断实现文档¶
本文档描述Paddle C-API的实现细节。Paddle C-API是多语言API的基础部分。Paddle需要暴露的API很多。先实现模型推断的API,通过模型推断API的实现作为一个样例,来进行讨论。至于为什么需要C-API,请参考Why Plain C。
Table of Contents¶
暴露接口原则¶
- 所有的接口均为C接口。即使用
extern "C"
- 除构造某种类型的函数(
paddle_matrix_create
等),其他函数均返回paddle_error
。且调用时不能抛出异常或出现运行时错误。 - 所有类型名为
paddle_类型名
,所有与类型相关的函数,函数名为paddle_类型名_函数名
- 如果某一个Paddle Core概念(GradientMachine/Matrix)需要被暴露到其他语言,那么
- 为了暴露的接口尽量简单。只暴露概念的接口,而不暴露概念的实现。即暴露
GradientMachine
或者Matrix
但不暴露RecurrentGradientMachine
和CpuSparseMatrix
。 - 暴露这个概念必要函数。
必要
是指,即完成某一个任务的最少函数。
- 为了暴露的接口尽量简单。只暴露概念的接口,而不暴露概念的实现。即暴露
- 不在
capi
接口层做过多封装。- 如果某一个Paddle概念必须要暴露,但是又过于琐碎。不在
capi
这一层进行封装,而是直接修改Paddle Core。让Paddle核心中,这一概念不再琐碎。
- 如果某一个Paddle概念必须要暴露,但是又过于琐碎。不在
目录结构¶
Paddle
`-- paddle
`-- capi
`-- examples # The example project for C-API.
`-- tests # unittests for C-API
`-- capi.h # C-API header file.
`-- capi_private.h # The shared header file between implementation sources.
`-- matrix.{h, cpp}
`-- gradient_machine.{h, cpp}
`-- ...
Paddle的C-API目录结构如上图表所示。这个目录中除了capi_private.h
之外的所有头文件,均会被安装到include/paddle路径下。C-API生成的二进制文件会被安装到lib
目录下。即,安装后的目录结构为
`-- include
`-- paddle
`-- capi.h
`-- matrix.h
`-- gradient_machine.h
`-- ...
`-- lib
`-- libpaddle_capi_shared.{so, dylib} # In mac, dynamic libary's file name extention is `dylib`
`-- libpaddle_capi_whole.a # static library for all symbols of Paddle.
实现方式¶
下面分别介绍某一类文件的实现方式。
capi.h¶
capi.h
是用户使用C-API时所唯一需要引入的头文件。在capi.h
中,引入了类型的头文件,matrix.h
, gradient_machine.h
。在引入其他类型的头文件时,使用相对路径的引用方式。即#include "matrix.h"
具体某种类型的头文件¶
具体某种类型的头文件,即例如matrix.h
,gradient_machine.h
等。在这些头文件中,包含了某种类型的类型定义和暴露的全部函数。
这个头文件不假设其他文件的引用顺序,即使用户直接引用某种类型的头文件,也不应该报错(虽然不鼓励这样)。如果某一个类型需要引用另一个类型,例如gradient_machine
需要引用matrix
,则直接引入另一种类型的头文件,即#include "matrix.h"
。
capi_private.h¶
capi_prviate.h
是各个实现中共享的头文件,他主要包含了实际暴露的类型结构。在用户使用C-API时,Paddle的类型全部退化成void *
,即typedef paddle_matrix void*
。但,对于每种C-API暴露的类型,均是在capi_private.h
中实现的结构体。
struct CMatrix {
int type = MatrixType;
std::shared_ptr<paddle::Matrix> mat;
};
通常,这个结构体包含两个项目。
type
是一个类型的标志。对于每种类型,type字段均不尽相同。这样,即使C-API接受的类型全是void *
,我们也可以确定每一个参数的类型。void some_c_api_function(void* some_instance) { int* type = (int *) some_instance; switch (*type) { case MatrixType: CMatrix* mat = (CMatrix *) some_instance; ... ... } }
这个结构体中的另一个项目是,Paddle Core中这一类型接口的智能指针(shared_ptr)。
- 使用智能指针的原因是: 用户可以安全的释放某个C-API的实例,而不必在意Paddle Core是否还在使用这个实例。
- 例如,用户通过C-API获得了神经网络的参数实例。当用户使用完这个参数后,直接删除这个参数即可。即便Paddle Core中的模型还在使用这个参数,这个参数也不会一并删除。
具体某种类型的实现文件¶
具体某种类型的实现文件,即matrix.cpp
, gradient_machine.cpp
等文件。在这些文件中,使用C++ 11实现了C-API的接口,并且使用extern "C"
导出这些接口。在实现过程中,对输入参数的安全性进行了必要的判断,并将C-API接口的参数转发给Paddle Core
。
libpaddle_capi_whole.a¶
libpaddle_capi_whole
是C-API导出的静态库。这个静态库包含了Paddle的全部符号。他是将libpaddle_gserver.a
, libpaddle_math.a
, libpaddle_capi.a
等全部静态库中的目标文件全部打包后产生的文件。具体使用方法为--whole-archive -lpaddle_capi_whole --no-whole-archive
。
examples¶
在样例中,使用C99
开发了模型预测的样例代码。具体请参考example/README.md。
编译选项¶
C-API的编译选项默认关闭,打开这个编译选项,需要在cmake的时候,设置
cmake ${YOUR_SOURCE_ROOT} -DWITH_C_API=ON -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF
编译C-API的时候推荐Paddle不嵌入Python解释器,也不生成SWIG
接口,具体原因参考Why Plain C。