arm汇编解析—tengine winograd_nhwc卷积实现

与xnn/qnn卷积的异同点

tengine中实现了NHWC的winograd,目前nhwc的卷积主要以xnn/qnn框架为典型,所以这里拿tengine中的U*V和xnn/qnn卷积做个比较,简单总结一下,因为笔者觉得有相似的地方,举一反三嘛~

区别的地方:

1、分块大小不同,tengine是4x12的分块,xnn/qnn一般是4x4或4x8 (浮点引擎)

2、winograd毕竟在kernel维度不需要累加,只需在channel维度累加即可,因此实现的时候遍历输入通道求和即可

3、xnn/qnn计算的时候是一个输入broad到多个kernel的output维度相乘,然后遍历kernel_w*kernel_h*input_c,输入可以同时做4个,这样可以得到4x8或者4x4的output分块输出结果;tengine是将kernel broad到输入块维度相乘

4、输入的pack方式不同,虽然xnn/qnn是采用indirect buffer的形式,但是从内存来看也指pack的wh维度上的值,即kenelsize*block*input_c,而tengine的输入pack是kernelsize*input*block,这样的pack方式取决于卷积和点乘的实现方式不同

实质上是一样的

相似的地方:

1、kernel的pack都是按照nchw的维度,即输出通道,然后是输入通道,最后是kernelsize维度

 

汇编代码解析

关于预加载指令讲解可以参考博客:

https://jzwdsb.github.io/2018/07/neon_frequently_use/

https://www.jianshu.com/p/5f75fa02c5d0

//函数声明:
extern void wino_sgemm_4x12_A17(float* output, float* input, float* kernel, long cin);
    .section .text, "ax" 
    //.section指示把代码划分成若干个段(Section),程序被操作系统加载执行时,每个段被加载到不同的地址,操作系统对不同的页面设置不同的读、写、执行权限
    //.text段保存代码,是只读和可执行的,后面那些指令都属于.text段
    //"ax"表示该节区可分配并且可执行, ax是 allocation  execute的缩写

    .align 5
    //2^5 4字节对齐

    .type wino_sgemm_4x12_A17 STT_FUNC

    .global wino_sgemm_4x12_A17
    //wino_sgemm_4x12_A17 是一个符号(Symbol),符号在汇编程序中代表一个地址,可以用在指令中,
汇编程序经过汇编器的处理之后,所有的符号都被替换成它所代表的地址值。在C语言中我们通过变量名访问一
个变量,其实就是读写某个地址的内存单元,我们通过函数名调用一个函数,其实就是跳转到该函数第一条指令
所在的地址,所以变量名和函数名都是符号,本质上是代表内存地址的。
    //.global指示告诉汇编器,wino_sgemm_4x12_A17这个符号要被链接器用到,所以要在目标文件的符号
表中标记它是一个全局符号。_start就像C程序的main函数一样特殊,是整个程序的入口,链接器在链接时会查
找目标文件中的wino_sgemm_4x12_A17符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序
都要提供一个wino_sgemm_4x12_A17符号并且用.global声明。如果一个符号没有用.global声明,就表示这
个符号不会被链接器用到

    .hidden wino_sgemm_4x12_A17

#void wino_sgemm_4x12_A17(float* output, 
#                         float* input, 
#                         float* kernel, 
#                         long cin)

