深入理解GPU

本文介绍了GPU的发展历程,从其起源与CPU分工,到图形渲染的详细流程,以及GPU如何通过多核并行和SIMT技术提升性能,特别是在深度学习中的应用,以NvidiaGeForce2080为例,展示了其惊人的10.06TFLOPS计算能力。
摘要由CSDN通过智能技术生成

前言

大家好,我是jiantaoyab,本篇文章给大家介绍GPU,大家看看这个文章GPU介绍有更详细的GPU介绍。

GPU历史

随着时代的发展,CPU的性能已经大幅度的提升了,为什么还需要单独的GPU呢?大家可以看看GPU历史,GPU 是随着我们开始在计算机里面需要渲染三维图形的出现,而发展起来的设备。

图形渲染的流程

现在我们电脑里面显示出来的 3D 的画面,其实是通过多边形组合出来的。可以看看下面这张图,你在玩的各种游戏,里面的人物的脸,并不是那个相机或者摄像头拍出来的,而是通过多边形建模(Polygon Modeling)创建出来的。

image-20240315103015727

而实际这些人物在画面里面的移动、动作,乃至根据光线发生的变化,都是通过计算机根据图形学的各种计算,实时渲染出来的。

对于图像进行实时渲染的过程,可以被分解成下面这样 5 个步骤:

  1. 顶点处理(Vertex Processing)
  2. 图元处理(Primitive Processing)
  3. 栅格化(Rasterization)
  4. 片段处理(Fragment Processing)
  5. 像素操作(Pixel Operations)

顶点处理

图形渲染的第一步是顶点处理。构成多边形建模的每一个多边形,都有多个顶点(Vertex)。这些顶点都有一个在三维空间里的坐标。但是我们的屏幕是二维的,所以在确定当前视角的时候,我们需要把这些顶点在三维空间里面的位置,转化到屏幕这个二维空间里面。这个转换的操作,就被叫作顶点处理。

这样的转化都是通过线性代数的计算来进行的。所以我们的建模越精细,需要转换的顶点数量就越多,计算量就越大。而且,这里面每一个顶点位置的转换,互相之间没有依赖,是可以并行独立计算的

image-20240315103214865

图元处理

图元处理,其实就是要把顶点处理完成之后的各个顶点连起来,变成多边形。其实转化后的顶点,仍然是在一个三维空间里,只是第三维的 Z 轴,是正对屏幕的“深度”。所以我们针对这些多边形,需要做一个操作,叫剔除和裁剪(Cull and Clip),也就是把不在屏幕里面,或者一部分不在屏幕里面的内容给去掉,减少接下来流程的工作量

image-20240315103251936

栅格化

屏幕分辨率是有限的。它是通过一个个“像素(Pixel)”来显示出内容的。

所以,对于做完图元处理的多边形,要通过栅格,把它们转换成屏幕里面的一个个像素点。

这个栅格化操作,有一个特点和上面的顶点处理是一样的,就是每一个图元都可以并行独立地栅格化。

image-20240315103406241

片段处理

在栅格化变成了像素点之后,我们的图还是“黑白”的。我们还需要计算每一个像素的颜色、透明度等信息,给像素点上色。这步操作,就是片段处理。这步操作,同样也可以每个片段并行、独立进行,和上面的顶点处理和栅格化一样。

image-20240315103428574

像素操作

把不同的多边形的像素点“混合(Blending)”到一起。可能前面的多边形可能是半透明的,那么前后的颜色就要混合在一起变成一个新的颜色;或者前面的多边形遮挡住了后面的多边形,那么我们只要显示前面多边形的颜色就好了。最终,输出到显示设备。

image-20240315103456235

image-20240315103535651

经过这完整的 5 个步骤之后,我们就完成了从三维空间里的数据的渲染,变成屏幕上你可以看到的 3D 动画了。这样 5 个步骤的渲染流程呢,一般也被称之为图形流水线(Graphic Pipeline)。

如果用CPU来渲染这个过程,要多少资源呢?

