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 neonpld [r1,#0x80] #預加載128Byte的input數據#一次計算4x4個輸入,即4個kernelsize維度x4個輸入通道push {r4, lr} #入棧保存r4和鏈接寄存器r14=lrvpush {d8-d15} #入棧保存d8-d15向量寄存器#q4-q15 12 x 128bit(12x4個float) 并置0vmov.i64 q4, #0x0vmov.i64 q5, #0x0vmov.i64 q6, #0x0vmov.i64 q7, #0x0vmov.i64 q8, #0x0vmov.i64 q9, #0x0vmov.i64 q10, #0x0vmov.i64 q11, #0x0vmov.i64 q12, #0x0vmov.i64 q13, #0x0vmov.i64 q14, #0x0vmov.i64 q15, #0x0cmp r3, #0x4 #r3-4 cpsr相關狀態置位blt loop4_end #if r3<4 then 跳轉lsr r4, r3, #0x2 #r4=r3/4loop4:#一次計算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, #1vmla.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, #0xc0vmla.f32 q13,q0, d6[1]vmla.f32 q14,q0, d7[0]vmla.f32 q15,q0, d7[1]bne loop4loop4_end:ands r3, r3, #0x3beq save_resultloop1: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, #0x1bne loop1save_result:、#最后保存4x12個output值vstm r0!, {d8-d23}vstm r0, {d24-d31}end:vpop {d8-d15}pop {r4,pc}.end?
總結
以上是生活随笔為你收集整理的arm汇编解析—tengine winograd_nhwc卷积实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: make: warning: Cloc
- 下一篇: 寻找重复数—leetcode287