AI框架外部用户贡献代码

AI框架外部用户贡献代码

概述

飞桨是百度自主研发的一款开源的深度学习框架,是主流深度学习框架中首个完全国产化的产品,已经在农业、医疗、林业、科研、服务等领域成功应用。无论是已入职场的深度学习从业者、爱好者,亦或是在校学生,百度飞桨非常欢迎大家能够在开源生态Github中贡献代码,与实时分享项目的成功应用和的奇思妙想。贡献的代码可以是模型、框架的算子、框架新增功能或者飞桨平台优化建议等。一旦贡献的代码被飞桨接受,将有机会让更多的深度学习用户受益。同时,为了促进深度学习快速发展和应用,飞桨会定期组织优秀代码展播和表彰等活动,可以随时关注飞桨官网了解更详细的信息。

深度学习的应用触及各行各业,再强大的模型库也无法完全匹配业务需求,往往需要结合业务和数据特点进行参数调优。因此,在使用飞桨进行模型开发时,需要经常与算子操作打交道。如果飞桨提供的算子(Operator, 简称OP)无法满足模型需求,可以使用自定义Python算子和自定义C++算子的功能编写。本文主要介绍飞桨算子的写法以及在GitHub上贡献代码的操作方法。

自定义算子

飞桨支持Python和C++两种类型算子,建议使用场景如下:

  • Python算子: 在模型实现的过程中,如果遇到缺失算子,建议先尝试使用已有的算子进行组合。如果已有的算子无法组合出需要的操作,可通过自定义Python算子的功能开发新的算子。
  • C++算子: 如果用若干算子组合出的算子在性能上无法满足要求时,可以通过自定义C++算子提升性能。

自定义Python算子

飞桨通过 paddle.static.py_func 接口在Python端编写算子,先了解下py_func接口。接口格式如下:

def py_func(func, x, out, backward_func=None, skip_vars_in_backward_input=None)

  • func : 是前向计算函数。在运行网络前向时,飞桨会调用 out = func(*x) ,根据前向输入 x 和前向函数 func 计算前向输出 out。
  • x : 是输入变量,可以是单个 Tensor 、 List[Tensor]或者tuple(Tensor)。
  • out : 是输出变量,可以是单个 Tensor 或者 List[Tensore] 。
  • backward_func : 是反向计算函数。若 backward_func 为 None ,则该Python Op没有反向计算逻辑;若 backward_func 不为 None,则飞桨会在运行网络反向时调用 backward_func 计算前向输入 x 的梯度。
  • skip_vars_in_backward_input: 为backward_func 中不需要的输入,可以是单个 Tensor 或者 List[Tensor] 。

Python算子由计算方法、输入变量和输出变量组成。一般来讲,通过飞桨paddle.static.py_func接口进行Python算子开发需要如下三步:

  1. 定义计算方法:定义前向函数和反向函数。
  2. 定义算子输出变量:创建前向输出变量。
  3. 调用算子组网:使用 paddle.static.py_func组建网络。

1. 定义计算方法

定义前向函数

若前向函数的输入为 x_1, x_2, …, x_n ,输出为y_1, y_2, …, y_m,则定义前向函数的格式为:

def foward_func(x_1, x_2, ..., x_n):

...

return y_1, y_2, ..., y_m

例如:定义relu的前向函数,代码如下:

def relu(x):

return np.maximum(x, 0.0)

定义反向函数

默认情况下,反向函数的输入参数顺序为: 所有前向输入变量 + 所有前向输出变量 + 所有前向输出变量的梯度,因此定义反向函数的格式为:

def backward_func(x_1, x_2, ..., x_n, y_1, y_2, ..., y_m, dy_1, dy_2, ..., dy_m):

...

return dx_1, dx_2, ..., dx_n

例如:定义relu的方向函数,代码如下:

def relu_grad(y, dy):

dx = np.zeros_like(y)

dx[y > 0] = 1.0

return np.array(dy) * dx


说明:

  • 若反向函数不需要某些前向输入变量或前向输出变量,可通过 skip_vars_in_backward_input 参数进行设置。
  • 前向函数和反向函数的输入均是 Tensor,输出是Tensor或numpy.array。Tensor和numpy.array可以相互转换。

