运行时环境(runtime environment)包括在兼容的NVDLA硬件上运行编译神经网络的软件。
它由两部分组成:
- 用户模式驱动(User Mode Driver, UMD): 这是应用程序的主接口,正如Compile library中所详述的,对神经网络进行逐层解析和编译后,会输出NVDLA Loadable存储格式的文件。用户模式运行时驱动程序加载这个文件,并将推理任务提交给内核模式驱动。
- 内核模式驱动(Kernel Mode Driver, UMD): 由内核模式驱动(Kernel Mode Driver)和引擎调度器(engine scheduler)组成,负责调度NVDLA上的编译网络,并对NVDLA寄存器进行编程以配置每个功能模块。
运行时环境使用保存为NVDLA Loadable镜像的神经网络存储表示,从NVDLA Loadable的角度来看,软件中的每个编译“层”在NVDLA实现中的功能块上都是可加载的。每一层包括关于它对其它层的依赖、它在存储器中用于输入和输出的缓存器(buffer)、以及用于执行每个功能块的特定配置信息。各层通过依赖关系图链接在一起,引擎调度器使用依赖关系图来调度各个层。Loadable NVDLA格式在Compiler实现和UMD实现之间是标准化的,所有符合NVDLA标准的实现都应该至少能够解释任何NVDLA Loadable映像,即使该实现可能缺少一些使用该Loadable映像运行推理所需的功能。
用户模式驱动程序栈和内核模式驱动程序栈都是作为定义的API存在的,并且希望用系统可移植层包装,在可移植层中维护核心实现通常需要相对较少的更改,这加快了需要在多个平台上运行NVDLA软件堆栈的工作。有了适当的可移植层,相同的核心实现应该可以在Linux和FreeRTOS上很容易地编译。类似地,在具有紧密耦合到NVDLA协控制器的“headed”实现中,可移植层的存在使得在该协控制器上运行与在没有这种伴随协控制器的“headless”实现中的主CPU上运行,使用相同的内核模式驱动程序成为可能。
用户模式驱动(UMD)
UMD提供了标准的应用编程接口(Application Programming Interface, API ),用于处理loadable镜像,将输入和输出张量绑定到内存位置,并将推理作业提交给KMD。该层使用一组定义的数据结构将神经网络加载到内存中,并以实现-定义的方式(implementation-defined fashion)将其传递给KMD。例如,在Linux上,这可能是一个ioctl(),将数据从用户模式驱动程序传递到内核模式驱动程序;在单进程系统中,KMD运行在与UMD相同的环境中,这可能是一个简单的函数调用,底层函数在用户模式驱动程序中实现。
Application Programming Interface
运行时库的接口实现的功能有:在从文件中重新读取可加载缓冲区后,执行函数处理从应用程序传来的可加载缓冲区,为张量和中间缓冲区分配内存,准备同步点,最后将推理作业提交给KMD。提交给KMD的推理任务被称为DLA任务。
主要接口介绍
对应的类名:nvdla::IRuntime
使用Runtime Interface 提交推理任务包括以下步骤:
1)创建NVDLA运行时实例
IRuntime *nvdla::createRuntime()
返回:IRuntime类实例。
2)获取NVDLA设备信息
NvU16 nvdla::IRuntime::getMaxDevices()
获取硬件配置支持的最大设备数量,运行时驱动程序支持向多个DLA设备提交推理作业,用户应用程序可以选择要使用的设备。一个任务不能跨设备分割,但一个任务只能提交给一个设备。
返回:支持的最大设备数。
NvU16 nvdla::IRuntime::getNumDevices()
从硬件配置支持的最大设备数量中获取可用设备的数量。
返回:可用设备数。
3)加载神经网络数据
NvError nvdla::IRuntime::load(const NvU8 *buf, int instance)¶
解析可从缓冲区加载的内容,并使用创建任务所需的信息更新ILoadable。
参数
buf:加载镜像buffer;
instance:加载这个神经网络的设备实例。
返回: NvError。
4)获取输入和输出张量信息
NvError nvdla::IRuntime::getNumInputTensors(int *input_tensors)
从加载的信息中获取神经网络输入张量。
参数
input_tensors:指针,用于更新输入张量的值。
返回:NvError。
NvError nvdla::IRuntime::getInputTensorDesc(int id, NvDlaTensor *tensors)
用于获取神经网络的输入张量描述符。
参数
id:张量的ID;
tensors:张量的描述符。
返回:NvError。
NvError nvdla::IRuntime::getNumOutputTensors(int *output_tensors)
从加载的信息中获取神经网络输出张量。
参数
output_tensors:指针,用于更新输出张量的值。
返回:NvError。
NvError nvdla::IRuntime::getOutputTensorDesc(int id, NvDlaTensor *tensors)
用于获取神经网络的输出张量描述符。
参数:
id:张量的ID;
tensors:张量的描述符。
返回:NvError。
5)更新输入和输出张量信息
仅当张量信息改变时才需要,并非所有参数都可以改变。
NvError nvdla::IRuntime::setInputTensorDesc(int id, const NvDlaTensor *tensors)
设置神经网络的输入张量描述符。
参数
id:张量的ID; tensors:张量的描述符。
返回:NvError。
NvError nvdla::IRuntime::setOutputTensorDesc(int id, const NvDlaTensor *tensors)
设置神经网络的输出张量描述符。
参数
id:张量的ID;
tensors:张量的描述符。
返回:NvError。
6)为输入和输出张量分配内存
NvDlaError allocateSystemMemory(void **h_mem, NvU64 size, void **pData)
为输入和输出张量分配可由NVDLA访问的DMA内存。
参数
h_mem: 存储内存句柄指针;
size:分配内存的大小;
pData: 分配内存的虚拟地址。
返回: NvError。
7)将内存句柄绑定到张量
NvError nvdla::IRuntime::bindInputTensor(int id, void *hMem)
NvError nvdla::IRuntime::bindOutputTensor(int id, void *hMem)
两个函数分别绑定输入和输出张量到内存句柄(memory handle).
参数
id:张量ID; hMem:
DLA内存句柄,由allocateSystemMemory()返回。
返回:NvError。
8)提交任务进行推理
提交推理任务,它阻塞了调用。
NvError nvdla::IRuntime::submit()
返回: NvError。
9)卸载神经网络资源
NvError nvdla::IRuntime::unload(int instance)
卸载网络数据,如果没有使用相同网络提交推理的计划,则释放所有用于神经网络的资源。
返回:NvError。
可移植层
UMD的可移植层(Portability layer)实现了访问NVDLA设备、分配DMA内存和将任务提交给底层驱动程序的功能。为了这个功能,UMD必须与KMD通信,并且通信接口依赖于操作系统。可移植层抽象了这个依赖于操作系统的接口。
主要类型和接口介绍
NvError类型
错误代码的枚举。
NvDlaHeap类型
内存堆来分配内存,NVDLA支持两个内存接口。通常,这些接口连接到DRAM(系统存储器)和内部SRAM,KMD可以根据内存类型维护单独的堆进行分配。
NvDlaMemDesc类型
内存描述符,它包括内存句柄和缓冲区大小。
NvDlaTask类型
DLA任务结构体,运行时驱动程序使用来自可加载的信息来填充它,并且被可移植层用来以实现定义的方式向KMD提交推理任务。
NvError NvDlaInitialize(void **session_handle)
该API用于初始化可移植层的会话(session),这可能包括分配维护诸如设备上下文、文件描述符之类的信息所需的一些结构,该函数可以为空。
参数: session_handle–更新会话句柄地址的[out]指针,该地址可以在此后调用的任何API中传递,可移植性层可以使用该地址来恢复会话信息。
返回 NvError。
void NvDlaDestroy(void * session_handle)
释放所有会话资源。
参数:session_handle–从NvDlaInitialize()获得的会话句柄地址。
NvError NvDlaOpen(void * session_handle,NvU32 instance,void **device_handle)
这个API应该用于打开DLA设备实例。
参数:session_handle–从NvDlaInitialize()获得的会话句柄地址。instance–SoC中有多个实例时要使用的NVDLA实例。
device_handle–[out]指针,用于更新设备上下文,它用于获取需要设备上下文进一步回调所需的设备信息。
返回 NvError
void NvDlaClose(void * session _ handle,void *device_handle)
关闭DLA设备实例
参数:
session_handle–从NvDlaInitialize()获得的会话句柄地址。
device_handle–从NvDlaOpen()获得的设备句柄地址。
NvError NvDlaSubmit(void *session_handle,void *device_handle,NvDlaTask *tasks,NvU32 num_tasks)
向KMD提交推理任务
参数:
session_handle–从NvDlaInitialize()获得的会话句柄地址。
device_handle–从NvDlaOpen()获得的设备句柄地址。
tasks–要提交进行推理的任务列表。
num_tasks–要提交的任务数。
返回 NvError
NvError NvDlaAllocMem(void *session _ handle,void *device_handle,void **mem_handle,void **pData,NvU32 size,NvDlaHeap heap)
分配、固定和映射DLA引擎可访问内存。例如,对于DLA位于IOMMU之后的系统的情况,此调用应确保为该内存创建IOMMU映射。对于Linux,内部实现可以使用现成的框架,比如ION。
参数:
session_handle–从NvDlaInitialize()获得的会话句柄地址。
device_handle–从NvDlaOpen()获得的设备句柄地址。
mem_handle–[out]由这个函数更新的内存句柄。
pData–[out]如果分配和映射成功,则提供一个虚拟地址,通过该地址可以访问内存缓冲区。
Size–要分配的缓冲区大小。
堆–实现定义的内存堆选择。
返回 NvError
NvError NvDlaFreeMem(void *session_handle,void *device_handle,void *mem_handle,void *pData,NvU32 size)
释放掉NvDlaAllocMem()分配的DMA内存。
参数:
session_handle–从NvDlaInitialize()获得的会话句柄地址。
device_handle–从NvDlaOpen()获得的设备句柄地址。
mem_handle–从NvDlaAllocMem()获得的内存句柄地址。
pData–NvDlaAllocMem()返回的虚拟地址。
Size–分配的缓冲区的大小。
返回 NvError
void NvDlaDebugPrintf(const char *format,...)
向调试控制台输出消息(如果有)。
参数: format–指向格式字符串的指针。