当前位置: 代码迷 >> 综合 >> 【ESWIN实习】番外篇一、Convolution 2D kernel 官方文档编译流程(整理版)
  详细解决方案

【ESWIN实习】番外篇一、Convolution 2D kernel 官方文档编译流程(整理版)

热度:68   发布时间:2023-12-13 01:37:47.0

文章目录

  • 1 概述
    • 1.1 符号说明
    • 1.2 代码结构
  • 2 接口说明
    • 2.1 uapcc 模板接口说明
      • 2.1.1 segmentation conv2d
      • 2.1.2 non-segmentation conv2d
      • 2.1.3 grouped conv2d
    • 2.2 kernel 实例化
      • 2.2.1 例化单个 kernel
    • 2.3 批量实例化
    • 2.4 编译与调用
    • 2.5 测试
  • 3 kernel 实现与优化


1 概述

1.1 符号说明

本文中所述的 computing kernel 实现均认为 input/weight/output 是按照 NHWC 格式存放,且计算模式均为 valid。

N:数量 Number、H:高度 Height、W:宽度 Width、C:??

现记欲作卷积的 input/weight/output 的 shape 分别为:

  • input shape = [in_N, in_H, in_W, in_C]
  • weight shape = [w_N, w_H,w_W, in_C]
  • outout shape = [in_N, out_H, out_W, w_N] //这里为啥是w_N ?
  • stride = [sx, sy]

stride 为步长。

实际上, out_H/out_W 可由其他 shape 参数直接计算出来,所以最终computing kernel 的 输 入 shape 参 数 只 有 in_N/in_H/in_W/in_C/w_N/w_H/w_W/sx/sy 这 9 个。 以下是 out_W/out_H 的计算公式:

  • out_W=Ceil((in_W-w_W+1)/sx)
  • out_H=Ceil((in_H-w_H+1)/sy)

那么, Convolution 2D 公式为:

在这里插入图片描述

1.2 代码结构

代码的依赖关系如下图:

在这里插入图片描述

其中, conv2d-core.h 提供公共的(优化的)计算函数。基于 conv2d-core.h,现已实现 conv2d-seg.h/conv2d-noseg.h/conv2d-group.h,提供 3 个函数模板,分别是(名字空间都属于::pint_dnn::conv2d_detail ):

函数模板都是在conv2d-*.h文件中定义的!

template< typename Real, int SET_OUT_DEPTH, int PE_DIM, FusionType fType>
void pint_conv2d();
template< typename Real, int SET_OUT_DEPTH, int PE_DIM, FusionType fType>
void pint_conv2d_seg();
template< typename _InputIter, typename _OutputIter, typename _Tp, int TILE_Z, int PE_DIM, FusionType fType>
void pint_conv2d_group();

有了函数模板,之后变量类型也可扩充至 int8 等。 conv2d.h 是对外提供的接口头文件,可供 opencl cuapcc c/c++调用(如2.2节中的图2.4)。 conv2d-*.cc 文件是为了实例化上述几个函数模板。

2 接口说明

2.1 uapcc 模板接口说明

2.1.1 segmentation conv2d

位置:PINT_SDK/core/kernels/dnn/conv2d/conv2d-seg.h
函数模板名称:pint_conv2d_seg
主要代码如下:

 pure convolution kernel.
template<typename _InputIt,typename _OutputIt,typename Real,int SEG_OUT_DEPTH,int PE_DIM,FusionType FusedType>
void pint_conv2d_seg(const _Distance n_input,const _Distance n_weight,const _Distance stride_x,const _Distance stride_y,_InputIt input_batch__,const _Distance offset_input,const _Distance ld_input_batch,const _Distance ld_input,const _Distance input_height,const _Distance input_width,_InputIt weight_batch__,const _Distance offset_weight,const _Distance ld_weight_batch,const _Distance ld_weight,const _Distance weight_height,const _Distance weight_width,_OutputIt output_batch__,const _Distance offset_output,const _Distance ld_output_batch,const _Distance ld_output,/* kernel configs. PE is 3D.*/const _Distance GRID_DIM_X/* =2 */,const _Distance GRID_DIM_Y/* =1 */,const _Distance seg_weight_dim_x,const _Distance seg_weight_dim_y,const _Distance TILE_OUT_X,const _Distance BLOCK_OUT_Y,/* fusion */const Real act_min,const Real act_max,_InputIt p_mean,const _Distance offset_p_mean,_InputIt p_var,const _Distance offset_p_var,_InputIt p_gama,const _Distance offset_p_gama,_InputIt p_beta,const _Distance offset_p_beta,_InputIt p_bias,const _Distance offset_p_bias,const Real epsilon)
{
    
... 
函数主体
...
}

