计算机组成原理 — GPU — CUDA 编程模型

Posted 范桂飓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机组成原理 — GPU — CUDA 编程模型相关的知识,希望对你有一定的参考价值。

目录

文章目录

GPGPU

GPU 起初是用来处理图像的,但是后来人们发现其并行运算原理不仅可以用在图形渲染上,也可以推广到一般的运算中。于是 GPU 的功能就进行了升级,可以进行稍微复杂的工作了,并且可编程,也是就有了 GPGPU(通用图形处理器)。

GPGPU 其实是对 GPU 的一种优化,让 GPU 更加的具有易用性和通用型,GPU 应用于 AI 就是 GPU 通用属性的一个方向,类似的方向有很多:挖矿、AI 训练、HPC 高性能计算等。

深度学习是模拟人脑神经系统而建立的数学网络模型,这个模型的最大特点是,需要大数据来训练。因此,对电脑处理器的要求,就是需要大量的并行的重复计算,GPU 正好有这个专长。

训练和推断是云端 AI 两大运行过程,训练产生算法,推断实现算法应用。

  • 训练:我们可以把深度学习的训练看成学习过程。人工神经网络是分层的、是在层与层之间互相连接的、网络中数据的传播是有向的。训练神经网络的时候,训练数据被输入到网络的第一层。然后所有的神经元,都会根据任务执行的情况,根据其正确或者错误的程度如何,分配一个权重参数(权值)。训练阶段需要大量数据运算,GPU 预计占 64% 左右市场份额,FPGA 和 ASIC 分别为 22% 和 14%。

  • 推理:就是深度学习把从训练中学习到的能力应用到工作中去。不难想象,没有训练就没法实现推断。我们人也是这样,通过学习来获取知识、提高能力。深度神经网络也是一样,训练完成后,并不需要其训练时那样的海量资源。推理阶段无需大量数据运算,GPU 将占据 42% 左右市场,FPGA 和 ASIC 分别为 34% 和 24%。

如果想用 GPU 做通用的计算,就要有更通用的编程工具。为此,很多针对 GPGPU 的并行计算架构就产生了,主要有 CUDA 和 OpenCL。

CUDA 编程模型

CUDA(Compute Unified Device Architecture,统一计算架构)是由 NVIDIA 所推出的一种集成技术,是对于 GPGPU 的正式名称,本质是一种外部接口编程模型。利用 CUDA 技术,使 GPU 能够解决复杂的计算问题。它包含了 CUDA 指令集架构(ISA)以及 GPU 内部的并行计算引擎。开发人员现在可以使用 C 语言来为 CUDA 架构编写程序。CUDA 配合适当的软件(e.g. MediaCoder、Freemake Video Converter)就可以利用 GPU 进行高清视频编码加速。视频解码方面亦然。简而言之,CUDA 是获取 NVIDIA GPU 运算能力的开发平台。所有基于 G80 及之后架构的民用与专业显卡或运算模块皆支持 CUDA 技术。

GPU 可以利用多个 CUDA 核心来做并行计算,而 CPU 只能按照顺序进行串行计算,同样运行 3000 次的简单运算,CPU 需要 3000 个时钟周期,而配有 3000 个 CUDA 核心的 GPU 运行只需要 1 个时钟周期。

在 GPUs(GPGPU)上使用图形 APIs 进行传统通用计算,CUDA 技术有下列几个优点:

  • 分散读取:代码可以从存储器的任意地址读取
  • 统一虚拟内存
  • 共享存储器:CUDA 公开一个快速的共享存储区域(每个处理器 48K),使之在多个进程之间共享。其作为一个用户管理的高速缓存,比使用纹理查找可以得到更大的有效带宽。
  • 与 GPU 之间更快的下载与回读。
  • 全面支持整型与位操作,包括整型纹理查找。

CUDA 的架构


CUDA 主要提供了 4 个重要的组件:CUDA C 和对应的 COMPILER(编译器),CUDA 库、CUDA RUNTIME 和 CUDA DRIVER。

  • CUDA C:其实就是标准 C 的变种,它加入 4 大特性:
    • 可以定义程序的哪部分运行在 GPU 或 CPU 上;
    • 可以定义变量位于 GPU 的存储类型;
    • 利用 KERNEL、BLOCK、GRID 来定义最原始的并行计算;
    • State 变量。
  • CUDA 开发库:基于 CUDA 技术所提供的应用开发库。CUDA 的 1.1 版提供了两个标准的数学运算库:CUFFT(离散快速傅立叶变换)和 CUBLAS(离散基本线性计算)的实现。这两个数学运算库所解决的是典型的大规模的并行计算问题,也是在密集数据计算中非常常见的计算类型。开发人员在开发库的基础上可以快速、方便的建立起自己的计算应用。此外,开发人员也可以在 CUDA 的技术基础上实现出更多的开发库。
  • CUDA 运行时环境:提供了应用开发接口和运行期组件,包括基本数据类型的定义和各类计算、类型转换、内存管理、设备访问和执行调度等函数。基于 CUDA 开发的程序代码在实际执行中分为两种,一种是运行在 CPU 上的宿主代码(Host Code),一种是运行在 GPU 上的设备代码(Device Code)。不同类型的代码由于其运行的物理位置不同,能够访问到的资源不同,因此对应的运行期组件也分为公共组件、宿主组件和设备组件三个部分,基本上囊括了所有在 GPGPU 开发中所需要的功能和能够使用到的资源接口,开发人员可以通过运行期环境的编程接口实现各种类型的计算。
  • CUDA 驱动:由于目前存在着多种 GPU 版本的 NVIDIA 显卡,不同版本的 GPU 之间都有不同的差异,因此 CUDA 驱动基本上可以理解为是 CUDA-enable 的 GPU 的设备抽象层,提供硬件设备的抽象访问接口。CUDA 提供运行期环境也是通过这一层来实现各种功能的。由于体系结构中硬件抽象层的存在,CUDA 今后也有可能发展成为一个通用的 GPU 标准接口,兼容不同厂商的 GPU 产品。

