当前位置: 首页 > article >正文

探索 PIE 在 ESP32-P4 上的应用

近年来,人工智能技术 (AI) 在图像识别、语音识别以及自然语言处理等领域取得了突破性进展,为嵌入式系统带来了更多的可能性。在将 AI 模型推理部署到 ESP32-P4 等嵌入式设备时,开发者总是希望能够持续优化模型的推理速度,以满足实时处理的需求。 

为了应对这一挑战,处理器指令拓展 (PIE, Processor Instruction Extensions) 应运而生。 PIE
是一组在 ESP32-S3/ESP32-P4 上新增的扩展指令,旨在提升特定 AI 与信号处理 (DSP, Digital Signal Processing) 算法的执行效率。基于单指令多数据 (SIMD, Single Instruction Multiple Data) 的设计理念,PIE 支持 8-bit、16-bit 以及 32-bit 的向量运算,能够显著提升数据运算效率。此外,对于乘法、位移、累加等运算指令,PIE 支持在数据计算的同时完成数据搬运操作,进一步提升单条指令的执行效率。 

ESP32-S3 与 ESP32-P4 的 PIE 差异: 

您可以在 ESP32-S3 和 ESP32-P4 上使用 PIE。虽然在这两种芯片上应用 PIE 的差别很小,但了解它们之间的细微差别对于优化您的应用性能至关重要: 

  • 指令架构差异:ESP32-S3 的 PIE 是基于 `TIE (Tensilica Instruction Extension)` 语言设计的,而 ESP32-P4 是支持标准的 `RV32IMAFCZc` 扩展,并包含一个自定义的 `Xhwlp` 硬件循环扩展,能够提高在硬件中执行 `for-loop` 的效率;
  • 内部结构差异:以乘法累加器 (MAC, Multiplication-accumulation) 为例,ESP32-S3 具有两个 160-bit 的累加器,而 ESP32-P4 具有两个 256-bit 的累加器; 
  • 指令格式差异:ESP32-S3 和 ESP32-P4 的 PIE 指令大体相似。但需要注意的是,ESP32-S3 的指令以 `ee` 开头,而 ESP32-P4 的指令以 `esp` 开头。此外,某些指令的功能或用法可能有所不同,详细信息请参阅相应的技术参考手册。 

上图显示了 PIE 中乘法累加器的数据路径,其中的虚线框突出显示了 ESP32-S3 和 ESP32-P4 之间的差异,蓝色代表 ESP32-S3,绿色代表 ESP32-P4。图中从左到右各块的详细说明如下: 

  • 内存 (Memory): 用于存储与快速访问数据;
  • 地址单元 (Address Unit): PIE 中的大部分指令都允许在一个周期内从 128-bit 的 Q 寄存器中并行加载或存储数据。此外,地址单元还提供了并行操作地址寄存器的功能,从而节省了更新地址寄存器的时间;
  • 向量寄存器组 (Vector Registers): 包含 8 个 128-bit 的向量寄存器 (QR)。每个寄存器可以表示为 16 x 8-bit、8 x 16-bit 或 4 x 32-bit 的数据向量;
  • QACC 累加寄存器: QACC 累加寄存器可用于 8-bit 或 16-bit 数据的乘累加运算。对于 ESP32-S3 而言,QACC 由 160-bit 的 QACC_H 与 160-bit 的 QACC_L 构成;对于 ESP32-P4 而言,QACC_H 与 QACC_L 的大小各为 256-bit。此外,可以将内存中的数据加载到 QACC 中,也可以将初始值重置为 0;
  • ACCX 累加寄存器: ACCX 是一个 40-bit 寄存器,其存储的数据既可以是 8-bit 数据的乘累加结果,也可以是 16-bit 数据的乘累加结果,取决于使用的指令。 

如果您对上述 PIE 架构还不熟悉,无需担心。本文将从实用角度出发,以直观的方式展示 PIE 的应用场景和性能表现。 

基本的指令格式与应用 

