GSoC 2021: 面向RISC-V平台的OpenCV DNN模块优化

OpenCV学堂 2021-11-10 14:01

作者:韩柳彤(中国科学院软件研究所智能软件研究中心博士研究生)


在2021年谷歌编程之夏(Google Summer of Code, GSoC)中,笔者使用RISC-V 向量扩展的Intrinsic函数优化了OpenCV DNN模块中多个函数,提高了OpenCV在RISC-V平台上的深度学习推理性能。


本文将简要介绍OpenCV DNN模块的架构和现有的RISC-V平台优化实现方式,之后给出使用Intrinsic函数优化DNN函数的思路,并举例说明实现方法。


OpenCV DNN

OpenCV的深度学习(DNN)模块是一个推理引擎,它支持大多数目前主流的深度学习框架所训练的模型,如TensorFlow、Caffe、Pytorch、ONNX等。DNN模块在2015年首次进入OpenCV项目,从2017年以来,DNN模块有了越来越多的特性,其中包括一系列推理引擎的后端(硬件平台)加速。目前,OpenCV DNN模块已经支持CUDA、Vulkan等GPU后端;在CPU后端方面,DNN模块使用了OpenCV 中的 Universal Intrinsics,已经支持SSE、AVX、Neon和RISC-V Vector等后端。


本项目基于之前的相关工作[1]为DNN模块进一步提供了面向RISC-V Vector平台的加速。下图是深度学习模块的整体架构图,本项目的主要工作在DNN层的实现部分。


现有的RISC-V平台优化

现有的面向RISC-V向量扩展平台的优化主要是基于Universal Intrinsics的循环向量化。

OpenCV 中的 Universal Intrinsics

Universal Intrinsics[2]也称统一向量指令,是OpenCV 4 版本中提供的硬件加速层,它抽象了不同指令集的向量指令。使用Universal Intrinsics编写加速算法可以做到一份实现代码在各个硬件平台上都获得向量加速的特性。目前,Universal Intrinsics 已经支持了Intel MMX、SSE、AVX、AVX512、ARM Neon 和 RISC-V Vector后端。


以寄存器加载指令为例,在Intel SSE指令集中,可以使用Intrinsic函数__m128 _mm_loadu_ps(float const* mem_addr)实现将内存中一组32bit浮点数加载到向量寄存器里的操作,而ARM Neon中的 float32x4_t vld1q_f32(float32_t const * mem_addr)和RISC-V Vector中的vfloat32m1_t vle32_v_f32m1 (const float32_t *mem_addr, size_t vl)函数都可以在各自平台上实现相同的操作。但由于各平台的Intrinsic(我们称之为Native Intrinsic)并不统一,因此使用其编写加速代码时,同一个算法需要有多个平台相关版本的实现,且不易扩展和维护。在Universal Intrinsic中,则定义了统一的接口v_load(const _Tp * mem_addr),使用Universal Intrinsic编写的向量加速代码可以通过编译时调度在不同平台上调用各自的Native Intrinsic,从而实现一套加速代码跨平台使用。


使用Native Intrinsic优化

为什么要用Native Intrinsics

Universal Intrinsics 为OpenCV中的加速算法提供了硬件抽象,但也正因为这层抽象而产生的额外开销(多条Native Intrinsics实现某条Universal Intrinsic,对象的构造和析构等)和更多平台相关信息的缺失(如寄存器个数等),使用Universal Intrinsics编写的加速算法性能通常不及直接使用特定平台的Native Intrinsics。因此,我们需要在牺牲一部分性能的跨平台加速(使用Universal Intrinsics)和牺牲跨平台性的最佳性能(使用Native Intrinsics)之间做出取舍。


考虑两种极端情况,即全使用Universal Intrinsics和全使用Native Intrinsics:对于第一种情况,通常是可接受的。虽然我们没有得到最佳性能,但OpenCV开发者们只需要使用Universal Intrinsics实现一次算法,就可以在任何拥有向量扩展的平台上获得还算不错的性能提升了——即使是未来出现的新平台也可以,只需要添加新平台的Universal Intrinsics后端实现,而不必修改算法;对于第二种情况,我们不愿意这样做,因为它太繁杂了。我们需要对每一个算法和每一个后端的组合都实现一次特定算法,虽然能够获得最佳的性能,但同时会带来很大的工作量,也不利于维护。即使我们依旧可以通过条件编译让一套源代码在各个平台上运行,但当算法需要修改,我们不得不修改所有平台上的实现;同样的,当一个新的后端平台被引入时,我们要为该平台版本添加的所有算法实现。