2. 定义算子输出变量

飞桨通过Program.current_block().create_var 创建前向输出变量,其中变量的名称name、数据类型dtype和维度shape为必选参数,格式如下:

import paddle

def create_tmp_var(program, name, dtype, shape):

return program.current_block().create_var(name=name, dtype=dtype, shape=shape)

# 开启静态图

paddle.enable_static()

# 手动创建前向输出变量

y_var = create_tmp_var(paddle.static.default_main_program(), 'output', 'float32', [-1, 1, 28, 28])

print(y_var)

3. 调用算子组网

在组网过程中,通过调用paddle.static.py_func将定义好的Python计算函数加入整个模型中,代码如下:

x = paddle.static.data(name='x', shape=[-1,1,28,28], dtype='int32')

paddle.static.py_func(func=relu, x=x, out=y_var, backward_func=relu_grad, skip_vars_in_backward_input=x)


说明:

  • 若在反向函数输入参数中不希望出现前向输入,可通过 skip_vars_in_backward_input 参数进行设置。
  • skip_vars_in_backward_input 只能跳过前向输入变量和前向输出变量,不能跳过前向输出的梯度。
  • py_func 的前向函数和反向函数中不应调用fluid.layers里的操作,原因如下:
    • fluid.layers里的操作是在组建网络阶段调用,输入参数为Python端的 Variable 。
    • 前向函数和反向函数是在网络运行时调用,且输入参数均为C++端的 LoDTensor 。
  • 若某个前向输出变量没有梯度,则 backward_func 的输入为 None 。若某个前向输入变量没有梯度,则 backward_func 的输出为None。

自定义Python算子实例

下面通过三个不同场景的实例,具体介绍Python算子的实现方法和注意事项。

实例1: 实现z=x + y的算子

import numpy as np

import paddle

paddle.enable_static()

#第一步: 定义加法的前向函数

def element_wise_add(x, y):

x = np.array(x)

y = np.array(y)

if x.shape != y.shape:

raise AssertionError("the shape of inputs must be the same!")

z = x + y

return z

def create_tmp_var(name, dtype, shape):

return paddle.static.default_main_program().current_block().create_var(

name=name, dtype=dtype, shape=shape)

x = paddle.static.data(name='x1', shape=[2, 3], dtype='int32')

y = paddle.static.data(name='y1', shape=[2, 3], dtype='int32')

#第二步: 创建前向输出变量, name是'output', 数据类型是'int32', 形状是[2,3]

output = create_tmp_var('output','int32', [2, 3])

#第三步: 使用py_func组建网络,设置前向计算函数,输入是[x,y]2个变量,输出是output

paddle.static.py_func(func=element_wise_add, x=[x, y], out=output)

exe=paddle.static.Executor(paddle.CPUPlace())

exe.run(paddle.static.default_startup_program())

x_arr = np.random.randint(1, 10, size=[2, 3], dtype='int32')

y_arr = np.random.randint(1, 10, size=[2, 3], dtype='int32')

out = exe.run(feed={'x1':x_arr, 'y1':y_arr},

fetch_list=[output])

print("{0} + \n{1} = \n{2}".format(x_arr, y_arr, out[0]))

[[7 6 2]

[7 9 2]] +

[[6 9 2]

[2 5 7]] =

[[13 15  4]

[ 9 14  9]]

/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/layers/nn.py:13402: DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()

args = inspect.getargspec(self._func)

/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/executor.py:1150: UserWarning: There are no operators in the program to be executed. If you pass Program manually, please use fluid.program_guard to ensure the current Program is being used.

warnings.warn(error_info)

实例2:实现relu构建神经网络的算子

import numpy as np

import paddle

#第一步: 定义前向和反向函数

#前向函数1个输入x,一个输出y=np.maximum(x, 0.0)

def relu(x):

return np.maximum(x, 0.0)

#反向函数2个输入:前向的输出y和y的梯度;一个输出:前向输入的梯度

def relu_grad(y, dy):

y = np.array(y)

