本文主要介绍静态图的主体设计思想和基本概念。
Fluid将神经网络描述为Program这一数据结构。
Program由Block组成,即 Program = List[Block]
。
Block由Operator和Variable组成,即 Block = List[Operator] + List[Variable]
。
与编程语言类比,我们可以将Program理解为程序,Block对应程序的控制流分支结构,如条件分支、循环分支等。Fluid的控制流Op(conditional_block,while,recurrent等)均通过Block表达。
不同Block里的变量可以重名。若父Block与子Block中存在同名变量,那么子Block的Operator运行时会优先找到子Block中的变量。
int func(int n) {
// Main Block
int a = 3;
int b = 4;
int c = a * b;
int d = c / 10;
...
int sum = 0;
if (n > 0) { // sub-block
for (int i = 0; i < 10; i++) {
sum += i; // sub-sub-block
}
} else { // sub-block
for (int i = 0; i < (-n); i++) {
sum -= i; // sub-sub-block
}
}
std::cout << d << " " << sum << std::endl;
return 0;
}
在组建神经网络的过程中会涉及两个Program,即startup program和main program。
startup program对应TensorFlow中的tf.global_initializer(),包含参数、learning rate、Optimizer Momentum等变量的初始化Op。
main program对应神经网络的主体结构。因此,在运行神经网络训练/预测时,我们需首先跑一次startup program进行初始化,然后跑多次main program进行训练/预测。
Fluid定义了全局默认的startup program和main program,即 fluid.default_startup_program()
和 fluid.default_main_program()
,调用 fluid.layers.xxx
API时,均会往全局默认的program中插入op。
若要切换全局的startup program和main program,可使用 fluid.program_guard()
,例如:
import paddle.fluid as fluid
startup_program = fluid.Program()
main_program = fluid.Program()
assert startup_program != fluid.default_startup_program()
assert main_program != fluid.default_main_program()
with fluid.program_guard(main_program, startup_program):
assert startup_program == fluid.default_startup_program()
assert main_program == fluid.default_main_program()
...
assert startup_program != fluid.default_startup_program()
assert main_program != fluid.default_main_program()
Python端的Program,Block,Operator,Variable分别对应于C++端的ProgramDesc,BlockDesc,OpDesc,VarDesc。
值得注意的是,Program是对神经网络的静态描述,其底层是Protobuf Description,因此在运行网络以前所有变量、Op均不存在。
Place表示设备,可以是GPU设备或CPU设备。
using Place = boost::variant<CUDAPlace, CPUPlace, CUDAPinnedPlace>;
boost::variant类似于C++的union,是一种类型安全的union,即multi-type, one-value。
同一设备的内存/显存的Place相同,即相同Place的Tensor的内存/显存空间在同一设备上。
DeviceContext表示设备,可以理解为是一个虚拟设备的概念,包含CPUDeviceContext和CUDADeviceContext等。理论上,一个Place可对应多个DeviceContext。
DeviceContext中包含一些额外的设备信息,例如cudaStream_t, cudnnHolder_t, cublasHandle_t等。
在目前Fluid的设计中,我们维护了一个全局的DeviceContextPool,记录了Place到DeviceContext的map(单映射,非multimap)。即在全局DeviceContextPool中,Place和DeviceContext是一一对应的。目前Fluid的所有单设备Op均运行在全局的DeviceContext中。
C++ Variable是一个类似于std::any的结构,可以存储任意类型的变量。最常见的,Variable里存储LoDTensor,但Variable还可以存储SelectedRows,ReaderHolder等。
Scope用于存储变量,Scope主要数据成员为:
class Scope {
std::unordered_map<std::string, std::unique_ptr<Variable>> vars_; // Scope中存储的变量
const Scope *parent_; // 父Scope
std::list<Scope *> kids_; // 子Scope
};
Scope与编程语言的作用域类似,当调用Scope::FindVar时,会首先在当前Scope中查找变量是否存在,若存在则直接返回,否则递归地从父Scope里寻找该变量。
Op主要包含4个信息:
std::string
类型,表示Op的名称,如"matmul","conv2d","reshape"等。std::map<std::string, std::vector<std::string>>
类型,表示Op的输入变量,map的key为slot名称,对应于OpProtoAndCheckerMaker中定义的名称;map的value为实际的变量名,对应于Scope中的变量名。std::map<std::string, std::vector<std::string>>
类型,表示Op的输出变量。key和value的含义与inputs类似。std::map<std::string, boost::variant<...>>
类型,表示Op的属性,例如transpose选择哪些维度进行转置等。OperatorBase是所有Op的基类,其Run方法的声明为:
void OperatorBase::Run(const Scope &scope, const platform::Place& place) {...}
运行Op时,需指明Scope和Place。Op运行过程中,会首先从Scope中获取输入输出变量,然后从Place中获取设备信息,进行计算。
OperatorWithKernel继承自OperatorBase,我们称继承自OperatorWithKernel的Op为有Kernel的Op。
Kernel的目的是为了区分不同的运行设备(CPU/GPU)、数据类型(float/double/int)、库(MKLDNN/CUDNN)、layout(NCHW/NHWC)等。
一个Op可以有多个Kernel实现,Kernel实现应继承自OpKernel<T>。
OperatorWithKernel重写了OperatorBase的RunImpl方法,进行了以下操作:
在Python端组网过程中,即调用fluid.layers.xxx API组网时,亦称为编译期,会往Program中插入Op,具体为:
每次往Python端Program插入Op时,均会走以下步骤:
在调用optimizer.minimize()的过程中,会发生以下几个动作:
当Python端的Program构建完毕后,Executor::Run会取出Program的Block 0中的所有OpDesc,调用OpRegistry::CreateOp方法根据OpDesc创建OperatorBase,然后调用OperatorBase::Run()方法运行所有Op,具体方式为:
void Executor::Run(const ProgramDesc &program, const Scope &scope, const platform::Place &place) {
std::vector<std::unique_ptr<OperatorBase>> ops;
for (auto &op_desc : program.Block(0).AllOps()) {
auto op = OpRegistry::CreateOp(op_desc);
ops.emplace_back(std::move(op));
}
for (auto &op : ops) {
op->Run(scope, place);
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。