0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何在裸机环境中运行KleidiAI微内核

Arm社区 ? 来源:Arm社区 ? 2025-08-08 15:16 ? 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

作者:Arm 工程部产品管理总监 Paul Black

Arm KleidiAI 是一款具有突破性意义的软件库,专为提升 Arm CPU 上的人工智能 (AI) 性能而设计。在此前发布的《Arm KleidiAI 助力 AI 框架性能提升》一文中,对 KleidiAI 进行了简要概述,并附有相关指南链接,其中详细说明了在 Linux 环境中运行 KleidiAI 矩阵乘法 (matmul) 微内核的分步操作,这份指南内容详实且极易上手。而本篇内容则将探索如何在裸机环境中运行 KleidiAI 内核,并通过测试多款 C/C++ 编译器,以确定如何能更高效地生成代码。

本文将介绍如何在裸机环境中运行 KleidiAI 微内核,并针对不同编译器在不同优化级别下的表现进行基础基准测试。文中会用到 Arm Development Studio 的相关组件,包括固定虚拟平台 (FVP),以及 Arm Compiler for Embedded (AC6) 的授权许可。与此同时,还提供了有关如何查看编译器已采用(或未采用)的优化的相关信息。

设置裸机项目

本文将评估的三个编译器分别是:

Arm Compiler for Embedded,更为人熟知名称的是 AC6

Arm GNU 工具链,即 GCC

新一代 Arm 嵌入式编译器 Arm Toolchain for Embedded (ATfE)。撰写本文时,该工具链还处于 Beta 测试阶段

为了在裸机项目中运行 KleidiAI 内核,可参考 Kleidi 指南中的说明。本文以 Arm Development Studio 中的 C++ 示例项目为基础进行开发:startup_Armv8-Ax1_AC6_CPP是 AC6 版本,startup_Armv8-Ax1_GCC_CPP是 GCC 版本,而 ATfE 的移植版本则包含在 ATfE 测试版下载包中。这三个编译器对应的项目功能相同,但需要对 Makefile 和链接脚本进行必要的修改。

各工具链的修复和更改

在粘贴 Kleidi 指南中提供的代码后,需对这三个项目进行以下简单修改以确保正常运行:

包含 float.h 头文件以定义 FLT_MAX

添加 KleidiAI 头文件的 include 路径

将架构更改为armv8.2-a+dotprod+i8mm

要运行此代码,需要一个具备 i8mm 扩展的 Arm 核心。此扩展在 Armv8.2-A 至 Armv8.5-A 架构中为可选功能,而在后续支持高级 SIMD 指令的核心中则为必选,因此 Arm Neoverse V1 是个不错的选择。Arm Development Studio 提供了 Neoverse V1 固定虚拟平台 (FVP),此处所选用的是-C cluster0.NUM_CORES=1 -C bp.secure_memory=false -C cache_state_modelled=0

启动代码中存在一段用于设置 SMPEN 的读-改-写序列,但这在 Neoverse V1 FVP 上会引发问题。由于是复用 Arm Cortex-A 的启动代码来适配 Neoverse 核心,因此需要进行一些修改,而在本场景中,移除该序列即可解决问题。理想情况下,应根据 Neoverse 核心的要求重新审阅启动代码,但就本次研究而言,确保代码正常运行便已足够。

添加了一些代码,用于向矩阵中填充随机数据。这一步可能并非必需,因为内存中原本就已填充了重复的非零模式。

此外,还需要对各个项目单独做一些修改。示例项目主要是实现处理器的启动,并未考虑在启动后运行较为复杂的负载任务:

在 ATfE 项目中,RAM 大小被设为 0x80000,这个容量过小,会导致堆与栈发生冲突。不过此问题很容易解决,因为即便是 FVP 的默认配置,其提供的 RAM 也远大于该数值。因此,我们可以在链接脚本中设置更大的 RAM 大小。

在 GCC 项目中,.init_array 段被分配到 0x80100000 地址,该地址过低,会与 .eh_frame 段产生冲突。移除这一地址设置即可解决问题。

至此就能成功在裸机环境中使用三款不同的工具链运行 KleidiAI 内核。接下来便可开展性能测试。

基准测试方法和结果

