保存时间:2026/3/29 21:31:36
log打印或其他方式获取到模型的实际数据类型,以实际获取到的信息为准。[100, 200]映射到8位无符号整数范围[0, 255]。import random
# 生成10个在100到200之间的随机32位浮点数
random_numbers = [random.uniform(100, 200) for _ in range(10)]
def linear_quantize(x, in_min, in_max, out_min, out_max):
# 线性量化公式
return ((x - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min
# 量化为8位无符号整数(0到255)
quantized_numbers = [linear_quantize(x, 100, 200, 0, 255) for x in random_numbers]
def integral_function(x):
# 简单的求平方积分示例,这里可以替换为任何你想要的积分函数
return sum(i**2 for i in x)
# 对原始32位浮点数和量化后的8位浮点数分别应用积分函数
integral_original = integral_function(random_numbers)
integral_quantized = integral_function(quantized_numbers)
| 原始32位浮点数 | 量化后8位浮点数 |
|---|---|
random_numbers[0] | quantized_numbers[0] |
random_numbers[1] | quantized_numbers[1] |
random_numbers[2] | quantized_numbers[2] |
random_numbers[3] | quantized_numbers[3] |
random_numbers[4] | quantized_numbers[4] |
random_numbers[5] | quantized_numbers[5] |
random_numbers[6] | quantized_numbers[6] |
random_numbers[7] | quantized_numbers[7] |
random_numbers[8] | quantized_numbers[8] |
random_numbers[9] | quantized_numbers[9] |
integral_originalintegral_quantized#include <stdio.h>
#include <math.h>
// 计算阶乘
double factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
// 计算标准正态分布累积分布函数的泰勒展开
double normal_cdf_taylor(double x, double precision) {
double sum = 0.5;
double term;
double prev_sum;
int n = 0;
do {
prev_sum = sum;
term = pow(-1, n) * pow(x, 2 * n + 1) / (pow(2, n) * factorial(n) * (2 * n + 1) * sqrt(2 * M_PI));
sum += term;
n++;
} while (fabs(sum - prev_sum) > precision);
return sum;
}
int main() {
double x = 1.0; // 测试的输入值
double precision = 0.01; // 假设的8位浮点数精度阈值,可调整
double result = normal_cdf_taylor(x, precision);
printf("当 x = %lf 时,标准正态分布累积分布函数的近似值为: %lf\n", x, result);
return 0;
}
factorial 函数用于计算一个整数的阶乘。normal_cdf_taylor 函数计算标准正态分布累积分布函数Φ(x) 的泰勒展开。它通过不断累加泰勒展开的项,直到相邻两次的和的差值小于指定的精度阈值 precision 为止。main 函数中,我们设定了一个测试的输入值 x 和精度阈值 precision ,然后调用 normal_cdf_taylor 函数进行计算,并输出结果。precision 只是一个示例,实际的8位浮点数精度会根据具体的格式和应用场景有所不同,你可能需要根据具体情况进行调整。 precision 就是用来表示你期望达到的精度阈值,你可以将其设置为接近8位浮点数实际精度的值,比如你可以将 precision 设置为 pow(2, -23),这样程序就会以这个精度要求来判断是否停止泰勒展开项数的增加。所以,8位浮点数精度在理论上有一个大致的范围,但在实际编程中,需要根据具体情况合理设置精度阈值来逼近这个理论精度。2 的 -23 次方(近似按照E5M2标准下8位浮点数精度),计算标准正态分布累积分布函数Φ(x) 的泰勒展开的C语言程序:#include <stdio.h>
#include <math.h>
// 计算阶乘
double factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
// 计算标准正态分布累积分布函数的泰勒展开
double normal_cdf_taylor(double x) {
double sum = 0.5;
double term;
double prev_sum;
// 按照E5M2标准设置精度
double precision = pow(2, -23);
int n = 0;
do {
prev_sum = sum;
term = pow(-1, n) * pow(x, 2 * n + 1) / (pow(2, n) * factorial(n) * (2 * n + 1) * sqrt(2 * M_PI));
sum += term;
n++;
} while (fabs(sum - prev_sum) > precision);
return sum;
}
int main() {
double x = 1.0; // 测试的输入值
double result = normal_cdf_taylor(x);
printf("当 x = %lf 时,标准正态分布累积分布函数的近似值为: %lf\n", x, result);
return 0;
}
factorial 函数用于计算整数的阶乘。normal_cdf_taylor 函数实现了标准正态分布累积分布函数Φ(x) 的泰勒展开计算。它根据设定的精度 precision(这里为 2 的 -23 次方)来判断是否继续累加泰勒展开的项,当相邻两次求和结果的差值小于该精度时停止计算。main 函数中,定义了一个测试输入值 x 为 1.0,调用 normal_cdf_taylor 函数计算并输出标准正态分布累积分布函数在 x 处的近似值。#include <stdio.h>
#include <math.h>
// 计算阶乘
double factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
// 计算标准正态分布累积分布函数的泰勒展开
double normal_cdf_taylor(double x) {
double sum = 0.5;
double term;
double prev_sum;
double precision = pow(2, -23);
int n = 0;
do {
prev_sum = sum;
term = pow(-1, n) * pow(x, 2 * n + 1) / (pow(2, n) * factorial(n) * (2 * n + 1) * sqrt(2 * M_PI));
sum += term;
n++;
} while (fabs(sum - prev_sum) > precision);
return sum;
}
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
int main() {
double input_32bit = 150.0; // 测试的32位浮点数
double in_min = 100.0; // 输入数据范围最小值
double in_max = 200.0; // 输入数据范围最大值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
double non_linear_result = normal_cdf_taylor((double)quantized_8bit);
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, 0.0, 1.0);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
factorial 函数用于计算阶乘。normal_cdf_taylor 函数计算标准正态分布累积分布函数的泰勒展开。linear_quantize 函数将 32 位浮点数线性量化为 8 位浮点数(范围 0-255)。inverse_linear_quantize 函数是 linear_quantize 的逆操作,将 8 位浮点数映射回原来的范围。main 函数中,定义了一个 32 位浮点数进行测试,依次执行线性量化、非线性变换、再次线性量化的操作,并计算和输出两次 8 位浮点数的差值。x 值,泰勒展开式的项可能会随着 n 的增大而增大,导致不收敛或者溢出的情况。x 较大时,分子上的 x 的幂次增长速度可能会超过分母的增长速度,从而使得项的值越来越大。x 的范围进行限制,或者采用其他更稳定的数值计算方法,例如使用误差函数 erf 来计算标准正态分布累积分布函数,因为 erf 函数在数值计算库中有更稳定的实现。erf 函数来计算标准正态分布累积分布函数:#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
int main() {
double input_32bit = 150.0; // 测试的32位浮点数
double in_min = 100.0; // 输入数据范围最小值
double in_max = 200.0; // 输入数据范围最大值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
// 使用误差函数erf计算标准正态分布累积分布函数
double non_linear_result = 0.5 * (1 + erf(((double)quantized_8bit - in_min) / ((in_max - in_min) * sqrt(2))));
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, 0.0, 1.0);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
erf 函数来计算标准正态分布累积分布函数,这样可以避免泰勒展开式可能出现的不收敛或溢出问题,并且计算更加稳定。term = pow(-1, n) * pow(x, 2 * n + 1) / (pow(2, n) * factorial(n) * (2 * n + 1) * sqrt(2 * M_PI)); 这一行,pow(-1, n) 就是用来控制正负号交替的。x 较大时,泰勒展开式的项绝对值依然可能会先增大后减小得非常慢,导致整体不收敛或者在计算过程中出现数值溢出的情况。x 取值下可能出现的问题以及如何调整:#include <stdio.h>
#include <math.h>
// 计算阶乘
double factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
// 计算标准正态分布累积分布函数的泰勒展开
double normal_cdf_taylor(double x) {
double sum = 0.5;
double term;
double prev_sum;
double precision = pow(2, -23);
int n = 0;
do {
prev_sum = sum;
// 这里通过pow(-1, n)来控制正负号交替
term = pow(-1, n) * pow(x, 2 * n + 1) / (pow(2, n) * factorial(n) * (2 * n + 1) * sqrt(2 * M_PI));
sum += term;
n++;
} while (fabs(sum - prev_sum) > precision && n < 1000);
// 增加一个迭代次数限制,防止x较大时无限循环,这里设为1000次,可根据需要调整
return sum;
}
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
int main() {
double input_32bit = 150.0; // 测试的32位浮点数,这里取值较小,可尝试增大观察情况
double in_min = 100.0; // 输入数据范围最小值
double in_max = 200.0; // 输入数据范围最大值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
double non_linear_result = normal_cdf_taylor((double)quantized_8bit);
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, 0.0, 1.0);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
normal_cdf_taylor 函数内通过 pow(-1, n) 来实现泰勒展开式各项正负号的交替。同时为了防止当 x 较大时程序因不收敛而陷入无限循环,添加了一个迭代次数的限制(这里设为 1000 次),你可以根据实际情况调整这个值或者采用其他更合适的数值计算方法(如之前提到的用 erf 函数)来处理不同 x 取值下的计算。 N 设为不超过 10 次时,出现结果是原来输入数的几千万倍甚至上亿倍的增加,这很可能是算法存在问题。x 的值较大时,泰勒展开式收敛得非常慢,甚至在有限的迭代次数内可能会出现数值不稳定的情况,导致结果偏差很大。erf 函数来计算标准正态分布累积分布函数,这样可以避免泰勒展开式在某些情况下的不稳定性。x 值在合理的范围内,对于超出范围的值,可能需要进行特殊处理或给出警告。同时,检查线性量化和逆线性量化过程中的参数设置,确保数据在转换过程中不会出现异常。normal_cdf_taylor 函数在计算标准正态分布累积分布函数的泰勒展开时出现了数值不稳定的问题。泰勒展开式在某些情况下(尤其是当输入值较大时)可能会导致数值发散,从而产生非常大或者非常小的结果,这就是为什么你看到的 term 和 sum 的值在不断增大或减小,甚至超出了合理范围。x 为 150,这已经超出了泰勒展开式能够有效计算的范围。term 和 sum 的值不断增大,最终可能会超出 double 类型的表示范围,导致数值溢出。erf)。在 C 语言中,math.h 库提供了 erf 函数,可以用来计算误差函数。标准正态分布累积分布函数可以通过误差函数表示为:#include <stdio.h>
#include <math.h>
// 计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
int main() {
double input_32bit = 150.0; // 测试的32位浮点数
double in_min = 100.0; // 输入数据范围最小值
double in_max = 200.0; // 输入数据范围最大值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("非线性变换boundary的结果: %lf, %lf\n", non_min, non_max);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
normal_cdf 函数:使用 erf 函数计算标准正态分布累积分布函数,避免了泰勒展开的数值不稳定问题。erf 函数是 <math.h> 标准库中预定义的函数,用于计算误差函数(Error Function)。误差函数在概率论、统计学以及偏微分方程等领域有广泛应用。erf 函数的原型如下:#include <math.h>
double erf(double x);
double 类型的参数 x,并返回 x 的误差函数值。误差函数的定义如下:erf(x)=π2∫0xe−t2dterf 函数属于 C99 标准的一部分。这意味着如果你使用的是支持 C99 标准的编译器(例如 GCC 等),就能直接使用该函数。不过,若编译器不支持 C99 标准,可能就无法直接使用 erf 函数。erf 函数:#include <stdio.h>
#include <math.h>
int main() {
double x = 1.0;
double result = erf(x);
printf("erf(%lf) = %lf\n", x, result);
return 0;
}
x = 1.0 时的误差函数值,并将结果打印输出。gcc -std=c99 your_program.c -o your_program -lm
-std=c99 选项表明使用 C99 标准进行编译,-lm 选项则是链接数学库。编译成功后,就可以运行生成的可执行文件:./your_program
erf 函数是 <math.h> 标准库中预定义的函数,用于计算误差函数。只要你的编译器支持 C99 标准,就能直接使用这个函数。normal_cdf_taylor 函数使用泰勒展开计算标准正态分布累积分布函数上。当输入值较大时,泰勒展开式收敛速度极慢甚至可能发散,并且由于浮点数精度限制,最终结果会趋近于 1.0,导致 non_min、non_max 和 non_linear_result 都为 1.0,从而使得后续的线性量化出现问题。erf 函数来替代泰勒展开式计算标准正态分布累积分布函数。erf 函数在数值计算上更加稳定,尤其适用于较大的输入值。#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
int main() {
double input_32bit = 150.0; // 测试的32位浮点数
double in_min = 100.0; // 输入数据范围最小值
double in_max = 200.0; // 输入数据范围最大值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("非线性变换boundary的结果: %lf, %lf\n", non_min, non_max);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
normal_cdf 函数:使用 erf 函数来计算标准正态分布累积分布函数。公式为 Φ(x)=21(1+erf(2x)),其中 Φ(x) 是标准正态分布累积分布函数。normal_cdf_taylor 替换为 normal_cdf。gcc -std=c99 quantize.cpp -o quantize -lm
./quantize
normal_cdf_taylor 函数normal_cdf_taylor 函数,它通过泰勒展开式来近似计算标准正态分布的累积分布函数。泰勒展开式的公式如下:Φ(x)≈21+∑n=0N2nn!(2n+1)2π(−1)nx2n+1其中 Φ(x) 是标准正态分布的累积分布函数。// 计算标准正态分布累积分布函数的泰勒展开
double normal_cdf_taylor(double x) {
double sum = 0.5;
double term;
double prev_sum;
double precision = pow(2, -23);
int n = 0;
do {
prev_sum = sum;
term = pow(-1, n) * pow(x, 2 * n + 1) / (pow(2, n) * factorial(n) * (2 * n + 1) * sqrt(2 * M_PI));
sum += term;
n++;
printf("n is %d, term=%lf, sum=%lf\n", n, term, sum);
} while (fabs(sum - prev_sum) > precision && n <= 10);
return sum;
}
x = 150.0 时,就会出现收敛问题,导致结果不准确。normal_cdf 函数normal_cdf 函数,它利用了 C 标准库中的 erf 函数来计算标准正态分布的累积分布函数。标准正态分布累积分布函数与误差函数 erf(x) 的关系如下:Φ(x)=21(1+erf(2x))// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
erf 函数是 C 标准库中经过优化的函数,它在数值计算上更加稳定,能够处理较大的输入值,避免了泰勒展开式的收敛问题和舍入误差。erf 函数的计算效率更高,能够更快地得到准确的结果。erf 函数的 normal_cdf 函数在数值稳定性和计算效率上都优于使用泰勒展开式的 normal_cdf_taylor 函数。非线性变换boundary的结果: 1.000000, 1.000000 表明 normal_cdf(in_min) 和 normal_cdf(in_max) 的值是一样的,这就致使在再次进行线性量化的时候,分母 (non_max - non_min) 为 0,从而引发了问题。normal_cdf 函数是用来计算标准正态分布累积分布函数的,若 in_min 和 in_max 的值比较大,normal_cdf 的结果就会趋近于 1。在你的代码中,in_min = 100.0 和 in_max = 200.0 相对较大,这就使得 normal_cdf(in_min) 和 normal_cdf(in_max) 都近似为 1。(non_max - non_min) 是否为 0 或者接近 0。要是接近 0 的话,就可以直接返回一个固定值(例如 0)。#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
int main() {
double input_32bit = 150.0; // 测试的32位浮点数
double in_min = 100.0; // 输入数据范围最小值
double in_max = 200.0; // 输入数据范围最大值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("非线性变换boundary的结果: %lf, %lf\n", non_min, non_max);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
linear_quantize 函数里添加了一个检查:if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
(in_max - in_min) 小于 1e-9,就直接返回 0,以此避免除零错误。normal_cdf(in_min) 和 normal_cdf(in_max) 非常接近甚至相等时,非线性变换后的区间就失去了区分度,后续的再次线性量化就会出现问题。要解决这个问题,可以从调整输入范围或者对非线性变换函数进行改进这两个方面入手。in_min 和 in_max,使得 normal_cdf(in_min) 和 normal_cdf(in_max) 有足够的差异。例如,将输入范围缩小到更能体现正态分布变化的区间。#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
int main() {
double input_32bit = 0.0; // 测试的32位浮点数,调整到更合适的值
double in_min = -3.0; // 输入数据范围最小值,调整到更合适的值
double in_max = 3.0; // 输入数据范围最大值,调整到更合适的值
// 第一步:将32位浮点数线性量化为8位浮点数数值
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
// 第二步:对量化后的8位浮点数数值进行积分函数的非线性变换
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
// 第三步:对非线性变换后的结果再进行8位的线性变换
unsigned char re_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
// 第四步:比较两个8位浮点数的差值
int difference = abs((int)quantized_8bit - (int)re_quantized_8bit);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("第一次线性量化后的8位浮点数: %d\n", quantized_8bit);
printf("非线性变换后的结果: %lf\n", non_linear_result);
printf("非线性变换boundary的结果: %lf, %lf\n", non_min, non_max);
printf("再次线性量化后的8位浮点数: %d\n", re_quantized_8bit);
printf("两个8位浮点数的差值: %d\n", difference);
return 0;
}
input_32bit 设为 0.0,in_min 设为 -3.0,in_max 设为 3.0。在标准正态分布中,这个区间能体现出累积分布函数的明显变化。linear_quantize 函数中添加了分母接近 0 的检查,避免除零错误。normal_cdf(in_min) 和 normal_cdf(in_max) 会有足够的差异,从而使非线性变换和再次线性量化有意义。[in_min, in_max] 内的浮点数 x 映射到一个输出范围 [0, 255] 内的无符号 8 位整数的过程。其计算公式为:q = \lfloor\frac{x - \text{in_min}}{\text{in_max} - \text{in_min}} \times 255\rfloor其中,q 是量化后的 8 位整数,x 是输入的 32 位浮点数,in_min 和 in_max 分别是输入数据范围的最小值和最大值。input_32bit 为 0.0,输入数据范围 in_min 为 -3.0,in_max 为 3.0。将这些值代入线性量化公式中:q1=⌊3−(−3)0−(−3)×255⌋=⌊63×255⌋=⌊127.5⌋=127所以,第一次线性量化后的结果为 127 是合理的。non_linear_result 为 0.5,non_min 为 0.001350,non_max 为 0.998650。将这些值代入线性量化公式中:q2=⌊0.998650−0.0013500.5−0.001350×255⌋=⌊0.99730.49865×255⌋=⌊0.5×255⌋=⌊127.5⌋=127所以,再次线性量化后的结果也为 127 是合理的。input_32bit 或者输入数据范围 in_min 和 in_max。unsigned char 来存储线性量化后的结果是合理的,下面从几个方面进行详细解释:unsigned char 类型是 8 位无符号整数,它的取值范围是 0 到 255,这和线性量化之后把 32 位浮点数映射到 0 - 255 范围的需求相匹配。linear_quantize 函数借助公式 (x - in_min) / (in_max - in_min) * 255 把输入的 32 位浮点数转换为一个 0 到 255 之间的数值,最后把这个数值强制转换为 unsigned char 类型,以此来表示量化后的结果。unsigned char 类型只占用 1 个字节的存储空间,相较于 32 位浮点数(通常占用 4 个字节),能显著减少存储空间。inverse_linear_quantize 函数,该函数能够把量化后的 unsigned char 类型数值逆量化为原来的 32 位浮点数范围。这表明使用 unsigned char 类型存储量化结果不会丢失必要的信息,后续可以通过逆量化操作恢复原始数据的近似值。#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
int main() {
double input_32bit = 1.5;
double in_min = -3.0;
double in_max = 3.0;
// 线性量化
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
printf("量化后的8位整数: %d\n", quantized_8bit);
// 逆量化
double recovered_32bit = inverse_linear_quantize(quantized_8bit, in_min, in_max);
printf("逆量化后的32位浮点数: %lf\n", recovered_32bit);
return 0;
}
linear_quantize 函数把 32 位浮点数 input_32bit 量化成 unsigned char 类型的 quantized_8bit,之后 inverse_linear_quantize 函数又把 quantized_8bit 逆量化为 32 位浮点数 recovered_32bit。unsigned char 类型来存储线性量化后的结果是合理且常见的做法。0。下面我们可以对代码进行一些修改来实现这个目的。input_32bit 值。#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
int main() {
double input_32bit = 1.0; // 测试的32位浮点数,选择非零值
double in_min = -3.0; // 输入数据范围最小值
double in_max = 3.0; // 输入数据范围最大值
// 第一步:普通线性量化
unsigned char normal_quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
double normal_quantized_float = inverse_linear_quantize(normal_quantized_8bit, in_min, in_max);
// 第二步:CDF变换后的量化
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
unsigned char cdf_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
double cdf_quantized_float = inverse_linear_quantize(cdf_quantized_8bit, non_min, non_max);
// 第三步:计算差值
double difference = fabs(normal_quantized_float - cdf_quantized_float);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("普通线性量化后的浮点数: %lf\n", normal_quantized_float);
printf("CDF变换后量化的浮点数: %lf\n", cdf_quantized_float);
printf("两种量化方式的差值: %lf\n", difference);
return 0;
}
input_32bit 的值从 0.0 改为 1.0,避免使用特殊值 0。linear_quantize 函数对输入值进行量化,然后使用 inverse_linear_quantize 函数将量化后的 8 位整数转换回浮点数。fabs 函数计算两种量化方式的差值。[non_min, non_max] 上。下面是修改后的代码:#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 将一个值从一个区间线性映射到另一个区间
double linear_map(double x, double old_min, double old_max, double new_min, double new_max) {
if (old_max - old_min < 1e-9) {
return new_min;
}
return new_min + (x - old_min) * (new_max - new_min) / (old_max - old_min);
}
int main() {
double input_32bit = 1.0; // 测试的32位浮点数,选择非零值
double in_min = -3.0; // 输入数据范围最小值
double in_max = 3.0; // 输入数据范围最大值
// 第一步:普通线性量化
unsigned char normal_quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
double normal_quantized_float = inverse_linear_quantize(normal_quantized_8bit, in_min, in_max);
// 第二步:CDF变换后的量化
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
unsigned char cdf_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
double cdf_quantized_float = inverse_linear_quantize(cdf_quantized_8bit, non_min, non_max);
// 第三步:将普通线性量化结果映射到CDF变换后的区间
double mapped_normal_quantized_float = linear_map(normal_quantized_float, in_min, in_max, non_min, non_max);
// 第四步:计算差值
double difference = fabs(mapped_normal_quantized_float - cdf_quantized_float);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("普通线性量化后的浮点数: %lf\n", normal_quantized_float);
printf("映射到CDF区间后的普通线性量化浮点数: %lf\n", mapped_normal_quantized_float);
printf("CDF变换后量化的浮点数: %lf\n", cdf_quantized_float);
printf("两种量化方式的差值: %lf\n", difference);
return 0;
}
linear_map 函数:该函数用于将一个值从旧的区间 [old_min, old_max] 线性映射到新的区间 [new_min, new_max]。linear_quantize 对输入值进行量化,再用 inverse_linear_quantize 转换回浮点数。linear_map 函数将普通线性量化的结果 normal_quantized_float 从 [in_min, in_max] 映射到 [non_min, non_max] 区间。#include <stdio.h>
#include <math.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// QELU 激活函数
double qelu(double x) {
if (x > 0) {
return x;
}
return 0;
}
int main() {
double input_32bit = 1.5; // 测试的32位浮点数
double in_min = -3.0; // 输入数据范围最小值
double in_max = 3.0; // 输入数据范围最大值
// 不量化情况下 QELU 的输出
double qelu_no_quantization = qelu(input_32bit);
// 普通线性量化
unsigned char quantized_8bit = linear_quantize(input_32bit, in_min, in_max);
double dequantized_linear = inverse_linear_quantize(quantized_8bit, in_min, in_max);
double qelu_linear_quantization = qelu(dequantized_linear);
// CDF 量化
double non_linear_result = normal_cdf(input_32bit);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
unsigned char cdf_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
double dequantized_cdf = inverse_linear_quantize(cdf_quantized_8bit, non_min, non_max);
double qelu_cdf_quantization = qelu(dequantized_cdf);
// 计算误差
double error_linear = fabs(qelu_no_quantization - qelu_linear_quantization);
double error_cdf = fabs(qelu_no_quantization - qelu_cdf_quantization);
printf("原始32位浮点数: %lf\n", input_32bit);
printf("不量化 QELU 输出: %lf\n", qelu_no_quantization);
printf("普通线性量化 QELU 输出: %lf,误差: %lf\n", qelu_linear_quantization, error_linear);
printf("CDF 量化 QELU 输出: %lf,误差: %lf\n", qelu_cdf_quantization, error_cdf);
if (error_linear < error_cdf) {
printf("普通线性量化误差更小\n");
} else {
printf("CDF 量化误差更小\n");
}
return 0;
}
qelu 函数:实现了 QELU 激活函数的逻辑。qelu 函数得到输出。linear_quantize 函数将输入量化为 8 位整数,再使用 inverse_linear_quantize 函数反量化回浮点数,最后调用 qelu 函数得到输出。qelu 函数得到输出。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0-255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// QELU 激活函数
double qelu(double x) {
if (x > 0) {
return x;
}
return 0;
}
int main() {
double num1 = -1.0; // 第一个32位浮点数
double num2 = 1.0; // 第二个32位浮点数
double in_min = -3.0; // 输入数据范围最小值
double in_max = 3.0; // 输入数据范围最大值
srand(time(0)); // 初始化随机数种子
// 在num1左侧、num1和num2之间、num2右侧分别选取随机变量进行测试
for (int i = 0; i < 3; ++i) {
double random_var;
if (i == 0) {
random_var = num1 - (double)rand() / RAND_MAX * 2; // 在num1左侧
} else if (i == 1) {
random_var = num1 + (num2 - num1) * (double)rand() / RAND_MAX; // 在num1和num2之间
} else {
random_var = num2 + (double)rand() / RAND_MAX * 2; // 在num2右侧
}
// 不量化情况下 QELU 的输出
double qelu_no_quantization = qelu(random_var);
// 普通线性量化
unsigned char quantized_8bit = linear_quantize(random_var, in_min, in_max);
double dequantized_linear = inverse_linear_quantize(quantized_8bit, in_min, in_max);
double qelu_linear_quantization = qelu(dequantized_linear);
// CDF 量化
double non_linear_result = normal_cdf(random_var);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
unsigned char cdf_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
double dequantized_cdf = inverse_linear_quantize(cdf_quantized_8bit, non_min, non_max);
double qelu_cdf_quantization = qelu(dequantized_cdf);
// 计算误差
double error_linear = fabs(qelu_no_quantization - qelu_linear_quantization);
double error_cdf = fabs(qelu_no_quantization - qelu_cdf_quantization);
printf("随机变量: %lf\n", random_var);
printf("不量化 QELU 输出: %lf\n", qelu_no_quantization);
printf("普通线性量化 QELU 输出: %lf,误差: %lf\n", qelu_linear_quantization, error_linear);
printf("CDF 量化 QELU 输出: %lf,误差: %lf\n", qelu_cdf_quantization, error_cdf);
if (error_linear < error_cdf) {
printf("普通线性量化误差更小\n");
} else {
printf("CDF 量化误差更小\n");
}
printf("\n");
}
return 0;
}
srand(time(0)) 初始化随机数种子,然后在不同区间生成随机变量 random_var。num1 左侧、num1 和 num2 之间、num2 右侧生成随机变量,以全面测试不同位置的量化效果。[batch_size, sequence_length, hidden_size]),在hidden_size维度上计算均值。比如输入张量X,均值\mu=\frac{1}{H}\sum_{i = 1}^{H}X_{i},其中H是hidden_size的大小,X_{i}是该维度上的元素。hidden_size维度上计算方差,方差\sigma^{2}=\frac{1}{H}\sum_{i = 1}^{H}(X_{i}-\mu)^{2}。\hat{X}=\frac{X-\mu}{\sqrt{\sigma^{2}+\epsilon}},\epsilon是一个很小的数(如1e-5),防止分母为零。Y=\gamma\hat{X}+\beta,其中\gamma和\beta是可学习的参数。[batch_size, sequence_length, hidden_size]。x>0时,y = x;x <= 0时,y = 0)对数据进行变换,引入非线性。\gamma和\beta采用特殊的初始化方法,以加快模型收敛。[batch_size, sequence_length, hidden_size]),在hidden_size维度上计算均值。假设输入张量为X,均值的计算公式为:H是hidden_size的大小,X_{i}是该维度上的元素。2. 计算方差:同样在hidden_size维度上计算方差,公式为:σ2=H1∑i=1H(Xi−μ)23. 归一化:利用均值和方差对输入进行归一化,公式为:X^=σ2+ϵX−μ这里的\epsilon是一个很小的数(如1e-5),作用是防止分母为零。4. 缩放和平移:归一化后,通常还会进行缩放和平移操作,得到最终结果:Y=γX^+β其中,\gamma和\beta是可学习的参数。[batch_size, sequence_length, hidden_size]。x>0时,y = x;x <= 0时,y = 0)对数据进行变换,引入非线性。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// QELU激活函数
double qelu(double x) {
if (x > 0) {
return x;
}
return 0;
}
int main() {
double num1 = -1.0; // 第一个32位浮点数
double num2 = 1.0; // 第二个32位浮点数
double in_min = -3.0; // 输入数据范围最小值
double in_max = 3.0; // 输入数据范围最大值
srand(time(0)); // 初始化随机数种子
// 在num1左侧、num1和num2之间、num2右侧分别选取随机变量进行测试
for (int i = 0; i < 3; ++i) {
double random_var;
if (i == 0) {
random_var = num1 - (double)rand() / RAND_MAX * 2; // 在num1左侧
} else if (i == 1) {
random_var = num1 + (num2 - num1) * (double)rand() / RAND_MAX; // 在num1和num2之间
} else {
random_var = num2 + (double)rand() / RAND_MAX * 2; // 在num2右侧
}
// 不量化情况下QELU的输出
double qelu_no_quantization = qelu(random_var);
// 普通线性量化
unsigned char quantized_8bit = linear_quantize(random_var, in_min, in_max);
double dequantized_linear = inverse_linear_quantize(quantized_8bit, in_min, in_max);
double qelu_linear_quantization = qelu(dequantized_linear);
// CDF量化
double non_linear_result = normal_cdf(random_var);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
unsigned char cdf_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
double dequantized_cdf = inverse_linear_quantize(cdf_quantized_8bit, non_min, non_max);
double qelu_cdf_quantization = qelu(dequantized_cdf);
// 平方后量化
double squared_var = random_var * random_var;
double squared_min = in_min * in_min;
double squared_max = in_max * in_max;
unsigned char squared_quantized_8bit = linear_quantize(squared_var, squared_min, squared_max);
double dequantized_squared = inverse_linear_quantize(squared_quantized_8bit, squared_min, squared_max);
double qelu_squared_quantization = qelu(dequantized_squared);
// 计算误差
double error_linear = fabs(qelu_no_quantization - qelu_linear_quantization);
double error_cdf = fabs(qelu_no_quantization - qelu_cdf_quantization);
double error_squared = fabs(qelu_no_quantization - qelu_squared_quantization);
printf("随机变量: %lf\n", random_var);
printf("不量化QELU输出: %lf\n", qelu_no_quantization);
printf("普通线性量化QELU输出: %lf,误差: %lf\n", qelu_linear_quantization, error_linear);
printf("CDF量化QELU输出: %lf,误差: %lf\n", qelu_cdf_quantization, error_cdf);
printf("平方后量化QELU输出: %lf,误差: %lf\n", qelu_squared_quantization, error_squared);
// 找出最小误差的量化方式
if (error_linear < error_cdf && error_linear < error_squared) {
printf("普通线性量化误差更小\n");
} else if (error_cdf < error_linear && error_cdf < error_squared) {
printf("CDF量化误差更小\n");
} else {
printf("平方后量化误差更小\n");
}
printf("\n");
}
return 0;
}
random_var平方后再量化的过程,包括计算平方后的范围[squared_min, squared_max],进行量化和逆量化,并计算经过QELU后的输出和误差。[in_min, in_max]后再进行比较:#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) { // 检查分母是否接近0
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
// QELU激活函数
double qelu(double x) {
if (x > 0) {
return x;
}
return 0;
}
int main() {
double num1 = -1.0; // 第一个32位浮点数
double num2 = 1.0; // 第二个32位浮点数
double in_min = -3.0; // 输入数据范围最小值
double in_max = 3.0; // 输入数据范围最大值
srand(time(0)); // 初始化随机数种子
// 在num1左侧、num1和num2之间、num2右侧分别选取随机变量进行测试
for (int i = 0; i < 3; ++i) {
double random_var;
if (i == 0) {
random_var = num1 - (double)rand() / RAND_MAX * 2; // 在num1左侧
} else if (i == 1) {
random_var = num1 + (num2 - num1) * (double)rand() / RAND_MAX; // 在num1和num2之间
} else {
random_var = num2 + (double)rand() / RAND_MAX * 2; // 在num2右侧
}
// 不量化情况下QELU的输出
double qelu_no_quantization = qelu(random_var);
// 普通线性量化
unsigned char quantized_8bit = linear_quantize(random_var, in_min, in_max);
double dequantized_linear = inverse_linear_quantize(quantized_8bit, in_min, in_max);
double qelu_linear_quantization = qelu(dequantized_linear);
// CDF量化
double non_linear_result = normal_cdf(random_var);
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
unsigned char cdf_quantized_8bit = linear_quantize(non_linear_result, non_min, non_max);
double dequantized_cdf = inverse_linear_quantize(cdf_quantized_8bit, non_min, non_max);
// 映射到普通线性量化范围
double mapped_cdf = map_to_range(dequantized_cdf, non_min, non_max, in_min, in_max);
double qelu_mapped_cdf_quantization = qelu(mapped_cdf);
// 平方后量化
double squared_var = random_var * random_var;
double squared_min = in_min * in_min;
double squared_max = in_max * in_max;
unsigned char squared_quantized_8bit = linear_quantize(squared_var, squared_min, squared_max);
double dequantized_squared = inverse_linear_quantize(squared_quantized_8bit, squared_min, squared_max);
// 映射到普通线性量化范围
double mapped_squared = map_to_range(dequantized_squared, squared_min, squared_max, in_min, in_max);
double qelu_mapped_squared_quantization = qelu(mapped_squared);
// 计算误差
double error_linear = fabs(qelu_no_quantization - qelu_linear_quantization);
double error_cdf = fabs(qelu_no_quantization - qelu_mapped_cdf_quantization);
double error_squared = fabs(qelu_no_quantization - qelu_mapped_squared_quantization);
printf("随机变量: %lf\n", random_var);
printf("不量化QELU输出: %lf\n", qelu_no_quantization);
printf("普通线性量化QELU输出: %lf,误差: %lf\n", qelu_linear_quantization, error_linear);
printf("CDF量化(映射后)QELU输出: %lf,误差: %lf\n", qelu_mapped_cdf_quantization, error_cdf);
printf("平方后量化(映射后)QELU输出: %lf,误差: %lf\n", qelu_mapped_squared_quantization, error_squared);
// 找出最小误差的量化方式
if (error_linear < error_cdf && error_linear < error_squared) {
printf("普通线性量化误差更小\n");
} else if (error_cdf < error_linear && error_cdf < error_squared) {
printf("CDF量化误差更小\n");
} else {
printf("平方后量化误差更小\n");
}
printf("\n");
}
return 0;
}
map_to_range函数:这个函数用于将一个值从一个区间线性映射到另一个区间。在本实验中,主要用于将CDF量化和平方后量化的结果映射到普通线性量化的范围[in_min, in_max]。dequantized_cdf后,使用map_to_range函数将其映射到普通线性量化范围,得到mapped_cdf,再计算经过QELU激活后的输出qelu_mapped_cdf_quantization和误差。dequantized_squared进行映射,得到mapped_squared,计算QELU输出qelu_mapped_squared_quantization和误差。[in_min, in_max]是固定的。不同的量化区间可能对结果产生影响,可以尝试动态调整量化区间,例如根据输入数据的统计特性(如均值、标准差)来确定量化区间,以优化量化效果并更准确地比较不同量化方法。prompt向量),保持为32位浮点数。另一组代表权重数据,分别用普通8位线性量化和平方量化后的8位浮点数表示。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
// QELU激活函数
double qelu(double x) {
if (x > 0) {
return x;
}
return 0;
}
// 计算平均绝对误差
double calculate_mae(double* results1, double* results2, int size) {
double mae = 0;
for (int i = 0; i < size; i++) {
mae += fabs(results1[i] - results2[i]);
}
return mae / size;
}
int main() {
srand(time(0));
int num_tests = 100;
int num_inputs = 10;
double in_min = -3.0;
double in_max = 3.0;
double* original_weights = (double*)malloc(num_inputs * sizeof(double));
double* linear_quantized_weights = (unsigned char*)malloc(num_inputs * sizeof(unsigned char));
double* squared_quantized_weights = (unsigned char*)malloc(num_inputs * sizeof(unsigned char));
double* inputs = (double*)malloc(num_inputs * sizeof(double));
double* original_results = (double*)malloc(num_inputs * sizeof(double));
double* linear_results = (double*)malloc(num_inputs * sizeof(double));
double* squared_results = (double*)malloc(num_inputs * sizeof(double));
for (int test = 0; test < num_tests; test++) {
// 生成随机原始权重和输入数据
for (int i = 0; i < num_inputs; i++) {
original_weights[i] = in_min + (double)rand() / RAND_MAX * (in_max - in_min);
inputs[i] = in_min + (double)rand() / RAND_MAX * (in_max - in_min);
}
// 普通8位线性量化权重
for (int i = 0; i < num_inputs; i++) {
linear_quantized_weights[i] = linear_quantize(original_weights[i], in_min, in_max);
}
// 平方量化权重
for (int i = 0; i < num_inputs; i++) {
double squared_weight = original_weights[i] * original_weights[i];
double squared_min = in_min * in_min;
double squared_max = in_max * in_max;
unsigned char quantized_squared = linear_quantize(squared_weight, squared_min, squared_max);
double dequantized_squared = inverse_linear_quantize(quantized_squared, squared_min, squared_max);
squared_quantized_weights[i] = map_to_range(dequantized_squared, squared_min, squared_max, in_min, in_max);
}
// 使用原始权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * original_weights[i];
original_results[i] = qelu(product);
}
// 使用线性量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double quantized_weight = inverse_linear_quantize(linear_quantized_weights[i], in_min, in_max);
double product = inputs[i] * quantized_weight;
linear_results[i] = qelu(product);
}
// 使用平方量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * squared_quantized_weights[i];
squared_results[i] = qelu(product);
}
// 计算误差
double linear_mae = calculate_mae(original_results, linear_results, num_inputs);
double squared_mae = calculate_mae(original_results, squared_results, num_inputs);
if (squared_mae < linear_mae) {
printf("Test %d: Squared quantization has smaller error.\n", test);
} else {
printf("Test %d: Linear quantization has smaller error.\n", test);
}
}
free(original_weights);
free(linear_quantized_weights);
free(squared_quantized_weights);
free(inputs);
free(original_results);
free(linear_results);
free(squared_results);
return 0;
}
squared_min = in_min * in_min 和 squared_max = in_max * in_max 来确定平方后的范围。如果 in_min 是负数,那么 squared_min 可能会小于 0,这可能会导致映射和量化的结果出现偏差。in_min * in_min 和 in_max * in_max。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
// QELU激活函数
double qelu(double x) {
if (x > 0) {
return x;
}
return 0;
}
// 计算平均绝对误差
double calculate_mae(double* results1, double* results2, int size) {
double mae = 0;
for (int i = 0; i < size; i++) {
mae += fabs(results1[i] - results2[i]);
}
return mae / size;
}
int main() {
srand(time(0));
int num_tests = 100;
int num_inputs = 10;
double in_min = -3.0;
double in_max = 3.0;
double* original_weights = (double*)malloc(num_inputs * sizeof(double));
double* linear_quantized_weights = (double*)malloc(num_inputs * sizeof(double));
double* squared_quantized_weights = (double*)malloc(num_inputs * sizeof(double));
double* inputs = (double*)malloc(num_inputs * sizeof(double));
double* original_results = (double*)malloc(num_inputs * sizeof(double));
double* linear_results = (double*)malloc(num_inputs * sizeof(double));
double* squared_results = (double*)malloc(num_inputs * sizeof(double));
for (int test = 0; test < num_tests; test++) {
// 生成随机原始权重和输入数据
for (int i = 0; i < num_inputs; i++) {
original_weights[i] = in_min + (double)rand() / RAND_MAX * (in_max - in_min);
inputs[i] = in_min + (double)rand() / RAND_MAX * (in_max - in_min);
}
// 普通8位线性量化权重
for (int i = 0; i < num_inputs; i++) {
linear_quantized_weights[i] = linear_quantize(original_weights[i], in_min, in_max);
}
// 平方量化权重
double squared_min = 1e9;
double squared_max = -1e9;
double* squared_weights = (double*)malloc(num_inputs * sizeof(double));
for (int i = 0; i < num_inputs; i++) {
squared_weights[i] = original_weights[i] * original_weights[i];
if (squared_weights[i] < squared_min) {
squared_min = squared_weights[i];
}
if (squared_weights[i] > squared_max) {
squared_max = squared_weights[i];
}
}
for (int i = 0; i < num_inputs; i++) {
unsigned char quantized_squared = linear_quantize(squared_weights[i], squared_min, squared_max);
double dequantized_squared = inverse_linear_quantize(quantized_squared, squared_min, squared_max);
squared_quantized_weights[i] = map_to_range(dequantized_squared, squared_min, squared_max, in_min, in_max);
}
free(squared_weights);
// 使用原始权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * original_weights[i];
original_results[i] = qelu(product);
}
// 使用线性量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double quantized_weight = inverse_linear_quantize(linear_quantized_weights[i], in_min, in_max);
double product = inputs[i] * quantized_weight;
linear_results[i] = qelu(product);
}
// 使用平方量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * squared_quantized_weights[i];
squared_results[i] = qelu(product);
}
// 计算误差
double linear_mae = calculate_mae(original_results, linear_results, num_inputs);
double squared_mae = calculate_mae(original_results, squared_results, num_inputs);
if (squared_mae < linear_mae) {
printf("Test %d: Squared quantization has smaller error.\n", test);
} else {
printf("Test %d: Linear quantization has smaller error.\n", test);
}
}
free(original_weights);
free(linear_quantized_weights);
free(squared_quantized_weights);
free(inputs);
free(original_results);
free(linear_results);
free(squared_results);
return 0;
}
in_min * in_min 和 in_max * in_max 可能带来的问题。[A, B],8位无符号整数的范围是[0, 255],线性量化公式为q = (x - A) / (B - A) * 255,其中x是32位浮点数,q是量化后的8位整数。在这种情况下,量化关系是线性的,输入值的变化与输出值的变化成固定比例。在实际应用中,这种量化方式简单直接,易于实现和理解。它适用于数据分布相对均匀,对精度要求不是极高的场景,比如一些简单的图像或音频数据处理,在不影响整体效果的前提下,可以快速减少数据存储和传输成本。float(通常为32比特)和double(通常为64比特),它们遵循IEEE 754标准。float(32位)和double(64位) ,也是基于IEEE 754标准。float类型,在大多数系统上对应C语言的double,即64比特。不过,Python的numpy库提供了一些低精度数值类型,虽然没有专门的8比特浮点数,但可以通过自定义数据类型或使用numpy的量化工具,在一定程度上模拟8比特浮点数的功能。例如,numpy的np.uint8类型是8比特无符号整数,可以用于量化数据的存储,再配合特定的量化算法来实现类似8比特浮点数的效果。float(通常32位)和double(通常64位),还有long double(不同系统下可能是64位、80位或128位等 )。_Float16这样的类型,它占用16位存储空间。_Float16这种类型在表示范围和精度上与标准浮点数类型不同,它一般由1位符号位、5位指数位和10位尾数位组成,能表示的数值范围和精度都相对有限,但可以满足一些对资源敏感且对精度要求不太高的应用场景。 #include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
// ReLU激活函数
double relu(double x) {
return (x > 0)? x : 0;
}
// 计算平均绝对误差
double calculate_mae(double* results1, double* results2, int size) {
double mae = 0;
for (int i = 0; i < size; i++) {
mae += fabs(results1[i] - results2[i]);
}
return mae / size;
}
int main() {
srand(time(0));
int num_tests = 100;
int num_inputs = 10;
double in_min = -3.0;
double in_max = 3.0;
double* original_weights = (double*)malloc(num_inputs * sizeof(double));
double* linear_quantized_weights = (double*)malloc(num_inputs * sizeof(double));
double* cdf_quantized_weights = (double*)malloc(num_inputs * sizeof(double));
double* inputs = (double*)malloc(num_inputs * sizeof(double));
double* original_results = (double*)malloc(num_inputs * sizeof(double));
double* linear_results = (double*)malloc(num_inputs * sizeof(double));
double* cdf_results = (double*)malloc(num_inputs * sizeof(double));
for (int test = 0; test < num_tests; test++) {
// 生成随机原始权重和输入数据
for (int i = 0; i < num_inputs; i++) {
original_weights[i] = in_min + (double)rand() / RAND_MAX * (in_max - in_min);
inputs[i] = in_min + (double)rand() / RAND_MAX * (in_max - in_min);
}
// 普通8位线性量化权重
for (int i = 0; i < num_inputs; i++) {
linear_quantized_weights[i] = linear_quantize(original_weights[i], in_min, in_max);
}
// CDF量化权重
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
for (int i = 0; i < num_inputs; i++) {
double non_linear_result = normal_cdf(original_weights[i]);
unsigned char quantized_non_linear = linear_quantize(non_linear_result, non_min, non_max);
double dequantized_non_linear = inverse_linear_quantize(quantized_non_linear, non_min, non_max);
cdf_quantized_weights[i] = map_to_range(dequantized_non_linear, non_min, non_max, in_min, in_max);
}
// 使用原始权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * original_weights[i];
original_results[i] = relu(product);
}
// 使用线性量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double quantized_weight = inverse_linear_quantize(linear_quantized_weights[i], in_min, in_max);
double product = inputs[i] * quantized_weight;
// 先对乘积结果进行8位线性量化再做ReLU运算
unsigned char quantized_product = linear_quantize(product, in_min, in_max);
double dequantized_product = inverse_linear_quantize(quantized_product, in_min, in_max);
linear_results[i] = relu(dequantized_product);
}
// 使用CDF量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * cdf_quantized_weights[i];
// 先对乘积结果进行8位线性量化再做ReLU运算
unsigned char quantized_product = linear_quantize(product, in_min, in_max);
double dequantized_product = inverse_linear_quantize(quantized_product, in_min, in_max);
cdf_results[i] = relu(dequantized_product);
}
// 计算误差
double linear_mae = calculate_mae(original_results, linear_results, num_inputs);
double cdf_mae = calculate_mae(original_results, cdf_results, num_inputs);
if (cdf_mae < linear_mae) {
printf("Test %d: CDF quantization has smaller error.\n", test);
} else {
printf("Test %d: Linear quantization has smaller error.\n", test);
}
}
free(original_weights);
free(linear_quantized_weights);
free(cdf_quantized_weights);
free(inputs);
free(original_results);
free(linear_results);
free(cdf_results);
return 0;
}
linear_quantize和inverse_linear_quantize函数用于线性量化和反量化。normal_cdf函数用于计算标准正态分布累积分布函数,用于CDF量化。map_to_range函数将值从一个区间映射到另一个区间。relu函数实现ReLU激活函数。calculate_mae函数计算平均绝对误差。num_tests和输入数量num_inputs,以及数据范围in_min和in_max。[-3.0, 3.0],数据分布是均匀随机的。这个范围和分布可能并不利于CDF量化发挥优势。可以尝试调整数据范围,比如扩大或缩小范围,或者改变数据分布方式,如采用正态分布来生成数据,看看是否能改变量化结果。例如,如果数据集中在某个较小的区间,CDF量化可能会因为其非线性的映射特性,在这个区间内更精细地表示数据,从而减少误差。in_min和in_max是固定的,这可能不是最优的选择。可以考虑根据数据的统计特征动态调整量化区间。比如,计算每次生成数据的均值和标准差,然后根据这些统计量来确定量化区间,这样能更好地适应不同的数据分布,使量化更精准。map_to_range函数将量化结果映射回原始范围,但这个映射过程可能会引入误差。可以检查映射的计算逻辑是否合理,或者尝试不同的映射方法。另外,normal_cdf函数中的erf函数计算可能存在一定的精度问题,可以考虑使用更精确的数值计算方法来实现erf函数,或者使用查找表的方式来提高计算效率和精度。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
// ReLU激活函数
double relu(double x) {
return (x > 0)? x : 0;
}
// 计算平均绝对误差
double calculate_mae(double* results1, double* results2, int size) {
double mae = 0;
for (int i = 0; i < size; i++) {
mae += fabs(results1[i] - results2[i]);
}
return mae / size;
}
// 生成服从正态分布的随机数
double normal_distribution(double mean, double std_dev) {
static int iset = 0;
static double gset;
double fac, rsq, v1, v2;
if (iset == 0) {
do {
v1 = 2.0 * (double)rand() / RAND_MAX - 1.0;
v2 = 2.0 * (double)rand() / RAND_MAX - 1.0;
rsq = v1 * v1 + v2 * v2;
} while (rsq >= 1.0 || rsq == 0.0);
fac = sqrt(-2.0 * log(rsq) / rsq);
gset = v1 * fac;
iset = 1;
return mean + std_dev * v2 * fac;
} else {
iset = 0;
return mean + std_dev * gset;
}
}
int main() {
srand(time(0));
int num_tests = 1000;
int num_inputs = 10;
double in_min = -3.0;
double in_max = 3.0;
double* original_weights = (double*)malloc(num_inputs * sizeof(double));
double* linear_quantized_weights = (double*)malloc(num_inputs * sizeof(double));
double* cdf_quantized_weights = (double*)malloc(num_inputs * sizeof(double));
double* inputs = (double*)malloc(num_inputs * sizeof(double));
double* original_results = (double*)malloc(num_inputs * sizeof(double));
double* linear_results = (double*)malloc(num_inputs * sizeof(double));
double* cdf_results = (double*)malloc(num_inputs * sizeof(double));
for (int test = 0; test < num_tests; test++) {
// 生成服从正态分布的随机原始权重和输入数据
for (int i = 0; i < num_inputs; i++) {
original_weights[i] = normal_distribution(0, 1);
inputs[i] = normal_distribution(0, 1);
}
// 普通8位线性量化权重
for (int i = 0; i < num_inputs; i++) {
linear_quantized_weights[i] = linear_quantize(original_weights[i], in_min, in_max);
}
// CDF量化权重
double non_min = normal_cdf(in_min);
double non_max = normal_cdf(in_max);
for (int i = 0; i < num_inputs; i++) {
double non_linear_result = normal_cdf(original_weights[i]);
unsigned char quantized_non_linear = linear_quantize(non_linear_result, non_min, non_max);
double dequantized_non_linear = inverse_linear_quantize(quantized_non_linear, non_min, non_max);
cdf_quantized_weights[i] = map_to_range(dequantized_non_linear, non_min, non_max, in_min, in_max);
}
// 使用原始权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * original_weights[i];
original_results[i] = relu(product);
}
// 使用线性量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double quantized_weight = inverse_linear_quantize(linear_quantized_weights[i], in_min, in_max);
double product = inputs[i] * quantized_weight;
// 先对乘积结果进行8位线性量化再做ReLU运算
unsigned char quantized_product = linear_quantize(product, in_min, in_max);
double dequantized_product = inverse_linear_quantize(quantized_product, in_min, in_max);
linear_results[i] = relu(dequantized_product);
}
// 使用CDF量化权重计算结果
for (int i = 0; i < num_inputs; i++) {
double product = inputs[i] * cdf_quantized_weights[i];
// 先对乘积结果进行8位线性量化再做ReLU运算
unsigned char quantized_product = linear_quantize(product, in_min, in_max);
double dequantized_product = inverse_linear_quantize(quantized_product, in_min, in_max);
cdf_results[i] = relu(dequantized_product);
}
// 计算误差
double linear_mae = calculate_mae(original_results, linear_results, num_inputs);
double cdf_mae = calculate_mae(original_results, cdf_results, num_inputs);
if (cdf_mae < linear_mae) {
printf("Test %d: CDF quantization has smaller error.\n", test);
} else {
printf("Test %d: Linear quantization has smaller error.\n", test);
}
}
free(original_weights);
free(linear_quantized_weights);
free(cdf_quantized_weights);
free(inputs);
free(original_results);
free(linear_results);
free(cdf_results);
return 0;
}
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
int main() {
double in_min = -3.0;
double in_max = 3.0;
// 打印原始范围的边界及其线性量化结果
unsigned char linear_min = linear_quantize(in_min, in_min, in_max);
unsigned char linear_max = linear_quantize(in_max, in_min, in_max);
printf("原始范围下限: %.2f, 线性量化下限: %hhu\n", in_min, linear_min);
printf("原始范围上限: %.2f, 线性量化上限: %hhu\n", in_max, linear_max);
// 计算CDF量化的边界
double cdf_min = normal_cdf(in_min);
double cdf_max = normal_cdf(in_max);
unsigned char cdf_quantized_min = linear_quantize(cdf_min, normal_cdf(in_min), normal_cdf(in_max));
unsigned char cdf_quantized_max = linear_quantize(cdf_max, normal_cdf(in_min), normal_cdf(in_max));
printf("CDF量化下限: %.6f, CDF量化后(未映射)下限: %hhu\n", cdf_min, cdf_quantized_min);
printf("CDF量化上限: %.6f, CDF量化后(未映射)上限: %hhu\n", cdf_max, cdf_quantized_max);
// 将CDF量化结果映射到目标区间
double mapped_cdf_min = map_to_range(cdf_min, normal_cdf(in_min), normal_cdf(in_max), in_min, in_max);
double mapped_cdf_max = map_to_range(cdf_max, normal_cdf(in_min), normal_cdf(in_max), in_min, in_max);
printf("CDF量化后映射到目标区间下限: %.2f\n", mapped_cdf_min);
printf("CDF量化后映射到目标区间上限: %.2f\n", mapped_cdf_max);
// 随机取一个输入浮点数并进行相关计算
srand(time(0));
double random_float = (double)rand() / RAND_MAX * (in_max - in_min) + in_min;
printf("\n随机输入浮点数: %.2f\n", random_float);
// 线性量化随机数
unsigned char linear_quantized_random = linear_quantize(random_float, in_min, in_max);
printf("随机数线性量化结果: %hhu\n", linear_quantized_random);
// CDF量化随机数并映射到目标区间
double cdf_result = normal_cdf(random_float);
unsigned char cdf_quantized_random = linear_quantize(cdf_result, normal_cdf(in_min), normal_cdf(in_max));
double mapped_cdf_random = map_to_range(cdf_result, normal_cdf(in_min), normal_cdf(in_max), in_min, in_max);
printf("随机数CDF量化结果(未映射): %.6f\n", cdf_result);
printf("随机数CDF量化后映射到目标区间结果: %.2f\n", mapped_cdf_random);
// 检查CDF量化结果是否在目标区间内
if (mapped_cdf_random >= in_min && mapped_cdf_random <= in_max) {
printf("随机数CDF量化后映射结果在目标区间内\n");
} else {
printf("随机数CDF量化后映射结果不在目标区间内\n");
}
return 0;
}
[-3, 3]区间映射到[0.001350, 0.998650],这是符合CDF函数性质的。因为CDF函数会将输入值映射到[0, 1]区间内,对于[-3, 3]这样的区间,经过CDF变换后得到的就是一个特定的子区间。[-3, 3],从理论上是可行的,并且当前结果显示随机数的CDF量化映射结果在目标区间内,这表明映射过程在一定程度上是正确的。[-3, 3]作为原始区间并进行这样的量化操作,不一定是最优的。不同的应用场景可能需要不同的原始区间,这会影响CDF变换后的区间以及最终的量化效果。可以尝试不同的原始区间,观察对量化结果和模型性能的影响。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
// ReLU激活函数
double relu(double x) {
return (x > 0)? x : 0;
}
int main() {
double in_min = -3.0;
double in_max = 3.0;
double weight = 1.33;
double random_param = 1.5;
// 权重1.33和随机参数1.5的线性量化
unsigned char linear_weight = linear_quantize(weight, in_min, in_max);
unsigned char linear_random = linear_quantize(random_param, in_min, in_max);
printf("权重1.33的线性量化结果: %hhu\n", linear_weight);
printf("随机参数1.5的线性量化结果: %hhu\n", linear_random);
// 计算线性量化后的ReLU比较结果
double linear_weight_dequant = inverse_linear_quantize(linear_weight, in_min, in_max);
double linear_random_dequant = inverse_linear_quantize(linear_random, in_min, in_max);
double linear_result = relu(linear_random_dequant - linear_weight_dequant);
printf("线性量化后ReLU比较结果: %.6f\n", linear_result);
// 权重1.33和随机参数1.5的CDF变换
double cdf_weight = normal_cdf(weight);
double cdf_random = normal_cdf(random_param);
printf("权重1.33的CDF变换结果: %.6f\n", cdf_weight);
printf("随机参数1.5的CDF变换结果: %.6f\n", cdf_random);
// CDF变换后的区间
double cdf_min = normal_cdf(in_min);
double cdf_max = normal_cdf(in_max);
// CDF变换后的值映射回[-3, 3]并量化
double mapped_cdf_weight = map_to_range(cdf_weight, cdf_min, cdf_max, in_min, in_max);
double mapped_cdf_random = map_to_range(cdf_random, cdf_min, cdf_max, in_min, in_max);
unsigned char cdf_mapped_weight = linear_quantize(mapped_cdf_weight, in_min, in_max);
unsigned char cdf_mapped_random = linear_quantize(mapped_cdf_random, in_min, in_max);
printf("权重1.33的CDF变换并映射量化结果: %hhu\n", cdf_mapped_weight);
printf("随机参数1.5的CDF变换并映射量化结果: %hhu\n", cdf_mapped_random);
// 计算CDF量化后的ReLU比较结果
double cdf_mapped_weight_dequant = inverse_linear_quantize(cdf_mapped_weight, in_min, in_max);
double cdf_mapped_random_dequant = inverse_linear_quantize(cdf_mapped_random, in_min, in_max);
double cdf_result = relu(cdf_mapped_random_dequant - cdf_mapped_weight_dequant);
printf("CDF量化后ReLU比较结果: %.6f\n", cdf_result);
return 0;
}
1.33和随机参数1.5的线性量化结果,并进行逆量化后通过ReLU函数比较,输出结果。[-3, 3]并进行量化,再逆量化后通过ReLU函数比较,输出结果。这样可以直观对比线性量化和CDF量化在相同计算逻辑下结果的差异。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// ReLU激活函数
double relu(double x) {
return (x > 0)? x : 0;
}
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
int main() {
srand(time(0));
double in_min = -3.0;
double in_max = 3.0;
double weight = 0.75;
double random_param = (double)rand() / RAND_MAX * (in_max - in_min) + in_min;
// 不量化的计算
double cdf_random_no_quant = normal_cdf(random_param);
double result_no_quant = relu(cdf_random_no_quant - weight);
printf("不量化时的结果: %.6f\n", result_no_quant);
// 线性量化
unsigned char linear_weight = linear_quantize(weight, in_min, in_max);
unsigned char linear_random = linear_quantize(random_param, in_min, in_max);
double linear_weight_dequant = inverse_linear_quantize(linear_weight, in_min, in_max);
double linear_random_dequant = inverse_linear_quantize(linear_random, in_min, in_max);
double linear_cdf_random = normal_cdf(linear_random_dequant);
double linear_result = relu(linear_cdf_random - linear_weight_dequant);
double linear_error = fabs(result_no_quant - linear_result);
printf("线性量化的结果: %.6f, 与不量化结果的误差: %.6f\n", linear_result, linear_error);
// CDF量化
double cdf_weight = normal_cdf(weight);
double cdf_random = normal_cdf(random_param);
double cdf_min = normal_cdf(in_min);
double cdf_max = normal_cdf(in_max);
double mapped_cdf_weight = map_to_range(cdf_weight, cdf_min, cdf_max, in_min, in_max);
double mapped_cdf_random = map_to_range(cdf_random, cdf_min, cdf_max, in_min, in_max);
unsigned char cdf_mapped_weight = linear_quantize(mapped_cdf_weight, in_min, in_max);
unsigned char cdf_mapped_random = linear_quantize(mapped_cdf_random, in_min, in_max);
double cdf_mapped_weight_dequant = inverse_linear_quantize(cdf_mapped_weight, in_min, in_max);
double cdf_mapped_random_dequant = inverse_linear_quantize(cdf_mapped_random, in_min, in_max);
double cdf_result = relu(cdf_mapped_random_dequant - cdf_mapped_weight_dequant);
double cdf_error = fabs(result_no_quant - cdf_result);
printf("CDF量化的结果: %.6f, 与不量化结果的误差: %.6f\n", cdf_result, cdf_error);
return 0;
}
normal_cdf计算CDF,relu实现ReLU函数,linear_quantize和inverse_linear_quantize进行线性量化和逆量化,map_to_range进行区间映射。in_min和in_max,选取权重weight和生成随机数random_param。#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
// 使用erf函数计算标准正态分布累积分布函数
double normal_cdf(double x) {
return 0.5 * (1 + erf(x / sqrt(2)));
}
// ReLU激活函数
double relu(double x) {
return (x > 0)? x : 0;
}
// 线性量化函数,将32位浮点数映射到8位浮点数范围(0 - 255)
unsigned char linear_quantize(double x, double in_min, double in_max) {
if (in_max - in_min < 1e-9) {
return 0;
}
return (unsigned char)((x - in_min) / (in_max - in_min) * 255);
}
// 逆线性量化,将8位浮点数映射回原来的范围
double inverse_linear_quantize(unsigned char q, double in_min, double in_max) {
return in_min + (double)q * (in_max - in_min) / 255;
}
// 将值从一个区间映射到另一个区间
double map_to_range(double x, double from_min, double from_max, double to_min, double to_max) {
return to_min + (x - from_min) * (to_max - to_min) / (from_max - from_min);
}
int main() {
srand(time(0));
double in_min = -3.0;
double in_max = 3.0;
double weight = 0.75;
double random_param = (double)rand() / RAND_MAX * (in_max - in_min) + in_min;
// 打印原始范围及线性量化结果
unsigned char linear_min = linear_quantize(in_min, in_min, in_max);
unsigned char linear_max = linear_quantize(in_max, in_min, in_max);
printf("原始范围下限: %.2f, 线性量化下限: %hhu\n", in_min, linear_min);
printf("原始范围上限: %.2f, 线性量化上限: %hhu\n", in_max, linear_max);
// 打印权重的线性量化结果
unsigned char linear_weight_quant = linear_quantize(weight, in_min, in_max);
printf("权重 %.2f 的线性量化结果: %hhu\n", weight, linear_weight_quant);
// 计算CDF变换后的范围
double cdf_min = normal_cdf(in_min);
double cdf_max = normal_cdf(in_max);
printf("CDF变换后范围下限: %.6f, 上限: %.6f\n", cdf_min, cdf_max);
// 打印权重的CDF变换结果及映射量化结果
double cdf_weight = normal_cdf(weight);
double mapped_cdf_weight = map_to_range(cdf_weight, cdf_min, cdf_max, in_min, in_max);
unsigned char cdf_mapped_weight_quant = linear_quantize(mapped_cdf_weight, in_min, in_max);
printf("权重 %.2f 的CDF变换结果: %.6f\n", weight, cdf_weight);
printf("权重 %.2f 的CDF变换并映射量化结果: %hhu\n", weight, cdf_mapped_weight_quant);
// 不量化的计算
double cdf_random_no_quant = normal_cdf(random_param);
double result_no_quant = relu(cdf_random_no_quant - weight);
printf("\n不量化时的计算: 随机数 %.2f 经CDF变换后为 %.6f, 与权重 %.2f 经ReLU比较结果: %.6f\n", random_param, cdf_random_no_quant, weight, result_no_quant);
// 量化测试
// 先对随机数进行CDF变换,再量化
double cdf_random = normal_cdf(random_param);
unsigned char cdf_random_quant = linear_quantize(cdf_random, cdf_min, cdf_max);
double cdf_random_dequant = inverse_linear_quantize(cdf_random_quant, cdf_min, cdf_max);
double mapped_cdf_random = map_to_range(cdf_random_dequant, cdf_min, cdf_max, in_min, in_max);
double linear_weight_dequant = inverse_linear_quantize(linear_weight_quant, in_min, in_max);
double linear_result = relu(mapped_cdf_random - linear_weight_dequant);
double cdf_mapped_weight_dequant = inverse_linear_quantize(cdf_mapped_weight_quant, in_min, in_max);
double cdf_result = relu(mapped_cdf_random - cdf_mapped_weight_dequant);
printf("\n量化测试: 随机数 %.2f 经CDF变换并量化后为 %hhu, 映射回原范围为 %.6f\n", random_param, cdf_random_quant, mapped_cdf_random);
printf("与线性量化权重比较ReLU结果: %.6f\n", linear_result);
printf("与CDF量化权重比较ReLU结果: %.6f\n", cdf_result);
return 0;
}
in_min和in_max的值),观察对量化结果和最终比较结果的影响。不同的量化区间可能会优化量化精度,减少与不量化结果的偏差。llama.cpp中,量化相关代码主要围绕将大语言模型(LLMs)的权重从高比特精度转换为低比特精度,以减少内存需求并加速推理。其量化操作涵盖多种技术,8比特量化是其中一种。llama.cpp的GitHub仓库(gggerganov/llama.cpp )中,量化相关代码分散在多个文件。quantize.h和quantize.cpp文件是核心,定义量化函数和数据结构。像quantize_weights函数负责具体量化操作,根据不同量化方法(如q4_k_m等)对模型权重进行处理。在模型加载部分代码里,也会调用量化相关逻辑,将量化后的权重正确加载到模型中进行推理。examples/quantize/README.md文档,详细介绍量化的示例用法。包括如何获取模型权重、安装依赖、将模型转换为ggml fp16格式,以及使用llama - quantize工具进行4比特量化(如q4_k_m方法 )的具体步骤。Quantization of LLMs with llama.cpp等文章也对llama.cpp量化技术进行讲解,介绍量化概念、不同量化技术(朴素量化、k-means量化等 )原理,以及在llama.cpp中实现量化的实际操作步骤和优势。make llama - quantize命令构建量化工具。在具体量化过程中,会读取模型的权重数据,按照上述类似的步骤进行量化处理,并将量化后的数据保存为特定格式(如gguf格式)。lama.cpp的做法一致呀。从你的描述来看,在模型量化过程中,先确定原始范围(如-3到3),经过CDF变换得到新的范围上下界,然后将经过CDF变换后的权重值根据这个新范围映射到8比特量化空间,具体就是将0到1之间的CDF变换值乘以255得到对应的整数值,这个整数值就是最终量化后要写回到新模型中的权重值,这种做法在模型量化中是比较常见的,可以有效减少模型存储空间和计算量,同时尽量保持模型的性能。