ECE408 Lecture 7 Convolution and Constant Memory
这次回顾ECE408 Lecture 7,这次介绍了卷积和Constant Memory。
课程主页:
搬运视频:
本讲目标
- 学习卷积,一种重要的并行计算模式;
- 广泛应用于信号、图像和视频处理;
- 许多科学和工程应用中的计算基础;
- 卷积神经网络(CNN)的关键组成部分;
- 重要的GPU技术;
- 利用高速缓存;
卷积计算公式
连续和离散情形:
卷积应用
- 一种在信号处理、数字记录、图像处理、视频处理、计算机视觉和机器学习中以各种形式使用的流行操作;
- 卷积通常作为滤波器执行,以某种上下文感知方式转换输入信号(音频、视频等);
- 某些滤波器平滑信号值,以便可以看到大趋势;
- 或高斯滤波器模糊图像、背景;
卷积运算
- 数组运算,其中每个输出数据元素都是相邻输入元素集合的加权和;
- 加权和计算中使用的权重由输入mask数组定义,通常称为卷积核;
1D卷积
基本内容
- 常用于音频处理
MASK_WIDTH
通常是奇数(为了对称性,本例中为5);MASK_RADIUS
是计算输出的每一侧的卷积中使用的元素数(本例中为 2);
计算P[2]
:
计算P[3]
:
边界值
- 计算输入数组边界附近的输出元素需要处理“幽灵”元素;
- 不同的策略(0,边界值的复制等);
示例:
代码
- 该内核将有效范围外的所有元素强制为
0
; - 每个线程计算
P
的一个元素;
代码:
__global__
void convolution_1D_kernel(float *N, float *M, float *P, int Mask_Width, int Width)
{
int i = blockIdx.x*blockDim.x + threadIdx.x;
float Pvalue = 0;
int N_start_point = i - (Mask_Width/2);
for (int j = 0; j < Mask_Width; j++) {
if (((N_start_point + j) >= 0) && ((N_start_point + j) < Width)) {
Pvalue += N[N_start_point + j]*M[j];
}
}
P[i] = Pvalue;
}
2D卷积
示例
这里通过几张图给出2D卷积的计算方式,边界值处理,以及幽灵单元:
M的访问模式
M
的元素称为mask(kernel,filter)系数;- 计算P的所有元素需要
M
; M
在grid执行期间不改变;
- 计算P的所有元素需要
- 在计算所有
P
元素时,以相同顺序访问M
元素; - 因此
M
很适合使用Constant Memory;
缓存
CUDA内存的程序员视图
- 每个线程可以:
- 读/写每线程寄存器(~1 个周期);
- 读/写每块shared memory (~5 个周期);
- 读/写每个网格的global memory(~500 个周期);
- 读每个网格的constant memory(在使用缓存的情况下~5 个周期);
内存层次结构
- 回顾:如果所有数据都在全局内存中,GPU的执行速度将受到全局内存带宽的限制;
- 我们在tiled矩阵乘法中使用共享内存来减少此限制;
- 另一个重要的解决方案:缓存和constant memory;
缓存存储内存行
回顾:内存针对突发进行了优化;
- 突变时会读取多个比特,例如1024位(128B);
- 连续的(线性)地址;
- 让我们称一次突发为行(line);
什么是缓存?
- 一组缓存行(和标签);
- 内存读取产生一行;
- 缓存存储该行的副本;
- 标签记录行的内存地址;
内存访问显示局部性
一个正在执行的程序:
- 从内存加载和存储数据;
- 考虑访问地址的顺序;
序列通常显示两种类型的局部性:
- 空间:访问X意味着很快将访问X+1(和X+2,等等);
- 时间:访问X意味着很快再次访问X;
缓存提高了两种类型的性能。
shared memory vs 缓存
- shared memory和缓存:
- 都在芯片上,具有相似的性能;
- 从Nvidia Volta一代开始,两者都使用相同的物理资源,动态分配!
- 有什么不同?
- 程序员控制shared memory内容(显式);
- 硬件决定高速缓存的内容(隐式);
GPU同时具有常量缓存和L1缓存
为了支持写入(修改行):
- 必须将更改复制回内存;
- 缓存必须跟踪修改状态;
- GPU中的L1缓存(用于全局内存访问)支持写入;
缓存常量/纹理内存:
- 特殊情况:行是只读的;
- 为常见的GPU内核访问模式启用比L1更高的吞吐量访问;
如何使用constant memory
host代码与以前的版本相似;
但是为
M
(掩码)分配设备内存:- 在所有函数之外;
- 使用
__constant__
(告诉GPU缓存是安全的);
要复制到设备内存,请使用
cudaMemcpyToSymbol(dest, src, size, offset = 0, kind = cudaMemcpyHostToDevice)
目标地址为
__constant__
;
代码示例
// global variable, outside any kernel/function
__constant__ float Mc[MASK_WIDTH][MASK_WIDTH];
// Initialize Mask
float Mask[MASK_WIDTH][MASK_WIDTH]
for(unsigned int i = 0; i < MASK_WIDTH * MASK_WIDTH; i++) {
Mask[i] = (rand() / (float)RAND_MAX);
if(rand() % 2) Mask[i] = - Mask[i]
}
cudaMemcpyToSymbol(Mc, Mask, MASK_WIDTH*MASK_WIDTH*sizeof(float));
ConvolutionKernel<<<dimGrid, dimBlock>>>(Nd, Pd);
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemonzzz!
评论
ValineLivere