×
分享到微信

打开微信,使用扫一扫进入页面后,点击右上角菜单,

点击“发送给朋友”或“分享到朋友圈”完成分享

智能计算系统实验 综合实验-文本识别OCR-EAST 经验分享 奡比岛2022-06-29 09:39:06 回复 查看 经验交流 高校课程 其他
智能计算系统实验 综合实验-文本识别OCR-EAST 经验分享
分享到:

3.1 Split+Sub+Concat合并算子的BCL实现

3.1.1 需求分析

要实现这个算子,首先要知道这个算子具体要做什么。首先尝试读论文《EAST: An Efficient and Accurate Scene Text Detector》和《PVANET: Deep but Lightweight Neural Networks for Real-time Detection》,从论文中大致读懂了算法的框架,如下图,但是并没有什么用:

在这里插入图片描述
图3-1 算法框架

之后从eval Python脚本中查找相关信息,其中某处引用了model.py中的model(images, weight_decay, is_training)函数,然后逐渐发现mean_image_subtraction(images, means)就是我要找的函数:

代码3-1 Split-Sub-Concat算子的Python实现

def mean_image_subtraction(images, means=[123.68, 116.78, 103.94]):
    '''
    image normalization
    :param images:
    :param means:
    :return:
    '''
    num_channels = images.get_shape().as_list()[-1]  # 获取最后一个维度
    if len(means) != num_channels:
      raise ValueError('len(means) must match the number of channels')
    # 在channel维度上拆分
    channels = tf.split(axis=3, num_or_size_splits=num_channels, value=images)   
    for i in range(num_channels):
        channels[i] -= means[i]  # 每个通道减不同的数字
    return tf.concat(axis=3, values=channels)123456789101112131415

从上述代码中可以看出,算子首先需要在通道上拆分,然后3个通道分别减去means的3个值,最后再拼接。

然后要计算means值:对每一张图片求平均,还是一个常数?从Python代码中发现是常数,所以这个means值应该是一个图片数据集所有图片的平均,不能自己计算。

另外还需要知道图像的batch、宽、高和通道分别在哪个维度上。从channels = tf.split(axis=3,...)中可以看出通道在第3个维度,另外按照习惯,第0个维度是batch_size,第1个维度是宽度,第2个维度是高度。从main.cpp中得知,图像的宽度是1280,高度是672。

3.1.2 算子设计

由于图像数据的排列方式,means的3个数字是交错的。而MLU处理连续的数据比较迅速,所以简单的对每个位置求余数,根据余数减去相应的值,耗时一定会比较长。

在这里插入图片描述
图3-2 图像数据和平均值的位置关系

所以需要在nram上构造一些数据块,以便进行整体的减法。这样的话需要(1)决定每个块的大小,(2)如何对块赋值。

对于问题(1),根据MLU的要求,块的大小需要是16的倍数;其次,为了让每次迭代,图像都减去相同的数据块,块大小需要是3的倍数;最后,在taskdim为16的情况下,nram块的大小不能超过512。因此我决定设置块大小为384=128×3

对于问题(2),最简单的思路是用for循环,如下所示:

代码3-2 用for循环对减数赋值

    __nram__ minuse[384];
    for(int i = 0; i < 384; ++i) {
        switch(i % 3) {
            case 0: minuse[i] = VALUE1; break;
            case 1: minuse[i] = VALUE2; break;
            case 2: minuse[i] = VALUE3; break;
        }
    }12345678

但是经过测试,这样的延迟比较高。所以我考虑不用计算的方式,而是直接赋值。所以我用下面的代码来生成代码,粘贴到mlu文件中。

代码3-3 用于生成代码的Python脚本

values = ["(half)123.68", "(half)116.78", "(half)103.94"]for i in range(384):
    print("minused_nram[%d] = %s;" % (i, values[i%3]))123

代码3-4 生成的部分代码

    minused_nram[0] = (half)123.68;
    minused_nram[1] = (half)116.78;
    minused_nram[2] = (half)103.94;
    minused_nram[3] = (half)123.68;
    minused_nram[4] = (half)116.78;
    minused_nram[5] = (half)103.94;
    ......1234567

接下来要对这种方法的速度进行测试,首先在使用这种方法的情况下,查看运行时间,然后把这些赋值的代码全部注释掉,再查看运行时间。经过比较得到结论:这种方法的速度是比较快的。

数据块构造完成之后如何做减法。BANGC提供了__bang_cycle_sub,其功能是,假设有长度m × n m×nm×n的被减数数组和长度为n nn的减数数组,则将被减数数组拆分成m mm份,分别减去减数数组。同时为了减少拷贝次数,我将块大小乘了64倍,但是减数minused_nram保持不变。运行结果见图3-2。