如果您的工作经常涉及 AI 模型推理或信号处理,您可能会经常遇到内存拷贝与向量计算等应用场景。以内存拷贝和向量加法为例,您可以通过内联汇编或 `.s` 文件来使用 PIE 指令。需要注意的是,PIE 中的大多数指令都可以在一个周期内从 128-bit Q 寄存器中加载或存储数据,因此,数据应 128-bit 对齐。 

内存拷贝加速 

在内存拷贝场景中,PIE 中的 8 个 128 位 Q 寄存器可用于读取和存储数据,从而实现内存拷贝加速。ESP32-P4 的 PIE 格式如下:

esp.vld.128.ip qu,rs1,imm  # 从内存中加载 128-bit 数据 
 
; 该指令将 128-bit 的数据从内存加载到寄存器 qu 中,同时指针按立即数 (imm) 进行递增。 
; imm 的取值范围为 -2048 到 2032,步长为 16。 
 
esp.vst.128.ip qu,rs1,imm  # 将 128-bit 数据写入到内存中 
 
; 该指令将寄存器 qu 中的 128 位数据存储到内存中,同时指针按立即数 (imm) 进行递增。 
; imm 的取值范围为 -2048 到 2032,步长为 16。

接下来,根据上述指令,以 `.s` 文件 的形式编写用于内存复制的代码。

.data 
    .align 16 
 
    .text 
    .align 4 
 
    .global memcpy_pie 
    .type   memcpy_pie, @function 
memcpy_pie: 
    # a0: store_ptr  
    # a1: load_ptr 
    # a2: length(bytes) 
 
    li x28,0 
    Loop: 
        esp.vld.128.ip q0, a1, 16 
        esp.vld.128.ip q1, a1, 16 
        esp.vld.128.ip q2, a1, 16 
        esp.vld.128.ip q3, a1, 16 
        esp.vld.128.ip q4, a1, 16 
        esp.vld.128.ip q5, a1, 16 
        esp.vld.128.ip q6, a1, 16 
        esp.vld.128.ip q7, a1, 16 
 
        esp.vst.128.ip q0, a0, 16 
        esp.vst.128.ip q1, a0, 16 
        esp.vst.128.ip q2, a0, 16 
        esp.vst.128.ip q3, a0, 16 
        esp.vst.128.ip q4, a0, 16 
        esp.vst.128.ip q5, a0, 16 
        esp.vst.128.ip q6, a0, 16 
        esp.vst.128.ip q7, a0, 16 
 
        addi x28, x28, 128 
        bge x28, a2, exit 
        j Loop 
    exit: 
        ret 

在上述汇编文件中,原始数据分别存储在寄存器 q0 至 q7 中,并回写到指定的内存块。

 

本文在 ESP32-P4 上进行了 `memcpy` 对比实验。ESP32-P4 的运行频率为 360 MHz,二级缓存大小为 128 KB,二级缓存行大小为 64 Bytes。测试结果显示,在重复 100 轮复制 2040 个字节的过程中,PIE 相较于标准库快 74.3%,相较于采用单字节拷贝的 ANSI C 版本快 97.2%。 

此外,深入探索内存拷贝速度差异的原因或许能给您带来更多启发。 

Newlib-esp32 包含了各个平台的 `memcpy` 实现方法。以基于 RISC-V 的 ESP32-P4 为例,与单字节拷贝相比,优化后的 `memcpy` 通过使用内存对齐和块级传输提高了数据的拷贝效率。同时,PIE 通过使用 8 个 128-bit 的寄存器来加载和存储数据,进一步加快了内存拷贝的速度。 

