基本概念
概念 |
描述 |
---|---|
同步/异步 |
本文中提及的同步、异步是站在调用者和执行者的角度,在当前场景下,若在Host调用接口后不等待Device执行完成再返回,则表示Host的调度是异步的;若在Host调用接口后需等待Device执行完成再返回,则表示Host的调度是同步的。 |
进程/线程 |
本文中提及的进程、线程,若无特别注明,则表示Host上的进程、线程。 |
Host |
Host指与Device相连接的X86服务器、ARM服务器,会利用Device提供的NN(Neural-Network )计算能力,完成业务。 |
Device |
Device指安装了昇腾AI处理器的硬件设备,利用PCIe接口与Host侧连接,为Host提供NN计算能力。若存在多个Device,多个Device之间的内存资源不能共享。 |
Context |
Context作为一个容器,管理了所有对象(包括Stream、Event、设备内存等)的生命周期。不同Context的Stream、不同Context的Event是完全隔离的,无法建立同步等待关系。 Context分为两种:
|
Stream |
Stream用于维护一些异步操作的执行顺序,确保按照应用程序中的代码调用顺序在Device上执行。 基于Stream的kernel执行和数据传输能够实现Host运算操作、Host与Device间的数据传输、Device内的运算并行。 Stream分两种:
|
Event |
支持调用pyACL接口同步Stream之间的任务,包括同步Host与Device之间的任务、同一个Device上的多个任务。 例如,若stream2的任务依赖stream1的任务,想保证stream1中的任务先完成,这时可创建一个Event,并将Event插入到stream1,在执行stream2的任务前,先同步等待Event完成。 |
AIPP |
AIPP(Artificial Intelligence Pre-Processing)用于在AI Core上完成图像预处理,包括色域转换(转换图像格式)、图像归一化(减均值/乘系数)和抠图(指定抠图起始点,抠出神经网络需要大小的图片)等。
AIPP区分为静态AIPP和动态AIPP。您只能选择静态AIPP或动态AIPP其中一种方式来处理图片,不能同时配置静态AIPP和动态AIPP两种方式。
|
动态batch/动态分辨率 |
在某些场景下,模型每次输入的batch size或分辨率是不固定的,如检测出目标后再执行目标识别网络,由于目标个数不固定导致目标识别网络输入batch size不固定。
|
动态维度(ND格式) |
为了支持Transformer等网络在输入格式的维度不确定的场景,需要支持ND格式下任意维度的动态设置。 ND表示支持任意格式,当前N ≤ 4。 |
通道 |
在RGB色彩模式下,图像通道就是指单独的红色R、绿色G、蓝色B部分。也就是说,一幅完整的图像,是由红色、绿色、蓝色三个通道组成的,它们共同作用产生了完整的图像。同样在HSV色系中指的是色调H、饱和度S、亮度V三个通道。 |
RC模式 |
以昇腾 AI 处理器的PCIe的工作模式进行区分,如果PCIe工作在主模式,可以扩展外设,则称为RC模式。 |
Device、Context、Stream之间的关系
- Device,用于指定计算设备。
- Device的生命周期源于首次调用acl.rt.set_device接口。
- 每次调用acl.rt.set_device接口,系统会进行引用计数加1;调用acl.rt.reset_device接口,系统会进行引用计数减1。
- 当引用计数减为零时,在本进程中Device上的资源不可用。
- Context,在Device下,一个Context一定属于一个唯一的Device。
- Context分隐式创建和显式创建。
- 隐式创建的Context(即默认Context),生命周期始于调用acl.rt.set_device接口,终结于调用acl.rt.reset_device接口使引用计数为零时。
隐式Context只会被创建一次,调用acl.rt.set_device接口重复指定同一个Device,只增加隐式创建的Context的引用计数。
- 显式创建的Context,生命周期始于调用acl.rt.create_context接口,终结于调用acl.rt.destroy_context接口。
- 若在某一进程内创建多个Context(Context的数量与Stream相关,Stream数量有限制,请参见acl.rt.create_stream),当前线程在同一时刻内只能使用其中一个Context,建议通过acl.rt.set_context接口明确指定当前线程的Context,增加程序的可维护性。
- 进程内的Context是共享的,可以通过acl.rt.set_context进行切换。
- Stream,是Device上的执行流,在同一个Stream中的任务执行严格保序。
- Stream分隐式创建和显式创建。
- 每个Context都会包含一个默认Stream,属于隐式创建,隐式创建的Stream生命周期同归属的Context。
- 用户可以显式创建Stream,显式创建的Stream生命周期始于调用acl.rt.create_stream,终结于调用acl.rt.destroy_stream接口。显式创建的Stream归属的Context被销毁或生命周期结束后,会影响该Stream的使用,虽然此时Stream没有被销毁,但不可再用。
- Task/Kernel,是Device上真正的任务执行体。
线程、Context、Stream之间的关系
- 一个用户线程一定会绑定一个Context,所有Device的资源使用或调度,都必须基于Context。
- 一个线程中当前会有一个唯一的Context在用,Context中已经关联了本线程要使用的Device。
- 可以通过acl.rt.set_context进行Device的快速切换。示例代码如下,仅供参考,不可以直接拷贝运行:
… ctx1, ret = acl.rt.create_context(0) #使用acl.rt.create_context接口通过传入Device Id创建Context。 stream, ret = acl.rt.create_stream() ret = acl.op.execute_v2(op_type, input_desc, inputs, output_desc, outputs, attr, stream) ctx2, ret = acl.rt.create_context(1) # 在当前线程中,创建ctx2后,当前线程对应的Context切换为ctx2,对应在Device 1进行后续的计算任务,本例中将在Device 1上进行op2的执行调用。 stream2, ret = acl.rt.create_stream() ret = acl.op.execute_v2(op_type2, input_desc, inputs, output_desc, outputs, attr, stream2) ret = acl.rt.set_context(ctx1); # 在当前线程中,通过Context切换,使后续计算任务在对应的Device 0上进行。 ret = acl.op.execute_v2(op3,...,s1) …
- 一个线程中可以创建多个Stream,不同的Stream上计算任务是可以并行执行,多线程场景下,也可以每个线程创建一个Stream,线程之间的Stream在Device上相互独立,每个Stream内部的任务是按照Stream下发的顺序执行。
- 多线程的调度依赖于运行应用的操作系统调度,多Stream调度Device侧,由Device上调度组件进行调度。
一个进程内多个线程间的Context迁移
- 一个进程中可以创建多个Context,但一个线程同一时刻只能使用一个Context。
- 线程中创建的多个Context,线程缺省使用最后一次创建的Context。
- 进程内创建的多个Context,可以通过acl.rt.set_context设置当前需要使用的Context。
图2 接口调用流程
默认Context和默认Stream的使用场景
- Device上执行操作下发前,必须有Context和Stream,这个Context、Stream可以显式创建,也可以隐式创建。隐式创建的Context、Stream就是默认Context、默认Stream。
默认Stream作为接口入参时,直接传0。
- 默认Context不允许用户执行acl.rt.get_context或acl.rt.set_context操作,也不允许执行acl.rt.destroy_context操作。
- 默认Context、默认Stream一般适用于简单应用,用户仅仅需要一个Device的计算场景下。多线程应用程序建议全部使用显式创建的Context和Stream。
示例代码如下,仅供参考,不可以直接拷贝运行:
# … ret = acl.init(config_path) ret = acl.rt.set_device(device_id) # 已经创建了一个default ctx,在default ctx中创建了一个default stream,并且在当前线程可用。 # … ret = acl.op.execute_v2(op1, input_desc, inputs, output_desc, outputs, attr, 0) # 最后一个0表示在default stream上执行算子op1。 ret = acl.op.execute_v2(op2, input_desc, inputs, output_desc, outputs, attr, 0) # 最后一个0表示在default stream上执行算子op2。 ret = acl.rt.synchronize_stream(0) # 等待计算任务全部完成(op1、op2执行结束),用户根据需要获取计算任务的输出结果。 # … ret = acl.rt.reset_device(device_id) # 释放计算设备0,对应的default ctx及default stream生命周期也终止。
多线程、多stream的性能说明
- 线程调度依赖运行的操作系统,Stream上下发了任务后,Stream的调度由Device的调度单元调度,但如果一个进程内的多Stream上的任务在Device存在资源争抢的时候,性能可能会比单Stream低。
- 当前昇腾AI处理器有不同的执行部件,如AI Core、AI CPU、Vector Core等,对应使用不同执行部件的任务,建议多Stream的创建按照算子执行引擎划分。
- 单线程多Stream与多线程多Stream(一个进程中可以包含多个线程,每个线程中一个Stream)性能上哪个更优,具体取决于应用本身的逻辑实现,一般来说前者性能略好,原因是相对后者,应用层少了线程调度开销。