在上世纪的时候,屏幕的分辨率还没有现在那么高。一般的 CRT 显示器也就是 640×480 的分辨率。这意味着屏幕上有 30 万个像素需要渲染。为了让我们的眼睛看到画面不晕眩,我们希望画面能有 60 帧。于是,每秒我们就要重新渲染 60 次这个画面。也就是说,每秒我们需要完成 1800 万次单个像素的渲染。从栅格化开始,每个像素有 3 个流水线步骤,即使每次步骤只有 1 个指令,那我们也需要 5400 万条指令,也就是 54M 条指令。

90 年代的 CPU 的性能是多少呢?93 年第一代 Pentium 处理器,主频是 60MHz,以这个性能来看,用 CPU 来渲染 3D 图形,基本上就要把 CPU 的性能用完了。因为实际的每一个渲染步骤可能不止一个指令,我们的 CPU 可能根本就跑不动这样的三维图形渲染。

所以我们的GPU出现了,既然图形渲染的流程是固定的,那我们直接用硬件来处理这部分过程,不用 CPU 来计算是不是就好了?

因为整个计算流程是完全固定的,不需要流水线停顿、乱序执行等等的各类导致 CPU 计算变得复杂的问题。我们也不需要有什么可编程能力,只要让硬件按照写好的逻辑进行运算就好了。

image-20240315103851606

可以看到和我们现代的显卡是很不同的,也没有现代显卡去进行各种加速深度学习的能力。

GPU发展

在 Voodoo 和 TNT 显卡的渲染管线里面,没有“顶点处理“这个步骤。在当时,把多边形的顶点进行线性变化,转化到我们的屏幕的坐标系的工作还是由 CPU 完成的。所以,CPU 的性能越好,能够支持的多边形也就越多,对应的多边形建模的效果自然也就越像真人。而 3D 游戏的多边形性能也受限于我们 CPU 的性能。无论你的显卡有多快,如果 CPU 不行,3D 画面一样还是不行。

所以,1999 年 NVidia 推出的 GeForce 256 显卡,就把顶点处理的计算能力,也从 CPU 里挪到了显卡里。不过整个图形渲染过程都是在硬件里面固定的管线来完成的。程序员们在加速卡上能做的事情呢,只有改配置来实现不同的图形渲染效果,并没有编程能力。

这个时候,程序员希望我们的 GPU 也能有一定的可编程能力。这个编程能力不是像 CPU 那样,有非常通用的指令,可以进行任何你希望的操作,而是在整个的渲染管线(Graphics Pipeline)的一些特别步骤,能够自己去定义处理数据的算法或者操作。

于是,从 2001 年的 Direct3D 8.0 开始,微软第一次引入了可编程管线(Programable Function Pipeline)的概念。

image-20240315104248438

些可以编程的接口,我们称之为Shader,中文名称就是着色器。之所以叫“着色器”,是因为一开始这些“可编程”的接口,只能修改顶点处理和片段处理部分的程序逻辑。我们用这些接口来做的,也主要是光照、亮度、颜色等等的处理,所以叫着色器。

这个时候的 GPU,有两类 Shader,也就是 Vertex Shader 和 Fragment Shader。

在进行顶点处理的时候,我们操作的是多边形的顶点;在片段操作的时候,我们操作的是屏幕上的像素点。

对于顶点的操作,通常比片段要复杂一些。所以一开始,这两类 Shader 都是独立的硬件电路,也各自有独立的编程接口。因为这么做,硬件设计起来更加简单,一块 GPU 上也能容纳下更多的 Shader

不过呢,大家很快发现,虽然我们在顶点处理和片段处理上的具体逻辑不太一样,但是里面用到的指令集可以用同一套。而且,虽然把 Vertex Shader 和 Fragment Shader 分开,可以减少硬件设计的复杂程度,但是也带来了一种浪费,有一半 Shader 始终没有被使用。在整个渲染管线里,Vertext Shader 运行的时候,Fragment Shader 停在那里什么也没干。Fragment Shader 在运行的时候,Vertext Shader 也停在那里发呆。

于是统一着色器架构(Unified Shader Architecture)就应运而生了。

既然大家用的指令集是一样的,那不如就在 GPU 里面放很多个一样的 Shader 硬件电路,然后通过统一调度,把顶点处理、图元处理、片段处理这些任务,都交给这些 Shader 去处理,让整个 GPU 尽可能地忙起来。这样的设计,就是我们现代 GPU 的设计。