dx = np.zeros_like(y)

dx[y > 0] = 1.0

return np.array(dy) * dx

def create_tmp_var(name, dtype, shape):

return paddle.static.default_main_program().current_block().create_var(

name=name, dtype=dtype, shape=shape)

# 开启静态图模式

paddle.enable_static()

x = paddle.static.data(name='x2', shape=[None, 16], dtype='float32')

y = paddle.static.data(name='y2', shape=[None, 1], dtype='int64')

fc = paddle.static.nn.fc(x, size=200)

#第二步:创建前向输出的变量,name是relu,数据类型和fc保持一致,shape和fc保持一致

act_var = create_tmp_var(name='relu', dtype=fc.dtype, shape=fc.shape)

#第三步:使用py_func组建网络,设置前向、反向计算函数,输入是fc,输出是act_var

act = paddle.static.py_func(func=relu, x=fc,

out=act_var, backward_func=relu_grad,

skip_vars_in_backward_input=fc)

prediction = paddle.static.nn.fc(act, size=10)

loss = paddle.nn.functional.cross_entropy(input=prediction, label=y)

loss = paddle.mean(loss)

optimizer = paddle.optimizer.SGD(learning_rate=0.001)

optimizer.minimize(loss)

exe = paddle.static.Executor(paddle.CPUPlace())

exe.run(paddle.static.default_startup_program())

x_arr = np.random.random(size=(1, 16)).astype('float32')

y_arr = np.random.randint(0, 10, size=[1, 1], dtype='int64')

out = exe.run(feed={'x2':x_arr, 'y2':y_arr},

fetch_list=[loss])

print(out[0])

实例3:实现输入输出为LoDTensor类型的算子

import numpy as np

import paddle

def scale(x):

seq_len = x.recursive_sequence_lengths()

x = np.array(x) * 2.

y = paddle.fluid.LoDTensor()

y.set(x, paddle.CPUPlace())

y.set_recursive_sequence_lengths(seq_len)

print(seq_len)

return y

def create_tmp_var(name, dtype, shape):

return paddle.static.default_main_program().current_block().create_var(

name=name, dtype=dtype, shape=shape)

# 开启静态图

paddle.enable_static()

x = paddle.static.data(name='x', shape=[5, 4], dtype='float32', lod_level=1)

output = create_tmp_var('output','float32', [5, 4])

paddle.static.py_func(func=scale, x=[x], out=output)

place = paddle.CPUPlace()

exe = paddle.static.Executor(place)

exe.run(paddle.static.default_startup_program())

x_arr = np.random.random(size=(5, 4)).astype('float32')

x_t = paddle.fluid.create_lod_tensor(x_arr, [[2, 3]], place)

out = exe.run(feed={'x':x_t},

fetch_list=[output],

return_numpy=False)

print("{0} * 2. = \n{1}\nsequence_length: {2}".format(x_arr,

np.array(out[0]), out[0].recursive_sequence_lengths()))

自定义C++算子

自定义C++算子的实现形式和飞桨框架里实现算子形式相同,用户在框架外部自定义算子,需要如下四步:

  1. 实现算子:算子的实现和注册需要遵守飞桨写新C++ OP的规范和步骤,其中实现反向OP为可选操作。
  2. 编译算子:编译生成动态链接库,目的是为了主程序运行时自动加载该动态库,获得编译时的算子定义,以及运行时调用此动态库中算子实现。
  3. 封装Python Layer接口:封装成Python Layer接口,以便搭建模型组网时调用。
  4. 单元测试:通过单元测试验证代码实现的正确性。

1. 实现C++算子

下面以实现relu算子为例,介绍实现C++算子的具体步骤。ReLU OP的实现有两种部署场景,CPU和GPU,用户可以按照实际业务需求,选择对应的实现方法。

场景一:ReLU OP的CPU实现

#include "paddle/fluid/framework/op_registry.h"

