You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升定点数据类型利用率?CNN FP32转Int16量化效果差求解

CNN Int16量化实现正确性与优化疑问

我尝试将卷积神经网络(CNN)从FP32转为Int16量化以降低内存占用,但效果很差,首次使用这类表示方式,对实现正确性存疑。

量化函数实现(均匀量化)

我用以下函数对输入数据和权重做量化,采用Q5.10格式:

#define FXP 16

int16_t quantize(float a, int fxp){
    int32_t maxVal = ((1 << (FXP-1)) - 1);
    int32_t value = a * (1 << fxp); //mapping
    
    //rounding
    if (a>=0){
        value += 0.5f;
    }else{
        value -= 0.5f;
    }
    
    //clipping
    if(value > maxVal){
        return (int16_t)maxVal;
    }else if(value < -maxVal){
    
        return -(int16_t)maxVal;
    }else{
        return (int16_t)value;
    }
}

int16_t value = quantize(test_data[i],10);

网络内运算实现

完成数值转换后,卷积等运算的实现如下:

for(int k=0; k<output_fea; k++){
      int32_t accumulator = 0;
      
      for(int l=minimum; l<maximum; l++){
        for(int j=0; j<input_fea; j++){
          accumulator += (data[l][j]*weights[k][l][j] + (1<<((FXP_VALUE-1))))>>FXP_VALUE; //both data and weights array are int16_t
        }
      }
      
      //before going from int32_t to int16_t
      if(accumulator>INT16_MAX){
        accumulator=INT16_MAX;
      }else if(accumulator<INT16_MIN){
        accumulator=INT16_MIN;
      }

      result[i][k] = (int16_t)ReLU(accumulator); //result is int16_t
    }  
  }

请问我的实现是否正确?有哪些步骤可以提升效果并减少近似误差?


问题分析与优化建议

一、现有实现的错误点

  • 量化参数不匹配:定义的FXP宏为16,但调用quantize时传入小数位为10,函数内用FXP计算的maxVal是16位有符号数最大值,若输入数据范围超过((1<<15)-1)/(1<<10)=31.998,会被过度截断,引发严重误差。
  • 卷积缩放时机错误:每次乘法后就缩放再累加,会导致截断误差多次累积,大幅降低精度。
  • ReLU与截断顺序错误:先将累加值截断到int16范围再做ReLU,会丢失超出范围的有效信息,且ReLU的作用时机不符合量化逻辑。

二、优化步骤与改进方法

  1. 统一量化参数,修正量化函数
    明确Q格式的整数位与小数位关联,让函数参数控制小数位,自动计算对应范围:

    int16_t quantize(float a, int frac_bits){
        const int total_bits = 16;
        const int32_t max_val = (1 << (total_bits - 1)) - 1;
        const int32_t min_val = -(1 << (total_bits - 1));
        
        int32_t scaled = a * (1 << frac_bits);
        // 四舍五入
        scaled += (a >= 0) ? 0.5f : -0.5f;
        // 截断到int16合法范围
        scaled = (scaled > max_val) ? max_val : scaled;
        scaled = (scaled < min_val) ? min_val : scaled;
        return (int16_t)scaled;
    }
    

    调用时传入10即可对应Q5.10,若输入数据超出[-32,31.998],需先做全局归一化或调整Q格式的整数位。

  2. 修正卷积运算的累加与缩放逻辑
    先完整累加所有乘法结果(用int32存储避免溢出),最后统一做缩放和处理,减少截断误差:

    for(int k=0; k<output_fea; k++){
        int32_t accumulator = 0;
        
        for(int l=minimum; l<maximum; l++){
            for(int j=0; j<input_fea; j++){
                // 直接累加int16相乘的int32结果,不提前缩放
                accumulator += (int32_t)data[l][j] * weights[k][l][j];
            }
        }
        
        // 两个Q5.10相乘结果为Q10.20,右移10位回到Q5.10,同时四舍五入
        int32_t scaled_accum = (accumulator + (1 << (10 - 1))) >> 10;
        
        // 先做ReLU,再截断到int16范围
        scaled_accum = (scaled_accum > 0) ? scaled_accum : 0;
        
        scaled_accum = (scaled_accum > INT16_MAX) ? INT16_MAX : scaled_accum;
        scaled_accum = (scaled_accum < INT16_MIN) ? INT16_MIN : scaled_accum;
        
        result[i][k] = (int16_t)scaled_accum;
    }
    
  3. 添加量化校准步骤
    不要凭经验选择Q格式,用校准数据集统计输入、权重的极值或99.9%分位数,计算最优缩放因子:

    • 权重用全局最大值确定缩放范围,确保所有权重都能被量化到int16内。
    • 输入和激活值用分位数避免极端值导致的过度截断。
  4. 补充偏差(Bias)的量化处理
    若网络包含bias,需将其量化到与卷积累加结果匹配的格式:输入和权重为Q5.10时,卷积累加结果为Q10.20,bias需量化为Q10.20格式后再加入累加,最后统一缩放回Q5.10。

  5. 验证量化前后的数值误差
    取一组FP32输入和权重,量化后反量化回FP32,计算MSE或MAE,若误差过大则调整Q格式的小数位与整数位。


内容的提问来源于stack exchange,提问作者Dresult

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 04:02:48