image-20240315104508978

为什么GPU在图形渲染、深度学习上那么快?

GPU中没有多余的分支条件和复杂的依赖关系,所以我们只留下取指令、指令译码、ALU 以及执行这些计算需要的寄存器和缓存就好了。

image-20240315104643398

多核并行和 SIMT

image-20240315104718351

CPU 里有一种叫作 SIMD 的处理技术。这个技术是说,在做向量计算的时候,我们要执行的指令是一样的,只是同一个指令的数据有所不同而已。

无论是顶点去进行线性变换,还是屏幕上临近像素点的光照和上色,都是在用相同的指令流程进行计算。

所以,GPU 就借鉴了 CPU 里面的 SIMD,用了一种叫作SIMT(Single Instruction,Multiple Threads)的技术。SIMT 比 SIMD 更加灵活,在 SIMD 里面,CPU 一次性取出了固定长度的多个数据,放到寄存器里面,用一个指令去执行,而 SIMT,可以把多条数据,交给不同的线程去处理。

各个线程里面执行的指令流程是一样的,但是可能根据数据的不同,走到不同的条件分支。这样,相同的代码和相同的流程,可能执行不同的具体的指令。这个线程走到的是 if 的条件分支,另外一个线程走到的就是 else 的条件分支了。

image-20240315104949137

于是,我们的 GPU 设计就可以进一步进化,也就是在取指令和指令译码的阶段,取出的指令可以给到后面多个不同的 ALU 并行进行运算。这样,我们的一个 GPU 的核里,就可以放下更多的 ALU,同时进行更多的并行运算了。

GPU 里的“超线程”

GPU 里的指令,可能会遇到和 CPU 类似的“流水线停顿”问题。在 GPU 上,我们一样可以做类似的事情,也就是遇到停顿的时候,调度一些别的计算任务给当前的 ALU。

和超线程一样,既然要调度一个不同的任务过来,我们就需要针对这个任务,提供更多的执行上下文。所以,一个 Core 里面的执行上下文的数量,需要比 ALU 多。

image-20240315105133170

GPU 在深度学习上

GPU 是一个可以进行“通用计算”的框架,我们可以通过编程,在 GPU 上实现不同的算法。另一方面,现在的深度学习计算,都是超大的向量和矩阵,海量的训练样本的计算。整个计算过程中,没有复杂的逻辑和分支,非常适合 GPU 这样并行、计算能力强的架构。

我们来看看NVidia 2080 显卡到底有多强的计算能力。

2080 一共有 46 个 SM(Streaming Multiprocessor,流式处理器),这个 SM 相当于 GPU 里面的 GPU Core,所以你可以认为这是一个 46 核的 GPU,有 46 个取指令指令译码的渲染管线。每个 SM 里面有 64 个 Cuda Core。你可以认为,这里的 Cuda Core 就是我们上面说的 ALU 的数量或者 Pixel Shader 的数量,46x64 呢一共就有 2944 个 Shader。然后,还有 184 个 TMU,TMU 就是 Texture Mapping Unit,也就是用来做纹理映射的计算单元,它也可以认为是另一种类型的 Shader

image-20240315105452656

2080 的主频是 1515MHz,如果自动超频(Boost)的话,可以到 1700MHz。而 NVidia 的显卡,根据硬件架构的设计,每个时钟周期可以执行两条指令。所以,能做的浮点数运算的能力,就是:
( 2944 + 184 ) × 1700 M H z × 2 = 10.06 T F L O P S (2944 + 184)× 1700 MHz × 2 = 10.06 TFLOPS (2944+184×1700MHz×2=10.06TFLOPS
存中…(img-Q1poC3ki-1710471550898)]

2080 的主频是 1515MHz,如果自动超频(Boost)的话,可以到 1700MHz。而 NVidia 的显卡,根据硬件架构的设计,每个时钟周期可以执行两条指令。所以,能做的浮点数运算的能力,就是:
( 2944 + 184 ) × 1700 M H z × 2 = 10.06 T F L O P S (2944 + 184)× 1700 MHz × 2 = 10.06 TFLOPS (2944+184×1700MHz×2=10.06TFLOPS
实在是太强大了!这也解释了为什么现在在深度学习中用GPU进行运算了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值