void * 
__inhibit_loop_to_libcall 
memcpy(void *__restrict aa, const void *__restrict bb, size_t n) 
{ 
  #define BODY(a, b, t) { \ 
    t tt = *b; \ 
    a++, b++; \ 
    *(a - 1) = tt; \ 
  } 
 
  char *a = (char *)aa; 
  const char *b = (const char *)bb; 
  char *end = a + n; 
  uintptr_t msk = sizeof (long) - 1; 
  if (unlikely ((((uintptr_t)a & msk) != ((uintptr_t)b & msk)) 
           || n < sizeof (long))) 
    { 
small: 
      if (__builtin_expect (a < end, 1)) 
    while (a < end) 
      BODY (a, b, char); 
      return aa; 
    } 
 
  if (unlikely (((uintptr_t)a & msk) != 0)) 
    while ((uintptr_t)a & msk) 
      BODY (a, b, char); 
 
  long *la = (long *)a; 
  const long *lb = (const long *)b; 
  long *lend = (long *)((uintptr_t)end & ~msk); 
 
  if (unlikely (lend - la > 8)) 
    { 
      while (lend - la > 8) 
    { 
      long b0 = *lb++; 
      long b1 = *lb++; 
      long b2 = *lb++; 
      long b3 = *lb++; 
      long b4 = *lb++; 
      long b5 = *lb++; 
      long b6 = *lb++; 
      long b7 = *lb++; 
      long b8 = *lb++; 
      *la++ = b0; 
      *la++ = b1; 
      *la++ = b2; 
      *la++ = b3; 
      *la++ = b4; 
      *la++ = b5; 
      *la++ = b6; 
      *la++ = b7; 
      *la++ = b8; 
    } 
    } 
 
  while (la < lend) 
    BODY (la, lb, long); 
 
  a = (char *)la; 
  b = (const char *)lb; 
  if (unlikely (a < end)) 
    goto small; 
  return aa; 
} 
 

数学运算加速 

以 `int16_t` 类型的向量加法为例,相较于内存拷贝而言,其增加了一条加法指令。ESP32-P4 上的指令格式如下: 

esp.vadd.s16 qv, qx, qy  
 
; 该指令对寄存器 qx 和 qy 中的 16 位数据执行向量有符号加法。 
; 然后,对计算得到的 8 个结果进行饱和处理,并将饱和后的结果写入寄存器 qv。

接下来,本文将以内联汇编方式编写 PIE 代码: 

void add_ansic(int16_t *x, int16_t *y, int16_t *z, int n) 
{ 
    for (int i = 0; i < n; i++) 
    { 
        z[i] = x[i] + y[i]; 
    } 
} 
 
void add_pie(int16_t *x, int16_t *y, int16_t *z, int n) 
{ 
    asm volatile( 
        " addi sp, sp, -32 \n" 
        " sw x31, 28(sp) \n" 
        " sw x30, 24(sp) \n" 
        " sw x29, 20(sp) \n" 
        " sw x28, 16(sp) \n" 
        " sw x27, 12(sp) \n" 
        " mv x31, %0 \n" 
        " mv x30, %1 \n" 
        " mv x29, %2 \n" 
        " mv x28, %3 \n" 
        "li x27, 0 \n" 
        "loop:\n" 
        " beq x27, x28, exit \n" 
        " esp.vld.128.ip q0, x31, 16 \n" 
        " esp.vld.128.ip q1, x31, 16 \n" 
        " esp.vld.128.ip q2, x31, 16 \n" 
        " esp.vld.128.ip q3, x31, 16 \n" 
        " esp.vld.128.ip q4, x30, 16 \n" 
        " esp.vld.128.ip q5, x30, 16 \n" 
        " esp.vld.128.ip q6, x30, 16 \n" 
        " esp.vld.128.ip q7, x30, 16 \n" 
 
        " esp.vadd.s16 q0, q0, q4 \n" 
        " esp.vadd.s16 q1, q1, q5 \n" 
        " esp.vadd.s16 q2, q2, q6 \n" 
        " esp.vadd.s16 q3, q3, q7 \n" 
 
        " esp.vst.128.ip q0, x29, 16 \n" 
        " esp.vst.128.ip q1, x29, 16 \n" 
        " esp.vst.128.ip q2, x29, 16 \n" 
        " esp.vst.128.ip q3, x29, 16 \n" 
 
        " addi x27, x27, 32 \n" 
        " j loop \n" 
        "exit:\n" 
        " lw x31, 28(sp) \n" 
        " lw x30, 24(sp) \n" 
        " lw x29, 20(sp) \n" 
        " lw x28, 16(sp) \n" 
        " lw x27, 12(sp) \n" 
        " addi sp, sp, 32 \n" 
        :: "r" (x), "r" (y) , "r" (z), "r" (n)     
    ); 
} 
 