但是,我们可以对一小部分算法采用特殊的策略:依赖条件编译,使用Native Intrinsics实现部分平台上的加速,以达到最佳性能。由于不同算法的调用次数和执行时间不同,我们显然希望进一步优化那些被频繁调用或执行时间更长的算法:在DNN中,我们选择了卷积和矩阵乘法。这样,我们仅需要维护特定平台上(目前有AVX和RVV)少量(在DNN中共4个)的算法,便能够得到可观的总体性能提升。

如何用Native Intrinsics

让我们具体看看这种策略是如何实现的,假设有两个数组进行点乘操作,公式为 c = a * b。我们可以给出下列四种实现方式[3]。


1. 使用标量实现

float a[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float b[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float c[16];
for(int i = 0; i < 16; i++){
 c[i] = a[i] * b[i];
}


2. 使用Universal Intrinsics 实现

for(int i = 0; i < 16; i+=4){    
    v_float32x4 va = v_load(a+i); //从内存a装载4个float到寄存器va    
    v_float32x4 vb = v_load(b+i); //从内存b装载4个float到寄存器vb    
    v_float32x4 vc = va*vb; //通过C++运算符重载实现了向量乘法,4个float一次完成    
    v_store(c+i, vc); //从寄存器vc存储4个float到内存c
}


3. 使用Intel AVX/SSE Native Intrinsics 实现

opt_AVX::fastMul(float* a, float* b, float* c, size_t n) {
    for(int i = 0; i < n; i+=4){    
        __mm128 va = _mm_loadu_ps(a+i); //从内存a装载4个float到寄存器va    
        __mm128 vb = _mm_loadu_ps(b+i); //从内存b装载4个float到寄存器vb    
        __mm128 vc = _mm_mul_ps(va,vb); //调用Intrinsics实现向量乘法
        _mm_store_ps (c+i, vc); //从寄存器vc存储4个float到内存c
    }
}


4. 使用RISC-V Vector Native Intrinsics 实现(假设向量寄存器长度为128bit,下同)

opt_RVV::fastMul(float* a, float* b, float* c, size_t n) {
    for(int i = 0; i < n; i+=4){    
        vfloat32m1_t va = vle32_v_f32m1(a+i, 4); //从内存a装载4个float到寄存器va    
        vfloat32m1_t vb = vle32_v_f32m1(b+i, 4); //从内存b装载4个float到寄存器vb    
        vfloat32m1_t vc = vfmul_vv_f32m1(va, vb, 4); //调用Intrinsics实现向量乘法
        vse32_v_f32m1 (c+i, vc, 4); //从寄存器vc存储4个float到内存c
    }
}


使用c++的条件编译,我们可以编写如下代码:

float a[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float b[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
float c[16];

#if CV_TRY_AVX //如果面向AVX平台,就使用AVX Native Intrinsics 实现的版本
    opt_AVX::fastMul(a, b, c, 16);

#elif CV_TRY_RVV //如果面向RVV平台,就使用RVV Native Intrinsics 实现的版本
    opt_RVV::fastMul(a, b, c, 16);

#elif CV_SIMD128 // 如果面向其他支持SIMD的平台,则使用 Universal Intrinsics 实现的版本
for(int i = 0; i < 16; i+=4){    
    v_float32x4 va = v_load(a+i); //从内存a装载4个float到寄存器va    
    v_float32x4 vb = v_load(b+i); //从内存b装载4个float到寄存器vb    
    v_float32x4 vc = va*vb; //通过C++运算符重载实现了向量乘法,4个float一次完成    
    v_store(c+i, vc); //从寄存器vc存储4个float到内存c
}
#else    //否则,没有向量扩展可用,我们只好使用标量实现
for(int i = 0; i < 16; i++){
     c[i] = a[i] * b[i];
}
#endif


DNN层实现中的热点算法的优化思路可以总结为:优先尝试特定平台的优化;如果没有实现或平台不匹配,则尝试Universal Intrinsics的优化;均不可用则使用最基础的标量版本。如下图所示:


如何更好使用RISC-V Vector Native Intrinsics

当我们使用Native Intrinsics编写加速代码时,我们还可以针对RISC-V Vector平台的特性进一步优化。例如,我们可以尽可能多的使用寄存器:考虑之前描述的数组乘法操作,在一次循环中,我们只用到了va, vb, vc三个向量寄存器。而在RISC-V Vector平台中,我们最多可以使用32个向量寄存器。显然,如果我们可以在每次循环中使用更多的寄存器,就可以处理更多的数据,以减少循环次数并提高性能。在RISC-V Vector平台中,多个向量寄存器可以被组合使用[4],从而让单条向量指令操作多个(一组)向量寄存器。


同样以处理数组点乘为例,假设待处理数组中各有64个float,在不使用寄存器分组时,每次可以处理4个float,需要循环16次;当将8个寄存器分为一组时,代码如下:

opt_RVV::fastMul(float* a, float* b, float* c, size_t n) {
    for(int i = 0; i < n; i+=4*8){
        vfloat32m1_t va = vle32_v_f32m8(a+i, 4*8); //将8个向量寄存器视为一个,装载32个float到va    
        vfloat32m1_t vb = vle32_v_f32m8(b+i, 4*8); //将8个向量寄存器视为一个,装载32个float到vb        
        vfloat32m1_t vc = vfmul_vv_f32m8(va, vb, 4*8); //调用Intrinsics实现向量乘法
        vse32_v_f32m1 (c+i, vc, 4*8); //存储到内存c
    }
}

在这种情况下,我们一共使用了24个向量寄存器,每次循环可以处理32个float,原本需要执行16次的循环仅需执行2次即可。


但随之而来的问题是,如果待处理的数组长度不是32的倍数,则最后一次循环将试图读取和写入超过数组边界的内存地址,我们将这类问题的解决方法称为尾端处理。通常,有两种策略处理尾端:放弃最后一次循环,转而使用标量;引入掩码。在实际中(如AVX版本的实现)都采用第一种策略,即在n-1向量循环后追加1个标量循环,这是因为掩码运算的开销通常很大,但追加标量循环的方式也会损失一部分性能,同时增加代码体积。而在RISC-V Vector平台中,我们可以使用向量长度寄存器vl解决这个问题[5]。


vl寄存器保存一个无符号整数,用于控制向量指令所操作寄存器内的元素个数。例如,vle32_v_f32m1(a,2)函数将从a内存空间加载两个float到向量寄存器中,而不是4个。在调用Intrinsics时,我们可以显式给出vl参数,从而在不增加开销的情况下更好的处理尾端:

opt_RVV::fastMul(float* a, float* b, float* c, size_t n) {
    int vl = 4*8;
    for(int i = 0; i < n; i+=vl){
        if (i + vl > n) // 如果再处理32个元素就要越界了(说明本次循环是最后一次,即尾端)
            vl = n - i; // 则只处理剩下的(n-i)个
        vfloat32m1_t va = vle32_v_f32m8(a+i, vl); //将8个向量寄存器视为一个,装载vl个float到va    
        vfloat32m1_t vb = vle32_v_f32m8(b+i, vl); //将8个向量寄存器视为一个,装载vl个float到vb        
        vfloat32m1_t vc = vfmul_vv_f32m8(va, vb, vl); //调用Intrinsics实现向量乘法
        vse32_v_f32m1 (c+i, vc, vl); //存储到内存c
    }
}


测试与总结

通过使用RISC-V Vector平台的Intrinsic实现加速算法,并使用寄存器分组和向量长度寄存器vl进一步优化,OpenCV DNN模块可以在RISC-V Vector平台上获得更好的推理性能。


在实际中,我们实现了卷积(Conv)、深度可分卷积(DepthwiseConv)、矩阵乘法(GEMM)和转置矩阵乘法(GEMM1T)四个算法,并在QEMU模拟器上验证了正确性。性能提升方面,由于缺少支持(0.10及以上版本)向量扩展的硬件设备或架构/指令级别的模拟器,目前仅使用 OpenCV DNN 测试集在 QEMU 模拟器上的执行时间作为性能指标。由于 QEMU 是功能仿真的模拟器,该性能指标仅具有十分有限的参考意义。


以下图表是在不同向量寄存器长度下,该优化工作与优化前的执行时间对比,以小时为单位。


以上是对GSoC 2021 优化面向RISC-V平台的OpenCV DNN模块项目的一个背景和实现思路简介,欢迎大家关注2021年11月16日的OpenCV Webinar网络直播,届时笔者会和大家分享包括卷积和矩阵乘法实现在内的更多项目细节,并介绍RISC-V Vector平台中向量长度不可知(Vector-Length Agnostic,VLA)特性在本项目中的应用。


参考资料

[1] 张尹同学的GSoC 2020项目 Optimize OpenCV for RISC-V 为 Universal Intrinsics 增加了RISC-V Vector后端

[2] 详见 OpenCV Docs: Universal intrinsics

https://docs.opencv.org/4.x/df/d91/group__core__hal__intrin.html#ga05bb5c33c35c2aa1d1c5809b9220d268

[3] 该例子和1,2两种实现代码摘自OpenCV中国团队文章:使用OpenCV中的universal intrinsics为算法提速 (1) ,作者为于仕琪老师。

[4] 详见RISC-V-Spec(https://github.com/riscv/riscv-v-spec/blob/master/v-spec.adoc#sec-inactive-defs: 3.4.2章节:Vector Register Grouping

[5] 详见RISC-V-Spec(https://github.com/riscv/riscv-v-spec/blob/master/v-spec.adoc#sec-inactive-defs: 5.4章节:Prestart, Active, Inactive, Body, and Tail Element Definitions




OpenCV学堂 专注计算机视觉开发技术分享,技术框架使用,包括OpenCV,Tensorflow,Pytorch教程与案例,相关算法详解,最新CV方向论文,硬核代码干货与代码案例详解!作者在CV工程化方面深度耕耘15年,感谢您的关注!
评论
  • 引言工程师作为推动科技进步和社会发展的核心力量,在各个领域发挥着关键作用。为深入了解工程师的职场现状,本次调研涵盖了不同行业、不同经验水平的工程师群体,通过问卷调查、访谈等方式,收集了大量一手数据,旨在全面呈现工程师的职场生态。1. 工程师群体基本信息行业分布:调研结果显示,工程师群体广泛分布于多个行业,其中制造业占比最高,达到 90%,其次是信息技术、电子通信、能源等行业。不同行业的工程师在工作内容、技术要求和职业发展路径上存在一定差异。年龄与经验:工程师群体以中青年为主,30 - 45 岁年
    Jeffreyzhang123 2024-12-27 17:39 72浏览
  • 采购与分销是企业运营中至关重要的环节,直接影响到企业的成本控制、客户满意度和市场竞争力。以下从多个方面介绍如何优化采购与分销:采购环节优化供应商管理供应商评估与选择:建立一套全面、科学的供应商评估体系,除了考虑价格因素,还要综合评估供应商的产品质量、交货期、信誉、研发能力、售后服务等。通过多维度评估,选择那些能够提供优质产品和服务,且与企业战略目标相契合的供应商。建立长期合作关系:与优质供应商建立长期稳定的合作关系,这种合作模式可以带来诸多好处。双方可以在信任的基础上进行深度沟通与协作,共同开展
    Jeffreyzhang123 2024-12-27 17:43 60浏览
  • 在当今这个数字化的时代,电子设备无处不在,从我们手中的智能手机、随身携带的笔记本电脑,到复杂的工业控制系统、先进的医疗设备,它们的正常运行都离不开一个关键的 “幕后英雄”—— 印刷电路板(Printed Circuit Board,简称 PCB)。PCB 作为电子设备中不可或缺的重要部件,默默地承载着电子元件之间的连接与信号传输,是整个电子世界的基石。揭开 PCB 的神秘面纱PCB,简单来说,就是一块由绝缘材料制成的板子,上面通过印刷、蚀刻等工艺形成了导电线路和焊盘,用于固定和连接各种电子元件。
    Jeffreyzhang123 2024-12-27 17:21 53浏览
  • 一、引言无人机,作为近年来迅速崛起的新兴技术产物,正以前所未有的速度改变着众多行业的运作模式,从民用领域的航拍、物流,到工业领域的测绘、巡检,再到军事领域的侦察、打击等,无人机的身影无处不在。为了深入了解无人机的现状,本次调研综合了市场数据、行业报告、用户反馈等多方面信息,全面剖析无人机的发展态势。二、市场规模与增长趋势随着技术的不断进步和成本的逐渐降低,无人机市场呈现出爆发式增长。近年来,全球无人机市场规模持续扩大,预计在未来几年内仍将保持较高的增长率。从应用领域来看,消费级无人机市场依然占据
    Jeffreyzhang123 2024-12-27 17:29 82浏览
  • 起源与基础20 世纪 60 年代:可编程逻辑设备(PLD)的概念出现,一种被称为 “重构能力” 的芯片的可编程性吸引了许多工程师和学者。20 世纪 70 年代:最早的可编程逻辑器件 PLD 诞生,其输出结构是可编程的逻辑宏单元,它的硬件结构设计可由软件完成,设计比纯硬件的数字电路更灵活,但结构简单,只能实现小规模电路。诞生与发展20 世纪 80 年代中期:为弥补 PLD 只能设计小规模电路的缺陷,复杂可编程逻辑器件 CPLD 被推出,它具有更复杂的结构,能够实现较大规模的电路设计。1988 年:
    Jeffreyzhang123 2024-12-27 10:41 66浏览
  • 一、前言 回首2024,对于我而言,是充满挑战与收获的一年。在这一年里,我积极参与了论坛的众多活动,不仅拓宽了我的认知边界(有些东西不是你做不到,而是你想不到),还让我在实践中收获了宝贵的经验和。同时,多种多样的论坛活动让我们全方面的接受新东西,连接新知识,多种类型的的活动交织了你我的2024。在这里说一说对过去一年的活动经历,进行一次年终总结,并谈谈我的收获和感受,以及对2025年的展望。二、活动足迹(一)快速体验:机智云Gokit2.0开发板初体验 机智云Gokit2.0开发板的体验活动让大
    无言的朝圣 2024-12-27 14:50 49浏览
  • 在当今科技飞速发展的时代,工业电子作为现代制造业的中流砥柱,正以前所未有的速度推动着各个行业的变革与进步。从汽车制造到航空航天,从智能家居到工业自动化,工业电子的身影无处不在,为我们的生活和生产带来了巨大的改变。工业电子的崛起与发展工业电子的发展历程可谓是一部波澜壮阔的科技进化史。追溯到上世纪中叶,电子技术开始逐渐应用于工业领域,最初主要是简单的电子控制装置,用于提高生产过程的自动化程度。随着半导体技术、计算机技术和通信技术的不断突破,工业电子迎来了爆发式的增长。集成电路的发明使得电子设备的体积
    Jeffreyzhang123 2024-12-27 15:40 67浏览
  • 在当今竞争激烈的商业世界中,供应链管理已成为企业生存与发展的核心竞争力之一。它就像一条无形的纽带,将供应商、制造商、分销商、零售商直至最终消费者紧密相连,确保产品和服务能够高效、顺畅地流转。今天,就让我们一同深入探索供应链管理的奥秘。供应链管理是什么简单来说,供应链管理是对从原材料采购、生产制造、产品配送直至销售给最终用户这一整个过程中,涉及的物流、信息流和资金流进行计划、协调、控制和优化的管理活动。它不仅仅是对各个环节的简单串联,更是一种通过整合资源、优化流程,实现整体效益最大化的管理理念和方
    Jeffreyzhang123 2024-12-27 17:27 53浏览
  • 在科技飞速发展的今天,汽车不再仅仅是一种交通工具,更是一个融合了先进技术的移动智能空间。汽车电子作为汽车产业与电子技术深度融合的产物,正以前所未有的速度推动着汽车行业的变革,为我们带来更加智能、安全、舒适的出行体验。汽车电子的发展历程汽车电子的发展可以追溯到上世纪中叶。早期,汽车电子主要应用于发动机点火系统和简单的电子仪表,功能相对单一。随着半导体技术的不断进步,集成电路被广泛应用于汽车领域,使得汽车电子系统的性能得到了显著提升。从电子燃油喷射系统到防抱死制动系统(ABS),从安全气囊到车载导航
    Jeffreyzhang123 2024-12-27 11:53 80浏览
  • 在当今这个科技飞速发展的时代,物联网(IoT)已经不再是一个陌生的概念,它正以一种前所未有的速度改变着我们的生活和工作方式,像一股无形的力量,将世界紧密地连接在一起,引领我们步入一个全新的智能时代。物联网是什么简单来说,物联网就是通过感知设备、网络传输、数据处理等技术手段,实现物与物、人与物之间的互联互通和智能化管理。想象一下,你的家里所有的电器都能 “听懂” 你的指令,根据你的习惯自动调节;工厂里的设备能够实时监测自身状态,提前预警故障;城市的交通系统可以根据实时路况自动优化信号灯,减少拥堵…
    Jeffreyzhang123 2024-12-27 17:18 50浏览
  • 在科技飞速发展的今天,医疗电子作为一个融合了医学与电子技术的交叉领域,正以前所未有的速度改变着我们的医疗模式和健康生活。它宛如一颗璀璨的明珠,在医疗领域绽放出耀眼的光芒,为人类的健康福祉带来了诸多惊喜与变革。医疗电子的神奇应用医疗电子的应用范围极为广泛,深入到医疗的各个环节。在诊断方面,各种先进的医学成像设备堪称医生的 “火眼金睛”。X 光、CT、MRI 等成像技术,能够清晰地呈现人体内部的结构和病变情况,帮助医生准确地发现疾病。以 CT 为例,它通过对人体进行断层扫描,能够提供比传统 X 光更
    Jeffreyzhang123 2024-12-27 15:46 56浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