阿里妹导读: 今年, 平头哥发布全球最强 AI 推理芯片含光 800. 在杭州城市大脑的业务测试中, 1 颗含光 800 的算力相当于 10 颗 GPU. 在业界标准的 ResNet-50 测试中, 含光 800 推理性能达到 78563IPS, 比目前业界最好的 AI 芯片性能高 4 倍. 究竟是什么模型打造出如此性能, 今天, 我们请来阿里高级技术专家一皓来聊聊它的编程模型.
前言
当我们手拿含光这把神兵利器的时候, 首先要了解这把剑的精华. 比如杨过用的玄铁重剑, 其剑法要诀是 "重剑无锋, 大巧不工", 其中境界, 远胜世上诸般最巧妙的剑招, 越是平平无奇的招数, 对方越难抗御, 如挺剑直刺, 劲力强猛, 轻重刚柔随心所欲, 刚劲柔劲混而为一, 威力远比变幻奇妙的剑招更大. 又如金蛇剑, 形状奇特, 却因此比之寻常长剑增添了不少用法, 配合 "金蛇秘笈", 颇多招式看来绝无用处或者甚不可解的, 尽成厉害招术. 虽然这是武侠小说中的创意, 但金大侠确是有深意: 剑不同而诀不同, 需为剑量身打造剑法心诀, 才能发挥剑的威力.
如果由我来理解, 含光的精髓在于 "利", 如一把削铁如泥的宝剑! 使用如此锋利的宝剑, 其剑道需要由繁化简, 由简而快, 达到返璞归真. 在芯片领域, 特定领域架构 (Domain-Specific Architecture, DSA) 正是一种由繁化简的思路. 虽然怎么去做到真正的由繁化简其实很难!
编程模型(Programming Model), 就是从开发者和用户的角度出发, 基于 NPU 的硬件系统, 软件系统的架构和特性, 提炼出怎么理解和使用 NPU 的要诀, 主要包括:
硬件模型: 设备和核心的抽象及相互关系
存储模型: 即存储的分级, 各级主要特性和相互的关系
深度神经网络在含光 NPU 系统上的结构模式
深度神经网络在含光 NPU 系统上的布局模式
执行网络的异构和并行模式
通过对这些的了解, 我们大致能在顶层抽象上, 对含光 800NPU 建立一个整体的模型, 理解编程使用含光 NPU 的基本要点, 为后面具体使用打下坚实的基础.
深度神经网络推理计算模型
在开始介绍含光 800NPU 的编程模型之前, 有必要看看深度神经网路推理的计算模型. 分析一下 CPU,GPU, 以及其他 AI 芯片的计算模型. 通过这个对比, 应该可以帮助大家理解含光的特点和编程模型.
推理计算特性
首先借用网络上的训练和推理介绍图来看看推理计算的最简单的计算模型是什么. 虽然这是一个非常非常基本的知识点, 还是请允许我再强调一遍.
如果只关注计算部分, 我们可以这么简单地理解:
训练是双向计算, 先将输入和网络模型参数做前向计算得到结果, 然后根据标签, 做逆向的计算, 修改网络模型参数.
推理的计算是单向的, 网络模型参数是训练之后固定下来不会再改变的, 可以认为是一个常数, 输入和模型参数计算之后直接就得到推理的结果了. 可是在当前几乎所有芯片的实际计算模型中, 并没有特别的体现这一个特性.
其他芯片计算模型
深度神经网络确实是名副其实的比较有 "深度"! 这就导致了当前主流的网络模型参数量都很大. 即使有很多轻型模型的网络模型设计, 加上量化, 剪枝等技术配合, 大部分网络模型还是远远大于当前芯片的 Level1 缓存的大小, 何况缓存还需要存放输入, 输出, 和中间临时计算结果.
看看来自于网络的两张对比图, 分别是 CPU,GPU,TPU 在存储子系统架构和计算单位两个方面的对比.
AI 推理计算时, 大多数的芯片系统中, 虽然计算的单元有区别, 比如 CPU 以 scalar 为主, GPU Tensor Core 以 vector 为主, 华为 DaVinci AI Cube Core 以 Tensor 为单位, 但输入输出 (即激活 / activation) 和模型参数 (即权重 / weight) 是无差别对待的, 把他们都看作是计算中的等价的操作数. 从存储系统以及计算上都没有体现出任何区别. 因此在编程开发中, 用户不需要特别地关注模型参数的管理.
在 TPU 的架构中, 激活 Activation 和权重 Weight 有了区分. 特别的, TPU 可以显式地管理权重, 使用独立的权重 FIFO 来调度. 由于权重的简单和特殊性, 这种方式能简化调度, 提高 FIFO 利用率, 保证更高的吞吐量.
含光 800 硬件模型
含光的设计中, 最重要的一点, 就是利用相对比较大的一级存储, 将网络模型的权重布局和分配好, 保证权重一次上传之后, 就能一直保存在本地存储中, 避免了这部分数据的频繁地上传. 这样既降低了对带宽的需求, 也让整个计算流水线的设计变得相对简单了.
下图是含光 800NPU 的一个核心的示意图, 详细芯片的架构图可见上一篇介绍. 交代一些此处的用词:
设备(Device): 此处指的是含光 800NPU(Neural-network Processing Unit), 一个设备就是一块 NPU 卡.
核心(Core): 一个完整的计算核心, 可独立调度运行的最小单位, 组成如下图:
本地存储(Local Memory): 核内存储, 核间连接. 含光 800 NPU 核心简图
每个含光 800 NPU 核包含 4 个 Tensor Array 和对应的本地存储 (LM);LM 里存放权重, 激活(输入, 输出, 临时), 等各种计算所需数据. 权重是一次性装载后常驻 LM, 其他数据是每个批次(batch) 流水处理的.
含光 800 编程模型
存储
从系统的角度来看, 存储系统可以分为 4 种不同级别:
L0 Cache/Buffer
特殊用途的 cache/buffer, 比如给累积缓存 (Accumulation buffer) 等. 由于这部分属于硬件控制, 从编程的角度来说完全不可见. 编程模型通常不需要考虑这一级别的存储.
Local Memory
片上核内存储, HanGuangAI 软件栈分配控制. HanGuangAI 应用层不能直接访问和控制核内存储, 没有相关操作的任何 API, 但可以通过核心分配来间接地影响使用情况.
Host Memory
映射到设备地址空间的主机内存(PageLocked Host Memory),NPU 可以 DMA 访问. 软件栈负责分配和使用这部分存储. 应用层也可以通过 HanGuangAI 的 API 直接分配和使用.
System Memory
CPU 访问的主机内存, NPU 不能直接访问.
后面两种存储是应用层可访问的. 特别的, 应用层可以通过 HanGuangRT API 创建相关的张量, 并读写数据. 在上传和下载过程中, 如果按照 NPU 的规则直接把数据放到 Host Memory, 相比放到 System Memory, 可以少一次拷贝, 对整体性能功耗是有好处的. 当然由于这部分 (Host Memory) 大小比较有限, 所以需要快速周转, 提高存储的利用率.
深度网络模型
深度神经网络是以图的方式表达, 以算子 (Operator) 做为节点. 从一个训练好的网络模型到含光 NPU 上的可执行指令. 网络模型需要量化和编译两个步骤:
1)量化
含光 800 NPU 主要算子是 8-bit 和 / 或 16-bit 整形和运算. 需要使用量化操作将 32-bit 浮点精度的网路模型转化成对应的整形运算. 为了让量化更能充分利用含光 NPU 的设计, HanGuangAI 整合了量化的功能, 使用了一些特有的量化技巧来达到更好的效果和效率.
HanGuangAI 软件栈提供 python 的量化接口. 量化分为两个步骤: 校准和量化. 两个步骤分别在原来的模型中插入了适当的校准和量化算子, 这些算子有 HanGuangAI 提供, 做为原始框架的自定义算子. 请参考 HanGuangAI 相关文档了解校准和量化的细节.
上图是量化产生的网路图的部分节点示范图
2)编译
高效的推理运行模式, 是将整个网络做提前编译(AOT), 生成优化的后端代码. 运行时把整个网络看作一个整体执行运算. 我们把这个可执行的整体叫做一个运行引擎(Engine).
如果所有算子都能在含光 NPU 上执行的话, 这个网路就变是一个单独的引擎. 实际在一些网络中, 像 TensorRT 一样, 有些算子现在的含光 NPU 软件或者硬件不支持. 这时候, 我们需要对整个网络做合适地分割, 分成一段一段能支持的子网络, 然后再对每一段子网络进行编译, 这个过程是由 HanGuangAI 软件栈来完成.
编译后的图结构是一个经过算子融合简化的图结构. 包含一个或多个 NPU 引擎算子 (AliNPUEngineOp) 和 0 到多个原来的算子. 如图:
另外, 编译后的网络模型的格式和输入一样, 比如原来是 Tenorflow 的 pb 格式, 编译后输出还是 pb 文件格式. 这样在插入我们的自定义算子之后, 可以保证网络模型能在原来的框架中运行.
3)网络的结构模式
综上所述, 从网络结构和算子属性的角度来说, 用于推理的模型有以下 4 种模式:
原始模型(Original Float Model)
校准模型(Calibration Model)
量化模型(Quantized Model)
编译模型(Compiled Model)
另外, 通常来说, 剪枝压缩不会改变网络结构, 这里没有特别的讨论相关影响.
异构执行
编译网络模型在 NPU 上的计算执行, 是以一个 NPU 引擎算子为运行单位的. NPU 引擎算子将通过 HanGuangAI 的软件栈, 执行在含光 NPU 上.
如果一个网络模型的所有算子都被编译在一个引擎算子里, 推理的计算运行就比较简单了. 但如果网络模型包含其他算子, 或者被分割成了多个引擎算子, 这时候, 其他的算子需要在其他的设备上运行, 比如 CPU,GPU.
比如上图的编译模型, AliNPUEngineOp 是有 NPU 执行的, 而 NonNPUOp(SparseToDense)可以在 CPU 上执行.
这种异构计算可以由几种工作模式:
基于框架, 这是一种简单的方式, 利用框架的多设备异构执行的支持. 因为编译后的模型文件格式一样的, 所以如果原来有框架可以执行, 可利用框架来执行. 框架可以根据设置将其他的算子转到对应的设备上执行.
实际应用中, 很多用户有自己的推理引擎, 以前是直接使用比如 TensorRT 在 GPU 上做推理, TensorRT 不支持的算子, 会有 CUDA 或者其他库在 GPU 上执行, 或者使用 CPU 库在 CPU 上执行. 现在使用含光 NPU, 只需要使用 HanGuangRT, 将 NPU 引擎算子的支持加到这个推理引擎中, 然后把其他 NPU 不支持在其他设备上实现.
后续, 我们会提供更多的方式友好地支持异构执行, 保证执行的方便和流畅.
权重装载
如前所述, 含光 NPU 的高效工作模式, 是需要将权重数据等全部事先分配好空间, 这样就避免运行过程中重复地从 Host 上传权重数据到 LM. 由于我们有很多不同的业务情况, 有些模型很小, 只需要少数的几兆空间, 有的模型很大, 有的模型被分割成多个引擎, 还有很多业务需要几个到十来个模型一起协同工作. 因此我们需要提供灵活多变的装载模式.
我们在编译的时候, 根据引擎需要的存储大小和可以使用的核心数等信息, 我们提供一些不同的解决方案:
单核单引擎
来源: https://yq.aliyun.com/articles/739823