随后,在与内存拷贝实验相同的条件下,进行了 100 轮 `int16_t` 的向量加法实验,每个向量的长度为 2048。测试结果显示,PIE 版本相比 ANSI C 版本的速度提升了 93.8%。 

综上所述,本文介绍了 PIE 的两个应用场景及其实现方法。此外,PIE 还包含其他指令,您可以参考上述流程进行测试。 

添加 PIE 相关组件到工程中 

如果您不想编写 PIE 指令,但仍想加速 AI 推理或信号处理,您可以尝试使用集成了 PIE 指令的组件来简化开发过程。以下是一些可以轻松集成到您的现有项目中,以加速 AI 和 DSP 性能的乐鑫官方组件: 

  • esp-tflite-micro: 在 `esp-tflite-micro` 中的 esp-nn 组件使用 PIE 优化了一些 AI 算子,从而加速了模型的推理时间;
  • esp-dl: esp-dl 提供了一种专为 ESP 系列芯片设计的轻量级、高效的神经网络推理框架,能够高效实现诸如 Conv2D、Pool2D、Gemm、Add 和 Mul 等常见的 AI 算子;
  • esp-dsp: esp-dsp 通过汇编优化实现矩阵乘法、点积以及 FFT 等数学运算,同时也提供了 ANSI-C 实现版本。

总结 

总而言之,PIE 包含如下特性: 

  • 支持 128-bit 位宽的向量数据操作,包括:乘法、加法、减法、累加、移位、比较等; 
  • 合并数据搬运指令与运算指令; 
  • 支持非对齐 128-bit 带宽的向量数据; 
  • 支持取饱和操作。 

借助 PIE 指令或相关组件,您可以尝试加速现有项目的计算过程。 

欢迎您按照本文步骤进行尝试并分享您的经验! 


http://www.kler.cn/a/441441.html

相关文章:

  • Hive之加载csv格式数据到hive
  • 从根源分析,调试,定位和解决MacOS ld: unsupported tapi file type ‘!tapi-tbd‘ in YAML file
  • MyBatis Plus 的 InnerInterceptor:更轻量级的 SQL 拦截器
  • Java 中的设计模式:经典与现代实践
  • Android OpenGL(六) 纹理
  • 【算法】字符串之227.基本计算器 -- 双栈的变形
  • 找出一个数组中出现次数最多的那个元素。:哈希表:JAVA
  • SQL, 将分段数不确定的字符串拆分成多列
  • Android之RecyclerView显示数据列表和网格
  • 2024-12-16 装有Ubuntu系统的移动硬盘使用windows系统对其进行格式化
  • SpringDataJpa-字段加解密存储
  • webpack打包流程及原理
  • LeetCode 283.移动零(超简单讲解)
  • 鸿蒙权限请求工具类
  • 力扣-图论-15【算法学习day.65】
  • 【PyTorch】实现在训练过程中自定义动态调整学习率
  • 测试工程师八股文04|计算机网络 和 其他
  • 【日常笔记】基本数据类型浅析 -int类型能存储哪些传感器数据
  • 减少 Flutter 应用体积的常用方法
  • 在线PDF合并工具 - 快速、免费、安全的文档处理解决方案 | Online PDF Merger Tool
  • 力扣--LCR 164.破解闯关密码
  • K8s 中Istio 的使用示例
  • ThinkPHP 5.1 的模板布局功能
  • CentOS7源码编译安装nginx+php+mysql
  • 前端单元测试实战:从零开始构建可靠的测试体系
  • vue2项目中如何把rem设置为固定的100px