inline void Quantize(int32_t multiplier, int32_t shift, int32_t total_size,
                     int32_t output_zp, int32_t* scratch, uint8_t* output) {
  ruy::profiler::ScopeLabel label("Quantize/uint8");
  int i = 0;
  const int32_t output_min = std::numeric_limits<uint8_t>::min();
  const int32_t output_max = std::numeric_limits<uint8_t>::max();

#ifdef USE_NEON
  const int32x4_t output_zp_dup = vdupq_n_s32(output_zp);
  const int32x4_t max_val_dup = vdupq_n_s32(output_max);
  const int32x4_t min_val_dup = vdupq_n_s32(output_min);

  using gemmlowp::RoundingDivideByPOT;
  using gemmlowp::SaturatingRoundingDoublingHighMul;

  for (; i <= total_size - 16; i += 16) {
    int32x4x4_t scratch_val;
    scratch_val.val[0] = vld1q_s32(scratch + i);
    scratch_val.val[1] = vld1q_s32(scratch + i + 4);
    scratch_val.val[2] = vld1q_s32(scratch + i + 8);
    scratch_val.val[3] = vld1q_s32(scratch + i + 12);

    int32x4x4_t temp_val =
        MultiplyByQuantizedMultiplier4Rows(scratch_val, multiplier, shift);

    temp_val.val[0] = vaddq_s32(temp_val.val[0], output_zp_dup);
    temp_val.val[1] = vaddq_s32(temp_val.val[1], output_zp_dup);
    temp_val.val[2] = vaddq_s32(temp_val.val[2], output_zp_dup);
    temp_val.val[3] = vaddq_s32(temp_val.val[3], output_zp_dup);

    temp_val.val[0] =
        vmaxq_s32(vminq_s32(temp_val.val[0], max_val_dup), min_val_dup);
    temp_val.val[1] =
        vmaxq_s32(vminq_s32(temp_val.val[1], max_val_dup), min_val_dup);
    temp_val.val[2] =
        vmaxq_s32(vminq_s32(temp_val.val[2], max_val_dup), min_val_dup);
    temp_val.val[3] =
        vmaxq_s32(vminq_s32(temp_val.val[3], max_val_dup), min_val_dup);

    const uint16x8_t result_1 =
        vcombine_u16(vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[0])),
                     vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[1])));
    const uint16x8_t result_2 =
        vcombine_u16(vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[2])),
                     vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[3])));
    const uint8x16_t result =
        vcombine_u8(vqmovn_u16(result_1), vqmovn_u16(result_2));
    vst1q_u8(output + i, result);
  }
#endif
  for (; i < total_size; ++i) {
    int32_t temp = MultiplyByQuantizedMultiplier(scratch[i], multiplier, shift);
    temp += output_zp;
    if (temp > output_max) {
      temp = output_max;
    }
    if (temp < output_min) {
      temp = output_min;
    }
    output[i] = static_cast<uint8_t>(temp);
  }
}
inline void Quantize(int32_t multiplier, int32_t shift, int32_t total_size,
                     int32_t output_zp, int32_t* scratch, uint8_t* output) {
  ruy::profiler::ScopeLabel label("Quantize/uint8");
  int i = 0;
  const int32_t output_min = std::numeric_limits<uint8_t>::min();
  const int32_t output_max = std::numeric_limits<uint8_t>::max();

#ifdef USE_NEON
  const int32x4_t output_zp_dup = vdupq_n_s32(output_zp);
  const int32x4_t max_val_dup = vdupq_n_s32(output_max);
  const int32x4_t min_val_dup = vdupq_n_s32(output_min);

  using gemmlowp::RoundingDivideByPOT;
  using gemmlowp::SaturatingRoundingDoublingHighMul;

  for (; i <= total_size - 16; i += 16) {
    int32x4x4_t scratch_val;
    scratch_val.val[0] = vld1q_s32(scratch + i);
    scratch_val.val[1] = vld1q_s32(scratch + i + 4);
    scratch_val.val[2] = vld1q_s32(scratch + i + 8);
    scratch_val.val[3] = vld1q_s32(scratch + i + 12);

    int32x4x4_t temp_val =
        MultiplyByQuantizedMultiplier4Rows(scratch_val, multiplier, shift);

    temp_val.val[0] = vaddq_s32(temp_val.val[0], output_zp_dup);
    temp_val.val[1] = vaddq_s32(temp_val.val[1], output_zp_dup);
    temp_val.val[2] = vaddq_s32(temp_val.val[2], output_zp_dup);
    temp_val.val[3] = vaddq_s32(temp_val.val[3], output_zp_dup);

    temp_val.val[0] =
        vmaxq_s32(vminq_s32(temp_val.val[0], max_val_dup), min_val_dup);
    temp_val.val[1] =
        vmaxq_s32(vminq_s32(temp_val.val[1], max_val_dup), min_val_dup);
    temp_val.val[2] =
        vmaxq_s32(vminq_s32(temp_val.val[2], max_val_dup), min_val_dup);
    temp_val.val[3] =
        vmaxq_s32(vminq_s32(temp_val.val[3], max_val_dup), min_val_dup);

    const uint16x8_t result_1 =
        vcombine_u16(vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[0])),
                     vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[1])));
    const uint16x8_t result_2 =
        vcombine_u16(vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[2])),
                     vqmovn_u32(vreinterpretq_u32_s32(temp_val.val[3])));
    const uint8x16_t result =
        vcombine_u8(vqmovn_u16(result_1), vqmovn_u16(result_2));
    vst1q_u8(output + i, result);
  }
#endif
  for (; i < total_size; ++i) {
    int32_t temp = MultiplyByQuantizedMultiplier(scratch[i], multiplier, shift);
    temp += output_zp;
    if (temp > output_max) {
      temp = output_max;
    }
    if (temp < output_min) {
      temp = output_min;
    }
    output[i] = static_cast<uint8_t>(temp);
  }
}