oneDNN
什么是oneDNN?
oneAPI 深度神经网络库 (oneDNN) 是一个开源的跨平台性能库,其中包含用于深度学习应用程序的基本构建块。该库针对英特尔架构处理器、英特尔处理器显卡和 Xe 架构显卡进行了优化。实验性的支持Arm* 64 位架构 (AArch64) 和 OpenPOWER* Power ISA (PPC64) 等其他架构。
oneDNN 适用于有兴趣提高英特尔 CPU 和 GPU 上应用性能的深度学习应用和框架开发人员。
oneDNN 支持:
- CNN 基元(卷积、内积、池化等)
- RNN 原语 (LSTM, Vanilla, RNN, GRU)
- 规范化(LRN、批处理、图层)
- 元素操作(ReLU,Tanh,ELU,Abs等)
- Softmax, Sum, Concat, Shuffle
- 从优化的数据布局重新排序
- 8 位整数、16 位、32 位和 bfloat16 浮点数据类型
编程模型
基本概念
介绍
oneDNN 编程模型的概述,并讨论了关键概念,包括基元、引擎、流和内存对象。
实质上,oneDNN 编程模型包括执行一个或多个基元来处理一个或多个内存对象中的数据。执行是在流的上下文中的引擎上执行的。
如图简要介绍了这些实体之间的关系。
Primitives
oneDNN是围绕基元(dnnl::primitive)的概念建立的。基元是一个封装了特定计算的对象,如前向卷积、后向LSTM计算或数据转换操作。此外,使用基元属性(dnnl::primitive_attr),某些基元可以表示更复杂的融合计算,如前向卷积后的ReLU。
基元和纯函数之间最重要的区别是,基元可以存储状态。
基元的一部分状态是不可变的。例如,卷积基元存储张量形状等参数,并可以预先计算其他依赖性参数,如缓存阻塞。这种方法使 oneDNN 基元能够预先生成专门为要执行的操作定制的代码。oneDNN编程模型假定,执行预计算所需的时间可通过重复使用同一基元来多次执行计算来得到补偿。
基元状态的可变部分被称为scratchpad。它是一个内存缓冲区,基元可仅在计算期间用于临时存储。scratchpad既可由基元对象拥有(这使该对象非线程安全),也可作为执行时参数。
Engines
引擎(dnnl::engine)是计算设备的抽象:CPU、系统中的特定GPU卡等。大多数基元都是为了在一个特定的引擎上执行计算而创建的。
Streams
流(dnnl::stream)封装了与特定引擎绑定的执行上下文。它们可以对应于OpenCL命令队列。
Memory Objects
内存对象(dnnl::memory)封装了分配给特定引擎、张量尺寸、数据类型和内存格式(张量指数映射到线性内存空间中的偏移的方式)的内存句柄。在执行过程中,内存对象被传递给基元。
Levels of Abstraction
oneDNN对基元和内存对象进行了多层次的抽象,以便为用户提供最大的灵活性。
在逻辑层面上,该库提供了以下抽象概念
- 内存描述符(dnnl::memory::desc)定义了张量的逻辑尺寸、数据类型以及数据在内存中的布局格式。特殊的格式any(dnnl::memory::format_tag::any)表示实际的格式将在以后被定义。
- 操作描述符(每个支持的基元都有一个)描述了一个操作的最基本属性,而没有指定例如将使用哪个引擎来计算它们。例如,卷积描述符描述了源、目标和权重张量的形状、传播种类(向前、向后的数据或权重)以及其他与实施无关的参数。
- 基元描述符(dnnl_primitive_desc_t;在 C++ API 中,每个支持的基元都有多种类型)处于操作描述符和基元之间的抽象级别,可用于检查特定基元实现的细节,如通过查询实现内存格式传播的预期内存格式(请参阅内存格式传播),而无需完全实例化基元。
内存格式传播
内存格式传播是正确使用oneDNN需要充分理解的核心概念之一。
参考代码
当你使用占位符内存格式 dnnl::memory::format_tag::any
为输入或输出创建卷积和内积基元时,卷积和内积基元选择内存格式。选择内存格式基于不同的情况,如硬件和卷积参数。卷积的推荐做法是使用占位符内存格式,因为在大多数有卷积的拓扑结构中,卷积是计算最密集