对于软件开发者来说,使用 CUDA 平台调用 CUDA 的加速库使用的语言包括:C、C++ 和 Fortran。C/C++ 编程者使用 UDAC/C++ 并用 nvcc 进行编译。Nvidia 的 LLVM 库是基于 C/C++ 编译器的。Fortran 的开发者能够使用 CUDA Fortran,编译使用 PGI CUDA Fortran。当然 CUDA 平台也支持其他的编程接口,包括 OpenCL,微软的 DirectCompute、OpenGL ComputeShaders 和 C++ AMP。第三方的开发者也可以使用 Python、Perl、Fortran、Java、Ruby、Lua、Haskell、R、MATLAB、IDL 由曼赛马提亚原生支持。

CUDA 的工作原理

GPU 是协处理器,与 CPU 端存储是分离的,故 GPU 运算时必须先将 CPU 端的代码和数据传输到 GPU,GPU 才能执行 kernel 函数。在 CUDA 中,程序的执行区域分为两部分,CPU(HOST)和 GPU(DEVICE),任务组织和发送是在 CPU 里完成的,而并行计算是在 GPU 里完成。每当 CPU 遇到需要并行计算的任务,则将要做的运算组织成 kernel,然后丢给 GPU 去执行。当然任务是通过 CUDA 系统来丢的,CUDA 在把任务正式提交给 GPU 前,会对 kernel 做些处理,让 kernel 符合 GPU 体系架构。

假设,我们先简单的把 GPU 当作拥有上百个核的 CPU,kernel 当成一个要创建为线程的函数。所以,CUDA 现在就要将你的 kernel 创建出上百个 thread,然后将这些 thread 送到 GPU 中的各个核上去运行,但为了更好的利用 GPU 资源,提高并行度,CUDA 还要将这些 thread 加以优化组织,将能利用共有资源的线程组织到一个 thread block 中,同一 thread block 中的 thread 可以通过 share memory 共享数据,每个 thread block 最高可拥有 512 个线程。拥有同样维度同样 kernel 的 thread block 被组织成一个 grid,而 CUDA 处理任务的最大单元便是 grid 了。

Grid、Block、Thread

利用 CUDA 进行编程时,一个 GRID 分为多个 Block,而一个 Block 可分为多个 Thread。

  • 多个 Blocks 可以组成 1 维、2 维或者 3 维的 Grid。kernel 函数可以访问 Grid 内部标识 Block 的内置变量 BlockIDX,也可以访问表示 Block 维度的内置变量 BlockDim。

  • 每个 Block 所能包含的 Thread 数量是有限制的,因为目前每个 Block 内的所有 Threads 都是在一个物理的处理器核中,并且共享了这个核有限的内存资源。当前的 GPU 中,每个 Block 最多能执行 1024 个 Thread。

Warp

Warp 是 GPU 执行程序时的调度单位,目前 CUDA 的 Warp 大小为 32,同在一个 Warp 的线程,以不同数据资源执行相同的指令。

  • Warp 调度单元与 Threads 的关系

上图反映了 Warp 作为调度单位的作用,每次 GPU 调度一个 Warp 里的 32 个线程执行同一条指令,其中各个线程对应的数据资源不同。

上图反映了 Warp 是如何排程的,即最终将 CUDA thread 映射到实际的物理硬件计算单元中执行(Thread => SP Block => SM Grid => GPU)。

一个 SM 只会执行一个 Block 里的 Warp,当该 Block 里 Warp 执行完后才会执行其他 Block 里的 Warp。进行划分时,最好保证每个 Block 里的 Warp 数量合理,那样一个 SM 可以交替执行里面的 Warp,从而提高效率。

此外,在分配 Block 时,要根据 GPU 的 SM 个数,分配出合理的 Block 数,让 GPU 的 SM 都利用起来,提利用率。分配时,也要考虑到同一个线程 Block 的资源问题,不要出现对应的资源不够。

CUDA threads 在执行时,可以访问多个 Memory Spaces,每个线程有自己的私有的 Local Memory。每个 Block 有一个 Shared Memory,Block 的所有线程都可以访问。

最后,所有线程都可以访问 Global Memory。不同的内存访问速度为:本地内存 > 共享内存 > 全局内存。

通过 cudaMalloc 函数分配的内存就是全局内存。核函数中用 shared 修饰的变量就是共享内存。 核函数定义的变量使用的就是本地内存。

以上是关于计算机组成原理 — GPU — CUDA 编程模型的主要内容,如果未能解决你的问题,请参考以下文章

CUDA编程入门极简教程(转)

cuda shader

OpenCL

关于CUDA编程模型的问题

cuda GPU 编程之共享内存的使用

CUDA学习和总结1