Files
opencv/modules/dnn/src/layers/dequantizelinear_layer.cpp
T
Varun Jaiswal 56dabe2b19 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
2026-10-24 12:31:46 +03:00

566 lines
26 KiB
C++

// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (C) 2026, BigVision LLC, all rights reserved.
// Third party copyrights are property of their respective owners.
#include "../precomp.hpp"
#include "layers_common.hpp"
#include "../net_impl.hpp"
#include "../onnx/onnx_dtype_convert.hpp"
#if defined(__x86_64__) || defined(_M_X64)
#include <immintrin.h>
#endif
namespace cv
{
namespace dnn
{
/*
DequantizeLinear layer, as defined in ONNX specification:
https://onnx.ai/onnx/operators/onnx__DequantizeLinear.html
Opset's 10 to 25 are covered.
*/
#if defined(__x86_64__) || defined(_M_X64)
template <typename _InpTp>
#if defined(__GNUC__) || defined(__clang__)
__attribute__((target("avx2")))
#endif
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((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)((int)src[j] - zp) * scale;
}
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;
parallel_for_(Range(0, (int)nblocks), [&](const Range& r) {
for (int i = r.start; i < r.end; i++) {
int64_t ofs = i * block;
int64_t len = std::min(block, total - ofs);
dequantizeLinearChunk_f32_avx2(inp + ofs, out + ofs, scale, zp, len);
}
});
}
#endif
template <typename _InpTp, typename _ScaleTp, typename _OutTp>
static void dequantizeLinear(const _InpTp* inp_, const _ScaleTp* scale_,
const _InpTp* zp_, _OutTp* out_,
int64_t nslices, int sz_a_,
int64_t slice_size_, int block_size_)
{
int bsz_ = std::max(block_size_, 1);
int nblocks_per_axis = (sz_a_ + bsz_ - 1) / bsz_;
int64_t nmacro_blocks = nslices * nblocks_per_axis;
CV_Assert(nmacro_blocks <= (int64_t)INT_MAX);
parallel_for_(Range(0, (int)nmacro_blocks), [&](const Range& r) {
int sz_a = sz_a_;
int64_t slice_size = slice_size_;
int block_size = block_size_;
int delta = 0;
int64_t scale_step = block_size > 0 ? slice_size : 1;
int64_t zp_step = zp_ ? scale_step : 0;
for (int i = r.start; i < r.end; i += delta) {
int slice_idx = i / nblocks_per_axis;
int block_idx = i - slice_idx * nblocks_per_axis;
int64_t block_ofs, scale_ofs;
if (block_size > 0) {
delta = std::min(nblocks_per_axis - block_idx, r.end - i);
block_ofs = (slice_idx*sz_a + block_idx*block_size)*slice_size;
scale_ofs = (slice_idx*nblocks_per_axis + block_idx)*slice_size;
} else {
delta = std::min(sz_a - block_idx, r.end - i);
block_ofs = (slice_idx*sz_a + block_idx)*slice_size;
scale_ofs = block_idx;
}
const _InpTp* inp = inp_ + block_ofs;
const _InpTp* zp = zp_ ? zp_ + scale_ofs : nullptr;
const _ScaleTp* sc = scale_ + scale_ofs;
_OutTp* out = out_ + block_ofs;
if (slice_size > 1 && block_size > 0) {
// Blocked mode: each of `delta` blocks has `block_size` rows along the
// axis (clipped at sz_a), and within each row the scale/zp vary across
// the `slice_size` trailing positions. The same `slice_size` scales/zps
// are reused for every row inside one block.
for (int k = 0; k < delta; k++) {
int rows = std::min(block_size, sz_a - (block_idx + k)*block_size);
for (int b = 0; b < rows; b++) {
for (int64_t j = 0; j < slice_size; j++) {
float scval = (float)sc[j];
_InpTp zpval = zp ? zp[j] : (_InpTp)0;
out[j] = _OutTp((inp[j] - zpval)*scval);
}
inp += slice_size;
out += slice_size;
}
sc += scale_step;
if (zp) zp += zp_step;
}
} else if (slice_size > 1) {
for (int k = 0; k < delta; k++, inp += slice_size, out += slice_size,
sc += scale_step, zp += zp_step) {
float scval = (float)*sc;
_InpTp zpval = zp ? *zp : (_InpTp)0;
for (int64_t j = 0; j < slice_size; j++)
out[j] = _OutTp((inp[j] - zpval)*scval);
}
} else if (block_size > 0 ) {
int bsz = block_size;
for (int k = 0; k < delta; k++, inp += bsz, out += bsz) {
bsz = std::min(bsz, sz_a - (block_idx + k)*block_size);
float scval = (float)sc[k];
_InpTp zpval = zp ? zp[k] : (_InpTp)0;
for (int j = 0; j < bsz; j++)
out[j] = _OutTp((inp[j] - zpval)*scval);
}
sc += delta;
zp += zp ? delta : 0;
} else {
if (zp) {
for (int j = 0; j < delta; j++) {
float scval = (float)sc[j];
_InpTp zpval = zp[j];
out[j] = _OutTp((inp[j] - zpval)*scval);
}
} else {
for (int j = 0; j < delta; j++) {
float scval = (float)sc[j];
out[j] = _OutTp(inp[j]*scval);
}
}
inp += delta;
out += delta;
}
}
});
}
// Native FP8 bytes through core's decode table, bit-identical to the ONNX reference.
template <typename _ScaleTp, typename _OutTp>
static void dequantizeLinearFp8Native(const uchar* inp, const _ScaleTp* scale, const uchar* zp,
_OutTp* out, const float* fp8lut,
int64_t nslices, int sz_a, int64_t slice_size)
{
parallel_for_(Range(0, (int)nslices), [&](const Range& r) {
for (int slice = r.start; slice < r.end; slice++) {
size_t base = (size_t)slice * sz_a * slice_size;
for (int a = 0; a < sz_a; a++) {
float sc = (float)scale[a];
float zpv = zp ? fp8lut[zp[a]] : 0.f;
for (int64_t j = 0; j < slice_size; j++) {
size_t idx = base + (size_t)a * slice_size + j;
out[idx] = _OutTp((fp8lut[inp[idx]] - zpv) * sc);
}
}
}
});
}
// E5M2 has no native depth; already decoded to real values upstream, held in
// CV_16F, or in CV_32F once setInput has widened a graph input.
template <typename _InpTp, typename _ScaleTp, typename _OutTp>
static void dequantizeLinearFp8Wide(const _InpTp* inp, const _ScaleTp* scale, const _InpTp* zp,
_OutTp* out, int64_t nslices, int sz_a, int64_t slice_size)
{
parallel_for_(Range(0, (int)nslices), [&](const Range& r) {
for (int slice = r.start; slice < r.end; slice++) {
size_t base = (size_t)slice * sz_a * slice_size;
for (int a = 0; a < sz_a; a++) {
float sc = (float)scale[a];
float zpv = zp ? (float)zp[a] : 0.f;
for (int64_t j = 0; j < slice_size; j++) {
size_t idx = base + (size_t)a * slice_size + j;
out[idx] = _OutTp(((float)inp[idx] - zpv) * sc);
}
}
}
});
}
// Dequantize INT8/UINT8 to FP32/FP16; out must be preallocated
static void dequantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp_,
int axis, int block_size, Mat& out)
{
Mat scale = scale_;
Mat zp = zp_;
CV_Assert(inp.isContinuous());
CV_Assert(scale.isContinuous());
CV_Assert(out.isContinuous());
// E5M2 rides in CV_16F, but widenHalfConstants() may already have promoted a constant
// zero_point to CV_32F, so the two can reach here at different widths.
if (!zp.empty() && zp.type() != inp.type() &&
(inp.type() == CV_16F || inp.type() == CV_32F) &&
(zp.type() == CV_16F || zp.type() == CV_32F))
zp_.convertTo(zp, inp.type());
int inptype = inp.type();
int outtype = out.type();
int sctype = scale.type();
int zptype = zp.type();
MatShape inpshape = inp.shape();
MatShape scshape = scale.shape();
MatShape zpshape = zp.shape();
int i, ndims = inpshape.dims;
int64_t nslices = 1, slice_size = 1;
CV_Assert(inptype == CV_8U || inptype == CV_8S || inptype == CV_32S ||
inptype == CV_8F_E4M3FN || inptype == CV_8F_E4M3FNUZ ||
inptype == CV_16F || inptype == CV_32F);
CV_Assert(sctype == CV_32F || sctype == CV_16F);
CV_Assert(outtype == CV_32F || outtype == CV_16F);
if (inptype == CV_8F_E4M3FN || inptype == CV_8F_E4M3FNUZ ||
inptype == CV_16F || inptype == CV_32F)
CV_Assert(block_size == 0); // block-wise FP8 dequantization not yet supported
if (!zp.empty()) {
CV_Assert(zp.isContinuous());
CV_Assert(zptype == inptype);
// ONNX's own test data pairs a rank-0 scale with a [1] zero_point.
CV_Assert(zpshape == scshape || (zpshape.total() == 1 && scshape.total() == 1));
}
if (ndims > 0)
axis = normalize_axis(axis, ndims);
else
axis = 0;
for (i = 0; i < axis; i++)
nslices *= inpshape[i];
for (i = axis+1; i < ndims; i++)
slice_size *= inpshape[i];
int sz_a = ndims > 0 ? inpshape[axis] : 1;
if (block_size == 0) {
size_t sc_total = scshape.total();
CV_Assert(scale.dims <= 1);
CV_Assert(sc_total == 1 || sc_total == (size_t)sz_a);
// unroll the innermost loop if the scale's/zp's are the same
if (sc_total == 1) {
slice_size *= sz_a;
sz_a = 1;
}
// avoid FP16 => FP32 conversion for scale inside the innermost loop
if (sctype == CV_16F && slice_size == 1 && nslices > 1) {
Mat temp;
scale_.convertTo(temp, CV_32F);
scale = temp;
sctype = CV_32F;
}
} else {
CV_Assert(block_size > 0);
CV_Assert(scale.dims == ndims);
for (int i = 0; i < ndims; i++) {
int inp_i = inpshape[i];
int sc_i = scshape[i];
if (i == axis) {
CV_Assert((inp_i + block_size - 1)/block_size == sc_i);
} else {
CV_Assert(sc_i == inp_i);
}
}
}
// 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 && 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];
int64_t total = nslices * slice_size;
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
if (inptype == CV_8U && sctype == CV_32F && outtype == CV_32F)
dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
reinterpret_cast<const float*>(scale.data),
reinterpret_cast<const uint8_t*>(zp.data),
reinterpret_cast<float*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8U && sctype == CV_16F && outtype == CV_32F)
dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
reinterpret_cast<const hfloat*>(scale.data),
reinterpret_cast<const uint8_t*>(zp.data),
reinterpret_cast<float*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8U && sctype == CV_32F && outtype == CV_16F)
dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
reinterpret_cast<const float*>(scale.data),
reinterpret_cast<const uint8_t*>(zp.data),
reinterpret_cast<hfloat*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8U && sctype == CV_16F && outtype == CV_16F)
dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
reinterpret_cast<const hfloat*>(scale.data),
reinterpret_cast<const uint8_t*>(zp.data),
reinterpret_cast<hfloat*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8S && sctype == CV_32F && outtype == CV_32F)
dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
reinterpret_cast<const float*>(scale.data),
reinterpret_cast<const int8_t*>(zp.data),
reinterpret_cast<float*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8S && sctype == CV_16F && outtype == CV_32F)
dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
reinterpret_cast<const hfloat*>(scale.data),
reinterpret_cast<const int8_t*>(zp.data),
reinterpret_cast<float*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8S && sctype == CV_32F && outtype == CV_16F)
dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
reinterpret_cast<const float*>(scale.data),
reinterpret_cast<const int8_t*>(zp.data),
reinterpret_cast<hfloat*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8S && sctype == CV_16F && outtype == CV_16F)
dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
reinterpret_cast<const hfloat*>(scale.data),
reinterpret_cast<const int8_t*>(zp.data),
reinterpret_cast<hfloat*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_32S && sctype == CV_32F && outtype == CV_32F)
dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
reinterpret_cast<const float*>(scale.data),
reinterpret_cast<const int32_t*>(zp.data),
reinterpret_cast<float*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_32S && sctype == CV_16F && outtype == CV_32F)
dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
reinterpret_cast<const hfloat*>(scale.data),
reinterpret_cast<const int32_t*>(zp.data),
reinterpret_cast<float*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_32S && sctype == CV_32F && outtype == CV_16F)
dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
reinterpret_cast<const float*>(scale.data),
reinterpret_cast<const int32_t*>(zp.data),
reinterpret_cast<hfloat*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_32S && sctype == CV_16F && outtype == CV_16F)
dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
reinterpret_cast<const hfloat*>(scale.data),
reinterpret_cast<const int32_t*>(zp.data),
reinterpret_cast<hfloat*>(out.data),
nslices, sz_a, slice_size, block_size);
else if (inptype == CV_8F_E4M3FN || inptype == CV_8F_E4M3FNUZ) {
const float* fp8lut = inptype == CV_8F_E4M3FN ? fp8_t::decodeLUT() : fp8a_t::decodeLUT();
const uchar* zpdata = zp.empty() ? nullptr : reinterpret_cast<const uchar*>(zp.data);
if (sctype == CV_32F && outtype == CV_32F)
dequantizeLinearFp8Native(reinterpret_cast<const uchar*>(inp.data), reinterpret_cast<const float*>(scale.data),
zpdata, reinterpret_cast<float*>(out.data), fp8lut, nslices, sz_a, slice_size);
else if (sctype == CV_16F && outtype == CV_32F)
dequantizeLinearFp8Native(reinterpret_cast<const uchar*>(inp.data), reinterpret_cast<const hfloat*>(scale.data),
zpdata, reinterpret_cast<float*>(out.data), fp8lut, nslices, sz_a, slice_size);
else if (sctype == CV_32F && outtype == CV_16F)
dequantizeLinearFp8Native(reinterpret_cast<const uchar*>(inp.data), reinterpret_cast<const float*>(scale.data),
zpdata, reinterpret_cast<hfloat*>(out.data), fp8lut, nslices, sz_a, slice_size);
else
dequantizeLinearFp8Native(reinterpret_cast<const uchar*>(inp.data), reinterpret_cast<const hfloat*>(scale.data),
zpdata, reinterpret_cast<hfloat*>(out.data), fp8lut, nslices, sz_a, slice_size);
}
else if (inptype == CV_16F) {
const hfloat* zpdata = zp.empty() ? nullptr : reinterpret_cast<const hfloat*>(zp.data);
if (sctype == CV_32F && outtype == CV_32F)
dequantizeLinearFp8Wide(reinterpret_cast<const hfloat*>(inp.data), reinterpret_cast<const float*>(scale.data),
zpdata, reinterpret_cast<float*>(out.data), nslices, sz_a, slice_size);
else if (sctype == CV_16F && outtype == CV_32F)
dequantizeLinearFp8Wide(reinterpret_cast<const hfloat*>(inp.data), reinterpret_cast<const hfloat*>(scale.data),
zpdata, reinterpret_cast<float*>(out.data), nslices, sz_a, slice_size);
else if (sctype == CV_32F && outtype == CV_16F)
dequantizeLinearFp8Wide(reinterpret_cast<const hfloat*>(inp.data), reinterpret_cast<const float*>(scale.data),
zpdata, reinterpret_cast<hfloat*>(out.data), nslices, sz_a, slice_size);
else
dequantizeLinearFp8Wide(reinterpret_cast<const hfloat*>(inp.data), reinterpret_cast<const hfloat*>(scale.data),
zpdata, reinterpret_cast<hfloat*>(out.data), nslices, sz_a, slice_size);
}
else if (inptype == CV_32F) {
const float* zpdata = zp.empty() ? nullptr : reinterpret_cast<const float*>(zp.data);
if (sctype == CV_32F && outtype == CV_32F)
dequantizeLinearFp8Wide(reinterpret_cast<const float*>(inp.data), reinterpret_cast<const float*>(scale.data),
zpdata, reinterpret_cast<float*>(out.data), nslices, sz_a, slice_size);
else if (sctype == CV_16F && outtype == CV_32F)
dequantizeLinearFp8Wide(reinterpret_cast<const float*>(inp.data), reinterpret_cast<const hfloat*>(scale.data),
zpdata, reinterpret_cast<float*>(out.data), nslices, sz_a, slice_size);
else if (sctype == CV_32F && outtype == CV_16F)
dequantizeLinearFp8Wide(reinterpret_cast<const float*>(inp.data), reinterpret_cast<const float*>(scale.data),
zpdata, reinterpret_cast<hfloat*>(out.data), nslices, sz_a, slice_size);
else
dequantizeLinearFp8Wide(reinterpret_cast<const float*>(inp.data), reinterpret_cast<const hfloat*>(scale.data),
zpdata, reinterpret_cast<hfloat*>(out.data), nslices, sz_a, slice_size);
}
else {
CV_Error_(Error::StsNotImplemented,
("the following combination of types is not supported in "
"DequantizeLinear: inp=%s, scale=%s, out=%s",
typeToString(inptype).c_str(),
typeToString(sctype).c_str(),
typeToString(outtype).c_str()));
}
}
class DequantizeLinearLayerImpl CV_FINAL : public DequantizeLinearLayer
{
public:
DequantizeLinearLayerImpl(const LayerParams& params)
{
setParamsFrom(params);
axis = params.get<int>("axis", 1);
block_size = params.get<int>("block_size", 0);
CV_Assert(block_size >= 0);
}
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
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,
std::vector<MatShape> &internals) const CV_OVERRIDE
{
size_t ninputs = inputs.size();
CV_Assert(2 <= ninputs && ninputs <= 3);
CV_Assert(requiredOutputs == 1);
outputs.assign(1, inputs[0]);
return true;
}
int getOutType() const
{
Net::Impl* netimpl_ = getNetImpl(this);
return netimpl_->enableFP16 ? CV_16F : CV_32F;
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>& outputs,
std::vector<MatType>& internals) const CV_OVERRIDE
{
size_t ninputs = inputs.size();
CV_Assert(2 <= ninputs && ninputs <= 3);
if (ninputs == 3) {
// Both widths are accepted on the E5M2 pair; see dequantizeLinear() above.
const bool wideFp8Pair = (inputs[0] == CV_16F || inputs[0] == CV_32F) &&
(inputs[2] == CV_16F || inputs[2] == CV_32F);
CV_Assert(inputs[0] == inputs[2] || wideFp8Pair);
}
outputs.assign(1, getOutType());
}
virtual void finalize(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr) CV_OVERRIDE
{
}
void forward(InputArrayOfArrays inputs_arr,
OutputArrayOfArrays outputs_arr,
OutputArrayOfArrays) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
int ninputs = inputs_arr.size(-1).area();
CV_Assert(2 <= ninputs && ninputs <= 3);
Mat inp = inputs_arr.getMat(0);
Mat scale = inputs_arr.getMat(1);
Mat zeropoint;
int outtype = getOutType();
MatShape inpshape = inp.shape();
if (ninputs >= 3) {
zeropoint = inputs_arr.getMat(2);
}
auto kind = outputs_arr.kind();
if (kind == _InputArray::STD_VECTOR_MAT) {
std::vector<Mat>& outs = outputs_arr.getMatVecRef();
outs.resize(1);
outs[0].fit(inpshape, outtype);
dequantizeLinear(inp, scale, zeropoint, axis, block_size, outs[0]);
} else if (kind == _InputArray::STD_VECTOR_UMAT) {
std::vector<UMat>& outs = outputs_arr.getUMatVecRef();
outs.resize(1);
outs[0].fit(inpshape, outtype);
Mat temp(inpshape, outtype);
dequantizeLinear(inp, scale, zeropoint, axis, block_size, temp);
temp.copyTo(outs[0]);
} else {
CV_Error(Error::StsNotImplemented, "");
}
}
};
Ptr<DequantizeLinearLayer> DequantizeLinearLayer::create(const LayerParams& params)
{
return Ptr<DequantizeLinearLayer>(new DequantizeLinearLayerImpl(params));
}
}}