Page 328 - 《软件学报》2020年第9期
P. 328
董晓 等:面向稀疏卷积神经网络的 GPU 性能优化方法 2949
我们沿卷积输出结果 Output 的 4 个维度进行任务划分.每个 GPU 线程负责计算 BN×BH×BW×BK 大小的输
出结果,并且相邻线程计算卷积输出中相邻位置的结果.其中,BN 表示数据批维度每个线程计算任务的大小,BK
表示卷积核维度每个线程计算任务大小,BH 和 BW 分别表示在高和宽维度每个线程计算任务大小.这样的任务
划分方式可以挖掘卷积计算过程中的数据重用机会.首先,计算 Output 上相邻位置的结果时,使用的 Input 中的
输入数据之间可能存在重叠.这带来了在不同线程之间重用输入数据的机会.我们利用共享内存实现在计算中
对输入数据的复用.一个线程块内的线程计算出需要使用的输入数据区域,并共同将这部分数据从全局内存中
读出,写入共享内存.在后续计算中,每个线程根据自己的线程编号,从共享内存中读取相应的数据.这样可以降
低计算过程中访问输入数据的延迟.其次,计算 Output 不同位置的结果时,使用的卷积参数是相同的,所以 Weight
也可以被不同的线程复用.由于卷积参数的取值在整个计算过程中是固定不变的,所以卷积参数可以存储在常
量内存中.同时,在计算时,不同的线程会同时访问相同的参数进行计算.因此,可以利用常量内存访问的合并机
制降低访存需求.算法 1 展示了我们设计的卷积模板代码,其中,blockIdx 和 threadIdx 分别表示每个线程所在的
线程块和线程在线程块内的位置信息,结合任务划分信息,每个线程可以计算出自己负责的计算区域.
算法 1. 卷积模板代码.
输入:Input,卷积输入数据;Weight,卷积参数;
输出:Output,卷积计算结果.
根据 blockIdx 和 threadIdx 计算每个线程计算任务的起始位置(N t ,K t ,H t ,W t )
将输入数据载入共享内存中的数组 sInput
for n←0 to BN−1 do
for h←0 to BH−1 do
for w←0 to BW−1 do
sum[0…BK−1]=0
for c←0 to C−1 do
for r←0 to R−1 do
for s←0 to S−1 do
I=sInput[f(n,c,h,w,r,s,threadIdx)] //从共享内存中读取相应的输入
for k←0 to BK−1 do
sum[k]+=Weight k,c,r,s *I //卷积参数与输入数据相乘,结果进行累加
end for
end for
end for
end for
Output[n+N t ][0+K t ][h+H t ][w+W t ]=sum[0] //将计算结果写回全局内存
Output[n+N t ][1+K t ][h+H t ][w+W t ]=sum[1]
…
Output[n+N t ][BK−1+K t ][h+H t ][w+W t ]=sum[BK−1]
end for
end for
end for
2.2 中间表示模板
在算法 1 中,每一个卷积参数 W k,c,r,s 与相应的输入数据之间进行乘法,乘法的结果累加起来,最终得到卷积
计算的结果.在稀疏的情况下,这个过程中存在大量的冗余操作.具体来说,当卷积参数具有稀疏性时,会存在
W k,c,r,s =0 的情况.此时,这个参数参与的乘法运算便是冗余的,因为删除这些乘法指令并不会影响最终的结果.另