怎样学好GPU编程?详解并行处理与GPU体系架构

点击下方卡片,关注“自动驾驶之心”公众号

ADAS巨卷干货,即可获取

>>点击进入→自动驾驶之心【模型部署】技术交流群

作者 | 王汝嘉 许金浩

编辑 | 自动驾驶之心

并行处理与GPU体系架构

笔记:王汝嘉 许金浩

课程讲师:韩博

课程链接:CUDA与TensorRT部署实战课程 (zdjszx.com)

引言:

大家使用TensorRT进行部署的时候,仅使用TensorRT的API将其转化为推理引擎(engine)跑在GPU上是不OK的。在部署的过程中,我们要考虑的事情有很多,包括模型和推理引擎有没有充分去利用GPU资源,假如你的CUDA core和Tensor core有很多,有没有把这些核全部给利用起来呢?你的模型的计算密度是多少呢?有没有在利用一些计算密度很低的模块,让CUDA core和Tensor core没有充分利用起来呢?你的模型的可并行性是多少呢?是不是有很多计算并没有充分使用GPU资源做并行化呢?等等。所以并行处理和GPU体系架构作为一个基础来讲解。

全搞定!基于TensorRT的CNN/Transformer/检测/BEV模型四大部署代码+CUDA加速!

3b11b0585924a79048bbcbc24fc3774b.png

火热开课!扫码领取优惠券!

主要内容:

并行处理简介

GPU并行处理

CUDA,CUDDNN,TENSOR,NEOVIM环境搭建

并行处理简介

Goal:理解并行处理的基本概念,理解SIMD,以及 (C/C++) 编程中常见的并行处理方式

串行处理与并行处理的区别

Sequential processing(串行)

  • 指令/代码块 依次执行,前一条指令执行结束以后才能执行下一条语句

一般来说,当程序有数据依赖或者分支等这些情况下需要串行

1.数据依赖:

f61a8a396caefc39a8bec7992e5175e6.png

2.分支:

9661a83f54a5498d4e2000f020508108.png

PS:data dependency的种类

  • - Flow dependency(上面的例子)

    当一条指令依赖于前一条指令的结果时,就会出现Flow依赖,也称为数据依赖或真正的依赖或read-after-write(RAW)。

  • - Anti dependency

    反依赖,也称为(write-after-read, WAR),发生在指令要求稍后更新的值时。

  • - Output dependency

    当指令的顺序将影响变量的最终输出值时,就会发生输出依赖,也称为write-after-write(WAW)。

  • - Control dependency

    如果指令B的结果决定了是否执行指令B,那么指令B对前面的指令a有控制依赖。

    更多可参见:Data dependency - Wikipedia

Sequential processing(串行)

  • 使用场景:复杂的逻辑计算(比如:操作系统)

示例:

f4e311e27beb6055cd465966c0ac3ee7.png 26f11ad8a4db4ba7a481460a088d13db.png

前提条件:

  • statement 2 依赖 statement 1

  • statement 5 依赖 statement 4

  • statement 3 是一个 循环,跟所有的statement没有任何关系

  • 有四个core可以使用

时钟周期 (执行时间) :

  • statement 1, statement 2: 5 cycles

  • statement 4, statement 5: 8 cycles

  • statement 3: 20 cycles

如果串行的话,需要花费46个cycles,太慢了。

既然我们有四个Core,那么我们为什么不用这四个Core来进行并行运算呢?

并行计算:

88ba89817a68f54ba7f404d7eb3137e4.png

简单的并行处理之后,时间花费变为了20 cycles,同时我们满足了程序的执行依赖,不会改变程序的执行结果。

statement3这个FOR循环还有什么可以优化的吗?

假设给定条件:

statement3的循环可以分割成多个子代码执行(每个子代码快 5cycles)

那么我们就可以将这个大的Statement3 FOR循环,拆分为4个小的子代码块来并行处理。

因为我们有4个Core,剩余一个Core没有使用,所以我们将这4个子代码块再分为两个部分,如下:

(默认拆分时可以满足依赖关系)

8a077d04371f426487682a1aa45b3a87.png

拆分FOR循环之后,时间花费变为了16 cycles!

再加一个依赖条件:

statement 5 可以在 statement 4 彻底执行结束前就知道所依赖的结果了:

也就是当statement4还未执行结束时,假设执行到了3/4时,就已经计算得出了statement 5所需要的依赖,

那么我们就可以让statement5和statement4的执行重叠,这种方法叫做'overlap',如下:

d3eeffc30a12e5efe5fcec099a9bf3b9.png