代码3-5 用__bang_cycle_sub做减法

#define ONELINE 384#define MUCH_DOUBLE 64
    int quotient = batch_num_ * 1280 * 672 * 3 / ONELINE;
    __nram__ half input_nram[ONELINE * MUCH_DOUBLE];
    quotient = quotient / MUCH_DOUBLE;
    for (int32_t i = taskId; i < quotient; i += taskDim) {
        offset = i * ONELINE * MUCH_DOUBLE;
        // 查看是否遍历了所有数据
        // __bang_printf("loop1 %d\t%d\t%d\n", taskId, i, offset);  
        __memcpy(input_nram, input_data_ + offset, 
                MUCH_DOUBLE * ONELINE * sizeof(half), GDRAM2NRAM);
        __bang_cycle_sub(input_nram, input_nram, minused_nram, 
                ONELINE * MUCH_DOUBLE, ONELINE);
        __memcpy(output_data_ + offset, input_nram, 
                MUCH_DOUBLE * ONELINE * sizeof(half), NRAM2GDRAM);}12345678910111213141516

在这里插入图片描述
图3-2 算子测试

3.2 算子集成

算子集成分为两步,用BCL将上述算子集成到CNPlugin中,和通过CNPlugin接口集成到TensorFlow框架中。

3.2.1 集成到CNPlugin框架

补全plugin_sbc_op.cc文件:

对于cnmlCreatePluginSBCOp函数,首先获得Kernel的ParamsBuffer,然后根据算子的函数声明,将第一个参数标记为Input,将第二个参数标记为Output,然后传入第三个参数batch_num_。然后用cnmlCreatePluginOp创建算子。

代码3-6 cnmlCreatePluginSBCOp函数

    // 补全cnmlCreatePluginSBCOp
    cnrtKernelParamsBuffer_t params;
    cnrtGetKernelParamsBuffer(&params);
    cnrtKernelParamsBufferMarkInput(params);
    cnrtKernelParamsBufferMarkOutput(params);
    cnrtKernelParamsBufferAddParam(params, &batch_num_, sizeof(int));
    // 由于main.cpp中只添加了3个参数,所以core_num_应该是不用添加的
    cnmlCreatePluginOp(
        op, "SBC",
        reinterpret_cast<void **>(&SBCKernel), params,
        SBC_input_tensors, 1,
        SBC_output_tensors, 1,
        nullptr, 0
    );

    cnrtDestroyKernelParamsBuffer(params);12345678910111213141516

对于cnmlComputePluginSBCOpForward函数,调用cnmlComputePluginOpForward_V4即可。

代码3-7 cnmlComputePluginSBCOpForward函数

    // 补全cnmlComputePluginSBCOpForward 
    cnmlComputePluginOpForward_V4(
        op,
        nullptr, inputs, input_num,
        nullptr, outputs, output_num,
        queue, nullptr
    );1234567

补全cnplugin.h文件:

定义cnmlPluginSBCOpParam_t结构体,如下

代码3-8 cnmlPluginSBCOpParam结构体定义

struct cnmlPluginSBCOpParam {
  int batch_num_;
  cnmlCoreVersion_t core_version;};typedef cnmlPluginSBCOpParam *cnmlPluginSBCOpParam_t;12345

添加函数声明,如下

代码3-9 cnml函数声明

cnmlStatus_t cnmlCreatPluginSBCOpParam(
    cnmlPluginSBCOpParam_t *param,
    int batch_num_);cnmlStatus_t cnmlDestroyPluginSBCOpParam(
    cnmlPluginSBCOpParam_t *param);cnmlStatus_t cnmlCreatePluginSBCOp(
    cnml Op_t *op,
    //cnmlPluginSBCOpParam_t param,
    cnmlTensor_t *SBC_input_tensors,
    cnmlTensor_t *SBC_output_tensors,
    int batch_num_);cnmlStatus_t cnmlComputePluginSBCOpForward(
    cnml Op_t op,
    void **inputs,
    int input_num, // == 1
    void **outputs,
    int output_num, // == 1
    cnrtQueue_t queue);123456789101112131415161718

然后将算子的文件拷贝到相关文件夹中,进行算子编译。在编译中出现了错误:tensorflow/stream_executor/mlu/mlu_api/lib_ops/mlu_lib_ops.cc:1926:96: error: 'cnmlCreatePluginPowerDifferenceOp' was not declared in this scope

过思考后发现,这个错误应该是mlu_lib_ops.cc调用了PowerDifference算子,而cnplugin.h中没有PowerDifference算子的信息。所以我将实验一中PowerDifference算子的声明也添加到了cnplu

版权所有 © 2022 寒武纪 Cambricon.com 备案/许可证号:京ICP备17003415号-1