mirror of
https://github.com/opencv/opencv.git
synced 2026-10-28 08:35:30 +08:00
Merge pull request #30289 from varun-jaiswal17:dnn-int8-cpu-perf
dnn : int8 optimisation - #30289 ### Correctness - **AVX-VNNI 1x1 conv, leftover pixels:** the last `H*W % 8` output pixels read the VNNI-repacked weights in the wrong order. Fixes the wrong top-1 of MobileNetV2 and DenseNet121 int8 with VNNI. - **Padded taps in scalar paths:** the depthwise (AVX2) tail and the NEON kxk scalar path skipped out-of-image taps instead of using the input zero point. The VNNI kxk kernel now never leaves a tile under 8 pixels. ### Speed - **First conv:** quantizing the float image is now parallel and vectorized. A float-input conv with up to 4 channels runs as a 1x1 conv over each pixel's window. First conv 5-6x faster (ResNet50: 0.88 -> 0.16 ms). `OPENCV_DNN_INT8_CONV_WINDOW_PACKING=0` disables the window packing. - **AVX-VNNI depthwise conv:** a new kernel transposes input rows into one row per channel and adds 4 taps for 8 pixels with one `pshufb` + `vpdpbusd`. Depthwise about 2x faster, output bit-identical. - **int8 MatMul at batch 1:** uses all threads instead of one. AlexNet int8: 2.75 -> 1.52 ms. - **Asymmetric padding:** convs with TF "SAME" padding at stride 2 are now fused to int8 instead of running in float between Dequantize and Quantize. Float convs in EfficientNet-Lite4 and MP hand pose: 5 -> 0-1. - **Broadcast int8 Add/Mul:** copies whole contiguous runs in parallel instead of computing an index per element. - **int8 Quantize/Dequantize:** AVX2 fast path for signed int8; per-tensor Q/DQ keep the block layout. ### Tests - Companion PR : opencv/opencv_extra#1436 (test data). ### Results (ms, i9-14900KS , default threads) | Model | 5.x before (ms) | this PR (ms) | ONNX Runtime 1.30 (ms) | Speedup vs 5.x | ORT / this PR | |---|---|---|---|---|---| | DenseNet121 int8 | 109.4 | 14.7 | 16.1 | 7.43x | 1.09x | | PP-OCRv3 det int8 | 65.7 | 12.7 | 33.1 | 5.15x | 2.60x | | SFace int8 | 8.1 | 3.0 | 19.0 | 2.75x | 6.43x | | EfficientNet-Lite4 int8 | 4.1 | 2.9 | 1.9 | 1.40x | 0.67x | | EfficientNet-Lite4 qdq | 4.6 | 3.1 | 2.0 | 1.49x | 0.65x | | MP hand pose int8 | 2.5 | 0.8 | 2.2 | 3.06x | 2.69x | | PPHumanSeg int8 | 2.6 | 1.9 | 5.6 | 1.41x | 3.02x | Co-authored with @Prasadayus ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
1 parent
048f050d5d
commit
56dabe2b19
9 files changed
+663
-114
No files matched your search
@@ -431,6 +431,45 @@ PERF_TEST_P_(DNNTestNetwork, EfficientDet_int8)
|
||||
processNet("dnn/tflite/coco_efficientdet_lite0_v1_1.0_quant_2021_09_06.tflite", "", inp);
|
||||
}
|
||||
|
||||
PERF_TEST_P_(DNNTestNetwork, YuNet_int8)
|
||||
{
|
||||
if (target != DNN_TARGET_CPU || backend != DNN_BACKEND_OPENCV)
|
||||
throw SkipTestException("");
|
||||
processNet("dnn/onnx/models/face_detection_yunet_2023mar_int8.onnx", "", cv::Size(640, 640));
|
||||
}
|
||||
|
||||
PERF_TEST_P_(DNNTestNetwork, SFace_int8)
|
||||
{
|
||||
if (target != DNN_TARGET_CPU || backend != DNN_BACKEND_OPENCV)
|
||||
throw SkipTestException("");
|
||||
processNet("dnn/onnx/models/face_recognition_sface_2021dec_int8.onnx", "", cv::Size(112, 112));
|
||||
}
|
||||
|
||||
PERF_TEST_P_(DNNTestNetwork, DenseNet_121_int8)
|
||||
{
|
||||
if (target != DNN_TARGET_CPU || backend != DNN_BACKEND_OPENCV)
|
||||
throw SkipTestException("");
|
||||
processNet("dnn/onnx/models/densenet-12-int8.onnx", "", cv::Size(224, 224));
|
||||
}
|
||||
|
||||
PERF_TEST_P_(DNNTestNetwork, EfficientNet_int8)
|
||||
{
|
||||
if (target != DNN_TARGET_CPU || backend != DNN_BACKEND_OPENCV)
|
||||
throw SkipTestException("");
|
||||
Mat sample = imread(findDataFile("dnn/dog416.png"));
|
||||
Mat inp = blobFromImage(sample, 1.0 / 255.0, Size(224, 224), Scalar(), true);
|
||||
transposeND(inp, {0, 2, 3, 1}, inp);
|
||||
processNet("dnn/onnx/models/efficientnet-lite4-11-int8.onnx", "", inp);
|
||||
}
|
||||
|
||||
PERF_TEST_P_(DNNTestNetwork, PPOCRv3_int8)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_512MB);
|
||||
if (target != DNN_TARGET_CPU || backend != DNN_BACKEND_OPENCV)
|
||||
throw SkipTestException("");
|
||||
processNet("dnn/onnx/models/text_detection_en_ppocrv3_2023may_int8.onnx", "", cv::Size(736, 736));
|
||||
}
|
||||
|
||||
PERF_TEST_P_(DNNTestNetwork, YOLOv8n_TFLite)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_512MB);
|
||||
|
||||
@@ -560,14 +560,6 @@ struct ModelFusionQDQ
|
||||
if (wz != 0) all_wzp_zero = false;
|
||||
}
|
||||
|
||||
bool symmetric_pads = true;
|
||||
size_t npads = conv->pads.size();
|
||||
size_t ndims_pad = npads / 2;
|
||||
for (size_t d = 0; d < ndims_pad && symmetric_pads; d++) {
|
||||
if (conv->pads[d] != conv->pads[d + ndims_pad])
|
||||
symmetric_pads = false;
|
||||
}
|
||||
|
||||
// Conv2Int8's VNNI kernel processes K0=8 output channels per
|
||||
// SIMD iteration. When ngroups>1 and Kg<K0, multiple groups map
|
||||
// to the same K0-wide output slot and clobber one another, so
|
||||
@@ -581,7 +573,7 @@ struct ModelFusionQDQ
|
||||
const bool kernelSupportsGrouping =
|
||||
conv->ngroups <= 1 || outChannelsPerGroup >= 8 || isDepthwise;
|
||||
|
||||
if (all_wzp_zero && symmetric_pads && kernelSupportsGrouping) {
|
||||
if (all_wzp_zero && kernelSupportsGrouping) {
|
||||
Mat bias = Mat::zeros(1, outCn, CV_32S);
|
||||
bool biasOk = true;
|
||||
int dq_bias_idx = -1;
|
||||
|
||||
@@ -493,11 +493,13 @@ static void convInt8BlockNEON(const void* inp_, const void* residual_,
|
||||
int zi = zi_base + ofsZYX[i * 3];
|
||||
int yi = yi_base + ofsZYX[i * 3 + 1];
|
||||
int xi = xi_base + ofsZYX[i * 3 + 2];
|
||||
if ((((unsigned)zi >= (unsigned)Di_l) | ((unsigned)yi >= (unsigned)Hi_l) |
|
||||
((unsigned)xi >= (unsigned)Wi_l)) != 0) continue;
|
||||
const int8_t* inptr = inpbaseptr + (((zi * Hi_l) + yi) * Wi_l + xi) * C0;
|
||||
bool outside = (((unsigned)zi >= (unsigned)Di_l) | ((unsigned)yi >= (unsigned)Hi_l) |
|
||||
((unsigned)xi >= (unsigned)Wi_l)) != 0;
|
||||
const int8_t* inptr = outside ? zbuf
|
||||
: inpbaseptr + (((zi * Hi_l) + yi) * Wi_l + xi) * C0;
|
||||
const int inpstep = outside ? 0 : iplanesize_l;
|
||||
const int8_t* wptr_vnni = wbaseptr + (size_t)i * C1Max * K0 * C0;
|
||||
for (int c1i = 0; c1i < cblocks; c1i++, inptr += iplanesize_l) {
|
||||
for (int c1i = 0; c1i < cblocks; c1i++, inptr += inpstep) {
|
||||
const int8_t* wp = wptr_vnni + (size_t)c1i * C0 * K0;
|
||||
for (int c0 = 0; c0 < C0; c0++) {
|
||||
int ival = inputIsU8 ? ((int)(uint8_t)inptr[c0] - 128)
|
||||
@@ -1205,11 +1207,17 @@ static void convInt8BlockDepthwise(const void* inp_, const void* residual_,
|
||||
for (int i = 0; i < ksize; i++) {
|
||||
int zi = zi_base + ofsZYX[i*3], yi = yi_base + ofsZYX[i*3+1],
|
||||
xi = xi_base + ofsZYX[i*3+2];
|
||||
__m256i inp32;
|
||||
if ((unsigned)zi>=(unsigned)Di || (unsigned)yi>=(unsigned)Hi ||
|
||||
(unsigned)xi>=(unsigned)Wi) continue;
|
||||
const int8_t* ip = inpbaseptr + (((zi*Hi)+yi)*Wi+xi)*C0;
|
||||
__m256i inp32 = _mm256_cvtepu8_epi32(
|
||||
_mm_xor_si128(_mm_loadl_epi64((const __m128i*)ip), v_xor));
|
||||
(unsigned)xi>=(unsigned)Wi) {
|
||||
inp32 = inputIsU8
|
||||
? _mm256_set1_epi32(inp_zp)
|
||||
: _mm256_set1_epi32((int)(uint8_t)((uint8_t)inp_zp ^ 0x80u));
|
||||
} else {
|
||||
const int8_t* ip = inpbaseptr + (((zi*Hi)+yi)*Wi+xi)*C0;
|
||||
inp32 = _mm256_cvtepu8_epi32(
|
||||
_mm_xor_si128(_mm_loadl_epi64((const __m128i*)ip), v_xor));
|
||||
}
|
||||
__m256i w32 = _mm256_cvtepi8_epi32(
|
||||
_mm_loadl_epi64((const __m128i*)(wbuf + i*K0)));
|
||||
acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(inp32, w32));
|
||||
@@ -1384,6 +1392,8 @@ static void convInt8BlockVNNI_kxk(const void* inp_, const void* residual_,
|
||||
int min_ntiles = std::max(1, (nthreads * 4 + nkb - 1) / nkb);
|
||||
int TILE = std::max(SPAT_BLOCK_SIZE,
|
||||
((planeblocks_ + min_ntiles - 1) / min_ntiles + SPAT_BLOCK_SIZE - 1) & ~(SPAT_BLOCK_SIZE - 1));
|
||||
while (TILE < planeblocks_ && planeblocks_ % TILE != 0 && planeblocks_ % TILE < SPAT_BLOCK_SIZE)
|
||||
TILE += SPAT_BLOCK_SIZE;
|
||||
int ntiles = (planeblocks_ + TILE - 1) / TILE;
|
||||
int total_tasks = nkb * ntiles;
|
||||
const __m256i v_xor = inputIsU8 ? _mm256_setzero_si256() : _mm256_set1_epi8((char)0x80);
|
||||
@@ -1847,20 +1857,19 @@ static void convInt8BlockVNNI_1x1(const void* inp_, const void* residual_,
|
||||
}
|
||||
|
||||
for (; p < p_end; p++) {
|
||||
alignas(32) int32_t acc[K0];
|
||||
memcpy(acc, biasbuf, K0 * sizeof(int32_t));
|
||||
|
||||
__m256i s = _mm256_load_si256((const __m256i*)biasbuf);
|
||||
const int8_t* inptr_s = inpbaseptr + (size_t)p * C0;
|
||||
const int8_t* wptr = wbaseptr;
|
||||
for (int c1 = 0; c1 < cblocks; c1++, inptr_s += iplanesize, wptr += C0 * K0) {
|
||||
for (int c0 = 0; c0 < C0; c0++) {
|
||||
int iv = inputIsU8 ? (int)(uint8_t)inptr_s[c0]
|
||||
: (int)(uint8_t)((uint8_t)inptr_s[c0] ^ 0x80u);
|
||||
const int8_t* wp = wptr + c0 * K0;
|
||||
for (int k0 = 0; k0 < K0; k0++)
|
||||
acc[k0] += iv * (int)wp[k0];
|
||||
}
|
||||
__m256i wg0 = _mm256_load_si256((const __m256i*)(wptr));
|
||||
__m256i wg1 = _mm256_load_si256((const __m256i*)(wptr + 32));
|
||||
__m256i x0 = _mm256_xor_si256(_mm256_set1_epi32(*(const int32_t*)(inptr_s )), v_xor);
|
||||
__m256i x1 = _mm256_xor_si256(_mm256_set1_epi32(*(const int32_t*)(inptr_s + 4)), v_xor);
|
||||
s = _mm256_dpbusd_epi32(s, x0, wg0);
|
||||
s = _mm256_dpbusd_epi32(s, x1, wg1);
|
||||
}
|
||||
alignas(32) int32_t acc[K0];
|
||||
_mm256_store_si256((__m256i*)acc, s);
|
||||
|
||||
int8_t* optr = outptr + (size_t)p * K0;
|
||||
const int8_t* rptr = resptr ? resptr + (size_t)p * K0 : nullptr;
|
||||
@@ -1886,6 +1895,200 @@ static void convInt8BlockVNNI_1x1(const void* inp_, const void* residual_,
|
||||
});
|
||||
}
|
||||
|
||||
static bool convInt8BlockDepthwiseVNNI(const void* inp_, void* out_, const ConvState& cs,
|
||||
const void* weights_, const int* bias,
|
||||
const float* multiplier, int inp_zp, int out_zp,
|
||||
const int8_t* activLUT, bool inputIsU8)
|
||||
{
|
||||
constexpr int C0 = 8, K0 = 8;
|
||||
const MatShape& inpshape = cs.inpshape;
|
||||
const MatShape& outshape = cs.outshape;
|
||||
if (cs.nspatialdims != 2 || inpshape.dims != 5 || outshape.dims != 5)
|
||||
return false;
|
||||
const int kh = cs.kshape[1], kw = cs.kshape[2];
|
||||
const int sy = cs.strides[1], sx = cs.strides[2];
|
||||
const int dy = cs.dilations[1];
|
||||
const int ksize = cs.wshape[2], C1Max = cs.wshape[3];
|
||||
if (cs.dilations[2] != 1 || sx > 4 || ksize != kh * kw)
|
||||
return false;
|
||||
|
||||
const int N = outshape[0], K1 = outshape[1], Ho = outshape[2], Wo = outshape[3];
|
||||
const int K = outshape.channels();
|
||||
const int Hi = inpshape[2], Wi = inpshape[3];
|
||||
const int padT = cs.pads[1], padL = cs.pads[2];
|
||||
const int G = (kw + 3) / 4;
|
||||
const int Wo8 = (Wo + 7) & ~7;
|
||||
const int Wp = std::max(padL + Wi, Wo8 * sx + 4 * G + 4 * sx) + 16;
|
||||
const uint8_t padval = inputIsU8 ? (uint8_t)inp_zp : (uint8_t)((uint8_t)inp_zp ^ 0x80u);
|
||||
|
||||
const int nthreads = std::max(1, cv::getNumThreads());
|
||||
int nbands = std::max(1, (nthreads * 4 + N * K1 - 1) / (N * K1));
|
||||
int band = (Ho + nbands - 1) / nbands;
|
||||
const int rowsBudget = std::max(1, (int)(((size_t)256 << 10) / ((size_t)C0 * Wp)));
|
||||
band = std::max(1, std::min(band, (rowsBudget - (kh - 1) * dy - 1) / sy + 1));
|
||||
nbands = (Ho + band - 1) / band;
|
||||
const int maxRows = (band - 1) * sy + (kh - 1) * dy + 1;
|
||||
|
||||
const int8_t* inp = (const int8_t*)inp_;
|
||||
int8_t* out = (int8_t*)out_;
|
||||
const int8_t* wdata = (const int8_t*)weights_;
|
||||
const int* ofsZYX = cs.coordtab.data();
|
||||
|
||||
parallel_for_(Range(0, N * K1 * nbands), [&](const Range& range) {
|
||||
AutoBuffer<uint8_t> planarBuf((size_t)maxRows * C0 * Wp);
|
||||
uint8_t* planar = planarBuf.data();
|
||||
AutoBuffer<int32_t> wqBuf((size_t)kh * G * C0);
|
||||
int32_t* wq = wqBuf.data();
|
||||
int32_t biasv[K0];
|
||||
float multv[K0];
|
||||
|
||||
alignas(32) int8_t gidx[32];
|
||||
for (int j = 0; j < 4; j++)
|
||||
for (int t4 = 0; t4 < 4; t4++)
|
||||
gidx[j * 4 + t4] = gidx[16 + j * 4 + t4] = (int8_t)(j * sx + t4);
|
||||
const __m256i vgather = _mm256_load_si256((const __m256i*)gidx);
|
||||
const __m256i vtr = _mm256_setr_epi8(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15,
|
||||
0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15);
|
||||
const __m128i v_xor = inputIsU8 ? _mm_setzero_si128() : _mm_set1_epi8((char)0x80);
|
||||
const uint8_t xorbyte = inputIsU8 ? 0 : 0x80;
|
||||
const __m256 vzp = _mm256_set1_ps((float)out_zp);
|
||||
|
||||
for (int t = range.start; t < range.end; t++) {
|
||||
int nk = t / nbands, b = t - nk * nbands;
|
||||
int n = nk / K1, k1 = nk - n * K1;
|
||||
int oy0 = b * band, oy1 = std::min(oy0 + band, Ho);
|
||||
int g0 = k1 * K0, n_g = std::min(K0, K - g0);
|
||||
|
||||
memset(wq, 0, sizeof(int32_t) * kh * G * C0);
|
||||
int8_t* wqb = (int8_t*)wq;
|
||||
for (int j = 0; j < n_g; j++) {
|
||||
int g = g0 + j;
|
||||
const int8_t* wg = wdata + (size_t)g * ksize * C1Max * C0 * K0 + (size_t)(g % C0) * K0;
|
||||
for (int i = 0; i < ksize; i++) {
|
||||
int ky = ofsZYX[i * 3 + 1] / dy, kx = ofsZYX[i * 3 + 2];
|
||||
wqb[((ky * G + kx / 4) * C0 + j) * 4 + (kx & 3)] = wg[(size_t)i * C1Max * C0 * K0];
|
||||
}
|
||||
biasv[j] = bias[g];
|
||||
multv[j] = multiplier[g];
|
||||
}
|
||||
for (int j = n_g; j < K0; j++) {
|
||||
biasv[j] = 0;
|
||||
multv[j] = 0.f;
|
||||
}
|
||||
|
||||
const int iy0 = oy0 * sy - padT;
|
||||
const int nrows = (oy1 - 1 - oy0) * sy + (kh - 1) * dy + 1;
|
||||
const int8_t* src = inp + (size_t)(n * K1 + k1) * Hi * Wi * C0;
|
||||
for (int r = 0; r < nrows; r++) {
|
||||
int iy = iy0 + r;
|
||||
uint8_t* prow = planar + (size_t)r * C0 * Wp;
|
||||
if (iy < 0 || iy >= Hi) {
|
||||
memset(prow, padval, (size_t)C0 * Wp);
|
||||
continue;
|
||||
}
|
||||
for (int c = 0; c < C0; c++) {
|
||||
memset(prow + c * Wp, padval, padL);
|
||||
memset(prow + c * Wp + padL + Wi, padval, Wp - padL - Wi);
|
||||
}
|
||||
const int8_t* sp = src + (size_t)iy * Wi * C0;
|
||||
uint8_t* dp = prow + padL;
|
||||
int x = 0;
|
||||
for (; x <= Wi - 8; x += 8, sp += 8 * C0, dp += 8) {
|
||||
__m128i a = _mm_xor_si128(_mm_loadu_si128((const __m128i*)sp), v_xor);
|
||||
__m128i bb = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(sp + 16)), v_xor);
|
||||
__m128i cc = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(sp + 32)), v_xor);
|
||||
__m128i dd = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(sp + 48)), v_xor);
|
||||
__m128i t0 = _mm_unpacklo_epi8(a, bb), t1 = _mm_unpackhi_epi8(a, bb);
|
||||
__m128i t2 = _mm_unpacklo_epi8(cc, dd), t3 = _mm_unpackhi_epi8(cc, dd);
|
||||
__m128i u0 = _mm_unpacklo_epi8(t0, t1), u1 = _mm_unpackhi_epi8(t0, t1);
|
||||
__m128i u2 = _mm_unpacklo_epi8(t2, t3), u3 = _mm_unpackhi_epi8(t2, t3);
|
||||
__m128i v0 = _mm_unpacklo_epi32(u0, u2), v1 = _mm_unpackhi_epi32(u0, u2);
|
||||
__m128i v2 = _mm_unpacklo_epi32(u1, u3), v3 = _mm_unpackhi_epi32(u1, u3);
|
||||
_mm_storel_epi64((__m128i*)dp, v0);
|
||||
_mm_storeh_pd((double*)(dp + Wp), _mm_castsi128_pd(v0));
|
||||
_mm_storel_epi64((__m128i*)(dp + 2 * Wp), v1);
|
||||
_mm_storeh_pd((double*)(dp + 3 * Wp), _mm_castsi128_pd(v1));
|
||||
_mm_storel_epi64((__m128i*)(dp + 4 * Wp), v2);
|
||||
_mm_storeh_pd((double*)(dp + 5 * Wp), _mm_castsi128_pd(v2));
|
||||
_mm_storel_epi64((__m128i*)(dp + 6 * Wp), v3);
|
||||
_mm_storeh_pd((double*)(dp + 7 * Wp), _mm_castsi128_pd(v3));
|
||||
}
|
||||
for (; x < Wi; x++, sp += C0, dp++)
|
||||
for (int c = 0; c < C0; c++)
|
||||
dp[c * Wp] = (uint8_t)sp[c] ^ xorbyte;
|
||||
}
|
||||
|
||||
for (int oy = oy0; oy < oy1; oy++) {
|
||||
const uint8_t* rbase = planar + (size_t)(oy - oy0) * sy * C0 * Wp;
|
||||
int8_t* orow = out + ((size_t)(n * K1 + k1) * Ho + oy) * Wo * K0;
|
||||
for (int ox0 = 0; ox0 < Wo; ox0 += 8) {
|
||||
__m256i s0 = _mm256_set1_epi32(biasv[0]), s1 = _mm256_set1_epi32(biasv[1]);
|
||||
__m256i s2 = _mm256_set1_epi32(biasv[2]), s3 = _mm256_set1_epi32(biasv[3]);
|
||||
__m256i s4 = _mm256_set1_epi32(biasv[4]), s5 = _mm256_set1_epi32(biasv[5]);
|
||||
__m256i s6 = _mm256_set1_epi32(biasv[6]), s7 = _mm256_set1_epi32(biasv[7]);
|
||||
for (int ky = 0; ky < kh; ky++) {
|
||||
const uint8_t* rk = rbase + (size_t)ky * dy * C0 * Wp + (size_t)ox0 * sx;
|
||||
const int32_t* wk = wq + ky * G * C0;
|
||||
for (int g = 0; g < G; g++, rk += 4, wk += C0) {
|
||||
#define DWV_MAC(c) { \
|
||||
const uint8_t* p_ = rk + (c) * Wp; \
|
||||
__m256i x_ = _mm256_inserti128_si256(_mm256_castsi128_si256( \
|
||||
_mm_loadu_si128((const __m128i*)p_)), \
|
||||
_mm_loadu_si128((const __m128i*)(p_ + 4 * sx)), 1); \
|
||||
s##c = _mm256_dpbusd_epi32(s##c, _mm256_shuffle_epi8(x_, vgather), \
|
||||
_mm256_set1_epi32(wk[c])); \
|
||||
}
|
||||
DWV_MAC(0); DWV_MAC(1); DWV_MAC(2); DWV_MAC(3);
|
||||
DWV_MAC(4); DWV_MAC(5); DWV_MAC(6); DWV_MAC(7);
|
||||
#undef DWV_MAC
|
||||
}
|
||||
}
|
||||
|
||||
#define DWV_Q(c) _mm256_cvtps_epi32(_mm256_add_ps(_mm256_mul_ps( \
|
||||
_mm256_cvtepi32_ps(s##c), _mm256_set1_ps(multv[c])), vzp))
|
||||
__m256i p01 = _mm256_packs_epi32(DWV_Q(0), DWV_Q(1));
|
||||
__m256i p23 = _mm256_packs_epi32(DWV_Q(2), DWV_Q(3));
|
||||
__m256i p45 = _mm256_packs_epi32(DWV_Q(4), DWV_Q(5));
|
||||
__m256i p67 = _mm256_packs_epi32(DWV_Q(6), DWV_Q(7));
|
||||
#undef DWV_Q
|
||||
__m256i b03 = inputIsU8 ? _mm256_packus_epi16(p01, p23) : _mm256_packs_epi16(p01, p23);
|
||||
__m256i b47 = inputIsU8 ? _mm256_packus_epi16(p45, p67) : _mm256_packs_epi16(p45, p67);
|
||||
b03 = _mm256_shuffle_epi8(b03, vtr);
|
||||
b47 = _mm256_shuffle_epi8(b47, vtr);
|
||||
__m256i lo = _mm256_unpacklo_epi32(b03, b47);
|
||||
__m256i hi = _mm256_unpackhi_epi32(b03, b47);
|
||||
__m256i px03 = _mm256_permute2x128_si256(lo, hi, 0x20);
|
||||
__m256i px47 = _mm256_permute2x128_si256(lo, hi, 0x31);
|
||||
|
||||
int8_t* optr = orow + (size_t)ox0 * K0;
|
||||
const int npix = std::min(8, Wo - ox0);
|
||||
if (npix == 8 && n_g == K0 && !activLUT) {
|
||||
_mm256_storeu_si256((__m256i*)optr, px03);
|
||||
_mm256_storeu_si256((__m256i*)(optr + 32), px47);
|
||||
} else {
|
||||
alignas(32) int8_t tmp[64];
|
||||
_mm256_store_si256((__m256i*)tmp, px03);
|
||||
_mm256_store_si256((__m256i*)(tmp + 32), px47);
|
||||
for (int j = 0; j < npix; j++) {
|
||||
int8_t* o = optr + j * K0;
|
||||
const int8_t* tj = tmp + j * K0;
|
||||
if (!activLUT)
|
||||
memcpy(o, tj, n_g);
|
||||
else if (inputIsU8)
|
||||
for (int c = 0; c < n_g; c++)
|
||||
o[c] = activLUT[(int)(uint8_t)tj[c]];
|
||||
else
|
||||
for (int c = 0; c < n_g; c++)
|
||||
o[c] = activLUT[(int)tj[c] + 128];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static void convInt8BlockVNNI(const void* inp_, const void* residual_,
|
||||
void* out_, const ConvState& cs,
|
||||
const void* weightsVNNI_,
|
||||
@@ -2145,12 +2348,22 @@ void convInt8Block(const void* inp_, const void* residual_,
|
||||
// Depthwise convolution has a dedicated kernel.
|
||||
if (cs.depthwise) {
|
||||
const int* dw_bias = biasVNNI_ ? biasVNNI_ : bias;
|
||||
#if CV_AVX_VNNI
|
||||
if (!residual_ && convInt8BlockDepthwiseVNNI(inp_, out_, cs, weights_, dw_bias, multiplier,
|
||||
inp_zp, out_zp, activLUT, inputIsU8))
|
||||
return;
|
||||
#endif
|
||||
convInt8BlockDepthwise(inp_, residual_, out_, cs, weights_,
|
||||
dw_bias, multiplier, inp_zp, out_zp, activLUT, inputIsU8);
|
||||
return;
|
||||
}
|
||||
#if CV_AVX_VNNI
|
||||
if (weightsVNNI_ && biasVNNI_) {
|
||||
bool useVNNI = weightsVNNI_ && biasVNNI_;
|
||||
if (useVNNI && cs.hasPadding()) {
|
||||
const MatShape& os = cs.outshape;
|
||||
useVNNI = os.total() / ((size_t)os[0] * os[1] * os.back()) >= 8;
|
||||
}
|
||||
if (useVNNI) {
|
||||
convInt8BlockVNNI(inp_, residual_, out_, cs, weightsVNNI_,
|
||||
biasVNNI_, multiplier, inp_zp, out_zp, activLUT, inputIsU8);
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,48 @@
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
static void quantizeRowInt8(const float* src, uint8_t* dst, int len, float inv_sc, int zp, bool is_u8)
|
||||
{
|
||||
int j = 0;
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
const int VECSZ = VTraits<v_float32>::vlanes();
|
||||
v_float32 v_inv = vx_setall_f32(inv_sc);
|
||||
v_int32 v_zp = vx_setall_s32(zp);
|
||||
for (; j <= len - 4 * VECSZ; j += 4 * VECSZ) {
|
||||
v_int32 q0 = v_add(v_round(v_mul(vx_load(src + j), v_inv)), v_zp);
|
||||
v_int32 q1 = v_add(v_round(v_mul(vx_load(src + j + VECSZ), v_inv)), v_zp);
|
||||
v_int32 q2 = v_add(v_round(v_mul(vx_load(src + j + 2 * VECSZ), v_inv)), v_zp);
|
||||
v_int32 q3 = v_add(v_round(v_mul(vx_load(src + j + 3 * VECSZ), v_inv)), v_zp);
|
||||
v_int16 h0 = v_pack(q0, q1), h1 = v_pack(q2, q3);
|
||||
if (is_u8)
|
||||
v_store(dst + j, v_pack_u(h0, h1));
|
||||
else
|
||||
v_store((schar*)dst + j, v_pack(h0, h1));
|
||||
}
|
||||
#endif
|
||||
for (; j < len; j++) {
|
||||
int q = cvRound(src[j] * inv_sc) + zp;
|
||||
dst[j] = is_u8 ? saturate_cast<uchar>(q) : (uint8_t)saturate_cast<schar>(q);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void copyBytes(uint8_t* dst, const uint8_t* src, int n)
|
||||
{
|
||||
if (n >= 16) {
|
||||
int i = 0;
|
||||
for (; i + 16 <= n; i += 16)
|
||||
memcpy(dst + i, src + i, 16);
|
||||
if (i < n)
|
||||
memcpy(dst + n - 16, src + n - 16, 16);
|
||||
} else if (n >= 8) {
|
||||
memcpy(dst, src, 8);
|
||||
memcpy(dst + n - 8, src + n - 8, 8);
|
||||
} else {
|
||||
for (int i = 0; i < n; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
static MatShape getWpackShapeInt8(const MatShape& wshape, int ngroups, int C0)
|
||||
{
|
||||
CV_Assert(wshape.dims >= 3);
|
||||
@@ -159,6 +201,14 @@ public:
|
||||
Mat activationLUT;
|
||||
Ptr<ActivationLayerInt8> activ;
|
||||
|
||||
Mat quantizedInput;
|
||||
|
||||
bool useWindows = false;
|
||||
Mat winWeights, winWeightsVNNI;
|
||||
ConvState winCs;
|
||||
Mat paddedInput;
|
||||
Mat windowInput;
|
||||
|
||||
bool addResidual;
|
||||
bool inputIsU8;
|
||||
|
||||
@@ -318,24 +368,154 @@ public:
|
||||
const float* src = fp32_nchw.ptr<float>();
|
||||
uint8_t* dst = int8_block.ptr<uint8_t>();
|
||||
|
||||
parallel_for_(Range(0, N * C1), [&](const Range& range) {
|
||||
for (int nc = range.start; nc < range.end; nc++) {
|
||||
int n = nc / C1, c1 = nc % C1;
|
||||
const int PIX_STRIPE = 2048;
|
||||
const int nstripes = (planesize + PIX_STRIPE - 1) / PIX_STRIPE;
|
||||
|
||||
parallel_for_(Range(0, N * C1 * nstripes), [&](const Range& range) {
|
||||
const int BLK = 256;
|
||||
AutoBuffer<uint8_t> qbuf_(C0 * BLK);
|
||||
uint8_t* qbuf = qbuf_.data();
|
||||
for (int t = range.start; t < range.end; t++) {
|
||||
int nc = t / nstripes, stripe = t - nc * nstripes;
|
||||
int n = nc / C1, c1 = nc - n * C1;
|
||||
int c_base = c1 * C0;
|
||||
int nzc = std::min(C0, C - c_base);
|
||||
const float* src_block = src + (n * C + c_base) * planesize;
|
||||
uint8_t* dst_block = dst + (size_t)(n * C1 + c1) * planesize * C0;
|
||||
for (int i = 0; i < planesize; i++) {
|
||||
const float* src_block = src + ((size_t)n * C + c_base) * planesize;
|
||||
uint8_t* dst_block = dst + ((size_t)n * C1 + c1) * planesize * C0;
|
||||
int i1 = std::min((stripe + 1) * PIX_STRIPE, planesize);
|
||||
for (int i = stripe * PIX_STRIPE; i < i1; i += BLK) {
|
||||
int len = std::min(BLK, i1 - i);
|
||||
for (int c = 0; c < nzc; c++)
|
||||
quantizeRowInt8(src_block + (size_t)c * planesize + i, qbuf + c * BLK,
|
||||
len, inv_sc, zp, is_u8);
|
||||
uint8_t* dst_px = dst_block + (size_t)i * C0;
|
||||
if (nzc < C0)
|
||||
memset(dst_px, 0, (size_t)len * C0);
|
||||
for (int c = 0; c < nzc; c++) {
|
||||
float x = src_block[c * planesize + i];
|
||||
int q = cvRound(x * inv_sc) + zp;
|
||||
if (is_u8)
|
||||
dst_block[i * C0 + c] = saturate_cast<uchar>(q);
|
||||
else
|
||||
dst_block[i * C0 + c] = (uint8_t)(int8_t)saturate_cast<schar>(q);
|
||||
const uint8_t* q = qbuf + c * BLK;
|
||||
for (int j = 0; j < len; j++)
|
||||
dst_px[(size_t)j * C0 + c] = q[j];
|
||||
}
|
||||
for (int c = nzc; c < C0; c++)
|
||||
dst_block[i * C0 + c] = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool canUseWindowPacking(const MatShape& outshape, int C0) const
|
||||
{
|
||||
static const bool enabled =
|
||||
utils::getConfigurationParameterBool("OPENCV_DNN_INT8_CONV_WINDOW_PACKING", true);
|
||||
if (!enabled || !float_input || ngroups != 1 || blobs.empty() || C0 != 8 ||
|
||||
wshape0.dims != 4 || outshape.dims != 5)
|
||||
return false;
|
||||
int C = wshape0[1], kh = wshape0[2], kw = wshape0[3];
|
||||
if (C > 4 || kh * kw == 1)
|
||||
return false;
|
||||
size_t Cw8 = (size_t)(kh * kw * C + C0 - 1) / C0 * C0;
|
||||
size_t nbytes = (size_t)outshape[0] * outshape[2] * outshape[3] * Cw8;
|
||||
return nbytes <= ((size_t)64 << 20);
|
||||
}
|
||||
|
||||
void initWindowConv(const MatShape& outshape, int C0)
|
||||
{
|
||||
int K = wshape0[0], C = wshape0[1], kh = wshape0[2], kw = wshape0[3];
|
||||
int Cw8 = (kh * kw * C + C0 - 1) / C0 * C0;
|
||||
if (winWeights.empty()) {
|
||||
const Mat& w = blobs[0];
|
||||
CV_Assert(w.type() == CV_8SC1 && w.isContinuous() && w.total() == (size_t)K * C * kh * kw);
|
||||
Mat w1x1(std::vector<int>{K, Cw8, 1, 1}, CV_8SC1, Scalar(0));
|
||||
const int8_t* src = w.ptr<int8_t>();
|
||||
int8_t* dst = w1x1.ptr<int8_t>();
|
||||
for (int k = 0; k < K; k++)
|
||||
for (int c = 0; c < C; c++)
|
||||
for (int ky = 0; ky < kh; ky++)
|
||||
for (int kx = 0; kx < kw; kx++)
|
||||
dst[(size_t)k * Cw8 + (ky * kw + kx) * C + c] =
|
||||
src[(((size_t)k * C + c) * kh + ky) * kw + kx];
|
||||
repackConvWeightsInt8(w1x1, winWeights, 1, C0);
|
||||
Mat unusedBias;
|
||||
repackWeightsForVNNI(winWeights, 1, K, Cw8, biasInt32, winWeightsVNNI, unusedBias);
|
||||
}
|
||||
int N = outshape[0], Ho = outshape[2], Wo = outshape[3];
|
||||
MatShape wininp = MatShape({N, Cw8, Ho, Wo}, DATA_LAYOUT_NCHW).toLayout(DATA_LAYOUT_BLOCK, C0);
|
||||
winCs.initConv(wininp, MatShape({K, Cw8, 1, 1}, DATA_LAYOUT_UNKNOWN), outshape, 1,
|
||||
{1, 1}, {1, 1}, {0, 0, 0, 0}, AUTO_PAD_NONE, false,
|
||||
FAST_ACTIV_NONE, nullptr, {});
|
||||
}
|
||||
|
||||
void packWindows(const Mat& fp32_nchw, int C0)
|
||||
{
|
||||
MatShape nchw = fp32_nchw.shape();
|
||||
CV_Assert(nchw.dims == 4 && C0 == 8);
|
||||
const int N = nchw[0], C = nchw[1], H = nchw[2], W = nchw[3];
|
||||
const int kh = cs.kshape[1], kw = cs.kshape[2];
|
||||
const int sy = cs.strides[1], sx = cs.strides[2];
|
||||
const int dy = cs.dilations[1], dx = cs.dilations[2];
|
||||
const int padT = cs.pads[1], padL = cs.pads[2];
|
||||
const int Ho = cs.outshape[2], Wo = cs.outshape[3];
|
||||
const int Hp = std::max(padT + H + cs.pads[1 + ConvState::MAX_CONV_DIMS], (Ho - 1) * sy + (kh - 1) * dy + 1);
|
||||
const int Wp = std::max(padL + W + cs.pads[2 + ConvState::MAX_CONV_DIMS], (Wo - 1) * sx + (kw - 1) * dx + 1);
|
||||
const int Cw = kh * kw * C, Cw8 = (Cw + C0 - 1) / C0 * C0, C1w = Cw8 / C0;
|
||||
|
||||
const float inv_sc = input_sc > 0.f ? 1.f / input_sc : 0.f;
|
||||
const int zp = input_zp;
|
||||
const bool is_u8 = inputIsU8;
|
||||
const uint8_t zpbyte = (uint8_t)input_zp;
|
||||
const float* src = fp32_nchw.ptr<float>();
|
||||
const size_t prow = (size_t)Wp * C;
|
||||
|
||||
paddedInput.fit({N * Hp, (int)prow}, CV_8UC1);
|
||||
uint8_t* pbuf = paddedInput.ptr<uint8_t>();
|
||||
parallel_for_(Range(0, N * Hp), [&](const Range& range) {
|
||||
AutoBuffer<uint8_t> qbuf_((size_t)C * W);
|
||||
uint8_t* qbuf = qbuf_.data();
|
||||
for (int t = range.start; t < range.end; t++) {
|
||||
int n = t / Hp, y = t - n * Hp - padT;
|
||||
uint8_t* drow = pbuf + (size_t)t * prow;
|
||||
if (y < 0 || y >= H) {
|
||||
memset(drow, zpbyte, prow);
|
||||
continue;
|
||||
}
|
||||
memset(drow, zpbyte, (size_t)padL * C);
|
||||
memset(drow + (size_t)(padL + W) * C, zpbyte, (size_t)(Wp - padL - W) * C);
|
||||
for (int c = 0; c < C; c++)
|
||||
quantizeRowInt8(src + (((size_t)n * C + c) * H + y) * W, qbuf + (size_t)c * W,
|
||||
W, inv_sc, zp, is_u8);
|
||||
uint8_t* d = drow + (size_t)padL * C;
|
||||
for (int x = 0; x < W; x++)
|
||||
for (int c = 0; c < C; c++)
|
||||
d[x * C + c] = qbuf[(size_t)c * W + x];
|
||||
}
|
||||
});
|
||||
|
||||
MatShape winshape = MatShape({N, Cw8, Ho, Wo}, DATA_LAYOUT_NCHW).toLayout(DATA_LAYOUT_BLOCK, C0);
|
||||
windowInput.fit(winshape, inputIsU8 ? CV_8UC1 : CV_8SC1);
|
||||
uint8_t* wdst = windowInput.ptr<uint8_t>();
|
||||
const size_t plane = (size_t)Ho * Wo * C0;
|
||||
parallel_for_(Range(0, N * Ho), [&](const Range& range) {
|
||||
AutoBuffer<uint8_t> win_(Cw8);
|
||||
uint8_t* win = win_.data();
|
||||
memset(win + Cw, 0, Cw8 - Cw);
|
||||
for (int t = range.start; t < range.end; t++) {
|
||||
int n = t / Ho, oy = t - n * Ho;
|
||||
const uint8_t* srow = pbuf + ((size_t)n * Hp + (size_t)oy * sy) * prow;
|
||||
uint8_t* drow = wdst + (size_t)n * C1w * plane + (size_t)oy * Wo * C0;
|
||||
for (int ox = 0; ox < Wo; ox++) {
|
||||
const uint8_t* s = srow + (size_t)ox * sx * C;
|
||||
for (int ky = 0; ky < kh; ky++) {
|
||||
const uint8_t* sk = s + (size_t)ky * dy * prow;
|
||||
uint8_t* wk = win + ky * kw * C;
|
||||
if (dx == 1) {
|
||||
copyBytes(wk, sk, kw * C);
|
||||
} else {
|
||||
for (int kx = 0; kx < kw; kx++)
|
||||
for (int c = 0; c < C; c++)
|
||||
wk[kx * C + c] = sk[(size_t)kx * dx * C + c];
|
||||
}
|
||||
}
|
||||
uint8_t* d = drow + (size_t)ox * C0;
|
||||
for (int c1 = 0; c1 < C1w; c1++)
|
||||
memcpy(d + c1 * plane, win + c1 * 8, 8);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -348,19 +528,20 @@ public:
|
||||
int ninputs = (int)input_arrs.total();
|
||||
CV_Assert(ninputs >= 1);
|
||||
|
||||
Mat inp_storage;
|
||||
Mat inp_ref = input_arrs.getMat(0);
|
||||
|
||||
MatShape inpshape;
|
||||
if (float_input) {
|
||||
CV_Assert(inp_ref.type() == CV_32F);
|
||||
int C0 = getNetImpl(this)->defaultC0;
|
||||
quantizeInterleaveBlock(inp_ref, inp_storage, C0);
|
||||
MatShape nchw = inp_ref.shape();
|
||||
if (nchw.layout == DATA_LAYOUT_UNKNOWN)
|
||||
nchw.layout = DATA_LAYOUT_NCHW;
|
||||
inpshape = nchw.toLayout(DATA_LAYOUT_BLOCK, getNetImpl(this)->defaultC0);
|
||||
} else {
|
||||
CV_Assert(inp_ref.type() == CV_8SC1 || inp_ref.type() == CV_8UC1);
|
||||
inpshape = inp_ref.shape();
|
||||
}
|
||||
|
||||
const Mat& inp = float_input ? inp_storage : inp_ref;
|
||||
MatShape inpshape = inp.shape();
|
||||
CV_Assert(inpshape.layout == DATA_LAYOUT_BLOCK);
|
||||
CV_Assert(inp.type() == CV_8SC1 || inp.type() == CV_8UC1);
|
||||
|
||||
int C0 = inpshape.back();
|
||||
|
||||
@@ -402,7 +583,7 @@ public:
|
||||
MatShape outshape = convInferShape(inpshape, wshape0, emptyKernelShape,
|
||||
ngroups, strides, dilations,
|
||||
pads, auto_pad, ceil_mode);
|
||||
int outtype = inp.type();
|
||||
int outtype = float_input ? (inputIsU8 ? CV_8UC1 : CV_8SC1) : inp_ref.type();
|
||||
|
||||
int outkind = output_arrs.kind();
|
||||
Mat out;
|
||||
@@ -427,9 +608,23 @@ public:
|
||||
if (cs.depthwise) {
|
||||
cs.wshape = getWpackShapeInt8(wshape0, ngroups, C0);
|
||||
}
|
||||
useWindows = canUseWindowPacking(outshape, C0);
|
||||
if (useWindows)
|
||||
initWindowConv(outshape, C0);
|
||||
prevInpshape = inpshape;
|
||||
}
|
||||
|
||||
const Mat* inp = &inp_ref;
|
||||
if (useWindows) {
|
||||
packWindows(inp_ref, C0);
|
||||
inp = &windowInput;
|
||||
} else if (float_input) {
|
||||
quantizeInterleaveBlock(inp_ref, quantizedInput, C0);
|
||||
inp = &quantizedInput;
|
||||
}
|
||||
const Mat& wts = useWindows ? winWeights : weights;
|
||||
const Mat& wtsVNNI = useWindows ? winWeightsVNNI : weightsVNNI;
|
||||
|
||||
const int8_t* lutptr = !activationLUT.empty() ? activationLUT.ptr<int8_t>() : nullptr;
|
||||
|
||||
const int* effectiveBiasVNNI = inputIsU8
|
||||
@@ -440,9 +635,9 @@ public:
|
||||
? biasBaselineU8.ptr<int>()
|
||||
: biasInt32.ptr<int>();
|
||||
|
||||
convInt8Block(inp.data, resptr, out.data, cs,
|
||||
weights.data,
|
||||
weightsVNNI.empty() ? nullptr : weightsVNNI.data,
|
||||
convInt8Block(inp->data, resptr, out.data, useWindows ? winCs : cs,
|
||||
wts.data,
|
||||
wtsVNNI.empty() ? nullptr : wtsVNNI.data,
|
||||
baselineBias,
|
||||
effectiveBiasVNNI,
|
||||
outMultiplier.ptr<float>(),
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
// A broadcast repeats whole contiguous runs, so the index arithmetic belongs once per run
|
||||
// rather than once per element.
|
||||
static Mat broadcastExpand(const Mat& src, const MatShape& dstShape)
|
||||
{
|
||||
MatShape srcShape = src.shape();
|
||||
@@ -27,27 +29,53 @@ static Mat broadcastExpand(const Mat& src, const MatShape& dstShape)
|
||||
if (srcShape == dstShape)
|
||||
return src;
|
||||
|
||||
MatShape outShape = dstShape;
|
||||
Mat out(outShape, src.type());
|
||||
const int total = (int)out.total();
|
||||
Mat out(dstShape, src.type());
|
||||
if (out.empty())
|
||||
return out;
|
||||
|
||||
std::vector<int> dstStrides(ndst);
|
||||
{ int s = 1; for (int d = ndst-1; d >= 0; d--) { dstStrides[d] = s; s *= dstShape[d]; } }
|
||||
// Innermost axes either all repeat one element, or all match and copy a contiguous run.
|
||||
int d = ndst - 1;
|
||||
int64_t inner = 1;
|
||||
const bool repeat = srcShape[d] == 1 && dstShape[d] != 1;
|
||||
for (; d >= 0 && (repeat ? srcShape[d] == 1 : srcShape[d] == dstShape[d]); d--)
|
||||
inner *= dstShape[d];
|
||||
|
||||
std::vector<int> srcStrides(ndst, 0);
|
||||
{ int s = 1; for (int d = ndst-1; d >= 0; d--) {
|
||||
srcStrides[d] = (srcShape[d] > 1) ? s : 0; s *= srcShape[d]; } }
|
||||
// Axes outside that run whose source extent is 1 repeat the whole run, so it is built
|
||||
// once and doubled. Without this a block layout's C0 axis caps the run at C0 elements.
|
||||
int64_t rep = 1;
|
||||
for (; d >= 0 && srcShape[d] == 1; d--)
|
||||
rep *= dstShape[d];
|
||||
const int64_t block = inner * rep;
|
||||
|
||||
const int nouter = d + 1;
|
||||
std::vector<int64_t> srcStrides(ndst, 0), rowStrides(nouter);
|
||||
{ int64_t s = 1; for (int i = ndst-1; i >= 0; i--) {
|
||||
srcStrides[i] = (srcShape[i] > 1) ? s : 0; s *= srcShape[i]; } }
|
||||
{ int64_t s = 1; for (int i = nouter-1; i >= 0; i--) {
|
||||
rowStrides[i] = s; s *= dstShape[i]; } }
|
||||
|
||||
const int nrows = (int)((int64_t)out.total() / block);
|
||||
const uint8_t* sp = src.ptr<uint8_t>();
|
||||
uint8_t* dp = out.ptr<uint8_t>();
|
||||
for (int i = 0; i < total; i++) {
|
||||
int rem = i, srcIdx = 0;
|
||||
for (int d = 0; d < ndst; d++) {
|
||||
int coord = rem / dstStrides[d]; rem %= dstStrides[d];
|
||||
srcIdx += coord * srcStrides[d];
|
||||
|
||||
parallel_for_(Range(0, nrows), [&](const Range& r) {
|
||||
for (int i = r.start; i < r.end; i++) {
|
||||
int64_t off = 0;
|
||||
for (int k = 0; k < nouter; k++)
|
||||
off += (i / rowStrides[k] % dstShape[k]) * srcStrides[k];
|
||||
uint8_t* row = dp + i * block;
|
||||
if (repeat)
|
||||
std::memset(row, sp[off], (size_t)inner);
|
||||
else
|
||||
std::memcpy(row, sp + off, (size_t)inner);
|
||||
for (int64_t done = inner; done < block; ) {
|
||||
const int64_t n = std::min(done, block - done);
|
||||
std::memcpy(row + done, row, (size_t)n);
|
||||
done += n;
|
||||
}
|
||||
}
|
||||
dp[i] = sp[srcIdx];
|
||||
}
|
||||
}, (double)std::max(1, getNumThreads() * 4));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,8 @@ public:
|
||||
Mat dstMat = output[0].reshape(1, outerSize);
|
||||
Mat dstMatInt32 = Mat(shape(dstMat), CV_32S);
|
||||
|
||||
const int nstripes = outerSize <= 4 ? 1 : getNumThreads();
|
||||
const size_t nouts = (size_t)outerSize * weightsMat.rows;
|
||||
const int nstripes = (int)std::max<size_t>(1, std::min<size_t>(getNumThreads(), nouts / 16));
|
||||
runInt8Gemm(srcMat, weightsMat, biasMat, outputMultiplier, dstMatInt32, nstripes, output_zp);
|
||||
if (output_type == CV_8U) {
|
||||
dstMatInt32.convertTo(dstMat, output_type, 1, 128);
|
||||
|
||||
@@ -26,30 +26,31 @@ namespace dnn
|
||||
*/
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
template <typename _InpTp>
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((target("avx2")))
|
||||
#endif
|
||||
static void dequantizeLinearChunk_u8_f32_avx2(const uint8_t* src, float* dst,
|
||||
float scale, int zp, int64_t len)
|
||||
static void dequantizeLinearChunk_f32_avx2(const _InpTp* src, float* dst,
|
||||
float scale, int zp, int64_t len)
|
||||
{
|
||||
__m256 vscale = _mm256_set1_ps(scale);
|
||||
__m256i vzp = _mm256_set1_epi32(zp);
|
||||
int64_t j = 0;
|
||||
for (; j <= len - 8; j += 8) {
|
||||
__m128i raw = _mm_loadl_epi64((__m128i*)(src + j));
|
||||
__m256i vi = _mm256_cvtepu8_epi32(raw);
|
||||
vi = _mm256_sub_epi32(vi, vzp);
|
||||
__m256 vf = _mm256_cvtepi32_ps(vi);
|
||||
vf = _mm256_mul_ps(vf, vscale);
|
||||
_mm256_storeu_ps(dst + j, vf);
|
||||
__m128i raw = _mm_loadl_epi64((const __m128i*)(src + j));
|
||||
// Signed input must sign-extend, not zero-extend.
|
||||
__m256i vi = std::is_signed<_InpTp>::value ? _mm256_cvtepi8_epi32(raw)
|
||||
: _mm256_cvtepu8_epi32(raw);
|
||||
__m256 vf = _mm256_cvtepi32_ps(_mm256_sub_epi32(vi, vzp));
|
||||
_mm256_storeu_ps(dst + j, _mm256_mul_ps(vf, vscale));
|
||||
}
|
||||
for (; j < len; j++)
|
||||
dst[j] = (float)(src[j] - zp) * scale;
|
||||
dst[j] = (float)((int)src[j] - zp) * scale;
|
||||
}
|
||||
|
||||
static void dequantizeLinearFast_u8_f32_avx2(const uint8_t* inp, float* out,
|
||||
float scale, int zp,
|
||||
int64_t total)
|
||||
template <typename _InpTp>
|
||||
static void dequantizeLinearFast_f32_avx2(const _InpTp* inp, float* out,
|
||||
float scale, int zp, int64_t total)
|
||||
{
|
||||
const int64_t block = 1024;
|
||||
int64_t nblocks = (total + block - 1) / block;
|
||||
@@ -58,7 +59,7 @@ static void dequantizeLinearFast_u8_f32_avx2(const uint8_t* inp, float* out,
|
||||
for (int i = r.start; i < r.end; i++) {
|
||||
int64_t ofs = i * block;
|
||||
int64_t len = std::min(block, total - ofs);
|
||||
dequantizeLinearChunk_u8_f32_avx2(inp + ofs, out + ofs, scale, zp, len);
|
||||
dequantizeLinearChunk_f32_avx2(inp + ofs, out + ofs, scale, zp, len);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -290,14 +291,19 @@ static void dequantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp_,
|
||||
|
||||
// Fast path: per-tensor dequantization uint8→float with AVX2 + proper parallelism
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
if (block_size == 0 && sz_a == 1 && inptype == CV_8U && outtype == CV_32F && sctype == CV_32F
|
||||
&& checkHardwareSupport(CV_CPU_AVX2)) {
|
||||
if (block_size == 0 && sz_a == 1 && outtype == CV_32F && sctype == CV_32F
|
||||
&& (inptype == CV_8U || inptype == CV_8S) && checkHardwareSupport(CV_CPU_AVX2)) {
|
||||
float sc = reinterpret_cast<const float*>(scale.data)[0];
|
||||
int zpval = zp.empty() ? 0 : (int)reinterpret_cast<const uint8_t*>(zp.data)[0];
|
||||
int64_t total = nslices * slice_size;
|
||||
dequantizeLinearFast_u8_f32_avx2(reinterpret_cast<const uint8_t*>(inp.data),
|
||||
reinterpret_cast<float*>(out.data),
|
||||
sc, zpval, total);
|
||||
if (inptype == CV_8U) {
|
||||
int zpval = zp.empty() ? 0 : (int)reinterpret_cast<const uint8_t*>(zp.data)[0];
|
||||
dequantizeLinearFast_f32_avx2(reinterpret_cast<const uint8_t*>(inp.data),
|
||||
reinterpret_cast<float*>(out.data), sc, zpval, total);
|
||||
} else {
|
||||
int zpval = zp.empty() ? 0 : (int)reinterpret_cast<const int8_t*>(zp.data)[0];
|
||||
dequantizeLinearFast_f32_avx2(reinterpret_cast<const int8_t*>(inp.data),
|
||||
reinterpret_cast<float*>(out.data), sc, zpval, total);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -447,6 +453,31 @@ public:
|
||||
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
|
||||
}
|
||||
|
||||
// Block layout reorders channels, so only a scalar scale survives it unchanged;
|
||||
// a per-channel or blocked one would be indexed against the wrong axis.
|
||||
bool isPerTensor() const
|
||||
{
|
||||
if (block_size != 0 || this->inputs.size() < 2)
|
||||
return false;
|
||||
Net::Impl* netimpl_ = getNetImpl(this);
|
||||
return netimpl_ && netimpl_->isConstArg(this->inputs[1]) &&
|
||||
netimpl_->argTensor(this->inputs[1]).total() == 1;
|
||||
}
|
||||
|
||||
int getLayouts(const std::vector<DataLayout>& actualInputs,
|
||||
std::vector<DataLayout>& desiredInputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<DataLayout>& outputs) const CV_OVERRIDE
|
||||
{
|
||||
size_t ninputs = actualInputs.size();
|
||||
CV_Assert(ninputs >= 1u);
|
||||
desiredInputs.assign(ninputs, DATA_LAYOUT_UNKNOWN);
|
||||
const DataLayout keep = isPerTensor() ? actualInputs[0] : DATA_LAYOUT_UNKNOWN;
|
||||
desiredInputs[0] = keep;
|
||||
outputs.assign(requiredOutputs, keep);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape> &inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape> &outputs,
|
||||
|
||||
@@ -17,39 +17,52 @@ namespace cv
|
||||
namespace dnn
|
||||
{
|
||||
|
||||
// Fast path for per-tensor quantization: float → uint8 with AVX2
|
||||
// Fast path for per-tensor quantization: float -> uint8 or int8 with AVX2
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
template <typename _OutTp>
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((target("avx2")))
|
||||
#endif
|
||||
static void quantizeLinearChunk_f32_u8_avx2(const float* src, uint8_t* dst,
|
||||
float scale, float zp_f,
|
||||
int64_t len)
|
||||
static void quantizeLinearChunk_f32_avx2(const float* src, _OutTp* dst,
|
||||
float scale, int zp_i, int64_t len)
|
||||
{
|
||||
const int lo_i = (int)std::numeric_limits<_OutTp>::lowest();
|
||||
const int hi_i = (int)std::numeric_limits<_OutTp>::max();
|
||||
__m256 vscale = _mm256_set1_ps(scale);
|
||||
// The zero-point is integral. Round x/scale FIRST, then add the integer zp;
|
||||
// adding an odd zp before rounding would flip round-half-to-even ties
|
||||
int zp_i = cvRound(zp_f);
|
||||
// Clamp in float: a value outside int32 range converts to INT_MIN, which then
|
||||
// saturates to the wrong end. Bounds carry the zero point so it can be added after.
|
||||
__m256 vlo = _mm256_set1_ps((float)(lo_i - zp_i));
|
||||
__m256 vhi = _mm256_set1_ps((float)(hi_i - zp_i));
|
||||
__m256i vzp = _mm256_set1_epi32(zp_i);
|
||||
|
||||
int64_t j = 0;
|
||||
for (; j <= len - 8; j += 8) {
|
||||
__m256 v = _mm256_loadu_ps(src + j);
|
||||
__m256i vi = _mm256_cvtps_epi32(_mm256_div_ps(v, vscale)); // round half-to-even
|
||||
vi = _mm256_add_epi32(vi, vzp); // + integer zero-point
|
||||
__m128i lo = _mm256_castsi256_si128(vi);
|
||||
__m128i hi = _mm256_extracti128_si256(vi, 1);
|
||||
__m128i packed16 = _mm_packs_epi32(lo, hi);
|
||||
__m128i packed8 = _mm_packus_epi16(packed16, packed16); // saturates to [0,255]
|
||||
__m256 v = _mm256_div_ps(_mm256_loadu_ps(src + j), vscale);
|
||||
// max then min, so a NaN takes the second operand and lands inside the range.
|
||||
v = _mm256_min_ps(_mm256_max_ps(v, vlo), vhi);
|
||||
// Round before adding the zero point; an odd one would flip half-to-even ties.
|
||||
__m256i vi = _mm256_add_epi32(_mm256_cvtps_epi32(v), vzp);
|
||||
__m128i packed16 = _mm_packs_epi32(_mm256_castsi256_si128(vi),
|
||||
_mm256_extracti128_si256(vi, 1));
|
||||
__m128i packed8 = std::is_signed<_OutTp>::value
|
||||
? _mm_packs_epi16(packed16, packed16)
|
||||
: _mm_packus_epi16(packed16, packed16);
|
||||
_mm_storel_epi64((__m128i*)(dst + j), packed8);
|
||||
}
|
||||
for (; j < len; j++)
|
||||
dst[j] = saturate_cast<uint8_t>(cvRound(src[j] / scale) + zp_i);
|
||||
// Same clamp as the vector loop: out of range cvRound gives INT_MIN and the zero point
|
||||
// then overflows. A NaN fails both compares and lands on the low bound, as MAXPS does.
|
||||
const float flo = (float)(lo_i - zp_i), fhi = (float)(hi_i - zp_i);
|
||||
for (; j < len; j++) {
|
||||
float v = src[j] / scale;
|
||||
v = v > flo ? v : flo;
|
||||
v = v < fhi ? v : fhi;
|
||||
dst[j] = (_OutTp)(cvRound(v) + zp_i);
|
||||
}
|
||||
}
|
||||
|
||||
static void quantizeLinearFast_f32_u8_avx2(const float* inp, uint8_t* out,
|
||||
float scale, float zp_f,
|
||||
int64_t total)
|
||||
template <typename _OutTp>
|
||||
static void quantizeLinearFast_f32_avx2(const float* inp, _OutTp* out,
|
||||
float scale, int zp_i, int64_t total)
|
||||
{
|
||||
const int64_t block = 1024;
|
||||
int64_t nblocks = (total + block - 1) / block;
|
||||
@@ -58,7 +71,7 @@ static void quantizeLinearFast_f32_u8_avx2(const float* inp, uint8_t* out,
|
||||
for (int i = r.start; i < r.end; i++) {
|
||||
int64_t ofs = i * block;
|
||||
int64_t len = std::min(block, total - ofs);
|
||||
quantizeLinearChunk_f32_u8_avx2(inp + ofs, out + ofs, scale, zp_f, len);
|
||||
quantizeLinearChunk_f32_avx2(inp + ofs, out + ofs, scale, zp_i, len);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -286,16 +299,23 @@ static void quantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp,
|
||||
}
|
||||
}
|
||||
|
||||
// Fast path: per-tensor quantization float→uint8 with AVX2 + proper parallelism
|
||||
// Fast path: per-tensor quantization float to uint8 or int8 with AVX2 + parallelism
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
if (block_size == 0 && sz_a == 1 && inptype == CV_32F && outtype == CV_8U && sctype == CV_32F
|
||||
&& checkHardwareSupport(CV_CPU_AVX2)) {
|
||||
if (block_size == 0 && sz_a == 1 && inptype == CV_32F && sctype == CV_32F
|
||||
&& (outtype == CV_8U || outtype == CV_8S) && checkHardwareSupport(CV_CPU_AVX2)) {
|
||||
float scale_f32 = reinterpret_cast<const float*>(scale.data)[0];
|
||||
float zp_f = zp.empty() ? 0.f : (float)reinterpret_cast<const uint8_t*>(zp.data)[0];
|
||||
int64_t total = nslices * slice_size;
|
||||
quantizeLinearFast_f32_u8_avx2(reinterpret_cast<const float*>(inp.data),
|
||||
if (outtype == CV_8U) {
|
||||
int zp_i = zp.empty() ? 0 : (int)reinterpret_cast<const uint8_t*>(zp.data)[0];
|
||||
quantizeLinearFast_f32_avx2(reinterpret_cast<const float*>(inp.data),
|
||||
reinterpret_cast<uint8_t*>(out.data),
|
||||
scale_f32, zp_f, total);
|
||||
scale_f32, zp_i, total);
|
||||
} else {
|
||||
int zp_i = zp.empty() ? 0 : (int)reinterpret_cast<const int8_t*>(zp.data)[0];
|
||||
quantizeLinearFast_f32_avx2(reinterpret_cast<const float*>(inp.data),
|
||||
reinterpret_cast<int8_t*>(out.data),
|
||||
scale_f32, zp_i, total);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -430,6 +450,31 @@ public:
|
||||
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
|
||||
}
|
||||
|
||||
// Block layout reorders channels, so only a scalar scale survives it unchanged;
|
||||
// a per-channel or blocked one would be indexed against the wrong axis.
|
||||
bool isPerTensor() const
|
||||
{
|
||||
if (block_size != 0 || this->inputs.size() < 2)
|
||||
return false;
|
||||
Net::Impl* netimpl_ = getNetImpl(this);
|
||||
return netimpl_ && netimpl_->isConstArg(this->inputs[1]) &&
|
||||
netimpl_->argTensor(this->inputs[1]).total() == 1;
|
||||
}
|
||||
|
||||
int getLayouts(const std::vector<DataLayout>& actualInputs,
|
||||
std::vector<DataLayout>& desiredInputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<DataLayout>& outputs) const CV_OVERRIDE
|
||||
{
|
||||
size_t ninputs = actualInputs.size();
|
||||
CV_Assert(ninputs >= 1u);
|
||||
desiredInputs.assign(ninputs, DATA_LAYOUT_UNKNOWN);
|
||||
const DataLayout keep = isPerTensor() ? actualInputs[0] : DATA_LAYOUT_UNKNOWN;
|
||||
desiredInputs[0] = keep;
|
||||
outputs.assign(requiredOutputs, keep);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape> &inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape> &outputs,
|
||||
|
||||
@@ -2421,6 +2421,11 @@ TEST_P(Test_ONNX_layers, Quantized_Eltwise_Broadcast)
|
||||
testONNXModels("quantized_eltwise_broadcast");
|
||||
}
|
||||
|
||||
TEST_P(Test_ONNX_layers, Quantized_BatchNorm_Mul_Add)
|
||||
{
|
||||
testONNXModels("quantized_bn_mul_add");
|
||||
}
|
||||
|
||||
TEST_P(Test_ONNX_layers, Quantized_LeakyReLU)
|
||||
{
|
||||
testONNXModels("quantized_leaky_relu");
|
||||
|
||||
Reference in new issue
Block a user