函数参数说明:

1、compile time arguments(编译时参数)

参数名称 参数含义
_InputIt
_OutputIt
Real 数据类型(float32 or int8)
SEG_OUT_DEPTH 每个 core 每次加载 SEG_OUT_DEPTH 个 weight 到 Dcache
PE_DIM systolic array 长度
FusedType Fusion 类型

2、runtime arguments(运行时参数)

参数名称 与相关参数关系 相关参数 参数含义
n_input = in_N
n_weight = w_N
stride_x = sx*in_C
stride_y = sy
input_batch__ input pointer
offset_input input offset (in word)
ld_input_batch in_H*ld_input
ld_input in_W*in_C set any number that >= in_W*in_C isok. And how to choose pitches whichcan acquiring highly performance willbe discussed below.
input_height = in_H
input_width = in_W*in_C
weight_batch__ weight pointer
offset_weight weight offset (in word)
ld_weight_batch w_H*ld_weight
ld_weight w_W*in_C set any number that >= in_W*in_C isok. And how to choose pitches whichcan acquiring highly performance willbe discussed below.
weight_height = w_H
weight_width = w_W*in_C
output_batch__ output_batch__
offset_output output offset (in word)
ld_output_batch out_H*ld_output
ld_output w_N*out_W
seg_weight_dim_x weight 分段后,每段 weight 在 X 方向(W*C 方向)的长度和 Y方向(H 方向)的长度。weight 的 W 和 C 是看成一个维度,即 weight 是 2 维的
seg_weight_dim_y weight 分段后,每段 weight 在 X 方向(W*C 方向)的长度和 Y方向(H 方向)的长度。weight 的 W 和 C 是看成一个维度,即 weight 是 2 维的
GRID_DIM_X 将所有 PE 组织成一个三维空间(一个 PE 表示此空间的一个点)。GRID_DIM_X 是 X 方向的长度。GRID_DIM_Y 是 Y 方向的长度。
GRID_DIM_Y 将所有 PE 组织成一个三维空间(一个 PE 表示此空间的一个点)。GRID_DIM_X 是 X 方向的长度。GRID_DIM_Y 是 Y 方向的长度。
OUTPUT_TILE_X 每个 core 加载 OUTPUT_TILE_X 个 input 的 Tile 到 Bcache(沿着 input 的 X 方向,也即 input 的 WC 方向)。Tile 的维度(矩形)是 seg_weight_dim_xseg_weight_dim_y。
OUTPUT_BLOCK_Y 每个 core 加载 OUTPUT_BLOCK_Y 个 input 的 Tile 到 Bcahce(沿着 input 的 Y 方向,也即 H 方向)。

2.1.2 non-segmentation conv2d

主要代码如下:

 pure convolution kernel.
