文章目录
- 前言
- 3.1、Adreno GPU
- 3.2、Adreno GPU 架构
- 3.2.1、Adreno硬件架构在OpenCL方面的高层视图
- 3.2.2、Waves and fibers
- 3.2.3、 Latency hiding
- 3.2.4 、L2缓存
- 3.2.5、工作组分配
- 3.2.6、Coalesced access(合并访问)
- 3.3 图形和计算负载之间的上下文切换
- 3.3.1 上下文切换
- 3.3.2 限制 GPU 上的内核/工作组执行时间
- 3.4 支持 OpenCL 标准特性
- 3.4 OpenCL 2.0主要特征
- 3.5.1 SVM 共享虚拟内存
- 3.5.2 OpenCL kernel enqueue kernel (KEK)
- 3.6 OpenCL 3.0的关键特性
- 3.6.1 feature macro
- 3.6.2 向后兼容
- 总结
前言
本章介绍高通对OpenCL的支持
3.1、Adreno GPU
Adreno GPU自A3x GPU以来完全支持OpenCL。随着OpenCL不同版本和配置文件的发展,Adreno GPU上的OpenCL支持也在不断演进。
在相同的OpenCL版本或配置文件中,Adreno GPU系列之间的一些特性和功能可能会有所差异,例如扩展的可用性、图像对象的最大尺寸、图像格式等。可以通过相关的OpenCL API函数,如clGetDeviceInfo或clGetPlatformInfo,查询属性的详细列表。
3.2、Adreno GPU 架构
3.2.1、Adreno硬件架构在OpenCL方面的高层视图
Adreno GPU支持丰富的图形和计算API,包括OpenGL ES、OpenCL、DirectX和Vulkan等。上图展示了Adreno硬件架构在OpenCL方面的高层视图,省略了仅与图形相关的硬件模块。每一代Adreno GPU之间存在许多差异,而对于OpenCL来说,这些差异是次要的。用于OpenCL执行的关键硬件模块如下:
-
着色器(或流处理器)(SP)
- Adreno GPU的核心模块,包含算术逻辑单元(ALU)、加载/存储单元、控制流单元、寄存器文件等关键硬件模块。
- 执行图形着色器(例如顶点着色器、片段着色器和计算着色器)以及OpenCL内核等计算工作负载。
- 每个SP对应于OpenCL中的一个或多个计算单元,因此执行一个或多个工作组。
- Adreno GPU可能包含一个或多个SP,取决于GPU系列和层级。低层级芯片组可能只有一个SP,而高层级或高级芯片组可能有多个SP。在图3-1中,有两个SP。
- SP通过第2级(L2)缓存加载和存储缓冲区对象和使用__read_write限定符定义的图像对象的数据(OpenCL 2.0+功能)。
- SP通过纹理处理器/L1模块从只读图像对象中加载数据。
-
纹理处理器(TP)和L1缓存
- L1缓存是只读的。
- TP从L1缓存或L1缓存未命中的情况下从L2缓存获取数据。
- TP根据内核的请求执行纹理操作,例如纹理获取和过滤。
-
L2缓存
- 支持读和写操作。
- 处理以下请求:
- 从SP加载/存储缓冲区数据。
- 图像数据存储。
- 从L1缓存加载图像数据。
- SP和TP只是L2缓存的众多客户之一。
3.2.2、Waves and fibers
Adreno GPU中执行的最小执行单元称为"fiber",一个fiber对应于OpenCL中的一个工作项。一组始终同步执行的fibers称为"wave",以下是这两个概念的一些特性:
-
SP和Waves:
- SP可以同时容纳多个活动 waves。
- SP可以同时执行一个或多个 waves 的ALU指令。
- 每个 waves 可以独立地取得前进,不受其他 waves 的状态影响。
- 在较新的Adreno GPU中,一个SP可以执行属于不同工作组的 waves。
- 一个 waves 对应于OpenCL中的一个子组。
-
fibers 的数量 和 wave 大小:
- wave 大小取决于Adreno GPU系列和层级以及编译器;其值可以是8、16、32、64、128等。可以通过API函数(例如sub_group_size())查询。
- Adreno GPU支持两种模式,即full wave 模式和 half wave 模式。详细信息请参见第9.2.1节。
- 一旦编译完成,对于给定的GPU,内核的 wave 大小是固定的。
-
wave 的数量 和 工作组大小:
- 在工作组中流水线处理的最大 wave 的数量取决于硬件和内核。
- 对于给定的内核,最大工作组大小是允许的最大 wave 数量和 wave 大小的乘积。
- 通常,内核越复杂,所需的寄存器越多。
- 总寄存器数对于给定的GPU是固定的。一个内核所需的寄存器越多,允许的最大 wave 数量就越少。
- 一个工作组可以具有一个或多个 wave ,取决于工作组大小。
- 例如,如果工作组大小小于或等于波大小,则一个波就足够。
OpenCL 1.x并没有 wave 的概念,而OpenCL 2.0及以上版本通过名为 cl_khr_subgroups 的扩展允许应用程序使用 wave 。许多子组函数从OpenCL 2.1开始已成为核心功能。OpenCL 3.0通过KHR扩展引入了一组新的子组减少和洗牌函数。此外,Adreno GPU通过供应商扩展支持一套其他子组/洗牌函数。详细信息请参见第9.2节。
3.2.3、 Latency hiding
隐藏延迟是GPU在进行高效并行处理并实现高吞吐量的最强大特性之一。以下是一个示例:
- SP开始执行第一个波。
- 经过几个ALU指令后,该波需要从外部存储器(可能是全局/局部/私有存储器)获取额外的数据以继续执行,但这些数据尚不可用。
- SP发送请求以获取该波的数据。
- SP切换执行到下一个准备就绪的波。
- SP继续执行下一个波,直到存在依赖尚未准备好的点。
- 如果第一个波的数据可用,SP可能会切换到下一个波或返回到第一个波。
通过这种方式,SP主要保持繁忙,全程工作,因为延迟或依赖关系可以很好地隐藏。如果内核没有足够的波来隐藏延迟,那么该内核可能受到延迟的限制。
3.2.4 、L2缓存
L2缓存是系统内存和SP之间的一个关键块。以下是了解其工作原理的几个关键点:
- 当一个工作项从系统内存读取一个字节时,内存系统不仅加载该字节,而是加载包含该字节的整个缓存行。在某个时刻,GPU硬件可能会逐出缓存行以存储新的缓存行。
- 当一个工作项向系统内存写入一个字节时,内存系统需要加载缓存行,对其进行修改,然后在某个时刻写出。
- 内核应在缓存行被逐出之前尽可能多地使用缓存行中的数据。
- 缓存大小和缓存行大小可能会在不同的GPU版本和层级之间变化,尽管大多数Adreno GPU的缓存行大小为64字节
- 开发人员可以通过clGetDeviceInfo API函数查询缓存配置列表。其中包括以下信息:
- 缓存类型(只读或读写,CL_DEVICE_GLOBAL_MEM_CACHE_TYPE)。
- 缓存行大小(CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE,例如64字节)。
- 缓存大小(CL_DEVICE_GLOBAL_MEM_CACHE_SIZE,例如256KB)。
- 开发人员可以通过将缓存大小除以缓存行大小(例如,对于缓存大小为256KB和缓存行大小为64字节,可得到4096个缓存行)来推导GPU中可用的缓存行数。
- 缓存抖动,即某些缓存行在重用之前必须被逐出,可能会严重影响性能,特别是在L2缓存过载时。
- 根据缓存大小,开发人员可以减少或限制工作组的工作负载,以避免过快填充缓存行并改善缓存局部性
- 开发人员可以通过clGetDeviceInfo API函数查询缓存配置列表。其中包括以下信息:
3.2.5、工作组分配
一个典型的OpenCL内核启动多个工作组。Adreno GPU将每个工作组分配给一个SP,每个SP同时处理一个或多个工作组。如果有剩余的工作组,它们将排队等待SP执行。多个SP不能处理一个工作组。
在早期的Adreno GPU中,一个SP一次只能处理一个工作组,一个工作组必须在SP上完成执行,才能启动另一个工作组。Adreno A6x和A7x的高级层级已经解除了这些限制,并完全支持每个SP的并发工作组执行。
以图3-2中的2D范围为例,假设这是一个具有4个SP的GPU。图3-3显示了不同的SP如何处理工作组。在这个例子中,有九个工作组。假设没有并发工作组在运行,每个工作组由一个SP执行。每个工作组有四个波,波的大小为16。
OpenCL标准既不定义工作组启动/执行的顺序,也不定义工作组同步的方法。对于Adreno GPU,开发人员不能假设工作组或波在SP中的启动顺序。
3.2.6、Coalesced access(合并访问)
合并访问是OpenCL和GPU并行计算的重要概念。基本上,这指的是底层硬件可以将多个工作项的数据加载/存储请求合并成一个请求,以提高数据加载/存储的效率。如果没有合并访问支持,硬件必须对每个单独的请求执行加载/存储操作,导致过多的请求和性能损失。
图3-4说明了合并数据加载与非合并数据加载之间的差异。为了合并来自多个工作项的请求,数据的地址通常需要是连续的,或者在某个地址范围内(例如,128位范围内)。在合并的情况下,Adreno GPU可以在一次事务中为四个工作项加载数据,而在没有合并的情况下,对于相同量的数据将需要四次事务。
3.3 图形和计算负载之间的上下文切换
3.3.1 上下文切换
如果在Adreno GPU上运行低优先级工作负载的同时需要进行高优先级任务,比如图形用户界面(UI)渲染,那么可以强制暂停低优先级任务,以便GPU切换到高优先级工作负载。当高优先级任务完成后,低优先级任务会恢复。这种工作负载切换的类型称为上下文切换。上下文切换通常是昂贵的,因为它需要复杂的硬件和软件操作。然而,它是一项重要的功能,以支持新兴和先进的对时关键任务,比如汽车应用。
3.3.2 限制 GPU 上的内核/工作组执行时间
有时,计算内核可能运行时间过长,触发警报导致 GPU 重置并引起不可预测的后果。通常,Android 设备上的 UI 渲染以固定频率进行,例如每 30 毫秒一次。长时间运行的计算内核可能导致 UI 延迟和不响应,影响用户体验。作为一个经验法则,内核执行时间应该在几十毫秒的范围内。
3.4 支持 OpenCL 标准特性
Adreno A3x GPU支持OpenCL 1.1嵌入式配置文件,而Adreno A4x GPU支持OpenCL 1.2完整配置文件,Adreno A5x和A6x GPU支持OpenCL 2.0完整配置文件。从OpenCL 1.1嵌入式配置文件到OpenCL 1.2完整配置文件,大多数变化都是在软件方面而不是硬件方面,比如改进的API函数。
从OpenCL 1.2完整配置文件到OpenCL 2.0完整配置文件,然而,引入了许多新的硬件特性,例如共享虚拟内存(SVM),内核排队-内核(KEK)等。表3-2列出了Adreno GPU跨OpenCL配置文件支持的主要差异。
3.4 OpenCL 2.0主要特征
3.5.1 SVM 共享虚拟内存
作为OpenCL 2.0中最重要的特性之一,共享虚拟内存(SVM)允许开发者编写能够在主机和设备之间共享复杂数据结构(如链表或树)的代码。在OpenCL 2.0之前,主机创建的指针可能无法直接被设备上的内核访问,与指针关联的数据也无法共享。
在OpenCL 2.0中引入SVM的支持后,主机和设备可以共享指针和可能包含指针的复杂数据结构。此外,OpenCL 2.0中的SVM还定义了内存一致性模型,使主机和内核可以使用原子操作进行同步交互。这使开发者能够以最小的同步成本在设备和主机之间编写高度交互的并行代码。在OpenCL 2.0之前,主机和设备只能在代码中明确指定的某些点上进行同步。
自Adreno A5x GPU以来,OpenCL 2.0完整配置文件已得到充分支持,包括对细粒度缓冲区SVM和原子操作支持等高级SVM功能的支持。平台上可能存在一些功能差异,建议使用API函数clGetDeviceInfo和标记CL_DEVICE_SVM_CAPABILITIES查询平台上确切可用的功能。
在OpenCL 3.0中,SVM通过功能宏机制变为可选功能,但最新的高级Adreno GPU仍然继续支持它。
3.5.2 OpenCL kernel enqueue kernel (KEK)
KEK功能允许内核中的工作项(即父内核)在不使用CPU主机的情况下排队子内核。该功能有助于处理在运行时才可知的工作负载。一个工作项可以根据运行时结果有条件地排队另一个内核。一个典型的用例可以是K均值聚类。
Adreno GPU实现了KEK功能,而无需GPU将信息传递给CPU主机以排队新的内核,从而节省了通信开销。在Adreno GPU中,子内核在父内核完成之前不会开始执行,尽管OpenCL标准允许子内核在等待其父内核完成的情况下执行。
3.6 OpenCL 3.0的关键特性
Khronos组织已正式发布了OpenCL 3.0标准。最显著的变化是以前OpenCL标准中许多必需和强制的功能变为可选。这一机制背后的动机是改善OpenCL生态系统的基础和影响力。通过降低通过OpenCL一致性测试的门槛,许多设备,如嵌入式设备、FPGA和其他专用硬件,可以被认证为符合OpenCL 3.0,而无需支持一些复杂的功能(例如浮点运算、共享虚拟内存、KEK等)。
从A7x开始,Adreno GPU将支持OpenCL 3.0。理想情况下,开发者应该能够在支持OpenCL 3.0的Adreno GPU上运行其OpenCL 1.x和2.x应用程序。
3.6.1 feature macro
OpenCL 3.0引入了一种称为“特性宏”(feature macro)的机制。这是为了将老版本中的大多数标准功能分类为不同的特性集,并在使用它们之前,开发者需要查询这些特性是否得到支持。查询功能的正确性和代码的可移植性是非常重要的。
3.6.2 向后兼容
为早于3.0版本开发的OpenCL应用程序,通常应该无需修改即可正常运行,因为通常会保证向后兼容性。然而,开发者应该开始使用最新的OpenCL 3.0版本编写代码,以便在长期内获得更好的支持。
总结
翻译为主,如有不足,敬请指出!