量化反量化介绍

为了更好地帮助您理解DSP示例包提供的quantize、dequantize前处理,本文从算法原理出发,分析CPU和DSP参考实现,同时对CPU和DSP参考实现进行一致性校验和性能评测。

原理介绍

量化反量化

关于量化反量化的介绍、公式及参考实现可参考关键概念章节对模型量化的介绍。

CPU参考实现

量化

static float32_t _round(float32_t input) {
  std::fesetround(FE_TONEAREST);
  float32_t result = nearbyintf(input);
  return result;
}

/**
 * 量化参考实现
 * @param[out] dst: 目标输出数据
 * @param[in] src: 源输入数据
 * @param[in] scale: 缩放值
 * @param[in] zero_point: zero_point值
 * @param[in] size: 数据大小
 * @return 如果成功则返回 0,否则返回定义的错误代码
 */
static void quantize_ref(int8_t *dst, const float32_t *src, float32_t scale,
                         float32_t zero_point, float32_t min, float32_t max,
                         int32_t size) {
  for (int32_t i = 0; i < size; ++i) {
    float32_t value = _round(src[i] / scale + zero_point);
    value = std::min(std::max(value, min), max);
    dst[i] = static_cast<int8_t>(value);
  }
}

反量化

/**
 * 反量化参考实现
 * @param[out] dst: 目标输出数据
 * @param[in] src: 源输入数据
 * @param[in] scale: 缩放值
 * @param[in] zero_point: zero_point值
 * @param[in] type: 源输入数据类型
 * @param[in] size: 数据尺寸
 * @return 如果成功则返回 0,否则返回定义的错误代码
 */
static void dequantize_ref(float32_t *dst, void *src, float32_t scale,
                           int32_t zero_point, int32_t size, uint8_t type) {
  if (type == HB_DSP_TENSOR_TYPE_S32) {
    const int32_t *ptr = static_cast<int32_t *>(src);
    for (int32_t i = 0; i < size; ++i) {
      int32_t data_diff{ptr[i] - zero_point};
      dst[i] = static_cast<float32_t>(data_diff) * scale;
    }
  } else if (type == HB_DSP_TENSOR_TYPE_S8) {
    for (int32_t i = 0; i < size; ++i) {
      const int8_t *ptr = static_cast<int8_t *>(src);
      int32_t data_diff{(int32_t)(ptr[i]) - zero_point};
      dst[i] = static_cast<float32_t>(data_diff) * scale;
    }
  } else {
    LOGE("Unsupported data type: {}", static_cast<int32_t>(type));
  }
}

DSP优化加速

DSP加速思路

向量计算

quantize/dequantize都是连续计算数据,非常适合SIMD向量优化。

分块策略

  1. J6 DSP有两个256kB的TCM,实际可用大约在210KB,在不超过可用总量情况下,每个tile尽可能的大,quantize主要考虑输入及输出,并且比例为4:1,dequantize同样主要考虑输入及输出,且比例为1:4;

  2. 同时,因为quantize线性计算,且输出小于输出数据大小,可以考虑使用inplace(输入输出使用统一tile,进一步增大可用tile)。

DSP优化实现

  1. quantize SIMD计算核心。
#define QUANTIZE_PROCESS                                                       \
  IVP_LAN_2XF32_IP(input1, al_px, INPUT);                                      \
  IVP_LAN_2XF32_IP(input2, al_px, INPUT);                                      \
  /* 计算: input * (1/scale) + offset + 0.5f */                                \
  xb_vecN_2xf32 hfvec1 = v_zero_point_0;                                       \
  xb_vecN_2xf32 hfvec2 = v_zero_point_1;                                       \
  IVP_MULAN_2XF32(hfvec1, input1, v_scale_0);                                  \
  IVP_MULAN_2XF32(hfvec2, input2, v_scale_1);                                  \
  /* 将向量四舍五入并限制在 U8/S8 范围内 */                                       \
  xb_vecN_2x32v hvec1 = IVP_TRUNCN_2XF32(                                      \
      IVP_MAXN_2XF32(IVP_MINN_2XF32(IVP_FIRINTN_2XF32(hfvec1), max), min), 0); \
  xb_vecN_2x32v hvec2 = IVP_TRUNCN_2XF32(                                      \
      IVP_MAXN_2XF32(IVP_MINN_2XF32(IVP_FIRINTN_2XF32(hfvec2), max), min), 0); \
  xb_vecNx16 output = IVP_MOVNX16_FROMN_2X32(                                  \
      IVP_SELN_2X32I(hvec2, hvec1, IVP_SELI_8B_EXTRACT_1_OF_2_OFF_0));		     \
	  IVP_SANX8S_IP(output, al_pz, OUTPUT);
  1. dequantize SIMD计算核心。
#define DEQUANTIZE_PROCESS_INT8                                              \
  xb_vecNx16 input1;                                                         \
  IVP_LANX8S_IP(input1, al_px, INPUT);                                       \
  input1 = IVP_SUBNX16(input1, v_zero_point);                                \
  xb_vecN_2x32v ll, lh;                                                      \
  ll = IVP_UNPKSNX16_L(input1);                                              \
  lh = IVP_UNPKSNX16_H(input1);                                              \
  xb_vecN_2xf32 output1 = IVP_MULN_2XF32(IVP_FLOATN_2X32(ll, 0), v_scale_0); \
  xb_vecN_2xf32 output2 = IVP_MULN_2XF32(IVP_FLOATN_2X32(lh, 0), v_scale_1);
  1. 框架部分可参考DSP算子实现小节中对ping pong IDMA实现的介绍。

一致性校验

将计算结果与CPU参考实现进行比较,一致则通过校验。