CUDA中如何选择 Block的尺寸
本文的硬件环境为 GTX650,首先来简单介绍一下 GTX650 显卡与 CUDA
编程相关的简单情况[1]:
l 是 Kepler架构的最低等级产品;
l 由一组 GPC (Graphics Processing Clusters)组成;
l 每个 GPC内含两个 SMX (Streaming Multiprocessors);
l 每个 SMX包括 192个 stream processor和 16个 texture unit;
l 因此 GTX650一共包括 384个 stream processor和 32个 texture unit。
下面是 GTX650的图示:
图 1 GTX650
顺便提一下,Kepler架构显卡的 GPC都是相同的。
GTX660有三组 GPC共 5个 SMX,因此拥有 960个(192×5)stream processor
和 80个(16×5)texture unit。
下面是 GTX660的图示[2]:
图 2 GTX660
GTX660Ti 有四组 GPC 共 7 个 SMX,因此拥有 1344 个(192×7)stream
processor和 112个(16×7)texture unit。
下面是 GTX660Ti的图示[3]:
图 3 GTX660Ti
SUME
COLL
ECTIO
N
让我们回到 GTX650显卡,使用 GPU-Z软件获得的显卡参数如图:
图 4 GPU-Z软件截图
GK107 代
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
GTX650 的核心代号,采用 28nm 低功耗技术,ROP (Render
Output Pipelines)个数为 16,TMU (Texture Mapping Unit)个数为 32,关于 ROP
和 TMU的作用请参考维基百科[4]。
在网上看到有人说:“CPU的核心要厉害很多,GPU的核心要慢很多。CPU
的 core 好比特种兵,GPU 的 core 好比一般的挖矿的农民”。我觉得非常经典,
由于 CPU 里很多的芯片面积是用来做缓存、分支预测、指令控制、流水线的,
所以 CPU的功能非常强大。BUT,GPU里每个 core的功能虽然很简单,但是数
量众多,完全以数量取胜了。图中的 Shaders 就是指显卡的处理器个数,我的
GTX650的处理器数量为 384个,比 i5的四核多了 96倍,上面提到 GTX660Ti
的处理器数量更高达 1344个,相信以后 GPU和 CPU的 core数量差距还会越来
越大。
图中的其他信息对于 CUDA编程几乎没什么帮助,所幸还有另一个 Z家族
的软件可以帮到我们。
使用 CUDA-Z软件获得的显卡参数如图:
SUME
COLL
ECTIO
N
图 5、6、7 CUDA-Z截图
可见,GTX650显卡的计算能力为 3.0。软件中的 Threads Dimensions其实称
为 Block Dimension Limit 更好一些吧,同样 Grid Dimensions 应该称为 Grid
Dimension Limit,个人建议。
关于图 7里的 Gi和Mi,这是二进制计量方式,举例如下[5]:
one kibibit 1 Kibit = 210 bit = 1024 bit
one kilobit 1 kbit = 103 bit = 1000 bit
one mebibyte 1 MiB = 220 B = 1 048 576 B
one megabyte 1 MB = 106 B = 1 000 000 B
one gibibyte 1 GiB = 230 B = 1 073 741 824 B
one gigabyte 1 GB = 109 B = 1 000 000 000 B
此外,建议大家瞅一眼图 7中单精度运算速度是双精度运算速度的多少倍,
然后记在心里。以及 Pinned Memory速度是 Pageable Memory速度的多少倍,同
样记着,将来会用到的。
flop/s表示 float operations per second。
iop/s表示 integer operations per second。
CUDA 自带一个很有用的计算工具 CUDA_Occupancy_Calculator.xls,该文
件的默认地址在如下目录:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\tools
或者
C:\ProgramData\NVIDIA Corporation\CUDA Samples\v5.0\tools
文件中可以查阅到不同计算能力的硬件规格,GTX650的规格如下表所示[6]:
表 1 计算能力为 3.0的 CUDA规格
项目 数值
Compute Capability 计算能力 3.0
SM Version sm_30
Threads / Warp
每个Warp可容纳的 Thread数,
即图 5中的”Warp Size” 32
Warps / Multiprocessor 每个 Multiprocessor可容纳的Warp数 64
Threads / Multiprocessor
每个 Multiprocessor可容纳的 Thread数,
即图 5中的”Threads Per Multiprocessor” 2048
Thread Blocks / Multiprocessor 每个 Multiprocessor可容纳的 Block数 16
Max Shared Memory / Multiprocessor (bytes)
每个 Multiprocessor 最多可用的共享内存
(bytes)
49152
Register File Size 即图 5中的”Registers Per Block” 65536
Register Allocation Unit Size 256
Allocation Granularity warp
Max Registers / Thread 每个线程最多可用的 Register数 63
Shared Memory Allocation Unit Size 256
Warp allocation granularity 4
Max Thread Block Size
每个 Block中可容纳的 Thread数,
即图 5中的”Threads Per Block” 1024
Shared Memory Size Configurations (bytes) 49152
[note: default at top of list] 16384
32768
Warp register allocation granularities 256
[note: default at top of list]
1. SM各种 Thread限制对 Block尺寸设计的影响
为了使得工作状态的线程能占满整个Multiprocessor,block中 thread的总数
应当大于 Threads Per Multiprocessor / Thread Blocks Per Multiprocessor,也即大于
2048/16=128。
计算示例:
l 如果设置 block尺寸为 8*8=64,根据 Threads Per Multiprocessor的限制
每个Multiprocessor(或称 Streaming Multiprocessor,以下用 SM代替)
可容纳 2048/64=32个 block;然而根据 Thread Blocks Per Multiprocessor
的限制每个 SM 只能容纳 16 个 block;因此每个 SM 上实际运行
SUME
COLL
ECTIO
N
64*16=1024个线程,此时 SM上还有 1024个线程空闲,显然这种设置
无法有效利用 GPU。
l 如果设置 block尺寸为 16*16=256,根据 Threads Per Multiprocessor的限
制每个 SM 可容纳 2048/256=8 个 block,小于 Thread Blocks Per
Multiprocessor的限制;此时 SM每次运行 8个 block共 2048个线程,
能够占满整个 SM。
l 如果设置 block尺寸为 32*32=1024,根据 Threads Per Multiprocessor的
限制每个 SM 可运行 2048/1024=2 个 block,小于 Thread Blocks Per
Multiprocessor的限制;此时 SM每次运行 2个 block共 2048个线程,
能够占满整个 SM。
注意事项 A:根据Max Thread Block Size的限制,block中线程个数上限为
1024。
注意事项 B:在 CUDA中,线程调度单位称作Warp,根据 Threads Per Warp
这个规格,每次以 32个线程为单位进行调度,因此 Block中的 Thread数目应当
是 32的倍数。
注意事项 C:Block 如果是多维的,每个维度也都有大小限制,图 5 中的
Threads Dimensions(其实应当称为 Block Dimension Limit)给出为:
1024*1024*64,也就是 Block 的第一维不能超过 1024,第二维不能超过 1024,
第三位不能超过 64。
综上所述,我们的初步设计结果是:在无需考虑向下兼容的情况下,对
GTX650显卡将 block尺寸设置为 32*32=1024应该是比较恰当的(哦,忘了说,
我主要用 CUDA做图像处理,因此 block尺寸都习惯设为二维)。
2. SM资源对 Block尺寸设计的影响
制约 block 尺寸分配的还有其他资源的限制,例如 shared memory、register
等。在每一个 SM上这些资源都是有限的,如果所有线程要求的资源总和过多,
CUDA只能通过强制减少 Block数来保证资源供应。
顺便提一下,Shared Memory和 Register都位于 GPU片上(相对的,Global
Memory和 Local Memory位于显存),速度超快的,想要 CUDA程序跑得快,对
Shared Memory和 Register的细心设计是必不可少的。
2.1 Register对 Block尺寸设计的影响
根据 Register File Size 的限制(CUDA-Z描述为 Regs Per Block,不准确,
应该是 Regs Per Multiprocessor),每个 SM上只能供应 65536个 Register。
根据Max Registers Per Thread的限制,每个线程不能使用超过 63个Register。
计算示例:
l 假设Block尺寸设计为 32*32=1024,每个Thread需要使用 32个Register,
则一个 SM上能承担的 Thread数量为 65536/32=2048,刚好可以满足需
求。
l Block尺寸同上,每个 Thread需要使用 33个 Register,则一个 SM上能
承担的 Thread 数量为 65536/33≈1985.94。但是,前面说过,如果请求
资源过多,CUDA将会通过强制减少 Block数(而不是 Thread数)来保
证资源供应。本示例中的情况可以满足 1个 Block(1024个 Thread)的
需求,不能满足 2个 Block(2048个 Thread)的需求,因此实际只有 1
个 Block 共 1024 个线程在运行。和上个示例相对比,因为多请求了 1
个 Register,就灭掉了其他 1024个 Thread的生存机会,不划算啊。
2.2 Shared Memory对 Block尺寸设计的影响
和 2.1基本类似。
根据Max Shared Memory Per Multiprocessor的限制,每个 SM上只能供应
49152 Byte的 Shared Memory。
首先需要明确一点:Shared Memory是分配给 Block而不是 Thread的,被每
个 Block内的所有 Thread共享(所以才叫做”Shared”),Block中的 Thread能够
合作运行也是基于这一点。
计算示例:
l 假设每个 Block使用了 20000 Byte的 Shared Memory,则一个 SM上能
承担的 Block数量为 49152/20000=2.4576,可以满足需求。
l 假设每个 Block使用了 30000 Byte的 Shared Memory,则一个 SM上能
承担的 Block 数量为 49152/30000=1.6384。本示例中的情况只能满足 1
个 Block的需求,因此实际只有 1个 Block在运行。如果这里 Block的
尺寸为 32*32=1024,则 SM中另外 1024个线程容量都被浪费了。
2.3 占用率计算器的使用
上面的计算乍一看比较复杂,所幸 nVidia已经提供了一个很好的计算工具,
这就是刚才提到 CUDA_Occupancy_Calculator.xls,如图 8所示。
这个工具的使用非常简单,只要遵循 3个步骤即可。1)和 2)是需要用户使用
下拉菜单进行选择的项目,3)是占用率计算结果。下面我们分别进行介绍。
1.)是选择 GPU的计算能力,前面说过可以使用 CUDA-Z软件查询,GTX650
显卡选择 3.0。
1.b)在表 1中已经给出了,数值为 49152。
2.)中的 Threads Per Block就设为前面计算得到的 32*32=1024。
那么一个程序使用的Register和 Shared Memory如何得到呢?这时可以使用
--ptxas-options=-v编译指令。
SUME
COLL
ECTIO
N
图 8 CUDA GPU占用率计算器
在 VS2008中可以如下操作:
a. 打开 Project属性;
图 9
b. 将属性中 CUDA Runtime API\GPU中的 Verbose PTXAS Output设置为
Yes;
图 10
c. 重新编译程序后,即可在 Output窗口中看到类似下面的信息
ptxas info : Compiling entry function '_Z8my_kernelPf' for 'sm_10'
ptxas info : Used 5 registers, 8+16 bytes smem
可以看出本程序使用了 5个 Register和 8+16个 Byte的 Shared Memory,
但是如果程序在运行时还定义了 2048 Byte的 external shared memory array,
则总的 Shared Memory占用应当是 2048+8+16=2072。将 Register和 Shared
Memory信息填入图 5中的 2.)后即可看到计算器的计算结果,如图 11所示。
图 11
3.)中显示的就是资源占用情况,可见 SM的占用率是 100%,没有计算能力
被浪费,说明这种配置是合理的。
SUME
COLL
ECTIO
N
至此,Block尺寸的设计基本完成。
3. Block尺寸的合理性
资源(Register和 Shared Memory)是稀缺的,一定要分配给最重要的语句。
虽然 Thread越多就越能隐藏访问延迟,但同时每个 Thread能够使用的资源也就
相对减少了,如何在这两者之间找到平衡,只有程序实际运行时才能得到检验。
现实和理论总是有差别,但如果能够把握基本的原理,肯定不会在通往最终目标
的路上偏离太多。
关于 Register和 Shared Memory的优化,线程同步对 Block尺寸的影响,以
及其他因素就留到下次再讲吧。
4. 推荐的书籍
CUDA 5.0自带的 pdf文档:
《CUDA C Programming Guide》 编程指南
《CUDA C Best Practices Guide》 优化指南
《CUDA API Reference Manual》 函数速查
《Tuning CUDA Applications for Kepler》 针对开普勒架构显卡的优化
专著:
《CUDA by Example》 nVidia两位工程师写的
《GPU高性能编程 CUDA实战》 上面那本书的中文版
《GPGPU编程技术》 不错
《GPU高性能运算之 CUDA》
《Programming Massively Parallel Processors》
参考文档:
[1] http://www.geforce.cn/hardware/desktop-gpus/geforce-gtx-650
[2] http://www.geforce.cn/hardware/desktop-gpus/geforce-gtx-660
[3] http://www.geforce.cn/hardware/desktop-gpus/geforce-gtx-660ti
[4] http://en.wikipedia.org/wiki/Texture_mapping_unit
[5] http://physics.nist.gov/cuu/Units/binary.html
[6] CUDA_Occupancy_Calculator.xls
李飞 sume@ustc.edu
2013/4/15