namespace paddle {

namespace operators {

// 1. 定义前向OP的输入X、输出Y、属性。

class Relu2OpMaker : public framework::OpProtoAndCheckerMaker {

public:

void Make() override {

AddInput("X", "The input tensor.");

AddOutput("Y", "Output of relu_op");

AddComment(R"DOC(

Relu Operator.

Y = max(X, 0)

)DOC");

}

};

// 1) 定义前向OP和InferShape实现,设置输出Y的shape。

class Relu2Op : public framework::OperatorWithKernel {

public:

using framework::OperatorWithKernel::OperatorWithKernel;

void InferShape(framework::InferShapeContext* ctx) const override {

auto in_dims = ctx->GetInputDim("X");

ctx->SetOutputDim("Y", in_dims);

}

};

// 2) 实现前向OP的Kernel计算函数: Y = max(0, X)。

using Tensor = framework::Tensor;

template <typename DeviceContext, typename T>

class Relu2Kernel : public framework::OpKernel<T> {

public:

void Compute(const framework::ExecutionContext& ctx) const override {

auto* in_t = ctx.Input<Tensor>("X");

auto* out_t = ctx.Output<Tensor>("Y");

auto x = in_t->data<T>();

// mutable_data分配内存、获取指针

auto y = out_t->mutable_data<T>(ctx.GetPlace());

for (int i = 0; i < in_t->numel(); ++i) {

y[i] = std::max(static_cast<T>(0.), x[i]);

}

}

};

// 2. 定义反向OP的输入Y和dY、输出dX、属性。如果不需要反向OP,此步骤可忽略。

template <typename T>

class Relu2GradMaker : public framework::SingleGradOpMaker<T> {

public:

using framework::SingleGradOpMaker<T>::SingleGradOpMaker;

void Apply(GradOpPtr<T> op) const override {

op->SetType("relu2_grad");

op->SetInput("Y", this->Output("Y"));

op->SetInput(framework::GradVarName("Y"), this->OutputGrad("Y"));

op->SetAttrMap(this->Attrs());

op->SetOutput(framework::GradVarName("X"), this->InputGrad("X"));

}

};

// 1) 定义反向OP和InferShape实现,设置dX的shape。如果不需要反向OP,此步骤可忽略。

class Relu2GradOp : public framework::OperatorWithKernel {

public:

using framework::OperatorWithKernel::OperatorWithKernel;

void InferShape(framework::InferShapeContext* ctx) const override {

auto in_dims = ctx->GetInputDim(framework::GradVarName("Y"));

ctx->SetOutputDim(framework::GradVarName("X"), in_dims);

}

};

// 2) 实现反向OP的kernel函数 dx = dy * ( y > 0. ? 1. : 0)如果不需要反向OP,此步骤可忽略。

template <typename DeviceContext, typename T>

class Relu2GradKernel : public framework::OpKernel<T> {

public:

void Compute(const framework::ExecutionContext& ctx) const override {

auto* dy_t = ctx.Input<Tensor>(framework::GradVarName("Y"));

auto* y_t = ctx.Input<Tensor>("Y");

auto* dx_t = ctx.Output<Tensor>(framework::GradVarName("X"));

auto dy = dy_t->data<T>();

auto y = y_t->data<T>();

auto dx = dx_t->mutable_data<T>(ctx.GetPlace());

for (int i = 0; i < y_t->numel(); ++i) {

dx[i] = dy[i] * (y[i] > static_cast<T>(0) ? 1. : 0.);

}

}

};

}  // namespace operators

}  // namespace paddle

namespace ops = paddle::operators;

using CPU = paddle::platform::CPUDeviceContext;

// 3. 注册前向和反向op。为了和框架内部的relu区分,这里注册的OP type为relu2。

REGISTER_OPERATOR(relu2,

ops::Relu2Op,

ops::Relu2OpMaker,

ops::Relu2GradMaker<paddle::framework::OpDesc>,

ops::Relu2GradMaker<paddle::imperative::OpBase>);

REGISTER_OPERATOR(relu2_grad, ops::Relu2GradOp);

// 注册CPU的Kernel

REGISTER_OP_CPU_KERNEL(relu2,

ops::Relu2Kernel<CPU, float>,

ops::Relu2Kernel<CPU, double>);

