AI系统-7Pytorch数字识别实战及算子介绍 中提到使用Pytorch在PC上实现的AI算法,AI系统-17NPU架构设计介绍 中提到了NPU中的硬件实现,那么现在就有一个问题:AI算法怎么在硬件上进行部署适配落地?首先就是NPU调度器把算法拆分做任务并行计算进行调度,其次就是需要AI编译器的支持进一步的在NPU硬件上并行。
1. NPU调度器介绍
这里还以华为的Ascend为例,参考:zhuanlan.zhihu.com/p/707141458…
可以看到芯片内部集成了硬件的任务调度器模块,这个模块主要是干啥的呢?我们首先从AI APP开始说明:
APP在不同Level的编程模型会被转换为不同的表达形式,即Stream、Task和Block,如上图所示。
所有的这些表达在不同级别的调度器中并行执行,详细介绍如下:
上图可以看到Pytorch、Tensorflow和MindSpore等DNN开发框架处于整个开发软件栈的顶端。
- 应用层:多个应用可以在同一个Ascend SoC上并行执行。
- Stream/Task层:图编译器会将每个应用编译成多个Stream,每个Stream中有多个Task。多个Stream的Task可以在TS (Task Scheduler)的调度下并行执行,同一个Stream的Task串行执行。
- Block层:每个Task可以被分成若干个Block(需要由开发者或编译器显式描述),不同的Block可以被分发到不同的Core上执行。
Ascend提供了软硬件结合的调度方案。Ascend开发软件栈支持原生的可微分编程范式和原生的执行模式,在开发效率和计算效率之间进行了折中。MindSpore是Ascend core的专用框架。它可以充分的利用Ascend的计算资源,实现很高的计算效率。一方面,开发者可以轻易的将DNN模型部署到各种Ascend设备上,这些设备也可以配备不同的Ascend core上。开发者可以使用high-level的编程模型实现一次性的开发和按需部署。另一方面,拥有更多架构知识的专家可以利用low-level的编程模型和库进一步提高计算效率。
DNN开发框架的输出被称为“Graph”,它表示算法中粗粒度的依赖关系。然后,如上图所示,Graph Engine会对“Graph”进行切分,将适合CPU逻辑处理的算子切分到CPU子图,将计算密集型算子切分到专用加速器子图,即被转换成Stream。其中,Stream是由若干个有序的Task组成。
2. 调度器的设计
如上图是AI芯片中A核负责任务分发,Accelerators也就是NPU负责任务执行,其拥有大量异构加速器的软硬件系统。由于加速器可能需要使用CPU生产的数据,因此内存的coherence和consistence的设计十分具有挑战。并且操作系统的堆栈、编译器以及调度器都需要重新设计,特别是为了进行并行计算,所以要在A核和NPU直接需要加一些执行并行的软硬件:调度器。
2.1 调度模型
学界和工业界一直在寻找不同级别的并行编程模型来提高计算性能,通常的并行编程模型有ILP (Instruction Level Parallelism)、TLP (Thread Level Parallelism)和DLP (Data Level Parallelism),如Figure 2。这三种并行模型均与计算机体系结构的发展相吻合。处理器中的指令乱序调度使得ILP的能力大幅增长;多核的设计使得TLP变尤为重要;vector计算单元的增加,例如GPGPU,使得DLP得到有效利用。
NPU芯片拥有大量的专用异构加速器。ILP、TLP和DLP并不能充分发挥异构加速器的计算潜能,因此选择一种新的编程模型作为AI算法和芯片的接口迫在眉睫。异构加速器的使用天然为task作为计算单元铺平了道路,TLP (Task Level Parallelism)也顺理成章的被引入进来。其中,task是将一组指令定义成原语的抽象层级,该原语可以在程序中重复使用。用户的线程可以由多个task组成,通常一个task是由芯片中一种加速器执行的。
如上图所示,多个task可以根据不同的算法和应用场景构成DAG实现细粒度并行,task之间的边表示依赖关系。
2.2 软件调度器
通常,基于task的并行计算环境由两个组件组成:(1)任务并行API和(2)任务runtime系统[2]。前者定义了开发人员描述并行性、依赖性、数据分发等的方式,而后者定义了环境的效率和能力。runtime确定了支持的体系结构、调度的目标、调度策略和异常处理等。但是,这种基于软件的调度器具有固有的缺点:
- 在CPU上运行调度程序实例是一种开销,当任务粒度较小、加速器数量很多时,软件调度会占用大量的CPU资源,并且使得端到端的计算效率发生显著下降;
- 任务在加速器上执行结束,必须通过具有长延迟的中断来通知CPU,进一步导致调度的latency变大;
- 调度的软件开销、延迟开销导致无法编译出细粒度的任务,导致无法利用细粒度的并行性;
2.3 硬件调度器
如果NPU系统中包含一个SoC级别的硬件任务调度器,则可以大大减轻上述缺点,编译器可以编译出细粒度的任务来充分利用细粒度的并行。如上图,将HTS (Hardware Task Scheduler)加入到包含CPU和各种加速器的系统中。
CPU可以将任务和任务间的依赖关系都推送到HTS,CPU只需要轮询任务完成队列、处理异常等。HTS会维护若干个任务队列,类似与OoO CPU中执行的指令。HTS会维护整个系统中每个加速器的忙闲状态,并将任务发送给空闲的加速器上。当任务执行结束时,加速器写任务完成描述符来通知HTS,这比中断处理快几个数量级。HTS可以处理任务间的依赖关系,当某个任务依赖解除时,HTS会以无序的方式调度该任务,这可以在异构ADS系统中带来巨大的速度提升。
硬件调度器TS,其实是一个CPU核和运行上面的固件,
- CPU可以为ARM或者性能高的RISCV,多核当然更好,调度的是stream里面的task,直接一个stream绑定一个固定的核那性能拉满,比较奢侈
- 固件基本RTOS够用,例如FreeRTOS,不能太复杂了
另外硬件子模块需要有自己的一些外围器件:
- 核间通信的Malibox硬件
- SRAM存储使用
- WDT看门狗
- UART串口
- CRU时钟复位电源硬件
- DMA支持提高数据交互速度
- 跟NPU通信的NoC等总线
- 其他的一些通信硬件
对应NPU调度器的启动,对应SoC芯片,一般固件都在flash的fip包里面存的,可以在ATF BL2搬运到NPU调度器核的SRAM或者TCM,然后触发执行。
作为SoC芯片中的大哥A核也是可以控制NPU调度器,例如系统休眠或者节能等需要停掉AI业务,那么A核会有一个驱动程序管理NPU调度器的生命周期。
3. AI推理引擎
参考:zhuanlan.zhihu.com/p/687336210…
图中可以看出,调度器之上有一个AI Runtime,也就是推理引擎,表面上看就是生成为了完成一个AI任务的有组织的Task图,在实际运行中其在A核中运行,如果要运行那些Task则通过队列发送到NPU调度器去进一步调度。
这些Task为组成成模式文件存储在UFS等文件系统里面,在运行时会被加载到DDR里面,并形成队列。但是这些Task从哪里来?这就涉及到很多幕后的AI算法部署工作。
3.1 AI推理引擎架构
我们从AI推理引擎的角度看下这个AI Runtime在那个位置,如下图:
可以看到这个AI Runtime也只是在运行时来组织AI任务,而这些Task在编译的时候就确定了,会在NPU运算的时候体现。所以在AI Runtime之上还有一堆的工作要做,对于AI Runtime3.6章节中进行具体介绍。
这些所有的功能涉及到模型在实际硬件中的落地,引出AI推理引擎概念:推理引擎作为 AI 系统中的关键组件,负责将训练好的模型部署到实际应用中,执行推理任务,从而实现智能决策和自动化处理。
优化阶段聚焦于将训练好的模型转换并优化成适合部署的形式:
- 模型转换工具负责将模型从研究阶段的格式转换为高效执行的格式,并进行图优化,减少计算负担。
- 模型压缩通过技术如剪枝、量化、知识蒸馏等减小模型大小,使之更适用于资源有限的环境。
- 端侧学习允许模型在部署后继续学习和适应新数据,无需返回服务器重新训练,提升了模型在特定场景或用户个性化需求下的表现。
- 其他组件包括 Benchmarking 工具,用于性能评测和调优指导,以及应用演示(App Demos),服务于模型能力展示与实战反馈收集,共同助力模型的高效部署与持续优化。
运行阶段确保模型在目标设备上的高效执行:
- 调度层管理模型加载、资源分配及任务调度,根据设备情况灵活安排计算任务。
- 执行层直接执行模型计算,针对不同硬件优化运算逻辑,有效利用 CPU、GPU 等资源。
一款好的推理引擎可以为用户服务带来实质性的收益,如上图中展示的柱状图,每个柱状图代表不同推理引擎在不同型号手机上的性能对比。图中出现推理引擎有 NCNN、MACE、TF-Lite、CoreML 和 MNN。
- NCNN:NCNN 是由腾讯优图实验室开发的一个轻量级、高性能的神经网络推理框架,设计初衷是为了在移动端和嵌入式设备上实现极致的推理速度。它支持离线模型转换,能够直接加载和执行从 Caffe、TensorFlow、PyTorch 等框架训练得到的模型。NCNN 的一个显著特点是无第三方依赖,且完全针对移动端优化,通过直接利用 CPU 的 SIMD 指令集来实现高性能计算,同时支持 GPU 加速。它的设计使得开发者可以在没有 GPU 的情况下,仍然获得较快的推理速度。
- MACE:MACE 是小米推出的移动端 AI 计算引擎,全称为 Mobile AI Compute Engine。MACE 设计用于优化在移动设备上的神经网络模型推理效率,支持 CPU、GPU 和 DSP 等多种硬件加速。它提供了一套完整的模型转换工具链,能够将 Caffe、TensorFlow、PyTorch 等框架训练的模型转换成 MACE 的运行格式。MACE 通过高度优化的内核库和硬件加速,实现模型在移动端的高效运行,特别关注功耗和性能的平衡。
- TF-Lite:TF-Lite 是谷歌 TensorFlow 团队推出的一个轻量级解决方案,专为移动和嵌入式设备设计。它是 TensorFlow 框架的一个子集,专注于模型的推理部分,旨在提供低延迟和低功耗的机器学习推理。TF-Lite 支持模型量化和自定义算子,能够显著减少模型体积和提高运行效率。它支持 CPU、GPU 和 NNAPI(Android 神经网络 API)等多种后端,便于开发者根据设备特性选择最佳推理策略。
- CoreML:CoreML 是 Apple 为 iOS 设备设计的机器学习框架,支持在 iPhone 和 iPad 上运行预先训练好的机器学习模型,无需互联网连接。CoreML 不仅限于神经网络,还支持多种机器学习模型,如支持向量机、决策树等。它紧密集成于 iOS、macOS、watchOS 和 tvOS 生态系统中,提供了非常低的延迟和高效的推理性能,特别适合苹果生态内的应用开发。
- MNN:MNN 是阿里巴巴达摩院开发的轻量级深度学习推理引擎,旨在为移动端和边缘设备提供高性能的推理能力。MNN 在设计上特别聚焦于内部业务模型的优化,如针对人脸检测等任务进行了深度优化,能够在老旧的硬件上(如 iPhone 6)实现快速推理。它支持多平台部署,包括 iOS、Android、Linux 等,以及 CPU、GPU 等多种硬件加速。MNN 通过半自动搜索的方式优化模型执行,实现了模型和设备多样性的高效支持,同时保持了模型更新的灵活性。
3.2 模型转换工具
模型转换首先面临的是格式的跨越。想象一个模型,最初在如 TensorFlow、PyTorch 或 MindSpore 这样的科研友好型框架下被训练出来,它的原始形态并不直接适用于生产环境中的推理引擎。因此,转换工具承担起了“翻译者”的角色,将模型从其诞生的框架语言翻译成一种或多种行业广泛接受的标准格式,如 ONNX,这不仅增强了模型的可移植性,也为模型的后续处理和部署提供了通用的接口。
- 跨框架兼容性:支持将模型从一种框架(如 TensorFlow、PyTorch)转换为另一种(如 ONNX、TensorRT),使得模型能够在不同的推理引擎上执行,增强了应用开发的灵活性和平台的通用性。
- 版本适应性:解决因框架升级导致的模型兼容问题,确保旧模型可以在新版推理引擎上正确运行,或新模型能回溯支持老版本系统。
- 标准化输出:转换后的模型通常被格式化为一种或多种行业标准格式,如 ONNX,这种标准化促进了生态系统中工具和服务的互操作性。
格式转换仅仅是冰山一角。真正的挑战在于如何对计算图进行深度优化,这是决定模型能否高效执行的关键。计算图,作为神经网络结构的数学抽象,其优化涉及到对图结构的精细剖析与重塑。其中,算子融合、布局转换、算子替换与内存优化,构成了优化的核心四部曲,每一步都是对模型性能极限的深刻探索与精心雕琢。 详细这里不说明了,可以参考:zhuanlan.zhihu.com/p/687336210…
3.3 模型压缩
模型压缩作为 AI 领域的一项核心技术,也是推理引擎架构中不可缺少的一部分,它旨在通过一系列精巧的策略减少模型的大小,同时保持其预测性能尽可能不变,甚至在某些情况下加速训练和推理过程。这一目标的实现,离不开量化、知识蒸馏、剪枝以及二值化等关键技术的综合运用,它们各自以独特的方式对模型进行“瘦身”,而又尽可能不牺牲其表现力。这些内容将会在后续文章中详细讲解,故此处只作简单介绍。
- 量化
量化技术的核心思想在于,将模型中的权重和激活函数从高精度浮点数转换为低精度数据类型,如 8 位整数或更甚者,二进制形式。这一转换不仅显著降低了模型的存储需求,也因为低精度运算在现代硬件上的高效实现而加速了推理过程。当然,量化过程中需要精心设计量化方案,如选择合适的量化区间、量化策略和误差补偿方法,以确保精度损失控制在可接受范围内,实现性能与精度的平衡。
2. 知识蒸馏
知识蒸馏,一个形象的比喻,是指利用一个庞大而复杂的“教师”模型(通常是准确率较高的模型)来指导一个较小的“学生”模型学习,使其在保持相对较高精度的同时,模型规模大幅减小。这一过程通过让学生模型模仿教师模型的输出分布或者直接利用教师模型的软标签进行训练,实现了知识的传递。知识蒸馏不仅限于模型大小的缩小,还为模型的轻量化设计开辟了新的思路,尤其是在资源受限的设备上部署模型时。
3. 剪枝技术
剪枝技术,正如其名,旨在去除模型中对预测贡献较小或冗余的权重和连接,实现模型结构的简化。这包括但不限于权重剪枝、通道剪枝和结构化剪枝等策略。通过设定一定的剪枝阈值或利用稀疏性约束,模型中的“无用枝条”被逐一识别并移除,留下的是更为精炼的核心结构。值得注意的是,剪枝过程往往伴随有重新训练或微调步骤,以恢复因剪枝可能带来的精度损失,确保模型性能不受影响。
4. 二值化
二值化,顾名思义,是将模型中的权重乃至激活值限制为仅有的两个离散值(通常是 +1 和 -1)。这种极端的量化方式进一步压缩了模型体积,简化了计算复杂度,因为二值运算可以在位级别高效实现。尽管二值化模型在理论上极具吸引力,实践中却面临着精度下降的挑战,需要通过精心设计的训练策略和高级优化技术来弥补。
3.4 端侧学习
端侧学习,作为 AI 领域的一个前沿分支,致力于克服传统云中心化模型训练的局限,通过将学习能力直接赋予边缘设备,如手机、物联网传感器等,实现数据处理的本地化和即时性。这一范式的两大核心概念——增量学习和联邦学习,正在重新定义 AI 模型的训练和应用方式,为解决数据隐私、网络延迟和计算资源分配等问题提供了创新途径
为了支撑高效的端侧学习,一个完备的推理引擎不仅仅是模型执行的平台,它还需要集成数据预处理、模型训练(Trainer)、优化器(Opt)以及损失函数(Loss)等核心模块,形成一个闭环的端到端解决方案。
联邦学习,则为解决数据隐私和跨设备模型训练提供了一条创新路径。在这一框架下,用户的个人数据无需上传至云端,而是在本地设备上进行模型参数的更新,之后仅分享这些更新(而非原始数据)至中心服务器进行聚合,形成全局模型。这一过程反复进行,直至模型收敛。联邦学习不仅保护了用户隐私,减少了数据传输的负担和风险,还允许模型从分布式数据中学习到更加丰富和多样化的特征,提升了模型的普遍适用性。其技术挑战在于设计高效且安全的参数聚合算法,以及处理设备异构性和通信不稳定性带来的问题。
3.5 中间表达IR
在现代推理引擎的设计与实现中,"中间表达"(Intermediate Representation, IR)扮演了至关重要的角色,它是连接模型训练与实际推理执行之间的桥梁。中间表达的核心目标是提供一种统一、高效的模型描述方式,使得不同来源、不同架构的模型能够被标准化处理,进而优化执行效率并增强平台间的兼容性。这一概念深入到模型优化、编译及执行的每一个环节,其重要性不言而喻。
中间表达为模型提供了丰富的优化空间。对计算图的优化工作大都集中在对模型进行中间表达之后,通过静态分析、图优化等技术,可以对模型进行裁剪、融合、量化等操作,减少计算量和内存占用,提升推理速度。这一过程如同将高级编程语言编译为机器码,但面向的是神经网络模型。
统一的中间表达形式确保模型能够在云、边、端等多类型硬件上自由部署,实现一次转换、处处运行的目标。它简化了针对特定硬件的适配工作,使得模型能在不同环境间无缝迁移,满足多样化应用需求。
围绕中间表达,可以形成一个包含工具链、库函数、社区支持在内的完整生态系统。开发者可以利用这些资源快速实现模型的调试、性能监控和持续优化,加速产品从原型到生产的整个周期。
3.6 AI Runtime
Runtime,即推理引擎的执行引擎,负责将中间表达形式的模型转换为可执行的指令序列,并将其部署到目标设备上执行。执行引擎不仅仅涉及模型的加载与执行两个基本步骤,还深入涵盖了多种策略和技术,以优化资源利用、提升运行效率,确保在多样化的硬件平台上都能实现高性能表现。我们以自动驾驶为例,来介绍 Runtime 技术在模型推理中的作用。
动态 Batch 处理
动态批处理(Batch)技术为推理引擎带来了前所未有的灵活性,它允许系统根据实时的系统负载状况动态地调整批次大小。在负载较轻的时段,如清晨或深夜,当车辆较少、系统接收的图像帧数量降低时,推理引擎能够智能地将多个图像帧合并成一个较大的批次进行处理。这一策略不仅显著提高了硬件资源的利用率,如 GPU 的大规模并行处理能力,而且减少了单位请求的计算开销,使系统能够在较低的负载下维持高效的推理性能。
反之,在高峰时段或紧急情况下,当系统面临高负载的挑战时,动态批处理技术能够迅速减少批次大小,确保每个请求都能得到及时响应,从而保证了自动驾驶系统的即时性和安全性。这种能够根据实时负载动态调整批次大小的能力,对于自动驾驶系统应对不可预测的流量波动至关重要,它不仅提升了系统的稳定性,还确保了在不同负载情况下系统都能保持高效运行。
异构执行
现代硬件平台融合了多元化的计算单元,包括 CPU、GPU 以及 NPU(神经网络处理器)等,每种处理器都拥有其独特的优势。异构执行策略通过智能分配计算任务,能够充分利用这些不同处理器的性能特点。具体而言,该策略会根据模型的不同部分特性和当前硬件状态,将计算任务分配给最合适的处理器执行。例如,对于计算密集型的卷积操作,它们通常会被卸载到 GPU 或 NPU 上执行,因为这类处理器在处理大量矩阵运算时表现出色;而涉及复杂控制流和数据预处理的任务,则更适合交由 CPU 来处理。
对于自动驾驶,物体检测模型经常需要执行多种类型的计算任务。其中,卷积层由于其计算密集型的特性,非常适合在 GPU 或 NPU 上执行,以充分利用其强大的并行处理能力。而逻辑判断、数据筛选等依赖复杂控制流的操作,则更适合在 CPU 上执行。通过采用异构执行策略,自动驾驶系统的推理引擎能够自动将卷积层任务调度到 GPU 等高性能处理器上,同时将数据预处理和后处理任务分配给 CPU 处理,从而实现整体计算流程的高效与快速。这种策略不仅提升了系统的性能,还确保了自动驾驶系统在各种场景下都能保持出色的响应速度和准确性。
内存管理与分配
在推理过程中,高效的内存管理和分配策略是确保运行效率的重中之重。这些策略涵盖了多个方面,如重用内存缓冲区以减少不必要的数据复制,智能地预加载模型的部分数据到高速缓存中以降低访问延迟,以及实施内存碎片整理机制来最大化可用内存资源。这些措施不仅有助于降低内存占用,还能显著提升数据读写速度,对模型的快速执行起到至关重要的作用。
在自动驾驶车辆的过程中,实时处理高分辨率图像对内存资源提出了极高的挑战。为了应对这一挑战,推理引擎一般采用智能化的内存管理策略。例如,通过循环缓冲区重用技术,推理引擎能够在处理新图像帧之前,复用先前帧所占用的内存空间来存储特征图等中间结果。这种做法不仅避免了频繁的内存分配与释放操作,减少了内存碎片的产生,还显著提高了内存使用效率和系统的整体响应速度。这些精细化的内存管理策略确保了自动驾驶系统能够高效、稳定地运行,为实时、准确的决策提供支持。
大小核调度
在移动设备上,大核(高性能核心)与小核(低功耗核心)之间的性能差异显著,为了最大化硬件资源的使用效率,推理引擎必须能够动态调整计算任务在这两种核心之间的分配。这要求推理引擎具备先进的调度能力,以便根据当前的任务负载和类型,智能地将任务分配给最合适的处理器核心。
对于采用大小核(big.LITTLE 架构)的处理器,推理引擎可以采用精细化的任务调度策略。具体来说,它可以将计算密集型、对性能要求高的任务,如自动驾驶中的车辆路径规划等复杂逻辑运算,分配给高性能大核处理,以确保足够的计算能力和处理速度。而对于数据预处理、简单状态监控等轻量级任务,则可以交给低功耗小核完成,以节约能耗并延长续航时间。
在自动驾驶的场景下,推理引擎的这种动态负载均衡能力显得尤为重要。它可以根据车辆实际运行中的任务需求,实时调整任务在大小核之间的分配,从而在确保处理复杂任务时拥有足够算力的同时,也能在执行轻量级任务时有效降低能耗,为自动驾驶系统提供更为高效、稳定和节能的运行环境。
多副本并行与装箱技术
在分布式系统或多核处理器架构中,多副本并行技术展现出其独特的优势。该技术通过创建模型的多个副本,并分配给不同的计算单元进行并行执行,实现了线性或接近线性的性能加速。与此同时,装箱(Batching)技术作为一种有效的并行处理策略,通过将多个独立的请求合并成一个批次进行集中处理,显著提升了系统在面对大量小型请求时的吞吐量和资源利用率。
在自动驾驶的实时应用中,对延迟响应的要求极为严格。多副本并行技术在这里得到了完美的应用,特别是在多 GPU 系统中。每个 GPU 运行模型的一个副本,能够同时处理多个传感器数据流,实现并行推理,从而大幅缩短决策时间,确保车辆能够快速响应各种路况。另一方面,装箱技术在处理单个高分辨率图像帧时同样表现出色。通过将图像分割成多个小区域,每个区域作为一个小批次进行处理,不仅保证了实时性,还显著提高了系统的整体吞吐量,使自动驾驶系统能够在复杂的驾驶环境中保持高效运行。
3.7 高性能算子
算子优化
算子优化是提高模型运行效率的关键。通过对模型中的算子进行优化,可以有效地减少计算量、降低内存使用、提高计算速度。常见的优化方法包括:
- 融合优化:在神经网络模型中,许多算子之间可能存在冗余的计算步骤或内存拷贝操作。融合优化旨在将相邻的算子合并成一个单一的算子,从而减少整体的计算次数和内存使用。这种优化策略能够显著提高模型的计算效率,特别是在硬件加速器(如 GPU 或 TPU)上运行时效果更为显著。
- 量化优化:量化是一种将浮点数运算转换为整数运算的技术,以减少计算复杂性和提高模型运行速度。通过降低数据的表示精度,量化可以减少计算所需的资源,并可能使模型在资源受限的设备上运行。尽管量化可能会导致模型精度的轻微下降,但在许多应用中,这种精度损失是可以接受的。
- 稀疏优化:稀疏优化是针对稀疏矩阵或稀疏张量进行的特殊优化。在神经网络模型中,权重矩阵或特征张量可能包含大量的零值元素,这些零值元素在计算过程中不会产生任何贡献。通过稀疏优化,我们可以跳过这些无效计算,只处理非零元素,从而提高计算效率。例如,使用稀疏矩阵乘法算法来替代常规的矩阵乘法算法,可以显著减少计算量。此外,稀疏优化还可以与量化优化相结合,进一步降低计算复杂度和内存占用。
算子调度
算子调度是高性能计算中至关重要的一个环节,它涉及到根据硬件资源的实际可用性和算子的特性,来合理规划和决定算子的执行顺序以及执行位置。在构建高性能算子层时,算子调度策略的选择直接影响到整个系统的计算效率和性能。
- 异构调度
异构调度是一种智能的算子调度策略,它根据算子的类型、复杂度和计算需求,结合不同硬件的特性(如 CPU、GPU、FPGA 等),将算子分配到最适合其执行的硬件上。例如,对于计算密集型算子,可能会优先将其调度到 GPU 上执行,因为 GPU 拥有更多的计算核心和更高的并行计算能力;而对于需要频繁内存访问的算子,可能会选择在 CPU 上执行,因为 CPU 在内存访问方面更具优势。通过异构调度,可以充分发挥不同硬件的优势,实现计算资源的最大化利用,从而提高整体的计算效率。
2. 流水线调度
流水线调度是一种高效的算子调度策略,它将多个算子按照一定的逻辑顺序排列,形成一条计算流水线。在流水线上,每个算子都有自己的处理单元和缓冲区,可以独立地进行计算,而无需等待前一个算子完成全部计算。当第一个算子完成一部分计算并将结果传递给下一个算子时,第一个算子可以继续处理新的数据,从而实现连续的、并发的计算。流水线调度可以极大地提高计算速度,减少等待时间,使得整个系统的吞吐量得到显著提升。
3.8 推理流程总结
根据以上的推理引擎结构介绍,我们可以得出以下的推理流程,这个流程涉及多个步骤和组件,包括其在离线模块中的准备工作和在线执行的过程,它们共同协作以完成推理任务。
首先,推理引擎需要处理来自不同 AI 框架的模型,比如 MindSpore、TensorFlow、PyTorch 或者 PaddlePaddle。这些框架训练得到的模型将被送至模型转换工具,进行格式转换,以适配推理引擎的特定格式。
转换后得到的推理模型,需要进行压缩处理。压缩模型是推理引擎中常见的步骤,因为未压缩的模型在实际应用中很少见。压缩后的模型,接下来需要进行环境准备,这一步骤涉及大量的配置工作,包括大小核的调度、模型文档的获取等,确保模型能够在正确的环境中运行。
完成环境准备后,推理引擎会进行开发和编译,生成用于执行推理的进程。这个推理进程是实际执行推理任务的核心组件,它依赖于推理引擎提供的 API,为用户提供模块或任务开发所需的接口。
开发工程师会按照这个流程进行工作,开发完成后,推理引擎将执行推理任务,使其在运行时(Runtime)中运行。此时,推理引擎的执行依赖于输入和输出的结果,这涉及到在线执行的部分。
开发推理程序是一个复杂的过程,涉及到模型的加载、配置、数据预处理、推理执行以及结果的后处理等多个步骤。下面仅简单提供一个示例,介绍如何开发一个推理程序。
- 模型转换
首先,需要将训练得到的模型转换为推理引擎能够理解的格式。这一步骤通常由模型转换工具完成,形成一个统一表达的格式。
2. 配置推理选项
这通常涉及到设置模型路径、选择运行的设备(如 CPU、GPU 等)、以及是否开启计算图优化等。一个 AI 框架会提供相关的 API 来设置这些选项,并在模型加载时自动应用。
Config config;
config.setModelPath("path_to_model");
config.setDeviceType("GPU");
config.setOptimizeGraph(true);
config.setFusion(true); // 开启算子融合
config.setMemoryOptimization(true); // 开启内存优化
3. 创建推理引擎对象
一旦配置选项设置完毕,下一步就是创建推理引擎对象。这个对象将负责管理整个推理过程,包括加载模型、执行推理等。创建推理引擎对象通常需要传递配置对象作为参数。
Predictor predictor(config);
4. 准备输入数据
在执行推理之前,必须准备好输入数据。这包括对原始数据进行预处理,比如减去均值、缩放等,以满足模型的输入要求。然后,需要获取模型所有输入 Tensor 的名称,并通过推理引擎获取每个输入 Tensor 的指针,最后将预处理后的数据复制到这些 Tensor 中。
// 预处理数据
auto preprocessed_data = preprocess(raw_data);
// 获取输入 Tensor 名称和指针
auto input_names = predictor->GetInputNames();
for (const auto& name : input_names) {
auto tensor = predictor->GetInputTensor(name);
tensor->copy(preprocessed_data);
}
5. 执行推理
一旦输入数据准备好,就可以执行推理了。这通常涉及到调用推理引擎的 Run 方法,该方法会启动模型的推理过程。
predictor->Run();
6. 获得推理结果并进行后处理
推理执行完成后,需要获取输出 Tensor,并将推理结果复制出来。然后,根据模型的输出进行后处理,比如在目标检测任务中,需要根据检测框的位置来裁剪图像。
// 获取输出 Tensor 名称和指针
auto out_names = predictor->GetOutputNames();
std::vector<ProcessedOutput> results;
for (const auto& name : out_names) {
auto tensor = predictor->GetOutputTensor(name);
ProcessedOutput data;
tensor->copy(data);
results.push_back(processOutput(data));
}
// 后处理,例如裁剪图像等
for (auto& result : results) {
postprocess(result);
}
参考: