选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2024-10-11
  • 更新:2024-10-15


资源

RVV 指令集文档,不同版本不兼容。。需要注意:

使用场景

RVV 即RV 矢量加速指令集,可以加速矢量运算或者数据并行运算。
最简单的应用场景就是将计算批量化,因为一个指令可以计算多个数据,类似 openmp 多核并行运行一样,最简单的场景就是加速 for 循环减少计算指令数量来达到加速效果,甚至可以优化内存拷贝比如 hwc 转 chw 内存拷贝可以使用RVV批量拷贝比for循环快。
只要指令集支持的运算就可以加速,比如常见的加减乘除和逻辑运算等,具体需要看芯片支持的RVV指令集文档(注意要对应版本,比如RVV0.7.1 和 RV1.0.0 就是不兼容的,以及芯片是否支持)。

如果要加速没有直接指令的算法,可以基于基本指令进行运算,比如ncnn 中的tanh 函数的实现。

例子

  1. #if __riscv_vector
  2. int n = size;
  3. while (n > 0)
  4. {
  5. size_t vl = vsetvl_e32m8(n);
  6. vfloat32m8_t _p = vle32_v_f32m8(ptr, vl);
  7. _p = tanh_ps(_p, vl);
  8. vse32_v_f32m8(ptr, _p, vl);
  9. ptr += vl;
  10. n -= vl;
  11. }
  12. #else // __riscv_vector
  13. for (int i = 0; i < size; i++)
  14. {
  15. *ptr = tanh(*ptr);
  16. ptr++;
  17. }
  18. #endif // __riscv_vector

vsetvl_e32m8 获取处理 32位数据(元素宽度 SEW)并且8倍寄存器宽度倍数(LMUL)的数据长度,得到一次型能处理的32位数据长度,如果硬件矢量寄存器的长度为128(VLEN),则这里 vl 为min(n, VLEN/SEW * LMUL), 这里即 min(n, 128/32 8) = min(n, 32) 个 32位数据。如果是vsetvl_e16m1 那就是获取 一次能处理的16位数据并且1倍寄存器宽度的长度,即 128 / 16 1 = 8 个16位宽数据。
这里寄存器宽度倍率使用1 2 4 8 的决定方式为你的数据长度,比如有 n = 18 个 32位数据,我是先vle32_v_f32m4(ptr, 16) 再调用 vle32_v_f32m1(ptr+16, 2) 好,还是直接调用 vle32_v_f32m8(ptr, 18) 好,比较两者效率: 一次性加载只需要一次指令,比分开多次指令好,虽然 vle32_v_f32m8 支持最大 32 个32位数据计算,指定18个硬件会自动调整有效长度(VL)为18,即只会计算这18个元素,但是仍然会分配32个元素的寄存器空间,所以从运算速度上来说分配更多寄存器减少指令更好,只是会有空闲的寄存器被分配。

vle32_v_f32m8 将数据提取 vl 个存到矢量寄存器中得到变量 _p,然后调用tanh_ps 进行批量计算,请且将结果拷贝到 ptr 内存区域即覆盖源值。

相比用CPU循环一个一个计算,使用RVV矢量运算效率会更高。

用RVV加速模型输入预处理(x-mean)*scale

对于标准化输入的代码就是将输入图像所有像素的值 减 mean 再 乘以scale 或者 除以std, 这个步骤在不同平台加速方法不一样,比如多核可以用Openmp并行运行, arm 可以SIMD指令加速等,对于RISCV可以用RVV加速,实测在 SG2002(c906)上 从 9~14ms 变成 1~2ms,速度提升明显。

  1. for (int i = 0; i < img_h * img_w; ++i)
  2. {
  3. *ptr_ch0 = ((float)*p - mean[0]) * scales[0];
  4. *ptr_ch1 = ((float)*(p + 1) - mean[1]) * scales[1];
  5. *ptr_ch2 = ((float)*(p + 2) - mean[2]) * scales[2];
  6. ++ptr_ch0;
  7. ++ptr_ch1;
  8. ++ptr_ch2;
  9. p += 3;
  10. }

RVV加速代码,核心思想就是用RVV指令进行批量运算:

  1. static inline void process_image_rvv(const uint8_t *img_data, int8_t *output, int img_h, int img_w, const float mean[3], const float scale[3]) {
  2. size_t total_pixels = img_h * img_w;
  3. const uint8_t *p = img_data;
  4. int8_t *ptr_ch0 = output;
  5. int8_t *ptr_ch1 = ptr_ch0 + total_pixels;
  6. int8_t *ptr_ch2 = ptr_ch1 + total_pixels;
  7. size_t vl = vsetvlmax_e8m2(); // Set the vector length to maximum for uint8_t
  8. vuint16m4_t v_zero = vmv_v_x_u16m4(0, vl);
  9. for (size_t n = total_pixels; n > 0;) {
  10. if (vl > n) vl = n;
  11. n -= vl;
  12. // Step 1: Load RGB channels (HWC format)
  13. vuint8m2_t v_r_u8 = vlse8_v_u8m2(p, 3, vl);
  14. vuint8m2_t v_g_u8 = vlse8_v_u8m2(p + 1, 3, vl);
  15. vuint8m2_t v_b_u8 = vlse8_v_u8m2(p + 2, 3, vl);
  16. // convert u8 to u16
  17. vuint16m4_t v_r_u16 = vwcvtu_x_x_v_u16m4(v_r_u8, vl);
  18. vuint16m4_t v_g_u16 = vwcvtu_x_x_v_u16m4(v_g_u8, vl);
  19. vuint16m4_t v_b_u16 = vwcvtu_x_x_v_u16m4(v_b_u8, vl);
  20. // convert u16 to u32
  21. vuint32m8_t v_r_u32 = vwcvtu_x_x_v_u32m8(v_r_u16, vl);
  22. vuint32m8_t v_g_u32 = vwcvtu_x_x_v_u32m8(v_g_u16, vl);
  23. vuint32m8_t v_b_u32 = vwcvtu_x_x_v_u32m8(v_b_u16, vl);
  24. vfloat32m8_t v_r_f32 = vfcvt_f_xu_v_f32m8(v_r_u32, vl); // Convert uint32 to float32
  25. vfloat32m8_t v_g_f32 = vfcvt_f_xu_v_f32m8(v_g_u32, vl); // Convert uint32 to float32
  26. vfloat32m8_t v_b_f32 = vfcvt_f_xu_v_f32m8(v_b_u32, vl); // Convert uint32 to float32
  27. // Step 4: Apply (x - mean) * scale
  28. v_r_f32 = vfmul_vf_f32m8(vfsub_vf_f32m8(v_r_f32, mean[0], vl), scale[0], vl);
  29. v_g_f32 = vfmul_vf_f32m8(vfsub_vf_f32m8(v_g_f32, mean[1], vl), scale[1], vl);
  30. v_b_f32 = vfmul_vf_f32m8(vfsub_vf_f32m8(v_b_f32, mean[2], vl), scale[2], vl);
  31. // convert f32 to u32
  32. vint32m8_t v_r_i32 = vfcvt_x_f_v_i32m8(v_r_f32, vl);
  33. vint32m8_t v_g_i32 = vfcvt_x_f_v_i32m8(v_g_f32, vl);
  34. vint32m8_t v_b_i32 = vfcvt_x_f_v_i32m8(v_b_f32, vl);
  35. // convert i32 to i16
  36. vint16m4_t v_r_i16 = vnclip_wv_i16m4(v_r_i32, v_zero, vl);
  37. vint16m4_t v_g_i16 = vnclip_wv_i16m4(v_g_i32, v_zero, vl);
  38. vint16m4_t v_b_i16 = vnclip_wv_i16m4(v_b_i32, v_zero, vl);
  39. // convert i16 to i8
  40. vint8m2_t v_r_i8 = vnclip_wx_i8m2(v_r_i16, 0, vl);
  41. vint8m2_t v_g_i8 = vnclip_wx_i8m2(v_g_i16, 0, vl);
  42. vint8m2_t v_b_i8 = vnclip_wx_i8m2(v_b_i16, 0, vl);
  43. // Step 6: Store the result in CHW format
  44. vse8_v_i8m2(ptr_ch0, v_r_i8, vl);
  45. vse8_v_i8m2(ptr_ch1, v_g_i8, vl);
  46. vse8_v_i8m2(ptr_ch2, v_b_i8, vl);
  47. // Step 7: Advance the pointers
  48. p += vl * 3;
  49. ptr_ch0 += vl;
  50. ptr_ch1 += vl;
  51. ptr_ch2 += vl;
  52. }
  53. }

灰度图

  1. static inline void process_image_gray_rvv(const uint8_t *img_data, int8_t *output, int img_h, int img_w, const float &mean, const float &scale) {
  2. size_t total_pixels = img_h * img_w;
  3. const uint8_t *p = img_data;
  4. int8_t *ptr_ch0 = output;
  5. size_t vl = vsetvlmax_e8m2();
  6. vuint16m4_t v_zero = vmv_v_x_u16m4(0, vl);
  7. for (size_t n = total_pixels; n > 0;) {
  8. if (vl > n) vl = n;
  9. n -= vl;
  10. vuint8m2_t v_r_u8 = vle8_v_u8m2(p, vl);
  11. vuint16m4_t v_r_u16 = vwcvtu_x_x_v_u16m4(v_r_u8, vl);
  12. vuint32m8_t v_r_u32 = vwcvtu_x_x_v_u32m8(v_r_u16, vl);
  13. vfloat32m8_t v_r_f32 = vfcvt_f_xu_v_f32m8(v_r_u32, vl); // Convert uint32 to float32
  14. v_r_f32 = vfmul_vf_f32m8(vfsub_vf_f32m8(v_r_f32, mean[0], vl), scale[0], vl);
  15. vint32m8_t v_r_i32 = vfcvt_x_f_v_i32m8(v_r_f32, vl);
  16. vint16m4_t v_r_i16 = vnclip_wv_i16m4(v_r_i32, v_zero, vl);
  17. vint8m2_t v_r_i8 = vnclip_wx_i8m2(v_r_i16, 0, vl);
  18. vse8_v_i8m2(ptr_ch0, v_r_i8, vl);
  19. p += vl;
  20. ptr_ch0 += vl;
  21. }
  22. }
文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-6oqzgq.jpg