Project Review-3 本科毕业设计

在神经网络中,存在着大量的参数,如果这些参数都用浮点数表示,将会消耗大量的硬件资源,这对于硬件资源有限的嵌入式平台是十分不友好的。在实际硬件实现中,往往采用将浮点数进行定点化处理,用定点数来表示浮点数。

数据的量化与截位

数据的量化

浮点数的定点化表示

在目前的神经网络量化策略中,一般采用16位的定点数来表示数据,且对网络的精度损失比较低。因此,本项目中均采用16位的定点小数来表示输入值和参数。16位的定点数一共有3部分组成,1位符号位,4位整数位,11位小数位。小数位的位数也称量化系数(若小数位宽为0,则该定点数只有整数部分,此时也称为定点整数)。为了便于表示,本文将有符号定点数的表示方式定义如下:
$$fix = mQn$$

其中,$m$ 表示 定点数的位宽,$n$ 表示小数位宽,即量化系数。

那么,其浮点数的定点化公式转化如公式1所示:
$$ fix = round(float\times2^{n})\tag{1}$$

其python代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 浮点数量化为定点数(dec)
def fp2fix(float_num, quant_width=11):
fix_num = round(float_num * (2**quant_width))

return fix_num

# 定点数(dec)转化为浮点数
def fix2fp(fix_num, bit_width=16, quant_width=11):
if (fix_num >= 2**(bit_width-1)):
float_num = (fix_num - 2**bit_width) / 2**quant_width
else:
float_num = fix_num / 2**quant_width

return float_num

定点数的位数扩展

在定点数的计算中,由于定点数的表示范围有限,对于$mQn$ 定点数的表示范围为
$$
-2^{m-n-1} \sim 2^{m-n-1}- \frac {1}{2^n}$$

例如16$Q$11的数据表示范围为 $-16 \sim 15.9995$。

在进行加法和乘法操作时,原先的位数已经无法正确表示计算后的结果,因此需要对数据进行位数的扩展。对于有符号定点数而言,在进行扩位时,整数部分需要进行符号位的扩展,小数部分则需要在末尾进行0补充。例如,将 $4Q2$ 定点数扩位成 $6Q3$ 的定点数,其具体扩位操作如下:
$$
\begin{align}
&(4Q2)4’b 10.11=1\times(-2^1)+0\times2^0+1\times2^{-1}+1*2^{-2}=-1.25\\
&(6Q3)6’b 110.110 =1\times(-2^2)+1\times2^1+0\times2^0+1\times2^{-1}+1\times2^{-2}+0\times2^{-3}=
-1.25\\
\end{align}$$

由上式可知,数据经过扩位后数据大小并不发生变化。

数据的截位

在定点数的加法和乘法运算中,为了保证数据的准确性,计算结果都是要进行扩位来保存的,但在实际中由于硬件资源有限,随着数据的频繁计算,不可能一直通过数据扩位来保存数据,因此在保证数据正确性的情况下,需要对数据进行截位处理。一种比较精确的处理方式是先对截位后的数据进行四舍五入处理,如果四舍五入过程中由于进位导致数据溢出,还需要对结果做饱和处理。

定点数的运算

两个有符号定点数相加,两个加数一定要对齐小数点并进行符号位的拓展,为了保证数据不溢出,和的总位宽为加数的位宽+1。例如 $aQm$ 和 $bQn$ 相加($a>b,m>n$),则两者的小数位宽要统一为m位,整数位宽统一为a位,且和的存储格式要用 $(a+1)Qm$存储。

两个有符号定点数相乘,为了保证积不溢出,积的总位宽为两个乘数位宽之和。例如 $aQm$ 和 $bQn$ 相乘, 则积的存储格式要用 $(a+b)Q(m+n)$ 存储。

四舍五入

数据的截位主要依据定点数定义的小数位宽进行截位,但是把数据只进行简单的截取会使得结果不够精确,工程上一般采用通过判断符号位然后根据截掉的小数部分进行四舍五入进位的方式来处理。

对于正数,首先判断截取部分的最高位是否为1,若最高位为1,则需要进位加1;反之不需要进位。对于负数,进位判断正好相反,如果截取部分最高位为1以及其它位也有为1的情况,由于是负数,所以此时不需要进位。但与正数不同的是,负数不进位时需要加1;反之需要进位时不需要加1。其 verilog 代码如下:

1
2
3
4
// 小数位低位截位 -> 四舍五入进位判断
assign carry_bit = calc_out[2*dwidth-1]?(calc_out[qwidth-1] & (|calc_out[qwidth-2:0])):calc_out[qwidth-1];

assign dout_round = {calc_out[2*dwidth-1], calc_out[2*dwidth-1:qwidth]} + carry_bit;

饱和截位

所谓的饱和截位是指如果计算结果超出了数据格式能表示的最大值,则用最大值来表示这个溢出的数据。同理,如果计算结果超过了数据的最小值,那么就用最小值来代替这个数据。其 verilog 代码如下:

1
2
3
4
// 整数位高位截位 -> 溢出进行饱和操作
assign dout = (dout_round[2*dwidth-qwidth:dwidth-1] == {(dwidth-qwidth+2){1'b0}}
|| dout_round[2*dwidth-qwidth:dwidth-1] == {(dwidth-qwidth+2){1'b1}})?
dout_round[-1:0]:{dout_round[2*dwidth-qwidth],{(dwidth-1){!dout_round[2*dwidth-qwidtdwidthh]}}};

卷积计算电路的计算结果对比

为了验证计算结果的正确性,将之前转化好的定点数进行输入,经过卷积模块计算后,其最终的结果如表1所示。并将卷积电路计算得到的结果与Matlab软件计算结果进行比较,比较结果如下:

fpga fix fpga fix->float result on matlab error
0xffe0 -0.01562500000 -0.01510682666 -0.0005
0xff96 -0.05175781250 -0.05172769939 -0.0000
0x0004 0.001953125000 0.002844710976 -0.0009
0xff91 -0.05419921875 -0.05379437370 -0.0004
0x001b 0.013183593750 0.014041541711 -0.0009
0xffda -0.01855468750 -0.01836803962 -0.0002
0xffaf -0.03955078125 -0.03915937634 -0.0004
0xffd3 -0.02197265625 -0.02108133655 -0.0009
0xffee -0.00878906250 -0.00842345750 -0.0003
表1:卷积计算电路的计算结果对比

由上表可知,该卷积电路计算结果基本正确,误差也在可接受范围之内,如果对数据精度有更高的要求,可以对定点数量化系数进行调整。

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×