异构计算与CUDA
异构计算与CUDA
- 异构计算
- 异构架构
- 计算能力
- 异构计算范例
- CUDA:一种异构计算平台
- 运行时API与驱动API
- 用GPU输出Hello World
- CUDA编程结构
异构计算
异构计算可以理解为让 CPU 和 GPU 协同工作来完成计算任务。CPU 是通用处理器,核心数量较少,但每个核心能力强,适合处理复杂逻辑、任务调度、分支判断和串行计算;GPU 则拥有大量计算核心,单个核心相对简单,但非常擅长同时处理大量相同或相似的计算任务,比如矩阵运算、图像处理、深度学习中的大规模并行计算等。
在异构计算中,通常由 CPU 负责程序的整体控制、数据准备和任务分发,再把适合并行处理的部分交给 GPU 执行,GPU 计算完成后再把结果返回给 CPU。这样可以充分发挥 CPU 的控制能力和 GPU 的并行计算能力,从而提高程序的运行速度和计算效率。比如在深度学习训练中,CPU 负责读取数据、调度训练流程,而 GPU 负责大量矩阵乘法和张量运算,这就是典型的 CPU+GPU 异构计算。
异构架构
异构架构指的是在同一个计算系统中同时使用不同类型的处理器来协同完成任务。这里主要指 CPU + GPU 的架构。CPU 和 GPU 的设计目标不同:CPU 更擅长复杂控制逻辑、任务调度、分支判断和串行计算;GPU 更擅长大规模并行计算,比如矩阵乘法、图像处理、深度学习训练等。异构架构的核心思想就是:让 CPU 做它擅长的控制和调度工作,把大量重复、可并行的计算交给 GPU 执行,从而提高整体计算性能。
这张图展示的就是一个典型的 CPU-GPU 异构计算架构。左边是 CPU,右边是 GPU,中间通过 PCIe Bus 连接。
- CPU 有比较大的 Control 控制单元,说明 CPU 很擅长处理复杂的逻辑控制,比如判断、调度、系统调用、程序流程管理等。CPU 也有少量比较强的 ALU,也就是算术逻辑单元,用来执行计算。下面的 Cache 比较大,表示 CPU 依赖缓存来降低访问内存的延迟。最下面是 CPU 侧的 DRAM,也就是主机内存。
- GPU 部分可以看到,右边上方是一大片密集的小格子,表示 GPU 内部有大量计算单元。图中红框标出的 SM 是 Streaming Multiprocessor,可以理解为 GPU 中的一个计算核心组。一个 GPU 里面通常有很多个 SM,每个 SM 里又包含很多 CUDA Core / ALU,因此 GPU 能同时运行大量线程,适合处理大规模并行任务。
- GPU 下面也有自己的 DRAM,通常叫显存,比如 GDDR 或 HBM。GPU 计算时主要访问自己的显存,而不是直接频繁访问 CPU 的主存。
- 中间的 PCIe Bus 表示 CPU 和 GPU 之间通过 PCIe 总线通信。数据通常需要先从 CPU 内存拷贝到 GPU 显存,GPU 完成计算后,再把结果从 GPU 显存拷贝回 CPU 内存。这个传输速度比 GPU 内部访问显存慢很多,所以在 CUDA 编程中,减少 CPU 和 GPU 之间的数据传输非常重要。
一个异构应用包含两种以上架构,所以代码也包括不止一部分:
- 主机代码
- 设备代码
衡量GPU计算能力的主要靠下面两种容量特征:
- CUDA核心数量(越多越好)
- 内存大小(越大越好)
相应的也有计算能力的性能指标:
- 峰值计算能力
- 内存带宽
峰值计算性能是用来评估计算容量的一个指标,通常定义为每秒能处理的单精度或双 精度浮点运算的数量。峰值性能通常用GFlops(每秒十亿次浮点运算)或TFlops(每秒万亿次浮点运算)来表示。内存带宽是从内存中读取或写入数据的比率。内存带宽通常用 GB/s表示。
计算能力
NVIDIA 的计算能力是 CUDA 用来描述 GPU 硬件架构和功能支持程度的版本号。
它决定了这张 GPU 能运行哪些 CUDA 特性,但不直接等同于性能高低。
真正的性能还要看 SM 数量、CUDA Core 数量、显存带宽、频率、Tensor Core 能力等因素。
异构计算范例
GPU计算并不是要取代CPU计算。对于特定的程序来说,每种计算方法都有它自己的优点。CPU计算适合处理控制密集型任务,GPU计算适合处理包含数据并行的计算密集型任务。GPU与CPU结合后,能有效提高大规模计算问题的处理速度与性能。
低并行逻辑复杂的程序适合用CPU
高并行逻辑简单的大数据计算适合GPU
因为CPU和GPU的功能互补性导致了CPU+GPU的异构并行计算架构的发展,这两种处理器的类型能使应用程序获得最佳的运行效果。
- CPU上的线程通常是重量级的实体。操作系统必须交替线程使用启用或关闭CPU执行 通道以提供多线程处理功能。上下文的切换缓慢且开销大。
- GPU上的线程是高度轻量级的。在一个典型的系统中会有成千上万的线程排队等待工 作。如果GPU必须等待一组线程执行结束,那么它只要调用另一组线程执行其他任务即可。
- CPU的核被设计用来尽可能减少一个或两个线程运行时间的延迟,而GPU的核是用来处理大量并发的、轻量级的线程,以最大限度地提高吞吐量。
CUDA:一种异构计算平台
CUDA平台不是单单指软件或者硬件,而是建立在Nvidia GPU上的一整套平台,并扩展出多语言支持。CUDA提供了两层API来管理GPU设备和组织线程,如图所示。
驱动API是一种低级API,它相对来说较难编程,但是它对于在GPU设备使用上提供了更多的控制。运行时API是一个高级API,它在驱动API的上层实现。每个运行时API函数都被分解为更多传给驱动API的基本运算。
运行时API与驱动API
- 运行时API和驱动API之间没有明显的性能差异。
- 这两种API是相互排斥的,你必须使用两者之一,从两者中混合函数调用是不可能的。
一个CUDA程序包含了以下两个部分的混合
- 在CPU上运行的主机代码
- 在GPU上运行的设备代码
NVIDIA的CUDA nvcc编译器在编译过程中将设备代码从主机代码中分离出来。主机代码是标准的C代码,使用C编译器进行编译。
nvcc 是从LLVM开源编译系统为基础开发的。
用GPU输出Hello World
- 用专用扩展名.cu来创建一个源文件。
- 使用CUDA nvcc编译器来编译程序。
//hello.cu#include<stdio.h>__global__voidhello_world(void){printf("GPU: Hello world!\n");}intmain(intargc,char**argv){printf("CPU: Hello world!\n");hello_world<<<1,10>>>();cudaDeviceReset();//if no this line ,it can not output hello world from gpureturn0;}nvcc hello.cu -o hello ./hello 输出: CPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world! GPU: Hello world!__global__:告诉编译器这个函数将会从CPU中调用,然后在GPU上执行。hello_world<<<1,10>>>():三重尖括号意味着从主线程到设备端代码的调用。一个内核函数通过一组线程来执行,所有线程执行相同的代码。三重尖括号里面的参数是执行配置,用来说明使用多少线程来执行内核函数。cudaDeviceRest():用来显式地释放和清空当前进程中与当前设备有关的所有资源。
CUDA编程结构
一个典型的CUDA编程结构包括5个主要步骤。
- 分配GPU内存。
- 从CPU内存中拷贝数据到GPU内存。
- 调用CUDA内核函数来完成程序指定的运算。
- 将数据从GPU拷回CPU内存。
- 释放GPU内存空间。