文章目录
- 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 c
或 uapcc 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 function
成opencl 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.a
、conv2d.h
等作为编译选项传入, 再构造出指定的 kernel name
, 根据 kernel name
创 建 kernel
对 象 。 具 体 代 码 请 参 见 :pint_sdk/test/kernels/dnn/conv2d/conv2d_seg_pure_fusion.h:309 行-456 行
。
其中, 第 309 行即为构造特定的 kernel name
。 Program
是用 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 实现与优化
待更新…