本次研究中使用了 FVP 的周期计数器来作为性能衡量指标。虽然它并非完美,但对于本次研究而言已经足够。由于三款编译器运行的是相同的工作负载,因此即便存在测量误差,其误差程度和分布位置也会保持一致。所以,作为一种性能参考指标,FVP 的周期计数完全能满足本次研究的需求。接着,分别在 -O0、-O1、-O2 和 -O3 这四个优化级别下,对三款编译器的周期计数进行了测量,以启动处理器核心、设置矩阵以及执行 KleidiAI 内核:

a06946c0-6e8e-11f0-a18e-92fbcf53809c.png

这里有两个值得关注的现象。首先,大部分优化效果在 -O1 级别就已显现。在 -O2 和 -O3 级别下虽有小幅提升(其中 GCC 的提升相对更明显),但提升幅度远不及 -O1 级别。这并不令人惊讶,因为 KleidiAI 内核本身已通过大量手工编写的汇编指令进行了优化,而在 Kleidi 内核外添加的代码既简短又简单。本文后续会深入分析所使用的优化手段。

其次,ATfE 的表现似乎明显快于 AC6 和 GCC。新一代 Arm 嵌入式编译器能在与 AC6 的对比中展现出如此优势,固然令人欣喜,但这一性能差距也促使我进行更深入的探究。

AC6 和 ATfE 的汇编器、编译器及 C++ 库组件均基于 LLVM 构建,两款工具链的主要差异体现在链接器和 C 库上(AC6 采用专有版本,ATfE 则使用开源版本)。因此,两者之间约 20% 的性能差距让我颇为好奇。我需要确保所有性能数据和基准测试结果都能适用于实际项目,所以必须进一步厘清 ATfE 的速度提升究竟源于何处。

深入分析

在这一部分对性能测试进行了简化,但同时也提升了复杂度。通过只关注 -O1 优化等级,以此简化了测试,因为大部分优化效果都体现在这一级别。与此同时,通过将代码分为三个部分来提高分析的粒度:

启动:所有启动代码,直至进入 main ()

准备:为矩阵分配内存,向矩阵填充随机数据

执行:运行 Kleidi 内核

周期计数如下:

a07a86d8-6e8e-11f0-a18e-92fbcf53809c.png

从 KleidiAI 内核的执行耗时来看,三款编译器的表现十分接近,ATfE 略领先于 AC6(约 1%),而 GCC 则稍显落后。在 -O2 和 -O3 级别下重新运行了该测试,如前文所述,随着优化级别的提高,GCC 在 -O3 级别时小幅反超,这正是高级别优化带来的提升效果之一。

在准备阶段,ATfE 与 AC6 的表现依然接近,GCC 则仍然落后。同样在 -O2 和 -O3 级别下重新测试后发现,在这些优化级别下,GCC 缩小了部分差距。这似乎表明,不同编译器会在不同优化级别中纳入特定的优化过程。

然而,ATfE 之所以能实现整体耗时的大幅缩短,关键提速点其实在启动阶段。我猜测,这可能是因为 ATfE 所使用的 Picolibc 在 C 库设置环节,比 AC6 采用的 ArmCLib 或 GCC 采用的 newlib 更轻量化。由于 ATfE 的主要提速点在于此,而测试项目本身的代码量较少,这就导致初始的性能对比结果存在偏差:如果增大工作负载,启动代码在整体运行时间中的占比就不会如此之高了。

分析编译器优化

若要了解 ATfE 采用(或未采用)哪些优化过程,可借助编译器选项-Rpass(或-Rpass-missed)。这两个选项后可接=.*(表示所有优化过程)或=(特定优化过程)。例如,使用-Rpass=inline可查看哪些函数调用已被内联,而使用-Rpass-missed=inline则能了解哪些调用未被内联。-Rpass-missed 选项对于开发者而言颇具价值,它能揭示 C/C++ 代码可如何调整,从而让编译器更易于优化。

快速查看了 ATfE 在 -O0、-O1、-O2 和 -O3 下级别下的优化过程,其结果如下:

即便在 -O0 级别,编译器仍会对部分 Arm C 语言扩展 (ACLE) 内联函数进行内联处理,例如 vaddq_s16(向量加法)。这一点是合理的,因为这类调用仅对应单条指令,因此在性能(得益于消除函数调用开销)与代码体积增加(因代码复制导致)之间不存在权衡问题。

