目录
定点法(Fixed Point Approximation)
动态定点法(Dynamic Fixed Point Approximation)
迷你浮点法(Minifloat Approximation)
乘法变移位法(Multiplier-free arithmetic)
知识直通车
参考github链接:https://github.com/Ewenwan/MVision/tree/master/CNN/Deep_Compression/quantization/Ristretto
参考csdn链接:https://blog.csdn.net/yiran103/article/details/80336425
定点法(Fixed Point Approximation)
所谓定点数和浮点数,
是指在计算机中一个数的小数点的位置是固定的还是浮动的:
如果一个数中小数点的位置是固定的,则为定点数;
如果一个数中小数点的位置是浮动的,则为浮点数。
一般来说,定点格式可表示的数值的范围有限,但要求的处理硬件比较简单。
而浮点格式可表示的数值的范围很大,但要求的处理硬件比较复杂。
IL.FL
固定整数位二进制长度和小数位二进制长度。
最大表示数:
Xmax = 2^(IL-1) - 2^(-FL)
32浮点数----->8位定点(Q4.4)
----->16位定点 ( Q8.8 Q9.7)
例如 0 1 1 0 1 1 0 1 , FL = 2
符号位: 0
尾数部分全部作为整数位: 1 1 0 1 1 0 1 B = 109D
小数部分最后两位,所以整体需要除以2^(2)
则表示的数为: R = (-1)^0 * 109 * 2^(-2) = 27.25
浮点数 ff 得到量化的数:
放大/缩小: sff = ff * 2^(fl)
取整: round(sff)
动态定点法(Dynamic Fixed Point Approximation)
CNN的不同部分具有显着的动态范围,
在大的层中,输出是数以千计的积累的结果,因此网络参数比层输出小得多。
定点只具有有限的能力来覆盖宽动态范围。
使用B位来表示,其中一位符号位s, fl是分数长度(小数长度),其余为整数位,
除去符号位s,后面的都是尾数,每个尾数位数字为xi
n = (-1)^s * 2^(-fl)*sum(2^i * xi) , 0 =< i <= B-2
第一项确定符号,
第二项确定分数比例,
第三项确定总值大小(所有尾数对应的十进制的值)。
由于网络中的中间值具有不同的范围,所以希望将定点数值分组为具有常数f1(不同小数长度)的组中。
所以分配给小数部分的比特数在 同一组内是恒定的,但与其他组相比是不同的。
这里每个网络层分为三组:
一个用于层输入,
一个用于权重,
一个用于层输出。
这可以更好地覆盖层激活和权重的动态范围,因为权重通常特别小,层的输入输出通常比较大(上一层各个神经元累计)。
上图为4个数 属于 两个不同组 的 动态定点数的例子。
注意第二组的 分数长度 是负数。
所以第二组的分数长度是-1,整数长度是9,位宽是8。
第一组的分数长度为2,整数长度为6,位宽为8.
动态定点法代码
template <typename Dtype>
void BaseRistrettoLayer<Dtype>::Trim2FixedPoint_cpu_featuremap(Dtype* data, const int cnt,
const int bit_width, const int rounding, int fl) {
for (int index = 0; index < cnt; ++index) {
// Saturate data
Dtype max_data = (pow(2, bit_width - 1) - 1);
Dtype min_data = -pow(2, bit_width - 1);
data[index] /= pow(2, -fl);
// clip (min,max)
data[index] = std::max(std::min(data[index], max_data), min_data);
data[index] = round(data[index]);
data[index] *= pow(2, -fl);
}
}
迷你浮点法(Minifloat Approximation)
不清楚浮点数二进制表示的可参考:https://blog.csdn.net/qq_20880415/article/details/99688542
IEEE-754 32位浮点数:
struct MYFLOAT
{
bool bSign : 1; // S 符号,表示正负,1位
char cExponent : 8; // E 指数,8位,存储的时候,比实际的多 127
unsigned long ulMantissa : 23; // M 尾数,23位
};
// B = (-1)^S * 2^E * M
// 不过指数部分在存储的时候回 加上127(使用移位存储, 正负表达的范围较平均)
// 所以 指数部分按二进制数转到10进制数后需要减去 127, E = E' - 127
bSign --- cExponent --- ulMantissa
符号位 --- 指数位 --- 尾数位
迷你浮点数 16bit 5位指数部分 10位位数部分(精度, 1/(2^(10)) = 0.0009765, 精确到小数点后3位 )
由于神经网络的训练是以浮点的方式完成的,
所以将这些模型压缩成比特宽度减少的浮点数是一种直观的方法。
为了压缩网络并减少计算和存储需求,Ristretto可以用比IEEE-754标准少得多的位表示浮点数。
我们在16bit、8bit甚至更小的数字上都遵循这个标准,但是我们的格式在一些细节上有所不同。
也就是说,根据分配给指数的比特数降低指数偏差:
bias = 2^(exp_bits−1) − 1, 这里exp_bits提供分配给指数的位数。
例如8位指数,为了表示正负,使用移位存储,
存储的数据为 原数据+127 , 基数 bais = 2^(8-1) - 1 =127
与IEEE标准的另一个区别是我们不支持非规范化的数字,I
NF和NaN。INF由饱和数字代替,
非规格化数字NaN 由0代替。
最后,分配给指数和尾数部分的位数不遵循特定的规则。
更确切地说,Ristretto选择指数位,以避免发生饱和。
迷你浮点数量化代码
typedef union {
float d;
struct {
unsigned int mantisa : 23;
unsigned int exponent : 8;
unsigned int sign : 1;
} parts;
} float_cast;
template <typename Dtype>
void BaseRistrettoLayer<Dtype>::Trim2MiniFloat_cpu(Dtype* data, const int cnt,
const int bw_mant, const int bw_exp, const int rounding) {
for (int index = 0; index < cnt; ++index) {
int bias_out = pow(2, bw_exp - 1) - 1;
float_cast d2;
// This casts the input to single precision
d2.d = (float)data[index];
int exponent=d2.parts.exponent - 127 + bias_out;
double mantisa = d2.parts.mantisa;
// Special case: input is zero or denormalized number
if (d2.parts.exponent == 0) {
data[index] = 0;
return;
}
// Special case: denormalized number as output
if (exponent < 0) {
data[index] = 0;
return;
}
// Saturation: input float is larger than maximum output float
int max_exp = pow(2, bw_exp) - 1;
int max_mant = pow(2, bw_mant) - 1;
if (exponent > max_exp) {
exponent = max_exp;
mantisa = max_mant;
} else {
// Convert mantissa from long format to short one. Cut off LSBs.
//相当于动态定点中的乘以标bw_mant后round
double tmp = mantisa / pow(2, 23 - bw_mant);
switch (rounding) {
case QuantizationParameter_Rounding_NEAREST:
mantisa = round(tmp);
break;
case QuantizationParameter_Rounding_STOCHASTIC:
mantisa = floor(tmp + RandUniform_cpu());
break;
default:
break;
}
}
// Assemble result
data[index] = pow(-1, d2.parts.sign) * ((mantisa + pow(2, bw_mant)) /
pow(2, bw_mant)) * pow(2, exponent - bias_out);
}
}
乘法变移位法(Multiplier-free arithmetic)
利用这种近似策略,乘法由位移代替。卷积运算由加法和乘法组成,其中乘法器需要更大的芯片面积。
这促使以前的研究通过使用整数幂权重来消除所有的乘法器。这些权重可以被认为是具有零尾数位的minifloat数值。
权重和层激活之间的乘法变成了位移。幂的两个参数可以写成如下:
n=(−1)^s * 2^exp
这里exp是一个整数,对于网络参数通常是负的。由于接近于零的小参数对网络输出没有太大影响,
所以可以忽略很小的指数。因此,可能的指数值可以显着减少。对于AlexNet,我们用{−8,−1}范围的值取得了很好的效果。
在这种近似模式下,激活是动态定点格式
乘法变移位法量化代码
template <typename Dtype>
void BaseRistrettoLayer<Dtype>::Trim2IntegerPowerOf2_cpu(Dtype* data,
const int cnt, const int min_exp, const int max_exp, const int rounding) {
for (int index = 0; index < cnt; ++index) {
float exponent = log2f((float)fabs(data[index]));
int sign = data[index] >= 0 ? 1 : -1;
switch (rounding) {
case QuantizationParameter_Rounding_NEAREST:
exponent = round(exponent);
break;
case QuantizationParameter_Rounding_STOCHASTIC:
exponent = floorf(exponent + RandUniform_cpu());
break;
default:
break;
}
exponent = std::max(std::min(exponent, (float)max_exp), (float)min_exp);
data[index] = sign * pow(2, exponent);
}
}