HSA运行时是一种精简的用户模式应用程序编程接口API,它提供了主机将计算内核启动到可用HSA代理程序所必须的接口。它可以分为两类:核心和扩展。HSA核心运行时API旨在支持HSA系统平台体系结构规范所需的操作,并且必须得到任何符合HSA的系统的支持。HSA扩展运行时API可以是HSA认可的或供应商特定的,并且对于符合HSA的系统是可选的。本章首先描述HSA核心运行时API,包括初始化和关闭、通知、系统和HSA代理信息、信号、队列、内存、代码对象和可执行文件,然后是经过HSA认证的运行时API。
4.1 引言
HSA标准将CPU、GPU和其他加速器集成到一个具有共享的高带宽内存系统的平台中,以支持各种将数据并行和任务并行的编程模型。为此,HSA基金会提出HSA平台系统体系结构、HSAIL和HSA运行时三个规范。
从硬件角度看,HSA平台体系结构的规范定义了一套系统体系结构要求,如HSA平台拓展发现、信号、同步、排队模型、体系结构排队语言AQL、内存模型等支持HSA的编程模型和系统软件基础设施。
异构系统体系结构的中间语言HSAIL的规范定义了一种可移植的低级编译器中间语言来表示GPU计算啮合的中间格式。高级编译器处理大多数优化过程,并为并行代码区域生成HSAIL。一个低级轻量级的编译器(称为终止器)将HSAIL翻译成目标机器代码。终止器可以在编译时、安装时或运行时调用。每个HSA代理提供它自己的终止器的实现。
HSA运行时规范定义了一个开销较低的用户模式API,它提供了上层语言运行时所必须得接口。HSA运行时设计的总体目标是提供可在HSA供应商中实现可移植的高性能调度机制。为了实现高性能调度,参数设置和内核启动机制在HSA平台系统体系结构中定义的硬件和规范级别进行架构。
目前有两个HSA认可的扩展,HSAIL终止化和图像。HSAIL终止化API运行应用程序以二进制格式BRIG编译一套HSAIL模块,生成供应商特定的代码对象,并检索这些代码对象。图像API允许应用程序指定从图片加载的图像,并将有关其资源布局和其他属性的信息存储在内存中。使用图像API,应用程序可以更有效地控制图像数据的分配和管理内存。
HSA运行时API是所有HSA供应商的标准,供应商负责提供自己的HSA运行时实现,以支持其平台中提供的所有HSA代理。HSA不提供结合不同供应商的运行时的机制。
当语言编译器为并行区域生成代码时,语言运行库将通过调用相应的HSA运行时例程来设置和分配并行区域到HSA代理。语言运行时还负责调用适当的HSA运行时API函数来初始化HSA运行时、选择目标设备、创建执行队列、管理内存等。终止器是HSA运行时的可选组件。应用程序可以通过HSAIL终止例程调用终止器,在执行应用程序期间将HSAIL模块转换为目标二进制。
4.2 HSA核心运行时API
HSA核心运行时API包括运行时初始化和关闭、运行时通知、系统和HSA代理信息、信号、AQL数据包和内存。
4.2.1 运行时的初始化和关闭
在应用程序可以使用HSA运行时之前,必须首先进行初始化。初始化的目的是创建一个运行时实例,并为创建的运行时实例分配资源。典型的运行时实例可能包括平台、拓扑、引用计数、队列和信号等信息。
当不再需要运行时实例时,创建运行时实例的应用程序将调用关闭例程来关闭运行时实例,与运行时实例关联的引用计数减1.如果当前HSA运行时实例的引用计数器小于1,则与运行时实例(队列、信号和拓扑信息等)关联的所有资源均被视为无效。
4.2.2 运行时的通知
HSA应用程序使用运行时通知来报告错误和事件。HSA运行时定义了两种通知:同步和异步。同步通知用于指示调用的HSA运行时例程是否成功执行。HSA运行时使用HSA运行时例程的返回值同步传递通知。HSA运行时将状态码定义为枚举,以捕获已执行的任何HSA运行时例程(除了某些队列索引或信号值API之外)的返回值。
当HSA运行时检测到异步事件或错误的发生时,它通过调用应用程序的适当的回调函数来传递异步通知。HSA运行时不执行任何默认回调。也就是说,所有回调函数都是用户定义的。在回调实现中使用阻塞函数时需要小心。
4.2.3 系统和HSA代理信息
根据HSA平台系统体系结构规范,HSA系统可以实现为小端或大端。机器模型可以实现为32位小型系统或64位大型系统。共享虚拟内促可以实现为基本配置文件或完整配置文件等。
HSA代理的一个重要特征是它是否有内核代理。代理属性示例包括:名称、支持设备的类型和支持的队列类型。如果HSA代理支持HSAIL指令集,并且支持AQL内核包格式的执行,则HSA代理是内核代理。内核代理可以使用内存操作向任何内核代理(包括自己)发送命令来构造和排队AQL数据包。内核代理由一个或多个计算单元组成,并公开与内核调度相关的丰富属性集,例如网格中波前大小或工作项的最=大数量。
4.2.4 信号
HSA代理可以通过使用一致的全局内存或使用信号相互通信。HSA信号值只能由内核代理使用特定的HSAIL机制来操作,而主机CPU则必须使用HSA运行时机制来操作。HSAIL和HSA运行时所需的机制是:
1. 分配HSA信号;
2. 销毁HSA信号;
3. 读取当前的HSA信号值;
4. 发送HSA信号值;
5. 原子读取-修改-写入HSA信号值;
6. 等待HSA信号符合指定的条件;
7. 等待HSA信号达到指定的条件,并要求最长的等待时间。
采用不透明的信号处理机制,信号值只能由HSA运行时例程或HSAIL指令操作,满足其使用限制。
由于信号值可能被多个代理同时操作,每个读或写信号都必须支持内存排序语义。可能得内存排序语义包括松弛、释放、获取和获取-释放。
HSA运行时定义了用于信号的原子读取-修改-写入更新和内存排序操作的组合的例程。
4.2.5 队列
在HSA平台系统体系结构规范中,定义了队列的类型、特征、属性和操作。HSA兼容的平台支持多个用户级队列的分配。用户级队列(简称队列)的特点是具有一定大小的运行时分配的用户级可访问虚拟内存,包含定义在体系结构排队语言中的数据包。队列与特定的HSA代理相关联,但HSA代理可能有多个队列连接到它。HSA软件操作基于内存的结构来配置硬件队列。是为了允许对HSA代理的硬件队列进行有效的软件管理。队列的内存由数据包处理器作为环形缓冲区处理,具有单独的内存位置定义该队列的写入和读取状态信息。数据包处理器在逻辑上是一个独立的代理。它的主要职责是代表相应的内核代理有效地管理队列。
队列被定义为包含可见部分和不可见部分的半透明对象。可见部分包括队列的类型、特性和属性。不可见部分包含读写索引。队列的类型可以是单/多生产者。内核队列用于将内核分配给代理,而代理队列则可用于派遣内置函数给代理。队列的属性包括类型、特征、基地址、门铃信号、大小和标识符。就用户代码而言,队列是只读数据结构。用户代码直接将其值写入队列结构会导致未定义的行为。但是,HSA代理可以直接修改基地址指向的缓存区的内存。它们还可以使用HSA运行时例程来访问门铃信号或代理程序调度队列。
为队列定义的操作包括分配队列、销毁队列、停用队列以及管理队列读写索引。失活和销毁之间的区别在于对销毁队列的任何操作都是无效的,但对于不活动的队列是有效的。队列的读写索引不能直接暴露给用户代码。相反,用户代码只能通过使用专用的HSA运行时例程来访问队列读写缩影。
4.2.6 体系结构排队语言
体系结构排队语言AQL为调度代理命令提供了一个标准的二进制接口。AQL允许HSA代理构建和排队它们自己的命令包,从而实现快速、低功耗的调度。AQL还为内核代理队列提交提供支持。AQL数据包是一种用户模式缓冲区,具有编码一个命令的特定格式。HSA运行时不提供任何创建、销毁或操纵AQL数据包的例程。
在HSA平台系统体系结构规范中,有6种类型的AQL包:
1. 内核调度包;
2. 代理调度包;
3. 屏障-与数据包;
4. 屏障-或数据包;
5. 供应商特定的数据包;
6. 无效的数据包;
所有数据包格式共享一个共同头文件描述了它们的类型、屏障位(强制数据包处理器按照顺序完成数据包)和其他属性。应用程序使用内核调度包将内核提交给内核代理;它使用代理程序调度包在HSA代理程序中启动内置函数。
屏障-与数据包允许应用程序指定最多五个信号相关性,并要求数据包处理器在执行之前解决这些相关性。数据包处理器将不会在该队列中启动任何数据包,直到屏障-与数据包完成。
供应商特定数据包的数据包格式是供应商定义的。数据包处理器针对特定于供应商的数据包的行为是特定于实现的,但不得导致特权升级或突破流程上下文。
数据包在提交之后,可能处于以下五种状态之一:排队、启动、活动、完成或错误。如果数据包处理器尚未开始解析数据包,则数据包处于排队状态。如果数据包正在被解析,数据包处于启动状态,但尚未开始执行。如果数据包的执行已经开始,则处于活动状态。如果内存释放栅栏与头文件中释放栅栏范围字段所指示的范围一起应用,并且完成信号(如果存在)递减,则数据包处于完成状态,不会有更多的数据包从队列中启动。队列无法恢复。它只能被禁用(如果他被许多进程共享)或被销毁(如果它不被其他进程共享)。
HSA运行时为启动阶段定义了两个错误代码,为活动阶段定义了一个错误代码。启动阶段:格式不 正确的AQL包和系统不能为数据包分配足够的资源。活动阶段通知内核期间触发了HSAIL异常。
4.2.7 内存
HSA运行时的一个重要功能是为HSA代理提供内存管理服务、HSA内存区域代表可由HSA代理直接访问的虚拟内存块。它揭示了有关虚拟内存块的特性以及如何从特定的HSA代理访问它。HSA内存区域分为4部分:全局、组、私有、只读。
全局段用于存储所有HSA代理可访问的数据。与全局段相关的区域分为两类:细粒度和粗粒度。在HSA运行时例程之外分配的内存仅在系统中支持完整配置文件的那些代理被认为是细粒度的。可用注册缓冲区向HSA运行时指示内存可能在不久的将来被内核代理访问。注册是一个性能提示,它允许HSA运行时实现知道那些内核代理访问,并提前做相应的优化。另外,用户只能从细粒度区域分配的内存将参数传递给内核。细粒度的内存可以被系统中的所有HSA代理同时访问。
只读段用于存储常量信息。与只读段相关的区域对于内核代理是私有的。在一个内核调度包中传递一个与一个代理相关的只读缓冲区,这个调度包由一个不同的代理调度执行,从而导致不确定行为。内核代理只允许对驻留在自己的只读段中的变量的地址执行读取操作。只读段的内容在应用程序的整个生命周期内都是持久的。
组段用于存储同一个工作组中所有工作项共享的信息。不能由其他工作组中的工作项或其他代理程序读取和写入。组内存在内核调度工作组中的工作项执行期间处于活动状态,并且咋工作组开始执行时未初始化。
私有段用于存储工作项的本地信息,仅对单个工作项可见。
组和私有内存的实际分配在内核开始执行之前自动发生。
4.2.8 代码对象和可执行文件
当内核调度包入队时,必须指定一个内核对象。内核对象是要执行的机器码的句柄。内核对象的创建由2个阶段组成:
1. 首先,内核源代码被编译或最终化到一个称为代码对象的目标机器特定表示。
2. 代码对象被加载到一个名为可执行文件的HSA运行时对象中。
从代码对象中检索一个内核对象句柄的流程如下:
1. 代码对象生成步骤:一个程序被编译成一个或多个包含要执行的内核的HSAIL模块。HSA运行时提供HSAIL终止化扩展例程,以供用户创建HSAIL模块并将其终止化为目标机器代码对象。
2. 代码对象序列化/反序列化:使应用程序能够读取离线编译过程中生成的代码对象,或者写出上一步骤中生成的代码对象以后备用。每个代码对象与多个符号相关联,每个符号表示原始源程序中的变量、内核和间接函数。
3. 可执行句柄生成:通过调用HSA运行时例程创建可执行句柄。HSA运行时将使用可执行句柄来加载一组可能与不同目标机器相关的代码对象句柄。
4. 代码对象加载:调用HSA运行时例程将代码对象添加(或加载)到可执行句柄。
5. 检索可执行符号:调用HSA运行时例程来检索给的那个内核和代理对应的可执行符号。表示内核的可执行符号公开了属性,它是最终用于启动内核的机器代码的句柄。
6. 检索内核对象:调用HSA运行时例程来检索与可执行符号相关联的内核对象句柄。
4.3 HSA运行时扩展
本节描述2个HSA认可的扩展--HSAIL终止化和图像。
4.3.1 HSAIL终止化
HSAIL终止化例程的目的是在运行期间将一组二进制格式的HSAIL模块BRIG完成到目标机器的特定的代码。目标机器代码被表示为一个代码对象。终止化流程如下:
1. 源编译:一个程序被编译成一个或多个HSAIL模块。其中一个HSAIL模块包含感兴趣的内核。在HSA运行时间之外执行。
2. HSAIL程序句柄创建:调用HSAIL终止化例程创建一个空HSAIL程序句柄。
3. HSAIL模块插入步骤:使用HSAIL终止化例程将HSAIL模块添加到步骤2 创建的HSAIL程序句柄。
4, HSAIL终止化:在将所有HSAIL模块添加到相应的HSA程序句柄后,HSAIL程序可以通过使用HSAIL终止化例程来完成。代码对象句柄可以被序列化到磁盘(离线编译),或者进一步处理以便启动(在线编译)。
4.3.2 图形和采样器
图片影像是由HSA兼容平台提供的一项功能。HSA将图形数据的存储和如何解释数据的描述分离开来。采样器提供图形数据解释的信息。在创建采样器句柄时,必须指定采样器的坐标模式、坐标寻址模式和坐标过滤器模式。采样器坐标模式指定图形坐标是否被标准化。采样器坐标寻址模式指定如何处理超出范围的图像坐标。采样器坐标过滤器模式指定如何对图像元素(最接近或线性)执行过滤。