wino_sgemm_4x12_A17:
.fpu neon
    pld        [r1,#0x80] #预加载128Byte的input数据
    #一次计算4x4个输入,即4个kernelsize维度x4个输入通道
    push        {r4, lr}  #入栈保存r4和链接寄存器r14=lr
    vpush        {d8-d15} #入栈保存d8-d15向量寄存器

    #q4-q15 12 x 128bit(12x4个float) 并置0
    vmov.i64    q4,  #0x0
    vmov.i64    q5,  #0x0
    vmov.i64    q6,  #0x0
    vmov.i64    q7,  #0x0
    vmov.i64    q8,  #0x0
    vmov.i64    q9,  #0x0
    vmov.i64    q10, #0x0
    vmov.i64    q11, #0x0
    vmov.i64    q12, #0x0
    vmov.i64    q13, #0x0
    vmov.i64    q14, #0x0
    vmov.i64    q15, #0x0

    cmp        r3, #0x4       #r3-4 cpsr相关状态置位
    blt        loop4_end      #if r3<4 then 跳转
    lsr        r4, r3, #0x2   #r4=r3/4

loop4:
    #一次计算4个inputchannel维度,跟cacheline吻合,每次在计算之前提前加载input和kernel的数据
    #并在每次大循环前预加载pld 输入数据
    vldm        r1!,{d0-d1}        // i[3-0][0]
    vldm        r2,{d2-d7}         // k[11-0][0]
    subs        r4, r4, #1
    vmla.f32    q4, q0, d2[0]
    vmla.f32    q5, q0, d2[1]
    vmla.f32    q6, q0, d3[0]
    vmla.f32    q7, q0, d3[1]
    vmla.f32    q8, q0, d4[0]
    vmla.f32    q9, q0, d4[1]
    vldr        d2,[r2,#0x30]
    vldr        d3,[r2,#0x38]
    vmla.f32    q10,q0, d5[0]
    vmla.f32    q11,q0, d5[1]
    vmla.f32    q12,q0, d6[0]
    vmla.f32    q13,q0, d6[1]
    vmla.f32    q14,q0, d7[0]
    vmla.f32    q15,q0, d7[1]
    vldm        r1!,{d0-d1}        // i[3-0][0]
    vldr        d4,[r2,#0x40]
    vldr        d5,[r2,#0x48]
    vmla.f32    q4, q0, d2[0]
    vmla.f32    q5, q0, d2[1]
    vmla.f32    q6, q0, d3[0]
    vmla.f32    q7, q0, d3[1]
    vldr        d6,[r2,#0x50]
    vldr        d7,[r2,#0x58]
    vmla.f32    q8, q0, d4[0]
    vmla.f32    q9, q0, d4[1]
    vmla.f32    q10,q0, d5[0]
    vmla.f32    q11,q0, d5[1]
    vldr        d2,[r2,#0x60]
    vldr        d3,[r2,#0x68]
    vmla.f32    q12,q0, d6[0]
    vmla.f32    q13,q0, d6[1]
    vmla.f32    q14,q0, d7[0]
    vmla.f32    q15,q0, d7[1]
    vldm        r1!,{d0-d1}        // i[3-0][0]
    vldr        d4,[r2,#0x70]
    vldr        d5,[r2,#0x78]
    vmla.f32    q4, q0, d2[0]
    vmla.f32    q5, q0, d2[1]
    vmla.f32    q6, q0, d3[0]
    vmla.f32    q7, q0, d3[1]
    vldr        d6,[r2,#0x80]
    vldr        d7,[r2,#0x88]
    vmla.f32    q8, q0, d4[0]
    vmla.f32    q9, q0, d4[1]
    vmla.f32    q10,q0, d5[0]
    vmla.f32    q11,q0, d5[1]
    vldr        d2,[r2,#0x90]
    vldr        d3,[r2,#0x98]
    vmla.f32    q12,q0, d6[0]
    vmla.f32    q13,q0, d6[1]
    vmla.f32    q14,q0, d7[0]
    vmla.f32    q15,q0, d7[1]
    vldm        r1!,{d0-d1}        // i[3-0][0]
    vldr        d4,[r2,#0xa0]
    vldr        d5,[r2,#0xa8]
    vmla.f32    q4, q0, d2[0]
    vmla.f32    q5, q0, d2[1]
    pld        [r1,#0x140]         //提前prefetch输入数据,为下一循环准备数据
    vmla.f32    q6, q0, d3[0]
    vmla.f32    q7, q0, d3[1]
    vldr        d6,[r2,#0xb0]
    vldr        d7,[r2,#0xb8]
    vmla.f32    q8, q0, d4[0]
    vmla.f32    q9, q0, d4[1]
    pld        [r2,#0x380]        //提前prefetch权重数据,为下一循环准备数据
    vmla.f32    q10,q0, d5[0]
    pld        [r2,#0x3c0]
    vmla.f32    q11,q0, d5[1]
    pld        [r2,#0x400]
    vmla.f32    q12,q0, d6[0]
    add        r2, r2, #0xc0
    vmla.f32    q13,q0, d6[1]
    vmla.f32    q14,q0, d7[0]
    vmla.f32    q15,q0, d7[1]
    bne        loop4

loop4_end:
    ands        r3, r3, #0x3
    beq        save_result

loop1:
    vldm        r1!,{d0-d1}        // i[3-0][0]
    vldm        r2!,{d2-d7}        // k[11-0][0]
    vmla.f32    q4, q0, d2[0]
    vmla.f32    q5, q0, d2[1]
    vmla.f32    q6, q0, d3[0]
    vmla.f32    q7, q0, d3[1]
    vmla.f32    q8, q0, d4[0]
    vmla.f32    q9, q0, d4[1]
    vmla.f32    q10,q0, d5[0]
    vmla.f32    q11,q0, d5[1]
    vmla.f32    q12,q0, d6[0]
    vmla.f32    q13,q0, d6[1]
    vmla.f32    q14,q0, d7[0]
    vmla.f32    q15,q0, d7[1]
    subs        r3, r3, #0x1
    bne        loop1

save_result:、
    #最后保存4x12个output值
    vstm        r0!, {d8-d23}
    vstm        r0, {d24-d31}

end:
    vpop       {d8-d15}
    pop        {r4,pc}

    .end


 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值