REGISTER_OP_CPU_KERNEL(relu2_grad,

ops::Relu2GradKernel<CPU, float>,

ops::Relu2GradKernel<CPU, double>);

场景二:ReLU OP的GPU实现

// relu_op.cu

#include "paddle/fluid/framework/op_registry.h"

namespace paddle {

namespace operators {

using Tensor = framework::Tensor;

template <typename T>

__global__ void KeRelu2(const T* x, const int num, T* y) {

int gid = blockIdx.x * blockDim.x + threadIdx.x;

for (int i = gid; i < num; i += blockDim.x * gridDim.x) {

y[i] = max(x[i], static_cast<T>(0.));

}

}

// 前向OP的kernel的GPU实现

template <typename DeviceContext, typename T>

class Relu2CUDAKernel : public framework::OpKernel<T> {

public:

void Compute(const framework::ExecutionContext& ctx) const override {

auto* in_t = ctx.Input<Tensor>("X");

auto* out_t = ctx.Output<Tensor>("Y");

auto x = in_t->data<T>();

auto y = out_t->mutable_data<T>(ctx.GetPlace());

auto& dev_ctx = ctx.template device_context<DeviceContext>();

int num = in_t->numel();

int block = 512;

int grid = (num + block - 1) / block;

KeRelu2<T><<<grid, block, 0, dev_ctx.stream()>>>(x, num, y);

}

};

template <typename T>

__global__ void KeRelu2Grad(const T* y, const T* dy, const int num, T* dx) {

int gid = blockIdx.x * blockDim.x + threadIdx.x;

for (int i = gid; i < num; i += blockDim.x * gridDim.x) {

dx[i] = dy[i] * (y[i] > 0 ? 1. : 0.);

}

}

// 反向OP的kernel的GPU实现

template <typename DeviceContext, typename T>

class Relu2GradCUDAKernel : public framework::OpKernel<T> {

public:

void Compute(const framework::ExecutionContext& ctx) const override {

auto* dy_t = ctx.Input<Tensor>(framework::GradVarName("Y"));

auto* y_t = ctx.Input<Tensor>("Y");

auto* dx_t = ctx.Output<Tensor>(framework::GradVarName("X"));

auto dy = dy_t->data<T>();

auto y = y_t->data<T>();

auto dx = dx_t->mutable_data<T>(ctx.GetPlace());

auto& dev_ctx = ctx.template device_context<DeviceContext>();

int num = dy_t->numel();

int block = 512;

int grid = (num + block - 1) / block;

KeRelu2Grad<T><<<grid, block, 0, dev_ctx.stream()>>>(y, dy, num, dx);

}

};

}  // namespace operators

}  // namespace paddle

using CUDA = paddle::platform::CUDADeviceContext;

// 注册前向的GPU Kernel

REGISTER_OP_CUDA_KERNEL(relu2,

paddle::operators::Relu2CUDAKernel<CUDA, float>,

paddle::operators::Relu2CUDAKernel<CUDA, double>);

// 注册反向的GPU Kernel

REGISTER_OP_CUDA_KERNEL(relu2_grad,

paddle::operators::Relu2GradCUDAKernel<CUDA, float>,

paddle::operators::Relu2GradCUDAKernel<CUDA, double>);


注意:

OP的type不能和飞桨已有的OP type相同,否则在Python中使用时会报错。


2. 编译算子

实现C++算子之后,可以通过编译算子的方式生成动态链接库,以便后续封装算子的Python Layer接口,供模型搭建组网以及运行时执行引擎时调用。对于C++算子的编译有两种方式:G++命令行编译、CMake工具编译。下面以命令行的编译方式为例,介绍编译算子的方法。


说明:

如果实现算子中包含CUDA程序,需要做如下处理:

  1. 通过NVCC对CUDA源文件(通常是.cu文件)进行编译,产生目标文件。
  2. 通过G++命令将NVCC编译产生的目标文件和C++源文件编译生成动态链接库。

算子实现中添加了飞桨核心框架的头文件引用,编译时需要链接核心框架动态库。通过paddle.sysconfig.get_include()和paddle.sysconfig.get_lib()查看头文件和链接库所在目录,代码如下:

# python