在 -O1 级别,编译器进行了大量的函数内联,尤其是对小型函数(如随机数生成器实现)。此外,若循环中某些指令或表达式无需在每次迭代时重新计算,编译器会将它们提升 (hoist) 到循环外部。

在 -O2 级别,编译器开始进行循环向量化,但部分向量化操作会推迟到 -O3 级别。编译器采用启发式算法来权衡每项优化的收益与成本。如同内联优化,在同一优化级别下,不同循环可能会采用不同的向量化策略,这一点值得关注。

在 - O3 级别,编译器还会对部分循环进行展开。

提升 (hoisting) 机制值得深入探究。以 KleidiAI 源文件中一段大幅简化的代码为例:

for (size_t dst_row_idx = 0; dst_row_idx < dst_num_rows; ++dst_row_idx) {?

for (size_t dst_byte_idx = 0; dst_byte_idx < dst_num_bytes_per_row; ++dst_byte_idx) {?

const size_t block_idx = dst_byte_idx / block_length_in_bytes;

const size_t nr_idx = block_idx % nr;

const size_t n0_idx = dst_row_idx * nr + nr_idx;

编译器注意到,在计算 n0_idx 时,其中的乘法部分无需放在内层循环中,因为在内层循环中,dst_row_idx 和 nr 均为常量:

src/kai_rhs_pack_nxk_qsi4cxp_qs4cxs1s0.c47: remark: hoisting mul [-Rpass=licm]

96 | const size_t n0_idx = dst_row_idx * nr + nr_idx;

| ^

编译器会将该乘法操作从内层循环提升 (hoist) 到外层循环,大致如下:

for (size_t dst_row_idx = 0; dst_row_idx < dst_num_rows; ++dst_row_idx) {?

const size_t hoist_temp = dst_row_idx * nr;

for (size_t dst_byte_idx = 0; dst_byte_idx < dst_num_bytes_per_row; ++dst_byte_idx) {?

const size_t block_idx = dst_byte_idx / block_length_in_bytes;

const size_t nr_idx = block_idx % nr;

const size_t n0_idx = hoist_temp + nr_idx;

开发者也可手动进行此类优化,但这可能会使代码变得不够简洁、清晰,难以理解和维护。编译器会考虑这些因素,从而让开发者能够专注于代码功能、清晰度和可维护性。

ATfE 的 -Rpass 选项输出包含大量信息,既涉及已应用的优化过程,也涉及未应用的过程。这些信息对于开发者而言非常有帮助,能让开发者了解编译器如何优化代码,并指导开发者对代码进行调整,以更好地配合编译器优化。这是一个庞大的主题,我将在后续博客中深入探讨。

结论

Arm Development Studio 提供了一套适用于裸机环境下 KleidiAI 内核实验的工具,包括便于快速上手的示例项目、用于测试的 FVP,以及 AC6 的授权(之后还将包含 ATfEP 的授权)。与所有软件开发工作一样,在评估编译器性能等指标时,需要考虑采集所有相关数据。在本案例中,很容易轻易得出“用 ATfE 构建的项目比用 AC6 构建的项目快约 20%”的结论。ATfE 会基于每项潜在优化的成本与收益做出启发式优化决策,并提供实用选项来查看已采用和未采用的优化。通过这些选项获取的信息,可用于调整代码,使编译器能够实现更多优化。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM
    ARM
    +关注

    关注

    134

    文章

    9390

    浏览量

    379687
  • 内核
    +关注

    关注

    3

    文章

    1422

    浏览量

    41637
  • cpu
    cpu
    +关注

    关注

    68

    文章

    11113

    浏览量

    218217
  • 人工智能
    +关注

    关注

    1810

    文章

    49210

    浏览量

    251392

原文标题:在裸机 Arm 环境中运行 Arm KleidiAI MatMul 内核

文章出处:【微信号:Arm社区,微信公众号:Arm社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    何在嵌入式Linux开发板上配置Qt运行环境

    本文基于I.MX6ULL芯片的Linux开发板,介绍如何在嵌入式Linux开发板上配置Qt运行环境,并运行Qt程序进行测试
    发表于 10-10 09:56 ?2964次阅读

    何在裸机系统中集成SystemView

    SystemView是嵌入式系统可视化分析工具,提供了对应用程序的完整洞察,包括时间轴、CPU负载、运行时间信息、上下文运行时信息等可视化窗口,能够帮助开发者获得对应用运行时行为的深入理解。除μC
    的头像 发表于 12-29 11:07 ?3112次阅读
    如<b class='flag-5'>何在</b><b class='flag-5'>裸机</b>系统中集成SystemView

    何在树莓派上安装并运行 Arduino 集成开发环境

    使用树莓派单板计算机,你可以运行各种应用程序,包括广受欢迎的Arduino集成开发环境(IDE)。这意味着你可以用它轻松地为通过USB连接到树莓派计算机的微控制器编程,以创建交互式电子项目。下面
    的头像 发表于 07-01 17:41 ?1374次阅读
    如<b class='flag-5'>何在</b>树莓派上安装并<b class='flag-5'>运行</b> Arduino 集成开发<b class='flag-5'>环境</b>!

    《电子发烧友电子设计周报》聚焦硬科技领域核心价值 第23期:2025.08.04--2025.08.08

    、安森美分析工业充电器拓扑结构选型 3、国际原厂一文详解高速模数转换器的奈奎斯特规则 4、如何在裸机环境运行
    发表于 08-08 20:47

    【OK210试用体验】之三裸机开发环境搭建

    一、裸机开发思路S5PV210裸机编程有好多种思路,主要思路有如下: 1.RVDS2.2开发环境。板子在NAND烧写UBOOT或者EBOOT,RVDS2.2编写c语言程序,jlink
    发表于 09-27 15:24

    请问裸机程序怎么做才可以直接下载到SDRAM运行

    ),对裸机程序多少有些了解。就是因为前面做过的这些验证性试验,编译出的bin文件可以直接下载到SDRAM运行,而韦老大的这些裸机程序还是比较明确的规定要烧到NAND Flash的0地
    发表于 08-02 05:45

    何在UltraEdit建立Verilog环境

    何在UltraEdit建立Verilog环境
    发表于 04-30 06:40

    可以将MCUXpresso用于该设备M7内核的软件开发,而不是A53内核,这是否正确?

    这样,那么我对此有一些疑问: 您只能在 A53 内核运行 Linux 等操作系统是否正确?你不能在 A53 内核上进行一些裸机编程吗? 我可以从 M7 核心访问所有外围设备(以
    发表于 05-29 07:41

    如何使用J-Link在A55内核上进行i.MX93 EVK裸机调试?

    我正在研究在 i.MX93 EVK 的 A-55 内核运行裸机代码(例如带有 Zephyr RTOS 的程序)。是否有很多关于如何使用 SEGGER J-Link 调试以这种方式运行
    发表于 06-05 07:00

    请问nuc980如何在裸机程序实现nuc980软件复位?

    nuc980如何在裸机程序实现nuc980软件复位?
    发表于 06-13 08:21

    请问nuc980如何在裸机程序实现nuc980软件复位?

    nuc980如何在裸机程序实现nuc980软件复位?
    发表于 09-04 08:22

    内核与宏内核的比较与分析

    混合内核实质上也是内核,而外内核是一种比较极端的设计方法,目前还处于研究阶段,所以我们就着重讨论宏内核
    发表于 03-17 16:05 ?11次下载
    <b class='flag-5'>微</b><b class='flag-5'>内核</b>与宏<b class='flag-5'>内核</b>的比较与分析

    环境监测设备的FreeRTOS低功耗

    笔者的团队专业从事环境监测设备的开发,在开发过程团队的工作人员提到了关于FreeRTOS低功耗的问题。RTOS低功耗与裸机跑的进入的方式不同。普通单片机进入的方式分为SLEEP、STOP
    发表于 12-31 19:08 ?1次下载
    <b class='flag-5'>环境</b>监测设备<b class='flag-5'>中</b>的FreeRTOS低功耗

    程序是如何在 CPU 运行的(二)

    在上一篇文章《程序是如何在 CPU 运行的(一)》笔者讲述了程序中一条一条指令以及一条一条数据是如何在 CPU
    发表于 02-07 11:10 ?1次下载
    程序是如<b class='flag-5'>何在</b> CPU <b class='flag-5'>中</b><b class='flag-5'>运行</b>的(二)

    Linux内核的编译和运行

    想让Linux内核代码跑起来,得先搭建编译和运行代码的环境
    发表于 06-23 11:56 ?1955次阅读
    Linux<b class='flag-5'>内核</b>的编译和<b class='flag-5'>运行</b>