template<typename _InputIt,typename _OutputIt,typename Real, // _Valint SEG_OUT_DEPTH,int PE_DIM,FusionType fType>
void pint_conv2d(const _Distance n_input,const _Distance n_weight,const _Distance stride_x,const _Distance stride_y,_InputIt input_batch__,const _Distance offset_input,const _Distance ld_input_batch,const _Distance ld_input,const _Distance input_height,const _Distance input_width,_InputIt weight_batch__,const _Distance offset_weight,const _Distance ld_weight_batch,const _Distance ld_weight,const _Distance weight_height,const _Distance weight_width,_OutputIt output_batch__,const _Distance offset_output,const _Distance ld_output_batch,const _Distance ld_output,/* kernel configs. PE is 3D.*/const _Distance GRID_DIM_X/* =2 */,const _Distance GRID_DIM_Y/* =1 */,const _Distance seg_weight_dim_x,const _Distance seg_weight_dim_y,const _Distance OUTPUT_TILE_X/* =32 */,const _Distance OUTPUT_BLOCK_Y/* =32 */,/* fusion */const Real act_min,const Real act_max,_InputIt p_mean,const _Distance offset_p_mean,_InputIt p_var,const _Distance offset_p_var,_InputIt p_gama,const _Distance offset_p_gama,_InputIt p_beta,const _Distance offset_p_beta,_InputIt p_bias,const _Distance offset_p_bias,const Real epsilon)
{
    
... 
函数主体
...
}

参数描述同上。

2.1.3 grouped conv2d

主要代码如下:

 pure grouped convolution kernel. 
 data layout:[NGHWC]