>>> import paddle

>>> print(paddle.sysconfig.get_include())

/paddle/pyenv/local/lib/python2.7/site-packages/paddle/include

>>> print(paddle.sysconfig.get_lib())

/paddle/pyenv/local/lib/python2.7/site-packages/paddle/libs

编译动态库代码如下:

include_dir=$( python -c 'import paddle; print(paddle.sysconfig.get_include())' )

lib_dir=$( python -c 'import paddle; print(paddle.sysconfig.get_lib())' )

echo $include_dir

echo $lib_dir

nvcc relu_op.cu -c -o relu_op.cu.o -ccbin cc -DPADDLE_WITH_CUDA -DEIGEN_USE_GPU -DPADDLE_USE_DSO -DPADDLE_WITH_MKLDNN -Xcompiler -fPIC -std=c++11 -Xcompiler -fPIC -w --expt-relaxed-constexpr -O3 -DNVCC \

-I ${include_dir} \

-I ${include_dir}/third_party \

g++ relu_op.cc relu_op.cu.o -o relu2_op.so -shared -fPIC -std=c++11 -O3 -DPADDLE_WITH_MKLDNN \

-I ${include_dir} \

-I ${include_dir}/third_party \

-L /usr/local/cuda/lib64 \

-L ${lib_dir} -lpaddle_framework -lcudart


说明:

  1. 通过NVCC编译CUDA源文件时,需要加编译选项 -DPADDLE_WITH_CUDA -DEIGEN_USE_GPU -DPADDLE_USE_DSO,在框架源码中会使用这些宏定义进行条件编译。用户自定义的C++ OP实现编译时,选项的开启状态需要和核心框架编译行为一致。如EIGEN_USE_GPU是使用Eigen数学库的GPU实现时需要增加的编译选项。
  2. 如果飞桨安装包中不包含MKLDNN库,则需要去掉编译选项-DPADDLE_WITH_MKLDNN。核心框架源码中(比如tensor.h)有使用此宏定义进行条件编译,该选项是否打开同样需要和核心框架编译行为保持一致。默认的飞桨安装包中含有MKLDNN库。
  3. 支持将多个OP编译到同一个动态库中。
  4. 通过pip方式安装的PaddlePaddle由GCC 4.8编译得到,由于GCC 4.8和GCC 5以上C++11 ABI不兼容,编写的自定义OP,需要通过GCC 4.8编译。若是GCC 5及以上的环境上使用自定义OP,推荐使用Docker安装PaddlePaddle,使得编Paddle和编译自定义OP的GCC版本相同。

3. 封装Python Layer接口

通过 load_op_library 接口加载动态库,在飞桨的主进程中执行用户自定义的OP。

# custom_op.py

import paddle.incubate as incubate

# 调用load_op_library加载动态库

incubate.load_op_library('relu2_op.so')

from paddle.incubate import LayerHelper

def relu2(x, name=None):

# relu2的type和在OP中定义的type相同

helper = LayerHelper("relu2", **locals())

# 创建输出Variable

out = helper.create_variable_for_type_inference(dtype=x.dtype)

helper.append_op(type="relu2", inputs={"X": x}, outputs={"Y": out})

return out


说明:

  1. 一个动态库只需使用paddle.incubate.load_op_library加载一次即可。
  2. Python接口的封装和PaddlePaddle框架内部的封装相同,更多的示例也可以阅读源码中[python/paddle/fluid/layers/nn.py](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/fluid/layers/nn.py)的代码示例。

4. 单元测试

经过上述步骤之后,可以通过单元测试验证代码的正确性。通过比对C++计算结果和Python计算结果,测试程序的正确性。

  • 静态图模式

import numpy as np

import paddle

from custom_op import relu2

paddle.enable_static()

data = paddle.static.data(name='data', shape=[None, 32], dtype='float32')

relu = relu2(data)

use_gpu = True # or False

paddle.set_device('gpu' if use_gpu else 'cpu')

exe = paddle.static.Executor()

x = np.random.uniform(-1, 1, [4, 32]).astype('float32')

out, = exe.run(feed={'data': x}, fetch_list=[relu])