到此我们就将时间花费通过并行和拆分的方式减少到了14 cycles.

但是上述并行不是用了5个Core吗?

回顾一下,我们在这里都做了哪些事情:

  • 把没有数据依赖的代码分配到各个core各自执行 (schedule, 调度)

  • 把一个大的loop循环给分割成多个小代码,分配到各个core执行(loop optimization)

  • 在一个指令彻底执行完以前,如果已经得到了想要得到的数据,可以提前执行下一个指令(pipeling, 流水线)

我们管这一系列的行为,称作parallelization (并行化)。我们得到的可以充分利用多核多线程的程序叫做**parallelized program(**并行程序)

并行处理的概念

Parallel processing(并行)

  • - 指令/代码块同时执行

  • - 充分利用multi-core(多核)的特性,多个core一起去完成一个或多个任务

  • - 使用场景:大规模科学计算(如天体预测),图像处理,深度学习等等

假设我们的机器有四个Core

多核机器完成一个任务:我们可以让这四个Core一起去执行FOR循环,我们把一个FOR循环分割,分割成一个一个子代码块,让每一个Core去执行一个子代码块

多核机器完成多个任务:假设一个程序很大,它有很多个任务,我们可以让一个Core去完成一个任务,每个Core任务完成之后,我们在程序结尾的时候做一个同步处理 。

72084f532b10860da922fd6b72ef3d6a.png

多核处理器架构:

硬件:

Main Memory: 主存

L3 Cache: L3 缓存

L2 Cache: L2 缓存

i-Cache(instruction cache):指令缓存

d-Cache (data cache):数据缓存

Core: 核

软件:

Host Operating System: 操作系统

程序:

APP 1: 程序1

...

Parallel processing(并行)

  • Loop parallelization(循环并行化)

    • resize, crop, blur(模糊处理), bgr2rgb, rgb2gray, dbscan(轮廓检测), findCounters(轮廓检测)

    • 大部分消耗时间长的程序中,要不然就是在I/O上的内存读写消耗时间上长,要不然就是在**loop(循环)**上。针对loop的并行优化是很重要的一个优化策略!

    • 在图像处理/深度学习中很多地方都是用到了循环

    • 比如说:pre/post process (前处理后处理)

    • 在比如说:DNN中的卷积(convolution layer)以及全连接层(Fully connected layer)

下面以4*4的二维矩阵进行举例:

Default ordering (默认遍历顺序): row-major

11c04556727e401dd7c85fe69d2ca5fb.png

Reordering (重排序):

2bc57a3a6e4b38b91821de38c252785b.png

比如对于Fortan语言,其存储方式是column major的,假设代码里写的是row major的遍历方式,我们就可以将row major的遍历顺序改为column major的遍历方式。(代码遍历方式==存储遍历方式)

这样可以减少Cache Miss,具体来说:我们在CPU程序优化中,要尽量减少程序的Cache Miss。在存取数据的时候,要考虑它的空间连续性和时间连续性,比如说一个元素被访问到之后,其旁边的元素也有很大的机率被访问,所以把这一列的数据放在一起,这样再去Cache里访问的时候,就可以降低Cache Miss。

(*)请注意,不同编程语言的默认的存储ordering是不同的。这里举几个例子:

row major: C/C++/Objective-C,Pascal,C# (行优先)

column major: Fortran, OpenGL, MATLAB,R,Julia (列优先)

所以不同语言的reordering效果会不同。这里建议大家测试一下

5baea950a5b3a9efa0a9ee4f7b4050d7.png

Picture From:Row and column major order - Row- and column-major order - Wikipedia

Vectorization:标量化:

默认情况下是一个元素一个元素进行访问的,

我们可以四个元素四个元素一起访问,这样会提高吞吐量。

注意,标量化需要有硬件支持。

2ecad2bc9bd4c8ffed0f3c98a595f1ef.png

Tiling(分块):

假如说我们有一个16 * 16的数组,那么我们将其分为四个4 * 4的数组,那么我们就可以对每一个小数组进行独立的遍历。

也就是说,这四个小部分的计算和遍历,其实是并行的。

80e3218be57b8bc685eb5cebf43e1a58.png 46269e954f04d8397abe23be38ef9759.png 5734c9be3fb9e3220c4b2decb811dcf2.png

以上是Loop parallelization中几个较为常用的方法,还有很多拓展方法,可以自己探索。后面也会细致的讲解。

Loop Parallelization不仅仅是在CPU的程序优化中使用到,其实在GPU的优化中也完美的体现了。

Parallel processing(并行)

  • Loop parallelization

b11c6aa03f2b59fa7c4828afee852162.png

