#量化反量化介绍
为了更好地帮助您理解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向量优化。
#分块策略
-
J6 DSP有两个256kB的TCM,实际可用大约在210KB,在不超过可用总量情况下,每个tile尽可能的大,quantize主要考虑输入及输出,并且比例为4:1,dequantize同样主要考虑输入及输出,且比例为1:4;
-
同时,因为quantize线性计算,且输出小于输出数据大小,可以考虑使用inplace(输入输出使用统一tile,进一步增大可用tile)。
#DSP优化实现
- 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);- 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);- 框架部分可参考DSP算子实现小节中对ping pong IDMA实现的介绍。
#一致性校验
将计算结果与CPU参考实现进行比较,一致则通过校验。