template<typename _InputIter,typename _OutputIter,typename _Tp,int TILE_Z,int PE_DIM,FusionType fType>
void pint_conv2d_group(const _Distance n_batch,const _Distance n_group,const _Distance n_weight_of_each_group,// weight num of each group. const _Distance stride_x,const _Distance stride_y,_InputIter input_batch,const _Distance offset_input,const _Distance ld_input_group,const _Distance ld_input_batch,const _Distance ld_input,const _Distance input_height,const _Distance input_width,_InputIter weight_batch,const _Distance offset_weight,const _Distance ld_weight_group,const _Distance ld_weight_batch,const _Distance ld_weight,const _Distance weight_height,const _Distance weight_width,_OutputIter output_batch,const _Distance offset_output,const _Distance ld_output_group,const _Distance ld_output_batch,const _Distance ld_output,/* kernel configs. PE is 5D.*/const _Distance GRID_DIM_X,const _Distance GRID_DIM_Y,const _Distance GRID_DIM_Z,const _Distance GRID_DIM_BATCH,const _Distance TILE_X,const _Distance BLOCK_Y,const _Distance /*BLOCK_BATCH*/,const _Distance /*BLOCK_GROUP*/,/* fusion */const _Tp act_min,const _Tp act_max,_InputIter p_mean,const _Distance offset_p_mean,_InputIter p_var,const _Distance offset_p_var,_InputIter p_gama,const _Distance offset_p_gama,_InputIter p_beta,const _Distance offset_p_beta,_InputIter p_bias,const _Distance offset_p_bias,const _Tp epsilon)
{
    
... 
函数主体
...
}

参数描述同上。

2.2 kernel 实例化

解释kernel实例化之前,我们先来了解一下计算调用层级,如下图 2.4 所示。

在这里插入图片描述

为了更好的复用代码,我们直接使用原生的uapcc c/c++ 进行编码(位于图 2.4 第 4 层, 主要是支持模板编程)。 uapcc c/c++级别提供的接口如 2.1.2 所示。
opencl API 层不能直接调用第 4 层的接口,需要将 uapcc c/c++接口重新包装为 opencl C99 language kernel(第 3 层)。这个包装的过程我们称之为kernel的实例化

2.2.1 例化单个 kernel

这里提供一个实例化单个 kernel 的例子来说明此流程。假设现在需要从 ::pint_dnn::conv2d_detail::pint_conv2d<…>(…)中 ( 以下 简 称pint_conv2d<…>) 包装出对应的 opencl C99 kernel

  • 第 1 步,实例化模板以及将其包装成具有 C 链接属性uapcc Clinkage function。 具体代码形式(位于 conv2d-noseg.cc):

在这里插入图片描述
其中, Clinkage function 的命名规则为:

Clinkage_pint_conv2d_{
    Real}_{
    SET_OUT_DEPTH}_{
    PE_DIM}_{
    fType}

这里假设 pint_conv2d<…>要实例化的模板参数(参见 2.1.2)为:

Real=f32,SET_OUT_DEPTH=1,PE_DIM=8,fType=fusionRelu

ArgList 详见 pint_conv2d<…>参数列表

上述代码在最新的dev-weekly中有所更改了!

  • 第 2 步在 uapcc c/c++的接口头文件(conv2d.h) 中声明第 1 步的 C 链接属性函数。 具体代码形式(位于 conv2d.h):

在这里插入图片描述

上述代码在最新的dev-weekly中有所更改了!

  • 第3步, 将uapcc接口头文件中的Clinkage function包装成真正的opencl kernel function。具体代码形式为(位于 conv2d.cl 中):

在这里插入图片描述

上述代码在最新的dev-weekly中有所更改了!

2.3 批量实例化

由于性能的需求, computing application 在运行中可能会根据不同的input/weight shape 来选择最优 configuration 对应的 opencl kernel

这里的configuration 指两部分:

  • 一是编译期的 uapcc 接口模板参数,例如???
  • 二是运行时参数,例如 weight 的分段大小、PE(systolic array) 划分等。kernel 实例化主要受第 1 部分影响。

对于 convolution 2D,我们会在 library 中提供多个 kernel 供选择(见后续 selector 章节),那这需要在编译期产生所有的 conv2d kernel。上一节说明了单个 kernel如何实例化,本节将说明如何批量实例化 kernel

  • 1、首先,需要将 2.2 节中的单次实例化的过程抽象成 C language macro(C语言的宏定义)。 以pint_conv2d<…>为例, 关键 macro 定义如下:

在这里插入图片描述

在这里插入图片描述

  • 2、除了将单次实例化做成 Marco,还需要批量在代码里自动展开这些 Marco。

在这里插入图片描述
做批量展开的 marco 可以控制展开的级数。上表所示的 marco 展开之后可以实例化 128 组 opencl kernel 定义 和 Clinkage function 声明

接下来要将这些宏加入到代码中让其自动展开。有 3 个地方需要使用到展开(可参考 2.2 节中的 3 个步骤)。

  • ①、conv2d-noseg.cc,包装 Clinkage function,代码如下:

在这里插入图片描述

  • ②、conv2d.h,批量声明 uapcc Clinkage function,代码如下:

在这里插入图片描述

  • ③、conv2d.cl,批量包装 uapcc Clinkage functionopencl kernel,代码如下:

在这里插入图片描述

2.4 编译与调用

先使用 riscv32-unknown-elf-g++ 将 1.2 节中提到的.cc 文件编译为.o, 然后再将这些.o 文件 ar 为 libpintdnn.a。 至此, uapcc library 层(图2.4的第四层)的编译就完成了。 具体 CMakeLists.txt 如下:

”文件 ar 为“???

cmake_minimum_required(VERSION 3.12)set(RISCV $ENV{
    PINT}/riscv)
option(RELEASE_MODE "release mode" OFF)
if (RELEASE_MODE)if (NOT DEFINED ENV{
    CI_TOP})message(FATAL_ERROR "env CI_TOP is not defined.")endif()set(UAPTOOL $ENV{
    CI_TOP}/release)
else()set(UAPTOOL $ENV{
    PINT})
endif()set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
set(CMAKE_CXX_COMPILER ${RISCV}/bin/riscv32-unknown-elf-g++)#set(cflags 
#"-Werror -nostartfiles -nostdlib -fsigned-char -fsigned-bitfields -march=rv32imf -mabi=ilp32f -O2 -fno-builtin -fconcepts")
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${cflags}")
set(cxxflags  "-Werror \-nostartfiles \-nostdlib \-fsigned-char \-fsigned-bitfields \-std=c++1z \-march=rv32imf \-mabi=ilp32f \-O2 \-fno-builtin \-fconcepts"#-funroll-loops")set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${cxxflags}")PROJECT(pintdnn)
option(LARGE_MEMORY "build large memory" OFF)##
set(SRC_DIRS${CMAKE_HOME_DIRECTORY}/conv2d${CMAKE_HOME_DIRECTORY}/image_ops${CMAKE_HOME_DIRECTORY}/rnn${CMAKE_HOME_DIRECTORY}/rnn_int8${CMAKE_HOME_DIRECTORY}/transformer${CMAKE_HOME_DIRECTORY}/tensor_ops)set(INCLUDE_DIR${UAPTOOL}/lib ${UAPTOOL}/include${CMAKE_HOME_DIRECTORY}/../common${CMAKE_HOME_DIRECTORY}/../blas${CMAKE_HOME_DIRECTORY}/../blas/level3${CMAKE_HOME_DIRECTORY}/../std${CMAKE_HOME_DIRECTORY}/image_ops)set(FILE_NAME# conv2d-i8*# conv2d-seg-i8*# crop_and_resize*# resize_images*# add-i8*# crop_pool*# cwise2*# cwise-f32*# split*)## find all unit.
foreach(d ${SRC_DIRS})#message("\n\n\n${d}\n\n\n")if ("${FILE_NAME}" STREQUAL "")aux_source_directory(${d} filex) # Note: filex will auto appended.else()foreach(f ${FILE_NAME})file(GLOB filex_ ${d}/${f})list(APPEND filex ${filex_})endforeach(f)endif()#message("\n\n\n${filex}\n\n\n")
endforeach(d)include_directories(${INCLUDE_DIR})if (LARGE_MEMORY)
## compile large memory
add_definitions(" -DLMEM")
add_library(${PROJECT_NAME}_lmem STATIC ${filex})
else()
## compile small memory
add_library(${PROJECT_NAME} STATIC ${filex})
endif()

文件内容与文档不一样,可能更新了!

对应的build.sh内容如下:

#!/bin/bashRELEASE_MODE=OFF
for i in $@; do {
    if [ "${i}"x == "release"x ]; then {
    RELEASE_MODE=ON } fi
} doneif [ ! -d './build/' ];then
mkdir ./build
else
echo 'build dir existed.'
ficd ./build#if [ ! -d './OutputReport' ];then
#mkdir ./OutputReport
#ficmake -DLARGE_MEMORY=OFF -DRELEASE_MODE=${RELEASE_MODE} .. 1>/dev/null 2>&1
make -j 16cmake -DLARGE_MEMORY=ON -DRELEASE_MODE=${RELEASE_MODE} .. 1>/dev/null 2>&1
#make clean
make -j 16cd -

之后在 opencl C API 层调用 clBuildProgram(…) 编译 conv2d.cl 文件, 并将libpintdnn.aconv2d.h 等作为编译选项传入, 再构造出指定的 kernel name, 根据 kernel name 创 建 kernel 对 象 。 具 体 代 码 请 参 见 :pint_sdk/test/kernels/dnn/conv2d/conv2d_seg_pure_fusion.h:309 行-456 行

在这里插入图片描述
其中, 第 309 行即为构造特定的 kernel nameProgram 是用 C++封装的类, 定义位于: pint_sdk/test/kernels/utility/clpp11.h。 函数 RunKernel 是对 opencl 执行kernel 过程的封装, 定义位于: pint_sdk/test/kernels/utility/__aux_device.hpp

2.5 测试

本测试主要侧重 conv2d 的各种 shape 边界, 所以测试程序必须能支持对某一连续区间的参数进行穷举遍历。 目前测试程序是 C/C++程序, 之后会再重构为以 python 为主。

程序位置: pint_sdk/test/kernels/dnn/conv2d/test_all_conv2d.cc

  • 1、设置参数遍历区间

在这里插入图片描述
每个参数 range 以一个三维数组表示(例如代码第 59 行), 数组的值从左到
右分别是: begin/step/end。 例如{2,2,8} => 数组{2,4,6}。

  • 2、设置要测试的卷积类型

在这里插入图片描述
代码第 189 行的 kernel_routine_range 是一个函数指针的 vector, 里面包含各类卷积或各种 fusion 类型的卷积。

  • 3、编译脚本位于: pint_sdk/test/kernels/dnn/build.sh

3 kernel 实现与优化

待更新…

  相关解决方案