前面扯了很多,不过大多都是在讲CUDA 在软体层面的东西;接下来,虽然Heresy 自己也不熟,不过还是来研究一下硬体的部分吧~毕竟要最佳化的时候,好像还是要大概知道一下相关的东西的。这部分主要参考资料是:
在研究硬体架构前,可能须要先回去看《nVidia CUDA简介》,稍微回顾一下在CUDA中thread、thread block、block grid的意义:一个CUDA的平行化的程式会被以许多个thread来执行,数个thread会被群组成一个block,而多个thread block则会再构成grid。也就是一个kernel程式会有一个grid,grid底下有数个block,每个block都是一群thread的群组。而在同一个block中的thread可以透过shared memory来沟通,也可以同步。
硬体基本架构
实际上在nVidia的GPU里,最基本的处理单元是所谓的SP(Streaming Processor),而一颗nVidia的GPU里,会有非常多的SP可以同时做计算;而数个SP会在附加一些其他单元,一起组成一个SM(Streaming Multiprocessor)。几个SM则会在组成所谓的TPC(Texture Processing Clusters)。
在G80/G92的架构下,总共会有128个SP,以8个SP为一组,组成16个SM,再以两个SM为一个TPC,共分成8个TPC来运作。而在新一代的GT200里,SP则是增加到240个,还是以8个SP组成一个SM,但是改成以3个SM组成一个TPC,共10组TPC。下面则是提供了两种不同表示方式的示意图。(可参考《NVIDIA G92终极状态!!》、《NVIDIA D10U绘图核心》)
对应到CUDA
而在CUDA 中,应该是没有TPC 的那一层架构,而是只要根据GPU 的SM、SP 的数量和资源来调整就可以了。
如果把CUDA 的Grid – Block – Thread 架构对应到实际的硬体上的话,会类似对应成GPU – Streaming Multiprocessor ??– Streaming Processor;一整个Grid 会直接丢给GPU 来执行,而Block 大致就是对应到SM, thread 则大致对应到SP。当然,这个讲法并不是很精确,只是一个简单的比喻而已。
SM 中的Warp 和Block
CUDA的device实际在执行的时候,会以Block为单位,把一个个的block分配给SM进行运算;而block中的thread,又会以「warp」为单位,把thread来做分组计算。目前CUDA的warp大小都是32,也就是32个thread会被群组成一个warp来一起执行;同一个warp里的thread,会以不同的资料,执行同样的指令。此外,在Compute Capability 1.2的硬体中,还加入了warp vote的功能,可以快速的进行warp内的简单统计。
基本上warp 分组的动作是由SM 自动进行的,会以连续的方式来做分组。比如说如果有一个block 里有128 个thread 的话,就会被分成四组warp,第0-31 个thread 会是warp 1、32-63 是warp 2、64-95 是warp 3、96-127 是warp 4。
而如果block 里面的thread 数量不是32 的倍数,那他会把剩下的thread 独立成一个warp;比如说thread 数目是66 的话,就会有三个warp:0-31、32-63、64-65 。由于最后一个warp 里只剩下两个thread,所以其实在计算时,就相当于浪费了30 个thread 的计算能力;这点是在设定block 中thread 数量一定要注意的事!
一个SM 一次只会执行一个block 里的一个warp,但是SM 不见得会一次就把这个warp 的所有指令都执行完;当遇到正在执行的warp 需要等待的时候(例如存取global memory 就会要等好一段时间),就切换到别的warp 来继续做运算,借此避免为了等待而浪费时间。所以理论上效率最好的状况,就是在SM 中有够多的warp 可以切换,让在执行的时候,不会有「所有warp 都要等待」的情形发生;因为当所有的warp 都要等待时,就会变成SM 无事可做的状况了~
下图就是一个warp 排程的例子。一开始是先执行thread block 1 的warp1,而当他执行到第六行指令的时候,因为需要等待,所以就会先切到thread block 的warp2 来执行;一直等到存取结束,且刚好有一个warp 结束时,才继续执行TB1 warp1 的第七行指令。
实际上,warp 也是CUDA 中,每一个SM 执行的最小单位;如果GPU 有16 组SM 的话,也就代表他真正在执行的thread 数目会是32*16 个。不过由于CUDA 是要透过warp 的切换来隐藏thread 的延迟、等待,来达到大量平行化的目的,所以会用所谓的active thread 这个名词来代表一个SM 里同时可以处理的thread 数目。
而在block 的方面,一个SM 可以同时处理多个thread block,当其中有block 的所有thread 都处理完后,他就会再去找其他还没处理的block 来处理。假设有16 个SM、64 个block、每个SM 可以同时处理三个block 的话,那一开始执行时,device 就会同时处理48 个block;而剩下的16 个block 则会等SM 有处理完block 后,再进到SM 中处理,直到所有block 都处理结束。
建议的数值?
在Compute Capability 1.0/1.1中,每个SM最多可以同时管理768个thread(768 active threads)或8个block(8 active blocks);而每一个warp的大小,则是32个thread,也就是一个SM最多可以有768 / 32 = 24个warp(24 active warps)。到了Compute Capability 1.2的话,则是active warp则是变为32,所以active thread也增加到1024。
在这里,先以Compute Capability 1.0/1.1 的数字来做计算。根据上面的数据,如果一个block 里有128 个thread 的话,那一个SM 可以容纳6 个block;如果一个block 有256 个thread 的话,那SM 就只能容纳3 个block。不过如果一个block 只有64 个thread 的话,SM 可以容纳的block 不会是12 个,而是他本身的数量限制的8 个。
因此在Compute Capability 1.0/1.1的硬体上,决定block大小的时候,最好让里面的thread数目是warp数量(32)的倍数(可以的话,是64的倍数会更好);而在一个SM里,最好也要同时存在复数个block。如果再希望能满足最多24个warp的情形下,block里的thread数目似乎会是96(一个SM中有8个block)、128(一个SM中有6个block)、192(一个SM中有4个block)、256(一个SM中有3个block)这些数字了~
而官方的建议则是一个block里至少要有64个thread,192或256个也是通常比较合适的数字(请参考Programming Guide) 。但是是否这些数字就是最合适的呢?其实也不尽然。因为实际上,一个SM 可以允许的block 数量,还要另外考虑到他所用到SM 的资源:shared memory、registers 等。在G80 中,每个SM 有16KB 的shared memory 和8192 个register。而在同一个SM 里的block 和thread,则要共用这些资源;如果资源不够多个block 使用的话,那CUDA 就会减少Block 的量,来让资源够用。在这种情形下,也会因此让SM 的thread 数量变少,而不到最多的768 个。
比如说如果一个thread 要用到16 个register 的话(在kernel 中宣告的变数),那一个SM 的8192 个register 实际上只能让512 个thread 来使用;而如果一个thread 要用32 个register,那一个SM 就只能有256 个thread 了~而shared memory 由于是thread block 共用的,因此变成是要看一个block 要用多少的shread memory、一个SM 的16KB 能分给多少个block 了。
所以虽然说当一个SM里的thread越多时,越能隐藏latency,但是也会让每个thread能使用的资源更少。因此,这点也就是在最佳化时要做取舍的了。
转自:
http://kheresy.wordpress.com/2008/07/09/cuda-%E7%9A%84-threading%EF%BC%9Ablock-%E5%92%8C-grid-%E7%9A%84%E8%A8%AD%E5%AE%9A%E8%88%87-warp/