版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
第第頁ARM匯編快速入門教程
本文主要分享如何快速上手(ARM)(匯編)開發(fā)的經(jīng)驗(yàn)、匯編開發(fā)中常見的Bug以及Debug方法、用的Convolu(ti)onDephtwise算子的匯編實(shí)現(xiàn)相對于(C++)版本的加速效果三方面內(nèi)容。
01前言
(神經(jīng)網(wǎng)絡(luò))模型能夠在移動(dòng)端實(shí)現(xiàn)快速推理離不開高性能算子,直接使用ARM匯編指令來進(jìn)行算子開發(fā)無疑會(huì)大大提高算子的運(yùn)算性能。初次接觸匯編代碼可能會(huì)覺得其晦澀難懂然后望而卻步,但ARM匯編開發(fā)一旦入門就會(huì)覺得語言優(yōu)美簡潔,如果再切換到ARMINTRIS(IC)指令開發(fā)反而覺得沒有直接寫匯編碼來的方便。我會(huì)在第一節(jié)分享純小白如何快速上手ARM匯編開發(fā)的經(jīng)驗(yàn),第二節(jié)會(huì)列舉在匯編開發(fā)中常見的Bug以及Debug方法,第三節(jié)會(huì)展示常用的ConvolutionDephtwise算子的匯編實(shí)現(xiàn)相對于C++版本的加速效果。如果你已經(jīng)能很熟練地使用ARM匯編指令進(jìn)行開發(fā)了,可以跳過第一節(jié)。
02從簡單函數(shù)上手
學(xué)習(xí)匯編開發(fā)重要的一點(diǎn)是通過學(xué)習(xí)現(xiàn)有函數(shù)的匯編代碼來實(shí)現(xiàn)自己的需求
我寫的第一個(gè)匯編算子是MaxPooling算子,算子本身的計(jì)算過程非常簡單。但當(dāng)我開始實(shí)現(xiàn)MaxPooling的匯編代碼時(shí),我不知道第一行代碼怎么寫,不知道開頭和結(jié)尾怎么寫,不知道中間的計(jì)算邏輯怎么寫。當(dāng)時(shí)我就在MNN庫的source文件夾下面找到了一份邏輯簡單的、自己非常熟悉的Relu算子當(dāng)做參照來實(shí)現(xiàn)MaxPooling.之所以我(推薦)用一個(gè)邏輯簡單的、自己非常熟悉的算子當(dāng)做學(xué)習(xí)匯編的模版,是因?yàn)楫?dāng)算子的計(jì)算邏輯簡單時(shí),我們才能把注意力放在匯編函數(shù)的聲明、傳參、讀取數(shù)據(jù)、存儲結(jié)果、返回等等這些大的流程上面,至于內(nèi)部的函數(shù)實(shí)現(xiàn)(如何計(jì)算一行數(shù)據(jù)的最大值,如何去計(jì)算一個(gè)(寄存器)中所有數(shù)據(jù)的累加和等等)可以暫時(shí)不去關(guān)注。學(xué)習(xí)一個(gè)新的東西時(shí),我們找的例子模版不能過于復(fù)雜,因?yàn)檫@會(huì)導(dǎo)致我們將注意力放在例子本身的實(shí)現(xiàn)細(xì)節(jié)中,而忽略了如何去入門,這樣會(huì)增加我們的學(xué)習(xí)成本。
匯編函數(shù)的開頭與結(jié)尾
函數(shù)定義以asm_function開頭,后加函數(shù)名(以MNNAvgPoolInt8ARM64為例):
asm_functionMNNAvgPoolInt8//加上函數(shù)的傳參解釋,方便后續(xù)對照使用對應(yīng)的寄存器//voidMNNAvgPoolInt8(int8_t*dst,int8_t*src,size_toutputWidth,//size_tinputWidth,size_tkernelx,size_tkernely,size_tstridesx,//ssize_tp(ad)dingx,ssize_tfactor);//Autoload:x0:dst,x1:src,x2:outputWidth,x3:inputWidth,//x4:kernelx,x5:kernely,x6:stridesx,x7:paddingx//Loadfromsp://w8:factor
傳參:ARM64用于傳參的寄存器有8個(gè):x0-x7.如果函數(shù)的參數(shù)大于8,就需要使用sp寄存器讀取剩余參數(shù)。例如AvgPoolInt8算子中的第9個(gè)參數(shù)factor讀?。?/p>
//x8寄存器存儲參數(shù)factor的值,不是必須使用x8寄存器,用其他寄存器也是可以的。ldrx8,[sp,#0]
ARM寄存器使用不當(dāng)會(huì)導(dǎo)致程序crash。這里總結(jié)了ARM32和AMR64的寄存器基本使用規(guī)則。ARM32中通用寄存器和向量寄存器都有16個(gè),每個(gè)向量寄存器的最大使用長度是128位。ARM32中用于傳參的寄存器有4個(gè):r0-r3。ARM32中r13寄存器就是sp寄存器,指向棧頂;r14寄存器也叫l(wèi)r寄存器,存儲函數(shù)的返回值地址;r15寄存器也叫pc寄存器,存儲將要執(zhí)行的下一條指令的地址。在進(jìn)行匯編開發(fā)時(shí),一般不使用r13和r15寄存器來存儲臨時(shí)變量。r9寄存器的使用在各個(gè)平臺上可能不同,為了防止出錯(cuò),一般也不用來存儲臨時(shí)變量。當(dāng)不需要使用r14存儲返回值地址的信息時(shí),也可以使用其存儲臨時(shí)變量。下圖中我總結(jié)了ARM32中寄存器的基本使用規(guī)則,關(guān)于各寄存器更加詳細(xì)的介紹參考。
ARM64中通用寄存器和向量寄存器的個(gè)數(shù)比ARM32多一倍,有32個(gè)。ARM64中向量寄存器的使用更加靈活,可以8bit,16bit,32bit,64bit使用。例如,v0表示128位的向量寄存器,d0,s0,h0分別表示v0的低64位,32位,16位。注意,d1,s1,h1表示v1寄存器的低64位,32位,16位,而不是緊接著v0的第二個(gè)相應(yīng)位。ARM64的寄存器使用見下圖。
我們可以用浮點(diǎn)操作指令把向量寄存器中的數(shù)當(dāng)做標(biāo)量來進(jìn)行計(jì)算,需要注意在ARMV8中浮點(diǎn)操作指令不支持對16bit的浮點(diǎn)數(shù)進(jìn)行計(jì)算,僅支持做16bit和32bit,64bit之間的轉(zhuǎn)換。
fadd(Sd),Sn,Sm//32bitSingleprecisionf(sub)Dd,Dn,Dm//64bitDoubleprecisionfcvtSd,Hn//half-precisiontosingle-precisionfcvtDd,Hn//half-precisiontodouble-precisionfcvtHd,Sn//single-precisiontohalf-precisionfcvtHd,Dn//double-precisiontohalf-precision
對上圖中的“用完恢復(fù)”寄存器的使用:一些復(fù)雜的函數(shù)需要的向量寄存器或者通用寄存器可能會(huì)非常多,那就需要我們在開頭加載這些寄存器,不然會(huì)報(bào)錯(cuò)segmentfault.加載方法如下:
//d8-d15表示使用v8-v15這8個(gè)寄存器的64位,(2*64)/8=16,//這就是每次sp移位時(shí)(#16*i)中16的來源。stpd14,d15,[sp,#(-16*9)]!stpd12,d13,[sp,#(16*1)]stpd10,d11,[sp,#(16*2)]stpd8,d9,[sp,#(16*3)]stpx27,x28,[sp,#(16*4)]stpx25,x26,[sp,#(16*5)]stpx23,x24,[sp,#(16*6)]stpx21,x22,[sp,#(16*7)]stpx19,x20,[sp,#(16*8)]
在函數(shù)的結(jié)尾需要釋放這些寄存器:
ldpx19,x20,[sp,#(16*8)]ldpx21,x22,[sp,#(16*7)]ldpx23,x24,[sp,#(16*6)]ldpx25,x26,[sp,#(16*5)]ldpx27,x28,[sp,#(16*4)]ldpd8,d9,[sp,#(16*3)]ldpd10,d11,[sp,#(16*2)]ldpd12,d13,[sp,#(16*1)]ldpd14,d15,[sp],#(16*9)ret//最后需加上ret返回
ARM32中寄存器的數(shù)量只有ARM64的一半,自動(dòng)傳參的寄存器僅r0-r3這四個(gè)寄存器,其他寄存器的加載方式和ARM64也不同,我們依然以MNNAvgPoolInt8為例,代碼的解釋和新手閉坑的地方我直接在下面的解釋中寫明。//函數(shù)定義asm_functionMNNAvgPoolInt8//voidMNNAvgPoolInt8(int8_t*dst,int8_t*src,size_toutputWidth,//size_tinputWidth,size_tkernelx,size_tkernely,size_tstridesx,//ssize_tpaddingx,ssize_tfactor);//Autoload:r0:dst,r1:src,r2:outputWidth,r3:inputWidth//r4:kernelx,r5:kernely,r7:stridesx,r8:paddingx,lr:factor//其他寄存器加載,注意lr寄存器每次必須被push進(jìn)來(可以不使用),不然會(huì)報(bào)錯(cuò)segmentfault.push{r4-r8,r10-r11,lr}//上一行push了8個(gè)寄存器,那么sp指針會(huì)向低地址移動(dòng)(8*4=32)個(gè)字節(jié)(ARM32每個(gè)指針占4個(gè)字節(jié)),//所以第五個(gè)參數(shù)“kernelx”加載時(shí)需要將sp的地址加(#32).//虛擬內(nèi)存中棧是從高地址向低地址擴(kuò)展的,而函數(shù)傳參是從右往左傳去棧中的,//所以后面的參數(shù)地址會(huì)比前面的高,即相對sp寄存器的地址增加的更多。ldrr4,[sp,#32]//kernelxldrr5,[sp,#36]//kernelyldrr7,[sp,#40]//stridesxldrr8,[sp,#44]//paddingxldrlr,[sp,#48]//factor//加載向量寄存器一定要放在利用sp寄存器來讀取所有函數(shù)參數(shù)之后,//否則不能正常讀取函數(shù)參數(shù)vpush
{q4-q7}
ARM32結(jié)尾對寄存器的釋放
//不需要poplr寄存器,但是必須poppc寄存器。//ARM32結(jié)尾不需要寫ret,這和ARM64不同。vpop{q4-q7}pop{r4-r8,r10-r11,pc}
核心功能的實(shí)現(xiàn)
寫匯編代碼之前,我們一定要先實(shí)現(xiàn)C++版本的代碼,保證C++版本的算子在ARM移動(dòng)端的計(jì)算結(jié)果是正確的。這樣做有兩個(gè)目的:第一,保證我們對算子的理解是正確并清晰的,否則寫匯編算子就是浪費(fèi)時(shí)間;第二,為匯編算子的輸出結(jié)果提供標(biāo)準(zhǔn)答案,因?yàn)橥瑯拥腃++代碼在不同的平臺上的計(jì)算結(jié)果可能會(huì)略有不同(但差異不會(huì)很大),我們需要保證匯編版本的算子和C++版本的算子計(jì)算結(jié)果在ARM平臺上完全一致。
匯編代碼中條件判斷和分支跳轉(zhuǎn)
MaxPooling算子通過遍歷局部區(qū)域的所有元素,進(jìn)而找到區(qū)域內(nèi)的最大值。這就涉及到循環(huán)指令、地址跳轉(zhuǎn)指令和比較兩個(gè)向量寄存器中對應(yīng)元素。關(guān)于指令的解釋我直接在代碼解釋中寫明。
比較兩個(gè)向量寄存器中對應(yīng)元素的大小
/*smax,smin比較整型數(shù)數(shù)據(jù)的大小ARM匯編有符號整數(shù)的指令一般以s開頭(signedint)無符號整數(shù)的指令一般以u開頭(unsignedint)浮點(diǎn)數(shù)據(jù)的指令一般以f開頭(float)*///比較v0和v1寄存器中的16個(gè)int8_t數(shù)據(jù),//并將對應(yīng)位置上的較大值存儲在v2的相應(yīng)位置上//b表示以8位來讀取數(shù)據(jù),相應(yīng)的匯編中h:16位,s:32位,d:64位smaxv2.16b,v0.16b,v1.16bsminv10.4s,v11.4s,v12.4s//比較v11和v12的4個(gè)int32_t數(shù)據(jù)的大小
循環(huán)執(zhí)行某一段代碼
如果需要在ARM匯編中循環(huán)執(zhí)行一段代碼,那我們需要自定義一個(gè)符號來標(biāo)記這一段代碼。以MaxPooling算子為例,假設(shè)每一個(gè)像素點(diǎn)含有16個(gè)Channel,我們需要得到被kernel覆蓋到的9個(gè)像素點(diǎn)上對應(yīng)Channel的最大值,即重復(fù)執(zhí)行比較指令9次。例如用Loop來標(biāo)記我們需要循環(huán)的代碼段:
1.movw7,#-0x80//給通用寄存器賦值-128,即int8_t類型的最小值2.dupv0.16b,w7//初始化v0,v0中存儲了16個(gè)-1283.movx10,#9//計(jì)數(shù)//循環(huán)Loop:3.ld1{v1.16b},[x0]//從地址x0中加載16個(gè)int8的數(shù)據(jù)到v1寄存器,與v0做比較4.smaxv0.16b,v0.16b,v1.16b//用v0記錄最終的比較結(jié)果5.addx0,x0,#1//移動(dòng)像素點(diǎn)的地址,這里我們假設(shè)9個(gè)像素點(diǎn)是連續(xù)的6.subx10,x10,#1//比較完一個(gè)像素點(diǎn)的16個(gè)Channel大小后,計(jì)數(shù)減17.cmpx10,#0//cmp是compare的縮寫:比較x10和0的大小8.bgtLoop//bgt是branchgreaterthan的縮寫,滿足條件就跳到分支Loop執(zhí)行//循環(huán)執(zhí)行結(jié)束9.st1{v0},[x1]//存儲寄存器v0中的16個(gè)int8_t數(shù)據(jù)到地址x1中//ARM匯編代碼是按照從上到下的順序來執(zhí)行的,//所以跳出Loop不需要額外的指令來表示結(jié)束該分支//當(dāng)不滿足x10>0時(shí),會(huì)直接執(zhí)行第9行代碼
如何查找需要的指令
靈活地運(yùn)用各種匯編指令往往能提高算子性能。
利用現(xiàn)成的匯編代碼查找指令
當(dāng)我們閱讀一些匯編代碼時(shí),根據(jù)匯編指令去查詢其功能是非常容易的,甚至根據(jù)指令名我們可以猜測出他的功能。但是當(dāng)我們第一次寫匯編代碼時(shí),想知道實(shí)現(xiàn)某個(gè)功能可以使用哪些指令往往很難。此時(shí)最關(guān)鍵的一點(diǎn),需要我們思考哪個(gè)函數(shù)中會(huì)用到我將要實(shí)現(xiàn)的功能,然后去參考他的匯編實(shí)現(xiàn)過程。比如寫Pooling算子的匯編代碼時(shí)不知道如何去進(jìn)行循環(huán)代碼段的編寫,我們就可以參考矩陣乘算子的匯編代碼去學(xué)習(xí)分支跳轉(zhuǎn),寄存器的比較等指令。當(dāng)我們不知道如何用匯編指令去實(shí)現(xiàn)浮點(diǎn)數(shù)轉(zhuǎn)整數(shù)的四舍五入時(shí),MNN中現(xiàn)成的Float2Int8函數(shù)一定會(huì)有相應(yīng)的指令實(shí)現(xiàn)這個(gè)功能。當(dāng)我們編寫了越來越多的匯編代碼,會(huì)接觸到更多的匯編指令,解決問題的思路和視野也更開闊。
利用關(guān)鍵詞在ARM官網(wǎng)查找指令
ARM官網(wǎng)列舉了所有匯編指令的用法,其中ARM64的指令手冊比ARM32更易查找和理解。一般ARM64的指令在ARM32系統(tǒng)都能找到對應(yīng)的等效指令。偶爾我們也需要ARMIntrisic指令來完成一些簡單函數(shù)的開發(fā),Intrisic指令可以參考。利用好功能的關(guān)鍵詞能提高查找指令的速度。例如某次(編程)中我需要查找哪些指令能實(shí)現(xiàn)“int8+int16->int16"的功能,顯然關(guān)鍵詞是"add".官網(wǎng)中會(huì)列舉適用于各種場景的向量加法指令,很快就可以定位到"saddwv0.8h,v1.8h,v2.8b"指令。
03ARM匯編Debug方法和常見錯(cuò)誤列舉
利用好“打印printf”
匯編代碼的調(diào)試一直是個(gè)難題,不能像C++代碼那樣一步步Debug查看變量的值,只能通過在函數(shù)調(diào)用的外層加打印的方式來查看匯編代碼的執(zhí)行結(jié)果。不過只要我們能利用好打印,匯編代碼的BUG排查就能簡單不少!具體來說,如果我們需要查看某個(gè)中間變量的值,我們可以在代碼內(nèi)部用返回值地址來存儲該值,從而我們可以在匯編代碼的外部打印該地址存儲的內(nèi)容,這樣間接地檢查代碼執(zhí)行的邏輯是否符合預(yù)期。
函數(shù)傳參錯(cuò)誤
函數(shù)傳參錯(cuò)誤非常容易被忽視,因?yàn)檫@個(gè)錯(cuò)誤很少會(huì)直接報(bào)錯(cuò)"segmentfault",而是發(fā)現(xiàn)匯編算子的結(jié)果和C++版本不一致時(shí),經(jīng)過一步步排查才發(fā)現(xiàn)傳參就出現(xiàn)了錯(cuò)誤。畢竟我們發(fā)現(xiàn)結(jié)果錯(cuò)誤時(shí),更習(xí)慣于去檢查匯編代碼中最復(fù)雜的邏輯,不太會(huì)想到代碼開頭的函數(shù)傳參就已經(jīng)錯(cuò)了。目前為止,我遇到過的傳參錯(cuò)誤就只有以下兩種:
1、除了整型以外的數(shù)據(jù)傳參應(yīng)該用指針傳入,而不是直接傳入?yún)?shù)值。浮點(diǎn)參數(shù)傳遞方式與編譯器及參數(shù)配置相關(guān),可能不同平臺下傳遞方式不一樣。如果直接浮點(diǎn)數(shù)值傳參,帶來的結(jié)果有可能是:浮點(diǎn)參數(shù)后面的參數(shù)數(shù)值都是前一個(gè)參數(shù)的數(shù)據(jù),也就是發(fā)生了傳參的偏移,導(dǎo)致計(jì)算結(jié)果對不上;如果恰巧你需要從某個(gè)參數(shù)中l(wèi)oad數(shù)據(jù),該參數(shù)的值受到了浮點(diǎn)參數(shù)錯(cuò)誤傳遞的影響,那有可能會(huì)報(bào)segmentfault的錯(cuò)誤。
//正確傳參,用指針傳遞浮點(diǎn)常數(shù)para0voidfunc(float*para0,float*dst)//錯(cuò)誤傳參,直接傳入常數(shù)para0voidfunc(floatpara0,float*dst)
2、傳參寄存器使用錯(cuò)誤
ARM64自動(dòng)傳參的寄存器有8個(gè):x0-x7,ARM32自動(dòng)傳參的寄存器有4個(gè):r0-r3。如果參數(shù)個(gè)數(shù)大于8(4),就需要從sp寄存器的相對位置來load參數(shù)。asm_functionMNNAvgPoolInt8//加上函數(shù)的傳參解釋,方便后續(xù)對照使用對應(yīng)的寄存器//voidMNNAvgPoolInt8(int8_t*dst,int8_t*src,size_toutputWidth,//size_tinputWidth,size_tkernelx,size_tkernely,size_tstridesx,//ssize_tpaddingx,ssize_tfactor);//Autoload:x0:dst,x1:src,x2:outputWidth,x3:inputWidth,//x4:kernelx,x5:kernely,x6:stridesx,x7:paddingx//Loadfromsp://w8:factor
3、整型參數(shù)建議使用ssize_t和size_t傳參
定義一個(gè)函數(shù):voidfunc(int8_t*dst,int8_t*src,float*pa(ram)s0,float*params1,intwidth,intheight,intkernelx,intkernely,intneedBro(adc)ast)
按照前面的介紹,第9個(gè)參數(shù)needBroadcast應(yīng)該由sp寄存器來加載,如:ldrx8,[sp,#0],如果我們需要比較needBroadcast和0的大小,寫成:cmpx8,#0,無論x8是否為0,代碼的判斷結(jié)果都會(huì)是false.除非將判斷語句寫成:cmpw8,#0.出現(xiàn)這種問題的原因在于,ssize_t和size_t這兩種類型,ARM64和ARM32會(huì)將其分別看做是64位和32位的數(shù)據(jù),而對于int類型的數(shù)據(jù),ARM64和ARM32上都會(huì)是32位的數(shù)據(jù),而ARM64的通用寄存器以x來使用是64位的(即x1,x2...),以w來使用才是32位的(即w1,w2...)。所以要比較x8與0的大小關(guān)系,應(yīng)是:cmp,w8,#0.
對于上述問題的更好的解決辦法是,函數(shù)聲明時(shí)將needBroadcast參數(shù)的類型定義成ssize_t,因?yàn)樵搮?shù)的取值可能是-1,1,0,我們將其定義成有符號類型。在匯編代碼中再次使用cmpx8,#0來比較結(jié)果就是正確的了,當(dāng)然此時(shí)我們還是用w8和0比較的話,結(jié)果也是正確的。
?
ARM32向量寄存器和參數(shù)加載的順序問題
在匯編開發(fā)中我遇到過這樣的問題,定義一個(gè)函數(shù)如下:
//voidMNNAvgPoolInt8(int8_t*dst,int8_t*src,size_toutputWidth,//size_tinputWidth,size_tkernelx,size_tkernely,size_tstridesx,//ssize_tpaddingx,ssize_tfactor);asm_functionMNNAvgPoolInt8//Autoload:r0:dst,r1:src,r2:outputWidth,r3:inputWidth//Loadfromsp:r4:kernelx,r5:kernely,r7:stridesx,r8:paddingx,lr:factor2.push{r4-r8,r10-r11,lr}3.vpush{q4-q6}4.ldrr4,[sp,#32]5.ldrr5,[sp,#36]6.ldrr7,[sp,#40]7.ldrr8,[sp,#44]8.
ldr
lr,
[sp,
#48]
//
lr:
factor
這樣可能不會(huì)出現(xiàn)報(bào)錯(cuò)segmentfault,但是參數(shù)的加載結(jié)果是錯(cuò)的。原因在于第3行vpush應(yīng)該在通過sp加載完所有的函數(shù)參數(shù)之后,而不是在此之前。因?yàn)閜ush了8個(gè)通用寄存器入棧之后,再push向量寄存器入棧,那么函數(shù)參數(shù)相對于sp寄存的位置就不再是(8x4=32).相對位置的偏移發(fā)生了變化。第3行的代碼應(yīng)該在第8行后面。
ARM64通用寄存器的使用問題
在ARM64中給通用寄存器賦整型數(shù)值
//通用寄存器的賦值只能用32位來使用寄存器movw10,#0//rightmovx10,#0//error//后續(xù)計(jì)算中要使用x10來進(jìn)行加減乘的計(jì)算,需要將w10擴(kuò)展成x10:uxtwx10,w10//w10中32位數(shù)據(jù)在x10的低32位中保持不變,x10的高32位填充為0.
sub,add等指令只能對整型數(shù)據(jù)操作,浮點(diǎn)類型數(shù)據(jù)需要使用fsub,fadd等fmovv1.4s,#1.0fmovv2.4s,#0.2fsubv1.4s,v1.4s,v2.4s
四舍五入的問題
ARM32和ARM64中浮點(diǎn)數(shù)取整的方式不一樣。ARM32中浮點(diǎn)數(shù)轉(zhuǎn)換成整數(shù)的指令(vcvt.s32.f32)是向負(fù)無窮取整的,在ARM32中沒有四舍五入的取整指令。需要在ARM32中實(shí)現(xiàn)四舍五入,可以這樣做:
//對寄存器q3中的4個(gè)浮點(diǎn)數(shù)據(jù)做四舍五入取整//q3:-1.4,4.5,1.1,-2.7->q3:-1,4,1,-3vmov.f32q1,#0.5vmov.f32q2,#-0.5vcgt.f32q12,q3,#0vbsl.f32q12,q1,q2//bitwiseselect.vadd.f32q13,q12,q3vcvt.s32.f32q3,q13
ARM64提供的取整指令更加靈活方便,有://q10:-1.4,4.5,1.1,-2.7fcvtasq1,q10//q1:-1,5,1,-3就近取整fcvtzsq2,q10//q2:-1,4,1,-2向0取整fcvtmsq3,q10//q3:-2,4,1,-3向負(fù)無窮取整fcvtpsq4,q10//q4:-1,5,2,-3向正無窮取整fcvtnsq4,q10//q4:-2,4,2,-2向最近的偶數(shù)取整
整型數(shù)據(jù)和浮點(diǎn)數(shù)據(jù)進(jìn)行數(shù)學(xué)運(yùn)算的問題
整型數(shù)據(jù)與浮點(diǎn)數(shù)據(jù)進(jìn)行相加或相乘等數(shù)學(xué)運(yùn)算之前,一定要先將整型數(shù)據(jù)轉(zhuǎn)換成浮點(diǎn)數(shù)據(jù)再進(jìn)行數(shù)學(xué)運(yùn)算,否則計(jì)算結(jié)果會(huì)出錯(cuò)。該過程經(jīng)常出現(xiàn)在Int8量化算子的開發(fā)中,往往是量化算子很難消除的計(jì)算負(fù)擔(dān)。用Binarymultiply的Int8量化算子舉例說明該過程:
//Int8量化的乘法算子,輸入和輸出均是Int8類型,但考慮到int8xint8會(huì)可能會(huì)導(dǎo)致越界,//在量化算子的實(shí)現(xiàn)過程中會(huì)將兩個(gè)輸入數(shù)據(jù)分別轉(zhuǎn)換成Float32數(shù)據(jù)之后相乘,//再將Float32的結(jié)果量化到Int8類型.sxtlv0.8h,v0.8b//int8x8_t->int16x8_tsxtlv1.8h,v1.8b//int8x8_t->int16x8_tsxtlv2.4s,v0.4h//v0的低64位數(shù)據(jù):int16x4_t->int32x4_tsxtl2v3.4s,v0.8h//v0的高64位數(shù)據(jù):int16x4_t->int32x4_tsxtlv4.4s,v1.4hsxtl2v5.4s,v1.8hscv(tf)v2.4s,v2.4s//int32x4_t->float32x4_tscvtfv3.4s,v3.4sscvtfv4.4s,v4.4sscvtfv5.4s,v5.4sfmulv2.4s,v2.4s,v6.4s//v6.4s:float32x4_t量化scale參數(shù)fmulv3.4s,v3.4s,v6.4sfmulv4.4s,v4.4s,v6.4sfmulv5.4s,v5.4s,v6.4s...
此處有同學(xué)可能會(huì)質(zhì)疑這么麻煩還有必要開發(fā)Int8量化的乘法算子嗎?具體原因可以參考之前關(guān)于開
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024-2030年中國室內(nèi)門行業(yè)發(fā)展現(xiàn)狀及前景趨勢分析報(bào)告
- 2024-2030年中國地波那非酮項(xiàng)目可行性研究報(bào)告
- 2024-2030年中國雙耳環(huán)行業(yè)發(fā)展?fàn)顩r規(guī)劃分析報(bào)告
- 眉山職業(yè)技術(shù)學(xué)院《系統(tǒng)仿真技術(shù)》2023-2024學(xué)年第一學(xué)期期末試卷
- 2024年版風(fēng)力發(fā)電項(xiàng)目施工合同詳細(xì)條款
- 馬鞍山職業(yè)技術(shù)學(xué)院《納米科學(xué)技術(shù)導(dǎo)論》2023-2024學(xué)年第一學(xué)期期末試卷
- 呂梁學(xué)院《藥物化學(xué)(I)》2023-2024學(xué)年第一學(xué)期期末試卷
- 2024年建筑行業(yè)工程承包協(xié)議更新版版B版
- 2021-2022學(xué)年云南省文山壯族苗族自治州高一上學(xué)期期中語文試題
- 洛陽商業(yè)職業(yè)學(xué)院《小學(xué)數(shù)學(xué)教學(xué)設(shè)計(jì)與技能訓(xùn)練》2023-2024學(xué)年第一學(xué)期期末試卷
- 《安全系統(tǒng)工程》期末考試卷及答案
- 數(shù)學(xué)師范-大學(xué)生職業(yè)生涯規(guī)劃書
- 科學(xué)閱讀材料(課件)二年級上冊科學(xué)教科版
- 2022年度尾礦庫安全風(fēng)險(xiǎn)辨識及分級管控表
- 投標(biāo)項(xiàng)目進(jìn)度計(jì)劃
- 關(guān)于發(fā)展鄉(xiāng)村產(chǎn)業(yè)的建議
- 登泰山記-教學(xué)課件
- 2024版水電費(fèi)繳費(fèi)協(xié)議范本
- 北師大版四年級數(shù)學(xué)上冊第五單元《方向與位置》(大單元教學(xué)設(shè)計(jì))
- 2024年西安交大少年班選拔考試語文試卷試題(含答案詳解)
- 2024年云南省昆明滇中新區(qū)公開招聘20人歷年重點(diǎn)基礎(chǔ)提升難、易點(diǎn)模擬試題(共500題)附帶答案詳解
評論
0/150
提交評論