2007年NVIDIA公司推出了CUDA[75],它的全称是Compute Unified Device Architecture(统一计算设备架构),是一种可以利用GPU作为并行计算设备的软硬件体系。CUDA使用类C语言进行编程,研究人员在开发程序时不必再借助复杂的图形学接口。几乎每台个人电脑都会安装GPU,因此CUDA的出现为在普通的个人电脑上实现大规模的计算提供了可能性。凭借CUDA这种通用的并行计算架构平台,研究人员能够借助GPU以百计的核运行以千计的线程解决极其复杂的并行计算难题。CUDA的版本也经历了不断地升级,目前最新的版本为CUDA 8。
CUDA是用于实现GPU并行计算的开发环境,但CUDA程序并不是仅仅运行在GPU上的,它也是需要CPU协作的。就像前文介绍过的一样,CPU与GPU硬件架构上的区别决定了它们在计算中扮演不同的角色。因此,“GPU比CPU更善于计算”这样的说法并不准确,要体现出GPU强大的计算能力一定有这样的前提:极大的计算量与规则的数据结构。在CUDA的编程模型中,CPU与GPU协同工作,各司其职。CPU负责进行一般的串行计算与逻辑性强的事务处理,GPU主要负责高度线程化的并行处理任务。由于CPU与GPU在计算中扮演的角色不同,CUDA编程模型将CPU作为主机(Host),GPU或者其它并行处理器作为协处理器(Co-processor)或者设备(Device),如图3-2所示。同时,CPU与GPU各自拥有相互独立的存储器地址空间:主机端的内存和设备端的显存。如何高效地实现存储器间的数据传输也是在CUDA程序开发过程中需要注意的。
如所示,GPU在程序中主要负责运行程序中的并行部分,这些运行在GPU(设备端)上的并行计算函数通常被称为内核函数(Kernel)。一个完整的CUDA程序是由一系列运行在设备端的kernel函数和主机端的串行代码共同组成的,一个单独的kernel函数并不能成为一个完整的程序,而是CUDA程序中可以被并行执行的步骤。
Kernel函数以线程网格(Grid)的形式组织,每个线程网格由若干个线程块(Block)组成,而每个线程块又由若干个线程(Thread)组成,如图3-3所示。实质上,kernel函数是以block为单位执行的,CUDA引入grid这一概念是为了表示可以被并行执行的block集合。一个kernel函数中存在着两个层次的并行:grid中不同的block间并行与block中不同的thread间并行,两层并行模型是CUDA的重要创新之一。
实际上,CUDA编程模型中设计的两层并行结构与GPU的硬件结构有关。如图3-4所示,在NVIDIA的GPU中,最基本的处理单元是流处理器(Streaming Processor, SP)一个NVIDIA的GPU中有数量众多的SP。多个SP加上寄存器(Registers), 共享内存(Shared Memory),纹理内存(Texture Cache)和常量内存(Constant Memory)等一起可以组成一个流多处理器(Streaming Multiprocessor, SM)。几个SM组成一个纹理处理集群(Texture Processing Clusters, TPC)。在CUDA模型中,每个SP实际对应一个thread,每个SM对应一个或几个block 。