np.allclose(out, np.maximum(x,0.))

  • 动态图模式

import numpy as np

import paddle

from custom_op import relu2

use_gpu = True  # or False

paddle.set_device('gpu' if use_gpu else 'cpu')

x = np.random.uniform(-1, 1, [4, 32]).astype('float32')

t = paddle.to_tensor(x)

out = relu2(t)

np.allclose(out.numpy(), np.maximum(x, 0.))

注意:

如果出现类似错误: relu2_op.so: cannot open shared object file: No such file or directory 以及 libpaddle_framework.so: cannot open shared object file: No such file or directory。需要将relu2_op.so所在路径以及libpaddle_framework.so路径(即paddle.sysconfig.get_lib()得到路径)设置到环境变量LD_LIBRARY_PATH中, 对于Linux环境设置:

# 假如relu2_op.so,路径是: paddle/test

# 假如libpaddle_framework.so,路径是: pyenv/local/lib/python2.7/site-packages/paddle/libs

export LD_LIBRARY_PATH=paddle/test:pyenv/local/lib/python2.7/site-packages/paddle/libs:$LD_LIBRARY_PATH


在GitHub上贡献代码

飞桨非常欢迎大家在开源生态Github中贡献代码,与实时分享深度学习项目的成功应用和的奇思妙想。下面以PaddlePaddle/Paddle repo为例,详细介绍在GitHub上提交代码的操作方法,流程如 图1 所示。

AI框架外部用户贡献代码

图1 在GitHub上贡献代码流程

说明:

在执行如下操作前,请确保本地已经安装GIT,下载路径:https://git-scm.com/download/win,选择与PC系统对应的版本。

创建本地GitHub环境

说明:

如果首次使用飞桨GitHub,需要先创建飞桨本地GitHub环境。如果已经创建了飞桨本地GitHub,此步骤可忽略。

Fork仓库

登录飞桨GitHub首页,单击 Fork,生成自己目录下的仓库,如 https://github.com/USERNAME/Paddle

Clone远程仓库到本地

任意选择一个本地目录,将远程仓库clone到本地,命令如下:

➜  git clone https://github.com/USERNAME/Paddle

➜  cd Paddle

创建本地分支

飞桨使用Git流分支模型进行开发、测试、发布和维护,特性开发和问题修复都要求在一个新的分支上完成。代码如下:

在 Paddle-develop 分支上,使用 git checkout -b 创建并切换到新分支。

➜  git checkout -b my-cool-stuff

说明:

在 checkout 之前,需要保持当前分支目录 clean,否则会把 untracked 的文件也带到新分支上,可以通过 git status 查看。

安装代码格式化插件

飞桨使用 pre-commit 管理 Git 预提交钩子,格式化源代码(C++,Python),在commit前自动检查代码基础质量的满足度(如每个文件只有一个 EOL,Git 中不允许添加大文件等)。

pre-commit测试是 Travis-CI 中单元测试的一部分,不满足钩子的 PR 不允许提交到飞桨。请在当前目录运行如下代码:

➜  pip install pre-commit

➜  pre-commit install

说明:

  1. 飞桨使用 clang-format 参数调整 C/C++ 源代码格式,请确保 clang-format 版本在 3.8 以上。
  2. 通过pip install pre-commit和conda install -c conda-forge pre-commit安装的yapf稍有不同。建议使用pip install pre-commit(飞桨开发人员使用的命令)。

更新本地仓库

说明:

如果是首次创建本地GitHub环境,代码已经和原仓库代码同步,此步骤可忽略。

同步原仓库代码如下:

  1. 通过 git remote 查看当前远程仓库的名字。

➜  git remote

origin

➜  git remote -v

origin  https://github.com/USERNAME/Paddle (fetch)

origin  https://github.com/USERNAME/Paddle (push)

这里 origin 是 clone 的远程仓库的名字,也就是自己用户名下的 Paddle。

  1. 创建一个原始 Paddle 仓库的远程主机,命名为 upstream。

➜  git remote add upstream https://github.com/PaddlePaddle/Paddle

➜  git remote

origin

