• 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} + {1} = {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. = {1} sequence_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 所示。

     

     图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,如下图所示。

     

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

     

    说明:

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

    签署CLA

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

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

     

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

     

     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

     

    人工智能芯片与自动驾驶
  • 相关阅读:
    贵有恒
    二叉树的中序遍历
    001.3或5的倍数
    静态成员的语法总结及应用-单例模式
    力扣42题(接雨水)
    算法笔记之二分查找
    素数筛算法之寻找每个数的最小素因子
    素数筛的算法感悟
    一维数组的逆序存放问题
    关于c++入门的几个基本代码之求和
  • 原文地址:https://www.cnblogs.com/wujianming-110117/p/14398519.html
Copyright © 2020-2023  润新知