双线性插值:用于resize,用四个点(Q12,Q22,Q11,Q21)的坐标值来计算中间点坐标值。每一个计算和旁边的计算是没有依赖关系的。

所以每一个计算是可以并行的。

卷积层:每一个卷积核的计算也是没有依赖关系的,所以每个卷积核的计算也是可以并行的。

全连接:利用9个输入计算输出A,B,C,D的四个计算过程也是相互独立的,所以也可以并行计算。

PS:容易混淆的几个概念

“并行”与“并发”的区别

  • 并行(parallel)

    • 物理意义上同时执行

  • 并发(concurrent)

    • 逻辑意义上同时执行

对于并发:

一个CPU要执行两个任务,先执行Task1,执行一半后,执行Task2,执行Task2一半后,执行Task1,执行完Task1,执行Task2,Task1和Task2都执行结束。

对于并行:

有两个GPU,分别要执行两个不同的任务,两个GPU同时工作。

本课程基于并行来讲,GPU CUDA用并行多。

并发对于编译器优化较多。

f4cd3cbd0cd39acb968d2b7cbd66b3d2.png

“进程”与“线程”的关系

  • 线程是进程的子集。一个进程可以有多个线程

举例:操作系统中打开一个小程序,系统默认为该小程序分配一个线程,这个小程序的打开非常慢,所以系统可以为其分配多个线程来分别完成小程序中的各个部分,以此来打开这个小程序。打开这个小程序的过程就是进程。

“多核”与“加速比”的关系

  • 双核的加速不一定就是两倍

  • 8核的加速比有时会差于4核

1 举例:单核处理一个程序需要4ms,双核处理一个同样程序则就需要2ms吗?

答案是错误的,需要的时间往往大于2ms.

2 举例:有时候核数越多,加速效果也不一定越好。因为核数越多,内存开销等也会增大,也会消耗时间。

还可参见:Speedup - Wikipedia

8ca952db4d1ef287aef48d160dc55aba.png

横轴:Number of processors 处理器个数(核数)

纵轴:Speedup 加速比

我们会发现,随着处理器个数 (核数) 的增加,加速比会降低,到达一定程度之后,加速比会饱和。

why: 程度的进程以及线程之间会有依赖关系,所以会有不能并行和不能完全并行的情况存在,所以就做不到理想的加速比保持。

常见的并行处理的概念

自古以来的非常非常热门的课题

  • • 编译器自动化并行优化

    TVM:针对深度神经网络DNN的编译器

    • • GCC, LLVM, TVM, …

  • • 针对For循环的并行优化

    • • tile, fuse, split, vectorization, …

  • • 计算图优化

    和pytorch和tensorflow中的计算图不同,其可以理解为是程序的流程图。

    针对程序的流程图进行优化。

    • • CFG, HTG, MTG, …

  • • 数据流

    • • Dataflow compiler

    • • Dataflow architecture

  • • Polyhedral

    • • Polyhedral compiler

  • • HPC

    • • High performance computing

  • • …

TensorRT也可以理解成是一个编译器,其输入模型,由TensorRT做各种优化后,输出推理引擎,来让模型在硬件上最优化程序运行时间。

SIMD

  • Single Instruction Multiple Data (同一条指令去执行多个数据)

ec89b1026b44b140ec9a21572c29941c.png a3da878e81378bb06b09638c93ceebd6.png

原本是要做四次同样的操作,通过SIMD,也就是将A1 A2 A3 A4放到一起,把B1 B2 B3 B4也放到一起,最后输出的结果C1 C2 C3 C4也放到一起,那么我们只需要执行一次”取指,读取数据,计算,写回数据“就可以了。

f22fca283d97bdfd48133687cbd09165.png

CUDA的tensor core非常擅长做4*4矩阵的乘法和加法。

具体来说,原本乘法(scalar operation)要执行16次,但是SIMD可以允许(A0,0 A0,1 A0,2 A0,3) 这一行 同时执行并写回结果。

SIMT Thread线程 在CUDA编程中,线程Tread是一个非常重要的概念,怎么利用GPU里的多个CUDA CORE(多核资源)呢?

用多线程化,用其来对卷积操作、矩阵操作来做优化处理。之后会详细讲解。

2d225a0c90a62b0d43f9b6ada3d9aa66.png

OpenMP,pthread:由二者声明了代码块之后,程序声明后的这一部分就可以并行进行。其可以是指令级别的并行化,也可以是代码块级别的并行化。

上边讲的,Multi Core Processor 是同构架构的,也就是Core的类型是相同的,都为CPU Core或者都为CUDA Core等等.