upstream

  1. 获取 upstream 的最新代码并更新当前分支。

➜  git fetch upstream

➜  git pull upstream develop

开始开发

在本例中,删除了 README.md 中的一行,并创建了一个新文件。

通过 git status 查看当前状态,会提示当前目录的一些变化,同时也可以通过 git diff 查看文件具体被修改的内容。

➜  git status

On branch test

Changes not staged for commit:

(use "git add <file>..." to update what will be committed)

(use "git checkout -- <file>..." to discard changes in working directory)

modified:   README.md

Untracked files:

(use "git add <file>..." to include in what will be committed)

test21   ···

no changes added to commit (use "git add" and/or "git commit -a")

代码提交

commit代码到本地仓库

先取消对 README.md 文件的改变,然后提交新添加的 test 文件。

➜  git checkout -- README.md

➜  git status

On branch test

Untracked files:

(use "git add <file>..." to include in what will be committed)

test

nothing added to commit but untracked files present (use "git add" to track)

➜  git add test

Git 每次提交代码,都需要写提交说明,这可以让其他人知道这次提交做了哪些改变,这可以通过git commit 完成。

➜  git commit

CRLF end-lines remover...............................(no files to check)Skipped

yapf.................................................(no files to check)Skipped

Check for added large files..............................................Passed

Check for merge conflicts................................................Passed

Check for broken symlinks................................................Passed

Detect Private Key...................................(no files to check)Skipped

Fix End of Files.....................................(no files to check)Skipped

clang-formater.......................................(no files to check)Skipped

[my-cool-stuff c703c041] add test file

1 file changed, 0 insertions(+), 0 deletions(-)

create mode 100644 233

需要注意的是:需要在commit中添加说明(commit message)以触发CI单测,写法如下:

# 触发develop分支的CI单测

➜  git commit -m "test=develop"

# 触发release/1.1分支的CI单侧

➜  git commit -m "test=release/1.1"

Push代码到远程仓库

将本地的修改推送到 GitHub 上,也就是 https://github.com/USERNAME/Paddle。

# 推送到远程仓库 origin 的 my-cool-stuff 分支上

➜  git push origin my-cool-stuff

完成Pull Request

此时,可以去 https://github.com/USERNAME/Paddle 下查看,会发现my-cool-stuff 分支,切换到所建分支,单击 New pull request,如下图所示。

AI框架外部用户贡献代码

选择目标分支,如下图所示。

AI框架外部用户贡献代码

说明:

可以在PR描述中标识PR的功能,接下来等待 review。如果有需要修改的地方,参照上述步骤更新origin中的对应分支即可。

签署CLA

首次向飞桨GitHhub提交Pull Request时,需要签署CLA(Contributor License Agreement)协议,以保证的代码可以正常合入,操作方式如下:

  • 查看PR中的 Check 部分,选择 license/cla ,单击detail,进入CLA网站,如下图所示。

AI框架外部用户贡献代码

点击CLA网站中的 Sign in with GitHub to agree ,完成后跳转到 Pull Request 页面,如下图所示。

AI框架外部用户贡献代码

CI测试

在Pull Request中每提交一次新的commit,都会触发CI单元测试,请确保commit message中已加入必要的修改说明。

注意:

Pull Request中的CI单元测试进程会持续几个小时,请及时关注。当所需的测试后都出现了绿色的对勾,表示本次的commit通过了CI单元测试。

后续处理

代码审查(Code Review)

提交PR后,开发人员会进行代码审查,如果提出修改意见,需要相应的进行确认或修改,再次提交代码。一旦code review通过,PR会被开发人员合入仓库。

删除远程分支

在 PR 被 merge 进主仓库后,可以在 PR 的页面删除远程仓库的分支。也可以使用 git push origin :分支名 删除远程分支,如:

➜  git push origin :my-cool-stuff

删除本地分支

最后,删除本地分支。

# 切换到 develop 分支

➜  git checkout develop

# 删除 my-cool-stuff 分支

➜  git branch -D my-cool-stuff

上一篇:五校联考模拟赛Day2T2矩阵(容斥原理)


下一篇:iOS内存管理(objective-c)