mirror of
https://github.com/opencv/opencv.git
synced 2026-10-28 16:46:46 +08:00
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
566 lines
26 KiB
C++
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));
|
|
}
|
|
|
|
}}
|