Heterogeneous Multicore Processor 是异构架构,也就是其Core的类型是不同的,CPU Core和GPU Core可以同时存在,还可以有DSP Core Fast Core等等。

我们在做模型部署的时候,不仅要想怎么让这个模型在GPU上变快,我们还要想模型的CPU和GPU之间的通信是什么样子的,我们哪些步骤放在CPU上执行快一点,哪些步骤放在GPU上执行快一点,哪些步骤放在DSP Core上快一点,以及即使有些计算可以用GPU加速,但是是否真的用GPU处理这块计算是最佳解。这就涉及了异步执行的做法,之后也会进行扩展。

本节课 (1.1 并行处理简介) 到此结束。

① 全网独家视频课程

BEV感知、毫米波雷达视觉融合、多传感器标定、多传感器融合、多模态3D目标检测、点云3D目标检测、目标跟踪、Occupancy、cuda与TensorRT模型部署、协同感知、语义分割、自动驾驶仿真、传感器部署、决策规划、轨迹预测等多个方向学习视频(扫码学习)

bcf34d098a11365b921f2318d50ceffd.png 视频官网:www.zdjszx.com

② 国内首个自动驾驶学习社区

近2000人的交流社区,涉及30+自动驾驶技术栈学习路线,想要了解更多自动驾驶感知(2D检测、分割、2D/3D车道线、BEV感知、3D目标检测、Occupancy、多传感器融合、多传感器标定、目标跟踪、光流估计)、自动驾驶定位建图(SLAM、高精地图、局部在线地图)、自动驾驶规划控制/轨迹预测等领域技术方案、AI模型部署落地实战、行业动态、岗位发布,欢迎扫描下方二维码,加入自动驾驶之心知识星球,这是一个真正有干货的地方,与领域大佬交流入门、学习、工作、跳槽上的各类难题,日常分享论文+代码+视频,期待交流!

616b5bd5b7afe1e90c204a64ed862c3c.png

③【自动驾驶之心】技术交流群

自动驾驶之心是首个自动驾驶开发者社区,聚焦目标检测、语义分割、全景分割、实例分割、关键点检测、车道线、目标跟踪、3D目标检测、BEV感知、多模态感知、Occupancy、多传感器融合、transformer、大模型、点云处理、端到端自动驾驶、SLAM、光流估计、深度估计、轨迹预测、高精地图、NeRF、规划控制、模型部署落地、自动驾驶仿真测试、产品经理、硬件配置、AI求职交流等方向。扫码添加汽车人助理微信邀请入群,备注:学校/公司+方向+昵称(快速入群方式)

f00541636363b13773d29b3127e7af24.jpeg

④【自动驾驶之心】平台矩阵,欢迎联系我们!

ece8068b6f53d5f1335f2d8b7be973ea.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CUDA编程是一种用于GPU并行计算的编程模型,它由NVIDIA推出并应用于其显卡产品系列。通过CUDA编程,开发者可以利用GPU的并行计算能力来加速各种计算任务。下面是一些关于CUDA编程GPU并行计算的重要概念: 1. GPU:图形处理器(Graphics Processing Unit)是一种专门用于处理图形和并行计算的硬件设备。与传统的中央处理器(CPU)相比,GPU具有更多的核心和更高的内存带宽,适合并行计算任务。 2. CUDA:Compute Unified Device Architecture(CUDA)是一种并行计算平台和编程模型,用于利用GPU进行通用目的的并行计算。CUDA提供了一套API和工具,使开发者能够直接在GPU上编写并运行并行计算代码。 3. 核函数(Kernel Function):在CUDA编程中,开发者可以定义一个称为核函数的特殊函数。核函数在GPU上并行执行,并且每个线程都会独立地执行该函数。通过合理设计核函数,开发者可以利用GPU的并行计算能力来加速各种计算任务。 4. 线程、块和网格:在CUDA编程中,GPU上的并行计算是以线程为基本单位进行的。线程被组织成块(block),而块又可以组织成网格(grid)。开发者可以通过调整块和网格的大小来优化并行计算的性能。 5. 内存管理:CUDA提供了多种类型的内存,开发者可以根据需要选择合适的内存类型。其中,全局内存(Global Memory)是GPU上所有线程都可以访问的共享内存,而共享内存(Shared Memory)是块内线程共享的高速缓存。合理地使用不同类型的内存可以提高并行计算的效率。 通过CUDA编程,开发者可以将适合并行计算的任务分配给GPU来加速处理。这种方式在科学计算、深度学习、图像处理等领域得到广泛应用,能够显著提高计算性能和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值