1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 07:43:03 +04:00

Merge pull request #28811 from abhishek-gola:qlinear_support

Added QLinear layer support #28811

closes: https://github.com/opencv/opencv/issues/26310

### 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
- [x] 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:
Abhishek Gola
2026-04-28 20:08:29 +05:30
committed by GitHub
parent a17db08c21
commit c83b86eb57
32 changed files with 1341 additions and 735 deletions
@@ -382,10 +382,28 @@ CV__DNN_INLINE_NS_BEGIN
bool ceil_mode;
};
struct Conv2Int8Params
{
String name;
std::vector<int> strides, dilations, pads;
int ngroups = 1;
AutoPadding auto_pad = AUTO_PAD_NONE;
bool ceil_mode = false;
float input_sc = 1.f;
int input_zp = 0;
float output_sc = 1.f;
int output_zp = 0;
bool per_channel = true;
bool input_is_u8 = false;
// blobs[0] = quantized weights, blobs[1] = fused bias, blobs[2] = output multiplier
Mat weights, bias, outputMultiplier;
};
class CV_EXPORTS Conv2Int8Layer : public Layer
{
public:
static Ptr<Conv2Int8Layer> create(const LayerParams& params);
static Ptr<Conv2Int8Layer> create(const Conv2Int8Params& params);
int input_zp, output_zp;
float input_sc, output_sc;
@@ -487,6 +505,21 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<PoolingLayerInt8> create(const LayerParams& params);
};
struct Pool2Int8Params
{
String name;
std::vector<int> kernel_shape, strides, dilations, pads;
AutoPadding auto_pad = AUTO_PAD_NONE;
bool ceil_mode = false;
bool is_global_pooling = false;
bool is_max_pool = true;
bool count_include_pad = false;
float input_sc = 1.f;
int input_zp = 0;
float output_sc = 1.f;
int output_zp = 0;
};
// New-engine int8 pooling with block memory layout (DATA_LAYOUT_BLOCK).
// Created by the QDQ graph fusion pass (graph_fusion_qdq.cpp) when it
// detects a DequantizeLinear -> Pooling -> QuantizeLinear pattern.
@@ -495,6 +528,7 @@ CV__DNN_INLINE_NS_BEGIN
{
public:
static Ptr<Pool2Int8Layer> create(const LayerParams& params);
static Ptr<Pool2Int8Layer> create(const Pool2Int8Params& params);
int input_zp, output_zp;
float input_sc, output_sc;
@@ -591,6 +625,21 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<InnerProductLayer> create(const LayerParams& params);
};
struct InnerProductInt8Params
{
String name;
int axis = 1;
int num_output = 0;
float input_sc = 1.f;
int input_zp = 0;
float output_sc = 1.f;
int output_zp = 0;
int output_type = CV_8S;
bool per_channel = true;
// blobs[0] = weights, blobs[1] = bias, blobs[2] = output multiplier
Mat weights, bias, outputMultiplier;
};
class CV_EXPORTS InnerProductLayerInt8 : public InnerProductLayer
{
public:
@@ -602,6 +651,7 @@ CV__DNN_INLINE_NS_BEGIN
// of per-Channel quantization. Otherwise, that means this layer contains per-Tensor quantized parameters.
bool per_channel;
static Ptr<InnerProductLayerInt8> create(const LayerParams& params);
static Ptr<InnerProductLayerInt8> create(const InnerProductInt8Params& params);
};
class CV_EXPORTS MVNLayer : public Layer
@@ -1239,6 +1289,17 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<ThresholdedReluLayer> create(const LayerParams &params);
};
struct ActivationInt8Params
{
String name;
String activationType; // "ReLUInt8", "SigmoidInt8", etc.
float input_sc = 1.f;
int input_zp = 0;
float output_sc = 1.f;
int output_zp = 0;
Mat activationLUT;
};
class CV_EXPORTS ActivationLayerInt8 : public ActivationLayer
{
public:
@@ -1247,6 +1308,7 @@ CV__DNN_INLINE_NS_BEGIN
Mat activationLUT;
static Ptr<ActivationLayerInt8> create(const LayerParams &params);
static Ptr<ActivationLayerInt8> create(const ActivationInt8Params &params);
};
class CV_EXPORTS SignLayer : public ActivationLayer
@@ -1303,10 +1365,21 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<EltwiseLayerInt8> create(const LayerParams &params);
};
struct Eltwise2Int8Params
{
String name;
std::vector<float> input_scales;
std::vector<int> input_zeropoints;
float output_sc = 1.f;
int output_zp = 0;
bool with_relu = false;
};
class CV_EXPORTS Eltwise2Int8Layer : public Layer
{
public:
static Ptr<Eltwise2Int8Layer> create(const LayerParams& params);
static Ptr<Eltwise2Int8Layer> create(const Eltwise2Int8Params& params);
std::vector<float> scales;
std::vector<int> zeropoints;
@@ -1722,6 +1795,30 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<MatMulLayer> create(const LayerParams &params);
};
struct MatMulInt8Params
{
String name;
int num_output = 0;
int inp_dims = 2;
float input_sc = 1.f;
int input_zp = 0;
float output_sc = 1.f;
int output_zp = 0;
int output_type = CV_8S;
bool per_channel = true;
Mat weights, bias, outputMultiplier;
};
class CV_EXPORTS MatMulInt8Layer : public MatMulLayer
{
public:
int input_zp, output_zp;
float input_sc, output_sc;
int output_type;
bool per_channel;
static Ptr<MatMulInt8Layer> create(const MatMulInt8Params& params);
};
class CV_EXPORTS ExpandLayer : public Layer
{
public:
+7
View File
@@ -495,6 +495,13 @@ CV__DNN_INLINE_NS_BEGIN
// so that forward() can assume that the outputs are already allocated.
virtual bool dynamicOutputShapes() const;
// returns true if the layer only rearranges data without changing values.
// Examples: Flatten, Reshape, Transpose, Permute, Squeeze, Unsqueeze,
// Concat, Split, Slice, Tile, MaxPool.
// Used by QDQ fusion to elide redundant dequantize-quantize pairs
// when the scale and zero point are the same.
virtual bool isDataShuffling() const;
// dumps attributes of the layer (e.g. strides, dilations in Convolution, MaxPool)
virtual std::ostream& dumpAttrs(std::ostream& strm, int indent) const;
+473 -215
View File
@@ -78,6 +78,17 @@ struct ModelFusionQDQ
{ p->output_sc = sc; p->output_zp = zp; }
}
bool areDqArgsConst(const DequantizeLinearLayer* dq) const
{
if (!dq || dq->inputs.size() < 2)
return false;
if (!netimpl->isConstArg(dq->inputs[1]))
return false;
if (dq->inputs.size() >= 3 && !netimpl->isConstArg(dq->inputs[2]))
return false;
return true;
}
template<typename LayerT>
bool getQdqPatternContext(Layer* layer_ptr,
size_t ninputs,
@@ -96,6 +107,8 @@ struct ModelFusionQDQ
q_data_in = inputs[0];
out_scale = inputs[1];
out_zp = inputs[2];
if (!netimpl->isConstArg(out_scale) || !netimpl->isConstArg(out_zp))
return false;
mid_layer_idx = producer_of.at(q_data_in.idx);
mid_layer = getLayer<LayerT>(newprog, mid_layer_idx);
return mid_layer != 0;
@@ -152,7 +165,8 @@ struct ModelFusionQDQ
getLayer<DequantizeLinearLayer>(newprog, dq_idx);
if (!dq || dq->inputs.size() < 3 ||
usecounts.at(add_inp.idx) != 1) {
usecounts.at(add_inp.idx) != 1 ||
!areDqArgsConst(dq)) {
break;
}
dq_ptrs.push_back(dq);
@@ -184,27 +198,25 @@ struct ModelFusionQDQ
? (int)elt_out_zp_m.at<uint8_t>(0)
: (int)elt_out_zp_m.at<int8_t>(0);
LayerParams eltwiseParams = makeLayerParamsFromOriginal(add, "Eltwise2Int8");
eltwiseParams.blobs.clear();
eltwiseParams.set("input_scales", DictValue::arrayReal(in_scales.data(), (int)in_scales.size()));
eltwiseParams.set("input_zeropoints", DictValue::arrayInt(in_zps.data(), (int)in_zps.size()));
eltwiseParams.set("scales", out_scale_val);
eltwiseParams.set("zeropoints", out_zp_val);
Ptr<Layer> eltwiseInt8 = createFusedLayer(eltwiseParams);
if (!eltwiseInt8.empty()) {
CV_Assert(dynamic_cast<Eltwise2Int8Layer*>(eltwiseInt8.get()));
fused_layer_idx = add_layer_idx;
newprog[add_layer_idx] = eltwiseInt8;
fused_inputs.swap(int8_inputs);
removed_args.push_back(q_data_in); // float add_out
for (const Arg& add_inp : add->inputs)
removed_args.push_back(add_inp);
Eltwise2Int8Params ep;
ep.name = add->name;
ep.input_scales = in_scales;
ep.input_zeropoints = in_zps;
ep.output_sc = out_scale_val;
ep.output_zp = out_zp_val;
Ptr<Eltwise2Int8Layer> eltwiseInt8 = Eltwise2Int8Layer::create(ep);
eltwiseInt8->netimpl = netimpl;
fused_layer_idx = add_layer_idx;
newprog[add_layer_idx] = eltwiseInt8;
fused_inputs.swap(int8_inputs);
removed_args.push_back(q_data_in); // float add_out
for (const Arg& add_inp : add->inputs)
removed_args.push_back(add_inp);
for (int dq_prog_idx : dq_prog_indices)
newprog[dq_prog_idx] = Ptr<Layer>();
for (int dq_prog_idx : dq_prog_indices)
newprog[dq_prog_idx] = Ptr<Layer>();
break;
}
break;
}
}
@@ -222,6 +234,7 @@ struct ModelFusionQDQ
const int relu_in_type = (dq && !dq->inputs.empty()) ? netimpl->argData(dq->inputs[0]).type : -1;
const bool relu_in_int8 = (relu_in_type == CV_8S || relu_in_type == CV_8U);
if (dq && dq->inputs.size() >= 3 &&
areDqArgsConst(dq) &&
relu_in_int8 && relu_out_int8 &&
usecounts.at(relu_in.idx) == 1) {
const float inp_sc = netimpl->argTensor(dq->inputs[1]).at<float>(0);
@@ -256,25 +269,23 @@ struct ModelFusionQDQ
}
}
LayerParams reluInt8Params = makeLayerParamsFromOriginal(relu, "ReLUInt8");
reluInt8Params.blobs.clear();
Ptr<Layer> reluInt8 = createFusedLayer(reluInt8Params);
if (!reluInt8.empty()) {
auto* reluInt8Layer = dynamic_cast<ActivationLayerInt8*>(reluInt8.get());
CV_Assert(reluInt8Layer);
reluInt8Layer->input_sc = inp_sc;
reluInt8Layer->input_zp = inp_zp;
reluInt8Layer->output_sc = out_sc;
reluInt8Layer->output_zp = out_zp_i;
reluInt8Layer->activationLUT = lookUpTable;
fused_layer_idx = relu_layer_idx;
newprog[relu_layer_idx] = reluInt8;
fused_inputs.assign(1, dq->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(relu_in);
newprog[dq_idx] = Ptr<Layer>();
break;
}
ActivationInt8Params ap;
ap.name = relu->name;
ap.activationType = "ReLUInt8";
ap.input_sc = inp_sc;
ap.input_zp = inp_zp;
ap.output_sc = out_sc;
ap.output_zp = out_zp_i;
ap.activationLUT = lookUpTable;
Ptr<ActivationLayerInt8> reluInt8 = ActivationLayerInt8::create(ap);
reluInt8->netimpl = netimpl;
fused_layer_idx = relu_layer_idx;
newprog[relu_layer_idx] = reluInt8;
fused_inputs.assign(1, dq->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(relu_in);
newprog[dq_idx] = Ptr<Layer>();
break;
}
}
}
@@ -299,8 +310,7 @@ struct ModelFusionQDQ
const Arg& add_inp = add2->inputs[k];
int dq_idx2 = producer_of.at(add_inp.idx);
DequantizeLinearLayer* dq2 = getLayer<DequantizeLinearLayer>(newprog, dq_idx2);
if (dq2 && dq2->inputs.size() >= 3) {
if (dq2 && dq2->inputs.size() >= 3 && areDqArgsConst(dq2)) {
dq_ptrs2.push_back(dq2);
dq_prog_indices2.push_back(dq_idx2);
int8_inputs2.push_back(dq2->inputs[0]);
@@ -326,7 +336,7 @@ struct ModelFusionQDQ
}
int elt_out_type2 = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
if (elt_out_type2 < 0) {
if (elt_out_type2 < 0 && netimpl->isConstArg(inputs[2])) {
const Mat& zp_tensor = netimpl->argTensor(inputs[2]);
elt_out_type2 = !zp_tensor.empty() ? zp_tensor.type() : CV_8S;
}
@@ -373,41 +383,38 @@ struct ModelFusionQDQ
: (int)out_zp_m2.at<int8_t>(0);
if (out_sc2 > 0.f) {
int relu_out_uc = usecounts.at(q_inp.idx);
LayerParams eltParams = makeLayerParamsFromOriginal(add2, "Eltwise2Int8");
eltParams.blobs.clear();
eltParams.set("input_scales", DictValue::arrayReal(in_scales2.data(), (int)in_scales2.size()));
eltParams.set("input_zeropoints", DictValue::arrayInt(in_zps2.data(), (int)in_zps2.size()));
eltParams.set("scales", out_sc2);
eltParams.set("zeropoints", out_zp_val2);
eltParams.set("with_relu", true);
Ptr<Layer> eltInt8 = createFusedLayer(eltParams);
if (!eltInt8.empty()) {
CV_Assert(dynamic_cast<Eltwise2Int8Layer*>(eltInt8.get()));
Eltwise2Int8Params ep2;
ep2.name = add2->name;
ep2.input_scales = in_scales2;
ep2.input_zeropoints = in_zps2;
ep2.output_sc = out_sc2;
ep2.output_zp = out_zp_val2;
ep2.with_relu = true;
Ptr<Eltwise2Int8Layer> eltInt8 = Eltwise2Int8Layer::create(ep2);
eltInt8->netimpl = netimpl;
fused_inputs = int8_inputs2;
if (relu_out_uc <= 1) {
fused_layer_idx = add_idx2;
newprog[add_idx2] = eltInt8;
newprog[relu_layer_idx2] = Ptr<Layer>();
removed_args.push_back(q_inp); // relu_out
removed_args.push_back(relu_in2); // add_out
for (size_t dk = 0; dk < add2->inputs.size(); dk++) {
removed_args.push_back(add2->inputs[dk]);
}
for (int dq_prog_idx : dq_prog_indices2) {
if (dq_prog_idx >= 0)
newprog[dq_prog_idx] = Ptr<Layer>();
}
} else {
int new_idx = (int)newprog.size();
newprog.push_back(eltInt8);
fused_layer_idx = new_idx;
usecounts.at(q_inp.idx) -= 1;
Eltwise2Int8Layer* elt2ptr = dynamic_cast<Eltwise2Int8Layer*>(eltInt8.get());
relu_to_eltwise[q_inp.idx] = {outputs[0], elt2ptr};
fused_inputs = int8_inputs2;
if (relu_out_uc <= 1) {
fused_layer_idx = add_idx2;
newprog[add_idx2] = eltInt8;
newprog[relu_layer_idx2] = Ptr<Layer>();
removed_args.push_back(q_inp); // relu_out
removed_args.push_back(relu_in2); // add_out
for (size_t dk = 0; dk < add2->inputs.size(); dk++) {
removed_args.push_back(add2->inputs[dk]);
}
break;
for (int dq_prog_idx : dq_prog_indices2) {
if (dq_prog_idx >= 0)
newprog[dq_prog_idx] = Ptr<Layer>();
}
} else {
int new_idx = (int)newprog.size();
newprog.push_back(eltInt8);
fused_layer_idx = new_idx;
usecounts.at(q_inp.idx) -= 1;
relu_to_eltwise[q_inp.idx] = {outputs[0], eltInt8.get()};
}
break;
}
}
}
@@ -431,6 +438,8 @@ struct ModelFusionQDQ
if (dq_x && dq_w &&
dq_x->inputs.size() >= 3 && dq_w->inputs.size() >= 3 &&
areDqArgsConst(dq_x) && areDqArgsConst(dq_w) &&
netimpl->isConstArg(dq_w->inputs[0]) &&
usecounts.at(conv_w.idx) == 1) {
float inp_sc = netimpl->argTensor(dq_x->inputs[1]).at<float>(0);
float out_sc = netimpl->argTensor(out_scale_arg).at<float>(0);
@@ -533,48 +542,41 @@ struct ModelFusionQDQ
outputMultiplier.at<float>(oc) = (inp_sc * wt_sc.at<float>(oc)) / out_sc;
}
LayerParams convInt8Params = makeLayerParamsFromOriginal(conv, "Conv2Int8");
{
if (!conv->strides.empty())
convInt8Params.set("stride", DictValue::arrayInt(conv->strides.data(), (int)conv->strides.size()));
if (!conv->dilations.empty())
convInt8Params.set("dilation", DictValue::arrayInt(conv->dilations.data(), (int)conv->dilations.size()));
if (!conv->pads.empty())
convInt8Params.set("pad", DictValue::arrayInt(conv->pads.data(), (int)conv->pads.size()));
Conv2Int8Params cp;
cp.name = conv->name;
cp.strides = conv->strides;
cp.dilations = conv->dilations;
cp.pads = conv->pads;
cp.ngroups = conv->ngroups;
cp.auto_pad = conv->auto_pad;
cp.ceil_mode = conv->ceil_mode;
cp.input_sc = inp_sc;
cp.input_zp = inp_zp;
cp.output_sc = out_sc;
cp.output_zp = out_zp;
cp.per_channel = per_channel;
cp.input_is_u8 = inputIsU8;
cp.weights = w_q;
cp.bias = biasFused;
cp.outputMultiplier = outputMultiplier;
Ptr<Conv2Int8Layer> convInt8 = Conv2Int8Layer::create(cp);
convInt8->netimpl = netimpl;
fused_layer_idx = conv_layer_idx;
newprog[conv_layer_idx] = convInt8;
fused_inputs.assign(1, dq_x->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(conv_w);
if (conv->inputs.size() == 3) {
removed_args.push_back(conv->inputs[2]);
if (dq_bias_idx >= 0)
newprog[dq_bias_idx] = Ptr<Layer>();
}
convInt8Params.set("num_output", outCn);
convInt8Params.set("group", conv->ngroups);
convInt8Params.set("input_scale", inp_sc);
convInt8Params.set("input_zeropoint", inp_zp);
convInt8Params.set("scales", out_sc);
convInt8Params.set("zeropoints", out_zp);
convInt8Params.set("per_channel", per_channel);
convInt8Params.set("input_is_u8", inputIsU8);
convInt8Params.blobs.resize(3);
convInt8Params.blobs[0] = w_q;
convInt8Params.blobs[1] = biasFused;
convInt8Params.blobs[2] = outputMultiplier;
Ptr<Layer> convInt8 = createFusedLayer(convInt8Params);
if (!convInt8.empty()) {
auto* convInt8Layer = dynamic_cast<Conv2Int8Layer*>(convInt8.get());
CV_Assert(convInt8Layer);
fused_layer_idx = conv_layer_idx;
newprog[conv_layer_idx] = convInt8;
fused_inputs.assign(1, dq_x->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(conv_w);
if (conv->inputs.size() == 3) {
removed_args.push_back(conv->inputs[2]);
if (dq_bias_idx >= 0)
newprog[dq_bias_idx] = Ptr<Layer>();
}
if (usecounts.at(conv_x.idx) == 1) {
removed_args.push_back(conv_x);
newprog[dq_x_idx] = Ptr<Layer>();
}
newprog[dq_w_idx] = Ptr<Layer>();
break;
if (usecounts.at(conv_x.idx) == 1) {
removed_args.push_back(conv_x);
newprog[dq_x_idx] = Ptr<Layer>();
}
newprog[dq_w_idx] = Ptr<Layer>();
break;
}
}
}
@@ -595,19 +597,22 @@ struct ModelFusionQDQ
float inp_sc = 0.f, out_sc = 0.f;
int inp_zp = 0, out_zp_i = 0;
int fc_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
if (fc_out_type < 0) {
if (fc_out_type < 0 && netimpl->isConstArg(inputs[2])) {
const Mat& zp_t = netimpl->argTensor(inputs[2]);
fc_out_type = !zp_t.empty() ? zp_t.type() : CV_8S;
}
const bool fc_out_int8 = (fc_out_type == CV_8S || fc_out_type == CV_8U);
int fc_in_type = (dq_x && !dq_x->inputs.empty()) ? netimpl->argData(dq_x->inputs[0]).type : -1;
if (fc_in_type < 0 && dq_x && dq_x->inputs.size() >= 3) {
if (fc_in_type < 0 && dq_x && dq_x->inputs.size() >= 3 &&
netimpl->isConstArg(dq_x->inputs[2])) {
const Mat& zp_t = netimpl->argTensor(dq_x->inputs[2]);
fc_in_type = !zp_t.empty() ? zp_t.type() : CV_8S;
}
const bool fc_in_int8 = (fc_in_type == CV_8S || fc_in_type == CV_8U);
if (dq_x && dq_w &&
dq_x->inputs.size() >= 3 && dq_w->inputs.size() >= 3 &&
areDqArgsConst(dq_x) && areDqArgsConst(dq_w) &&
netimpl->isConstArg(dq_w->inputs[0]) &&
fc_in_int8 && fc_out_int8 &&
usecounts.at(mm_x.idx) == 1 && usecounts.at(mm_w.idx) == 1) {
inp_sc = netimpl->argTensor(dq_x->inputs[1]).at<float>(0);
@@ -619,7 +624,7 @@ struct ModelFusionQDQ
out_sc = netimpl->argTensor(out_scale).at<float>(0);
const Mat& fc_out_zp_m = netimpl->argTensor(out_zp);
out_zp_i = fc_out_zp_m.depth() == CV_8U
? (int)fc_out_zp_m.at<uint8_t>(0)
? (int)fc_out_zp_m.at<uint8_t>(0) - 128
: (int)fc_out_zp_m.at<int8_t>(0);
if (!(inp_sc > 0.f && out_sc > 0.f))
break;
@@ -647,35 +652,31 @@ struct ModelFusionQDQ
outputMultiplier.at<float>(ioc) = (inp_sc * wt_sc.at<float>(ioc)) / out_sc;
}
int firstInpDims = (int)netimpl->argData(mm_x).shape.size();
int axis = std::max(1, firstInpDims - w_q.dims + 1);
LayerParams fcInt8Params = makeLayerParamsFromOriginal(mm, "InnerProductInt8");
fcInt8Params.set("num_output", outCn);
fcInt8Params.set("axis", axis);
fcInt8Params.blobs.resize(3);
fcInt8Params.blobs[0] = weights;
fcInt8Params.blobs[1] = bias;
fcInt8Params.blobs[2] = outputMultiplier;
Ptr<Layer> fcInt8 = createFusedLayer(fcInt8Params);
if (!fcInt8.empty()) {
auto* fcInt8Layer = dynamic_cast<InnerProductLayerInt8*>(fcInt8.get());
CV_Assert(fcInt8Layer);
fcInt8Layer->input_zp = inp_zp;
fcInt8Layer->input_sc = inp_sc;
fcInt8Layer->output_zp = out_zp_i;
fcInt8Layer->output_sc = out_sc;
fcInt8Layer->output_type = fc_out_type;
fcInt8Layer->per_channel = per_channel;
fused_layer_idx = mm_layer_idx;
newprog[mm_layer_idx] = fcInt8;
fused_inputs.assign(1, dq_x->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(mm_x);
removed_args.push_back(mm_w);
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<Layer>();
break;
}
MatMulInt8Params fp;
fp.name = mm->name;
fp.num_output = outCn;
fp.inp_dims = firstInpDims;
fp.input_sc = inp_sc;
fp.input_zp = inp_zp;
fp.output_sc = out_sc;
fp.output_zp = out_zp_i;
fp.output_type = fc_out_type;
fp.per_channel = per_channel;
fp.weights = weights;
fp.bias = bias;
fp.outputMultiplier = outputMultiplier;
Ptr<MatMulInt8Layer> mmInt8 = MatMulInt8Layer::create(fp);
mmInt8->netimpl = netimpl;
fused_layer_idx = mm_layer_idx;
newprog[mm_layer_idx] = mmInt8;
fused_inputs.assign(1, dq_x->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(mm_x);
removed_args.push_back(mm_w);
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<Layer>();
break;
}
}
}
@@ -711,19 +712,22 @@ struct ModelFusionQDQ
DequantizeLinearLayer* dq_x = getLayer<DequantizeLinearLayer>(newprog, dq_x_idx);
DequantizeLinearLayer* dq_w = getLayer<DequantizeLinearLayer>(newprog, dq_w_idx);
int fc_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
if (fc_out_type < 0) {
if (fc_out_type < 0 && netimpl->isConstArg(inputs[2])) {
const Mat& zp_t = netimpl->argTensor(inputs[2]);
fc_out_type = !zp_t.empty() ? zp_t.type() : CV_8S;
}
const bool fc_out_int8 = (fc_out_type == CV_8S || fc_out_type == CV_8U);
int fc_in_type = (dq_x && !dq_x->inputs.empty()) ? netimpl->argData(dq_x->inputs[0]).type : -1;
if (fc_in_type < 0 && dq_x && dq_x->inputs.size() >= 3) {
if (fc_in_type < 0 && dq_x && dq_x->inputs.size() >= 3 &&
netimpl->isConstArg(dq_x->inputs[2])) {
const Mat& zp_t = netimpl->argTensor(dq_x->inputs[2]);
fc_in_type = !zp_t.empty() ? zp_t.type() : CV_8S;
}
const bool fc_in_int8 = (fc_in_type == CV_8S || fc_in_type == CV_8U);
if (dq_x && dq_w &&
dq_x->inputs.size() >= 3 && dq_w->inputs.size() >= 3 &&
areDqArgsConst(dq_x) && areDqArgsConst(dq_w) &&
netimpl->isConstArg(dq_w->inputs[0]) &&
fc_in_int8 && fc_out_int8 &&
usecounts.at(mm_x.idx) == 1 && usecounts.at(mm_w.idx) == 1) {
float inp_sc = netimpl->argTensor(dq_x->inputs[1]).at<float>(0);
@@ -734,7 +738,7 @@ struct ModelFusionQDQ
float out_sc_val = netimpl->argTensor(out_scale).at<float>(0);
const Mat& fc_out_zp_m = netimpl->argTensor(out_zp);
int out_zp_i = fc_out_zp_m.depth() == CV_8U
? (int)fc_out_zp_m.at<uint8_t>(0)
? (int)fc_out_zp_m.at<uint8_t>(0) - 128
: (int)fc_out_zp_m.at<int8_t>(0);
if (inp_sc > 0.f && out_sc_val > 0.f) {
Mat w_q = netimpl->argTensor(dq_w->inputs[0]);
@@ -773,37 +777,162 @@ struct ModelFusionQDQ
}
if (biasOk) {
int firstInpDims = (int)netimpl->argData(mm_x).shape.size();
int fc_axis = std::max(1, firstInpDims - w_q.dims + 1);
LayerParams fcInt8Params = makeLayerParamsFromOriginal(mm2, "InnerProductInt8");
fcInt8Params.set("num_output", outCn);
fcInt8Params.set("axis", fc_axis);
fcInt8Params.blobs.resize(3);
fcInt8Params.blobs[0] = weights;
fcInt8Params.blobs[1] = bias;
fcInt8Params.blobs[2] = outputMultiplier;
Ptr<Layer> fcInt8 = createFusedLayer(fcInt8Params);
if (!fcInt8.empty()) {
auto* fcInt8Layer = dynamic_cast<InnerProductLayerInt8*>(fcInt8.get());
CV_Assert(fcInt8Layer);
fcInt8Layer->input_zp = inp_zp;
fcInt8Layer->input_sc = inp_sc;
fcInt8Layer->output_zp = out_zp_i;
fcInt8Layer->output_sc = out_sc_val;
fcInt8Layer->output_type = fc_out_type;
fcInt8Layer->per_channel = per_channel;
fused_layer_idx = add_bias_idx;
newprog[add_bias_idx] = fcInt8;
fused_inputs.assign(1, dq_x->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(add_bias->inputs[mm_inp_k]); // matmul out
removed_args.push_back(mm_x);
removed_args.push_back(mm_w);
newprog[mm2_idx] = Ptr<Layer>();
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<Layer>();
break;
MatMulInt8Params fp2;
fp2.name = mm2->name;
fp2.num_output = outCn;
fp2.inp_dims = firstInpDims;
fp2.input_sc = inp_sc;
fp2.input_zp = inp_zp;
fp2.output_sc = out_sc_val;
fp2.output_zp = out_zp_i;
fp2.output_type = fc_out_type;
fp2.per_channel = per_channel;
fp2.weights = weights;
fp2.bias = bias;
fp2.outputMultiplier = outputMultiplier;
Ptr<MatMulInt8Layer> mmInt8 = MatMulInt8Layer::create(fp2);
mmInt8->netimpl = netimpl;
fused_layer_idx = add_bias_idx;
newprog[add_bias_idx] = mmInt8;
fused_inputs.assign(1, dq_x->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(add_bias->inputs[mm_inp_k]); // matmul out
removed_args.push_back(mm_x);
removed_args.push_back(mm_w);
newprog[mm2_idx] = Ptr<Layer>();
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<Layer>();
break;
}
}
}
}
}
}
}
// fuse DequantizeLinear -> Gemm -> QuantizeLinear into MatMulInt8
// Gemm: Y = alpha * A @ B + beta * C (with optional transA/transB)
{
int gemm_layer_idx = -1;
GemmLayer* gemm = 0;
if (getQdqPatternContext<GemmLayer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in, out_scale, out_zp,
gemm_layer_idx, gemm) &&
gemm->inputs.size() >= 2 && !gemm->trans_a) {
// Gemm inputs: A, B, [C]
const Arg gemm_a = gemm->inputs[0];
const Arg gemm_b = gemm->inputs[1];
int dq_a_idx = producer_of.at(gemm_a.idx);
int dq_b_idx = producer_of.at(gemm_b.idx);
DequantizeLinearLayer* dq_a = getLayer<DequantizeLinearLayer>(newprog, dq_a_idx);
DequantizeLinearLayer* dq_b = getLayer<DequantizeLinearLayer>(newprog, dq_b_idx);
int g_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
if (g_out_type < 0 && netimpl->isConstArg(inputs[2])) {
const Mat& zp_t = netimpl->argTensor(inputs[2]);
g_out_type = !zp_t.empty() ? zp_t.type() : CV_8S;
}
const bool g_out_int8 = (g_out_type == CV_8S || g_out_type == CV_8U);
int g_in_type = (dq_a && !dq_a->inputs.empty()) ? netimpl->argData(dq_a->inputs[0]).type : -1;
if (g_in_type < 0 && dq_a && dq_a->inputs.size() >= 3 &&
netimpl->isConstArg(dq_a->inputs[2])) {
const Mat& zp_t = netimpl->argTensor(dq_a->inputs[2]);
g_in_type = !zp_t.empty() ? zp_t.type() : CV_8S;
}
const bool g_in_int8 = (g_in_type == CV_8S || g_in_type == CV_8U);
if (dq_a && dq_b &&
dq_a->inputs.size() >= 3 && dq_b->inputs.size() >= 3 &&
areDqArgsConst(dq_a) && areDqArgsConst(dq_b) &&
netimpl->isConstArg(dq_b->inputs[0]) &&
g_in_int8 && g_out_int8 &&
usecounts.at(gemm_a.idx) == 1 && usecounts.at(gemm_b.idx) == 1) {
float inp_sc = netimpl->argTensor(dq_a->inputs[1]).at<float>(0);
const Mat& g_zp_m = netimpl->argTensor(dq_a->inputs[2]);
int inp_zp = g_zp_m.depth() == CV_8U
? (int)g_zp_m.at<uint8_t>(0) - 128
: (int)g_zp_m.at<int8_t>(0);
float out_sc_val = netimpl->argTensor(out_scale).at<float>(0);
const Mat& g_out_zp_m = netimpl->argTensor(out_zp);
int out_zp_i = g_out_zp_m.depth() == CV_8U
? (int)g_out_zp_m.at<uint8_t>(0) - 128
: (int)g_out_zp_m.at<int8_t>(0);
if (inp_sc > 0.f && out_sc_val > 0.f) {
Mat w_q = netimpl->argTensor(dq_b->inputs[0]);
Mat w_sc_m = netimpl->argTensor(dq_b->inputs[1]);
Mat w_zp_m = netimpl->argTensor(dq_b->inputs[2]);
if (!w_q.empty() && w_q.depth() == CV_8S && w_q.dims == 2) {
bool all_wzp_zero = true;
for (size_t t = 0; all_wzp_zero && t < w_zp_m.total(); t++) {
int wz = w_zp_m.depth() == CV_8S ? (int)w_zp_m.at<int8_t>((int)t)
: (int)w_zp_m.at<uint8_t>((int)t);
if (wz != 0) all_wzp_zero = false;
}
if (all_wzp_zero) {
// Apply Gemm's transB: if transB, weights are already [N, K],
// otherwise [K, N] and we need to transpose.
Mat weights = gemm->trans_b ? w_q : w_q.t();
int outCn = weights.size[0];
float alpha_val = gemm->alpha;
Mat wt_sc = (w_sc_m.total() == (size_t)outCn)
? w_sc_m.reshape(1, 1)
: Mat(1, outCn, CV_32F, Scalar(w_sc_m.at<float>(0))).clone();
bool per_channel = w_sc_m.total() == (size_t)outCn;
// Fuse optional bias (C input of Gemm)
Mat float_bias;
if (gemm->inputs.size() >= 3)
float_bias = netimpl->argTensor(gemm->inputs[2]);
Mat bias(1, outCn, CV_32S);
Mat outputMult(1, outCn, CV_32F);
bool biasOk = true;
for (int ioc = 0; ioc < outCn && biasOk; ioc++) {
float denom = alpha_val * inp_sc * wt_sc.at<float>(ioc);
if (std::abs(denom) < 1e-12f) { biasOk = false; break; }
int zp_correction = -inp_zp * (int)cv::sum(weights.row(ioc))[0];
if (!float_bias.empty() && float_bias.total() == (size_t)outCn) {
float b_val = 0.f;
if (float_bias.depth() == CV_32F)
b_val = float_bias.at<float>(ioc);
else if (float_bias.depth() == CV_64F)
b_val = (float)float_bias.at<double>(ioc);
bias.at<int>(ioc) = cvRound(gemm->beta * b_val / denom) + zp_correction;
} else {
bias.at<int>(ioc) = zp_correction;
}
outputMult.at<float>(ioc) = denom / out_sc_val;
}
if (biasOk) {
int firstInpDims = (int)netimpl->argData(gemm_a).shape.size();
MatMulInt8Params gp;
gp.name = gemm->name;
gp.num_output = outCn;
gp.inp_dims = firstInpDims;
gp.input_sc = inp_sc;
gp.input_zp = inp_zp;
gp.output_sc = out_sc_val;
gp.output_zp = out_zp_i;
gp.output_type = g_out_type;
gp.per_channel = per_channel;
gp.weights = weights;
gp.bias = bias;
gp.outputMultiplier = outputMult;
Ptr<MatMulInt8Layer> gemmInt8 = MatMulInt8Layer::create(gp);
gemmInt8->netimpl = netimpl;
fused_layer_idx = gemm_layer_idx;
newprog[gemm_layer_idx] = gemmInt8;
fused_inputs.assign(1, dq_a->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(gemm_a);
removed_args.push_back(gemm_b);
newprog[dq_a_idx] = Ptr<Layer>();
newprog[dq_b_idx] = Ptr<Layer>();
break;
}
}
}
@@ -816,44 +945,57 @@ struct ModelFusionQDQ
PoolingLayer* pool = 0;
if (getQdqPatternContext<PoolingLayer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in, out_scale, out_zp,
pool_layer_idx, pool) && pool->inputs.size() == 1) {
Arg pool_in = pool->inputs[0];
int dq_idx = producer_of.at(pool_in.idx);
DequantizeLinearLayer* dq = getLayer<DequantizeLinearLayer>(newprog, dq_idx);
float inp_sc = 0.f, out_sc = 0.f;
int inp_zp = 0, out_zp_i = 0;
const int pool_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
const bool pool_out_int8 = (pool_out_type == CV_8S || pool_out_type == CV_8U);
const int pool_in_type = (dq && !dq->inputs.empty()) ? netimpl->argData(dq->inputs[0]).type : -1;
const bool pool_in_int8 = (pool_in_type == CV_8S || pool_in_type == CV_8U);
if (dq && dq->inputs.size() >= 3 && pool_in_int8 && pool_out_int8 && usecounts.at(pool_in.idx) == 1) {
inp_sc = netimpl->argTensor(dq->inputs[1]).at<float>(0);
const Mat& pool_zp_m = netimpl->argTensor(dq->inputs[2]);
inp_zp = pool_zp_m.depth() == CV_8U
? (int)pool_zp_m.at<uint8_t>(0)
: (int)pool_zp_m.at<int8_t>(0);
out_sc = netimpl->argTensor(out_scale).at<float>(0);
const Mat& pool_out_zp_m = netimpl->argTensor(out_zp);
out_zp_i = pool_out_zp_m.depth() == CV_8U
? (int)pool_out_zp_m.at<uint8_t>(0)
: (int)pool_out_zp_m.at<int8_t>(0);
bool isGlobalAve = pool->globalPooling;
bool isMax = !isGlobalAve;
if ((isGlobalAve && inp_sc > 0.f && out_sc > 0.f) ||
(isMax && std::abs(inp_sc - out_sc) < 1e-6f && inp_zp == out_zp_i)) {
LayerParams poolInt8Params = makeLayerParamsFromOriginal(pool, "Pool2Int8");
poolInt8Params.blobs.clear();
poolInt8Params.set("input_scale", inp_sc);
poolInt8Params.set("input_zeropoint", inp_zp);
poolInt8Params.set("scales", out_sc);
poolInt8Params.set("zeropoints", out_zp_i);
poolInt8Params.set("is_max_pool", isMax);
poolInt8Params.set("global_pooling", isGlobalAve);
Ptr<Layer> poolInt8 = createFusedLayer(poolInt8Params);
if (!poolInt8.empty()) {
auto* poolInt8Layer = dynamic_cast<Pool2Int8Layer*>(poolInt8.get());
CV_Assert(poolInt8Layer);
pool_layer_idx, pool) &&
pool->inputs.size() == 1) {
Arg pool_in = pool->inputs[0];
int dq_idx = producer_of.at(pool_in.idx);
DequantizeLinearLayer* dq = getLayer<DequantizeLinearLayer>(newprog, dq_idx);
float inp_sc = 0.f, out_sc = 0.f;
int inp_zp = 0, out_zp_i = 0;
const int pool_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
const bool pool_out_int8 = (pool_out_type == CV_8S || pool_out_type == CV_8U);
const int pool_in_type = (dq && !dq->inputs.empty()) ? netimpl->argData(dq->inputs[0]).type : -1;
const bool pool_in_int8 = (pool_in_type == CV_8S || pool_in_type == CV_8U);
if (dq && dq->inputs.size() >= 3 &&
areDqArgsConst(dq) &&
pool_in_int8 && pool_out_int8 &&
usecounts.at(pool_in.idx) == 1) {
inp_sc = netimpl->argTensor(dq->inputs[1]).at<float>(0);
const Mat& pool_zp_m = netimpl->argTensor(dq->inputs[2]);
inp_zp = pool_zp_m.depth() == CV_8U
? (int)pool_zp_m.at<uint8_t>(0)
: (int)pool_zp_m.at<int8_t>(0);
out_sc = netimpl->argTensor(out_scale).at<float>(0);
const Mat& pool_out_zp_m = netimpl->argTensor(out_zp);
out_zp_i = pool_out_zp_m.depth() == CV_8U
? (int)pool_out_zp_m.at<uint8_t>(0)
: (int)pool_out_zp_m.at<int8_t>(0);
bool isGlobalAve = pool->globalPooling;
bool isMax = !isGlobalAve;
if ((isGlobalAve && inp_sc > 0.f && out_sc > 0.f) ||
(isMax && std::abs(inp_sc - out_sc) < 1e-6f && inp_zp == out_zp_i)) {
Pool2Int8Params p;
p.name = pool->name;
p.kernel_shape.assign(pool->kernel_size.begin(), pool->kernel_size.end());
p.strides.assign(pool->strides.begin(), pool->strides.end());
if (!pool->pads_begin.empty()) {
p.pads.reserve(pool->pads_begin.size() + pool->pads_end.size());
for (size_t v : pool->pads_begin) p.pads.push_back((int)v);
for (size_t v : pool->pads_end) p.pads.push_back((int)v);
}
if (pool->padMode == "SAME")
p.auto_pad = AUTO_PAD_SAME_UPPER;
else if (pool->padMode == "VALID")
p.auto_pad = AUTO_PAD_VALID;
p.ceil_mode = pool->ceilMode;
p.is_global_pooling = isGlobalAve;
p.is_max_pool = isMax;
p.input_sc = inp_sc;
p.input_zp = inp_zp;
p.output_sc = out_sc;
p.output_zp = out_zp_i;
Ptr<Pool2Int8Layer> poolInt8 = Pool2Int8Layer::create(p);
poolInt8->netimpl = netimpl;
fused_layer_idx = pool_layer_idx;
newprog[pool_layer_idx] = poolInt8;
fused_inputs.assign(1, dq->inputs[0]);
@@ -864,6 +1006,59 @@ struct ModelFusionQDQ
}
}
}
// fuse DequantizeLinear -> DataShuffling/MaxPool -> QuantizeLinear
// when DQ and Q have same scale/zp, the op can work directly on int8 data.
// Uses Layer::isDataShuffling() to identify eligible layers.
{
QuantizeLinearLayer* ql_shuffle = dynamic_cast<QuantizeLinearLayer*>(layer_ptr);
if (ql_shuffle && ninputs == 3 && usecounts.at(inputs[0].idx) == 1) {
Arg ql_data = inputs[0];
Arg ql_scale = inputs[1];
Arg ql_zp = inputs[2];
int shuffle_idx = producer_of.at(ql_data.idx);
Layer* shuffle_layer = (shuffle_idx >= 0 && !newprog[shuffle_idx].empty())
? newprog[shuffle_idx].get() : nullptr;
bool is_shuffle = shuffle_layer && shuffle_layer->isDataShuffling();
if (is_shuffle && shuffle_layer->inputs.size() >= 1 &&
usecounts.at(ql_data.idx) == 1) {
Arg shuffle_inp = shuffle_layer->inputs[0];
int dq_idx = producer_of.at(shuffle_inp.idx);
DequantizeLinearLayer* dq = getLayer<DequantizeLinearLayer>(newprog, dq_idx);
if (dq && dq->inputs.size() >= 3 &&
usecounts.at(shuffle_inp.idx) == 1) {
float dq_sc = netimpl->argTensor(dq->inputs[1]).at<float>(0);
float q_sc = netimpl->argTensor(ql_scale).at<float>(0);
const Mat& dq_zp_m = netimpl->argTensor(dq->inputs[2]);
const Mat& q_zp_m = netimpl->argTensor(ql_zp);
int dq_zp = dq_zp_m.depth() == CV_8U
? (int)dq_zp_m.at<uint8_t>(0) : (int)dq_zp_m.at<int8_t>(0);
int q_zp = q_zp_m.depth() == CV_8U
? (int)q_zp_m.at<uint8_t>(0) : (int)q_zp_m.at<int8_t>(0);
int in_type = netimpl->argData(dq->inputs[0]).type;
int out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
bool in_int8 = (in_type == CV_8S || in_type == CV_8U);
bool out_int8 = (out_type == CV_8S || out_type == CV_8U);
if (in_int8 && out_int8 &&
std::abs(dq_sc - q_sc) < 1e-6f && dq_zp == q_zp) {
fused_layer_idx = shuffle_idx;
fused_inputs.assign(1, dq->inputs[0]);
for (size_t si = 1; si < shuffle_layer->inputs.size(); si++)
fused_inputs.push_back(shuffle_layer->inputs[si]);
removed_args.push_back(ql_data);
removed_args.push_back(shuffle_inp);
newprog[dq_idx] = Ptr<Layer>();
break;
}
}
}
}
}
ActivationLayerInt8* activ_int8 = dynamic_cast<ActivationLayerInt8*>(layer_ptr);
@@ -913,6 +1108,69 @@ struct ModelFusionQDQ
}
}
for (size_t j = 0; j < newprog.size(); j++)
{
if (newprog[j].empty()) continue;
MaxPoolLayer* maxpool = dynamic_cast<MaxPoolLayer*>(newprog[j].get());
if (!maxpool || maxpool->inputs.size() != 1)
continue;
Arg mp_out = maxpool->outputs[0];
bool consumer_is_ql = false;
for (size_t j2 = j + 1; j2 < newprog.size(); j2++) {
if (newprog[j2].empty()) continue;
bool is_consumer = false;
for (const Arg& a : newprog[j2]->inputs)
if (a.idx == mp_out.idx) { is_consumer = true; break; }
if (is_consumer) {
consumer_is_ql = dynamic_cast<QuantizeLinearLayer*>(newprog[j2].get()) != nullptr;
break;
}
}
if (consumer_is_ql)
continue;
int cur = producer_of.at(maxpool->inputs[0].idx);
QuantizeLinearLayer* ql = nullptr;
while (cur >= 0 && !newprog[cur].empty()) {
ql = dynamic_cast<QuantizeLinearLayer*>(newprog[cur].get());
if (ql) break;
Layer* l = newprog[cur].get();
if (l->inputs.empty()) { ql = nullptr; break; }
cur = producer_of.at(l->inputs[0].idx);
}
if (!ql || ql->inputs.size() < 2 ||
!netimpl->isConstArg(ql->inputs[1]) ||
(ql->inputs.size() >= 3 && !netimpl->isConstArg(ql->inputs[2])))
continue;
float inp_sc = netimpl->argTensor(ql->inputs[1]).at<float>(0);
const Mat& inp_zp_m = ql->inputs.size() >= 3 ? netimpl->argTensor(ql->inputs[2]) : Mat();
int inp_zp = inp_zp_m.empty() ? 0 :
inp_zp_m.depth() == CV_8U ? (int)inp_zp_m.at<uint8_t>(0) :
(int)inp_zp_m.at<int8_t>(0);
Pool2Int8Params p;
p.name = maxpool->name;
p.kernel_shape = maxpool->kernel_shape;
p.strides = maxpool->strides;
p.dilations = maxpool->dilations;
p.pads = maxpool->pads;
p.auto_pad = maxpool->auto_pad;
p.ceil_mode = maxpool->ceil_mode;
p.is_max_pool = true;
p.input_sc = inp_sc;
p.input_zp = inp_zp;
p.output_sc = inp_sc;
p.output_zp = inp_zp;
Ptr<Pool2Int8Layer> poolInt8 = Pool2Int8Layer::create(p);
poolInt8->netimpl = netimpl;
poolInt8->inputs = maxpool->inputs;
poolInt8->outputs = maxpool->outputs;
newprog[j] = poolInt8;
modified = true;
}
if (modified) {
std::vector<int> uc(nargs, 0);
for (const auto& layer : newprog) {
@@ -212,6 +212,32 @@ public:
}
}
Conv2Int8LayerImpl(const Conv2Int8Params& p)
{
name = p.name;
type = "Conv2Int8";
strides = p.strides;
dilations = p.dilations;
pads = p.pads;
ngroups = p.ngroups;
auto_pad = p.auto_pad;
ceil_mode = p.ceil_mode;
input_sc = p.input_sc;
input_zp = p.input_zp;
output_sc = p.output_sc;
output_zp = p.output_zp;
per_channel = p.per_channel;
inputIsU8 = p.input_is_u8;
addResidual = false;
if (!p.weights.empty()) {
wshape0 = p.weights.shape();
biasInt32 = p.bias;
outMultiplier = p.outputMultiplier;
blobs = { p.weights, p.bias, p.outputMultiplier };
}
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs, const int,
std::vector<MatType>& outputs,
@@ -341,6 +367,10 @@ public:
cs.initConv(inpshape, wshape0, outshape, ngroups,
strides, dilations, pads, auto_pad, ceil_mode,
FAST_ACTIV_NONE, nullptr, {});
if (cs.depthwise) {
cs.wshape = getWpackShapeInt8(wshape0, ngroups, C0);
}
prevInpshape = inpshape;
}
@@ -367,5 +397,10 @@ Ptr<Conv2Int8Layer> Conv2Int8Layer::create(const LayerParams& params)
return Ptr<Conv2Int8Layer>(new Conv2Int8LayerImpl(params));
}
Ptr<Conv2Int8Layer> Conv2Int8Layer::create(const Conv2Int8Params& params)
{
return Ptr<Conv2Int8Layer>(new Conv2Int8LayerImpl(params));
}
} // namespace dnn
} // namespace cv
@@ -37,11 +37,21 @@ public:
{
slope = params.get<float>("slope");
}
}
ActivationLayerInt8Impl(const ActivationInt8Params &p)
{
name = p.name;
type = p.activationType;
input_sc = p.input_sc;
input_zp = p.input_zp;
output_sc = p.output_sc;
output_zp = p.output_zp;
activationLUT = p.activationLUT;
#ifdef HAVE_TIMVX
tvActType = getTimVXActType(type);
#endif
}
virtual bool supportBackend(int backendId) CV_OVERRIDE
@@ -360,5 +370,10 @@ Ptr<ActivationLayerInt8> ActivationLayerInt8::create(const LayerParams& params)
return Ptr<ActivationLayerInt8>(new ActivationLayerInt8Impl(params));
}
Ptr<ActivationLayerInt8> ActivationLayerInt8::create(const ActivationInt8Params& params)
{
return Ptr<ActivationLayerInt8>(new ActivationLayerInt8Impl(params));
}
}
}
@@ -57,6 +57,18 @@ public:
offset = 0.f;
}
Eltwise2Int8LayerImpl(const Eltwise2Int8Params& p)
{
name = p.name;
type = "Eltwise2Int8";
scales = p.input_scales;
zeropoints = p.input_zeropoints;
output_sc = p.output_sc;
output_zp = p.output_zp;
withRelu = p.with_relu;
offset = 0.f;
}
void ensureCoeffs()
{
if (!coeffs.empty() || scales.empty())
@@ -409,5 +421,10 @@ Ptr<Eltwise2Int8Layer> Eltwise2Int8Layer::create(const LayerParams& params)
return Ptr<Eltwise2Int8Layer>(new Eltwise2Int8LayerImpl(params));
}
Ptr<Eltwise2Int8Layer> Eltwise2Int8Layer::create(const Eltwise2Int8Params& params)
{
return Ptr<Eltwise2Int8Layer>(new Eltwise2Int8LayerImpl(params));
}
} // namespace dnn
} // namespace cv
@@ -57,6 +57,42 @@ public:
}
}
FullyConnectedLayerInt8Impl(const InnerProductInt8Params& p)
{
name = p.name;
type = "InnerProductInt8";
input_sc = p.input_sc;
input_zp = p.input_zp;
output_sc = p.output_sc;
output_zp = p.output_zp;
axis = p.axis;
per_channel = p.per_channel;
output_type = p.output_type;
if (!p.weights.empty()) {
int numOutput = p.num_output;
int innerSize = (int)p.weights.total() / numOutput;
CV_Assert(p.weights.dims >= 2 && (size_t)(innerSize * numOutput) == p.weights.total());
CV_Assert((size_t)numOutput == p.bias.total());
blobs = { p.weights.clone(), p.bias.clone(), p.outputMultiplier.clone() };
weightsMat = blobs[0] = blobs[0].reshape(1, numOutput);
int vecsize = weightsMat.cols;
if (vecsize % VEC_ALIGN != 0)
{
int vecsize_aligned = (int)alignSize(vecsize, VEC_ALIGN);
Mat weightsBuf(weightsMat.rows, vecsize_aligned, weightsMat.type());
Mat wpadding = weightsBuf.colRange(vecsize, vecsize_aligned);
wpadding.setTo(Scalar::all(0));
weightsMat = weightsBuf.colRange(0, vecsize);
blobs[0].copyTo(weightsMat);
}
biasMat = blobs[1] = blobs[1].reshape(1, 1);
outputMultiplier = blobs[2];
}
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
@@ -508,5 +544,10 @@ Ptr<InnerProductLayerInt8> InnerProductLayerInt8::create(const LayerParams& para
return Ptr<InnerProductLayerInt8>(new FullyConnectedLayerInt8Impl(params));
}
Ptr<InnerProductLayerInt8> InnerProductLayerInt8::create(const InnerProductInt8Params& params)
{
return Ptr<InnerProductLayerInt8>(new FullyConnectedLayerInt8Impl(params));
}
}
}
@@ -0,0 +1,296 @@
// 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.
#include "../precomp.hpp"
#include "layers_common.hpp"
#include <opencv2/dnn/shape_utils.hpp>
namespace cv
{
namespace dnn
{
class MatMulInt8LayerImpl CV_FINAL : public MatMulInt8Layer
{
public:
enum { VEC_ALIGN = 32 };
MatMulInt8LayerImpl(const MatMulInt8Params& p)
{
name = p.name;
type = "MatMulInt8";
input_sc = p.input_sc;
input_zp = p.input_zp;
output_sc = p.output_sc;
output_zp = p.output_zp;
output_type = p.output_type;
per_channel = p.per_channel;
inp_dims = p.inp_dims;
num_output = p.num_output;
if (!p.weights.empty()) {
int numOutput = p.num_output;
int innerSize = (int)p.weights.total() / numOutput;
CV_Assert(p.weights.dims >= 2 && (size_t)(innerSize * numOutput) == p.weights.total());
CV_Assert((size_t)numOutput == p.bias.total());
blobs = { p.weights.clone(), p.bias.clone(), p.outputMultiplier.clone() };
weightsMat = blobs[0] = blobs[0].reshape(1, numOutput);
int vecsize = weightsMat.cols;
if (vecsize % VEC_ALIGN != 0)
{
int vecsize_aligned = (int)alignSize(vecsize, VEC_ALIGN);
Mat weightsBuf(weightsMat.rows, vecsize_aligned, weightsMat.type());
Mat wpadding = weightsBuf.colRange(vecsize, vecsize_aligned);
wpadding.setTo(Scalar::all(0));
weightsMat = weightsBuf.colRange(0, vecsize);
blobs[0].copyTo(weightsMat);
}
biasMat = blobs[1] = blobs[1].reshape(1, 1);
outputMultiplier = blobs[2];
}
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
std::vector<MatShape> &) const CV_OVERRIDE
{
CV_CheckEQ(inputs.size(), (size_t)1, "");
CV_CheckEQ(blobs[0].dims, 2, "");
int numOut = blobs[0].size[0];
CV_Assert((size_t)numOut == blobs[1].total());
// Preserve all batch dimensions, replace last dim with num_output.
// Input: [B1, B2, ..., M, K] → Output: [B1, B2, ..., M, num_output]
int ndims = inputs[0].dims;
CV_Assert(ndims >= 1);
MatShape outShape = inputs[0];
outShape[ndims - 1] = numOut;
outputs.resize(1, outShape);
return false;
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>& outputs,
std::vector<MatType>& internals) const CV_OVERRIDE
{
outputs.assign(requiredOutputs, output_type);
internals.clear();
}
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
return backendId == DNN_BACKEND_OPENCV;
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr,
OutputArrayOfArrays internals_arr) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
std::vector<Mat> input, output;
inputs_arr.getMatVector(input);
outputs_arr.getMatVector(output);
// Flatten all dimensions except the last (K) into a single batch dim.
// Input: [B1, B2, ..., M, K] → [B1*B2*...*M, K]
int ndims = input[0].dims;
int K = input[0].size[ndims - 1];
int outerSize = (int)(input[0].total() / K);
Mat srcMat0 = input[0].reshape(1, outerSize);
Mat srcMat;
if (srcMat0.type() == CV_8U) {
srcMat0.convertTo(srcMat, CV_8S, 1, -128);
} else {
srcMat = srcMat0;
}
Mat dstMat = output[0].reshape(1, outerSize);
Mat dstMatInt32 = Mat(shape(dstMat), CV_32S);
const int nstripes = outerSize <= 4 ? 1 : getNumThreads();
runInt8Gemm(srcMat, weightsMat, biasMat, outputMultiplier, dstMatInt32, nstripes, output_zp);
if (output_type == CV_8U) {
dstMatInt32.convertTo(dstMat, output_type, 1, 128);
} else {
dstMatInt32.convertTo(dstMat, output_type);
}
}
virtual int64 getFLOPS(const std::vector<MatShape> &inputs,
const std::vector<MatShape> &outputs) const CV_OVERRIDE
{
CV_UNUSED(inputs);
long flops = 0;
int innerSize = blobs[0].size[1];
for (size_t i = 0; i < outputs.size(); i++) {
flops += total(outputs[i]) * innerSize;
}
return flops;
}
private:
// Int8 GEMM: C_int32 = A_int8 @ W_int8^T + bias_int32
// Then: output = clamp(round(C_int32 * multiplier) + output_zp)
static void runInt8Gemm(const Mat& srcMat, const Mat& weights, const Mat& biasMat,
const Mat& outputMultiplier, Mat& dstMat, int nstripes, int outZp)
{
CV_Assert(srcMat.dims == 2 && srcMat.cols == weights.cols &&
dstMat.rows == srcMat.rows && dstMat.cols == weights.rows &&
srcMat.type() == weights.type() && srcMat.type() == CV_8S &&
dstMat.type() == CV_32S && biasMat.type() == CV_32S &&
biasMat.isContinuous() && (int)biasMat.total() == dstMat.cols);
Int8GemmBody p;
p.srcMat = &srcMat;
p.weights = &weights;
p.biasMat = &biasMat;
p.outputMultiplier = &outputMultiplier;
p.dstMat = &dstMat;
p.nstripes = nstripes;
p.outZp = outZp;
p.useAVX2 = checkHardwareSupport(CPU_AVX2);
p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX;
p.useLASX = checkHardwareSupport(CPU_LASX);
p.useRVV = checkHardwareSupport(CPU_RVV);
parallel_for_(Range(0, nstripes), p, nstripes);
}
class Int8GemmBody : public ParallelLoopBody
{
public:
Int8GemmBody() : srcMat(0), weights(0), biasMat(0), outputMultiplier(0),
dstMat(0), nstripes(0), outZp(0),
useAVX2(false), useAVX512(false), useLASX(false), useRVV(false) {}
void operator()(const Range& r) const CV_OVERRIDE
{
int valign = MatMulInt8LayerImpl::VEC_ALIGN;
int nsamples = srcMat->rows;
int nw0 = weights->rows;
int k, vecsize = srcMat->cols;
int vecsize_aligned = (int)alignSize(vecsize, VEC_ALIGN);
size_t total = (size_t)nsamples * nw0;
size_t stripeSize = (total + nstripes - 1) / nstripes;
size_t stripeStart = r.start * stripeSize;
size_t stripeEnd = r.end == nstripes ? total : std::min(r.end * stripeSize, total);
size_t wstep = weights->step1();
AutoBuffer<int8_t> srcbuf(vecsize_aligned + valign);
int8_t* sptr = alignPtr(srcbuf.data(), (int)(valign * sizeof(int8_t)));
for (k = vecsize; k < vecsize_aligned; k++)
sptr[k] = 0;
for (size_t ofs = stripeStart; ofs < stripeEnd; )
{
int sampleIdx = (int)(ofs / nw0);
int delta = (int)(ofs - (size_t)sampleIdx * nw0);
const int8_t* sptr_ = srcMat->ptr<int8_t>(sampleIdx);
const int8_t* wptr = weights->ptr<int8_t>(delta);
int* dptr = dstMat->ptr<int>(sampleIdx) + delta;
const int* biasptr = biasMat->ptr<int>() + delta;
const float* multptr = outputMultiplier->ptr<float>() + delta;
int nw = std::min(nw0 - delta, (int)(stripeEnd - ofs));
memcpy(sptr, sptr_, vecsize * sizeof(sptr[0]));
#if CV_TRY_AVX512_SKX
if (useAVX512)
opt_AVX512_SKX::fastGEMM1T(sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp);
else
#endif
#if CV_TRY_AVX2
if (useAVX2)
opt_AVX2::fastGEMM1T(sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp);
else
#endif
#if CV_TRY_LASX
if (useLASX)
opt_LASX::fastGEMM1T(sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp);
else
#endif
#if CV_TRY_RVV && CV_RVV
if (useRVV)
opt_RVV::fastGEMM1T(sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp);
else
#endif
#if CV_RVP052
if (1)
opt_RVP052::fastGEMM1T(sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp);
else
#endif
{
int i = 0;
#if CV_SIMD128
for (; i <= nw - 4; i += 4, wptr += 4 * wstep)
{
v_int32x4 vs0 = v_setzero_s32(), vs1 = v_setzero_s32(),
vs2 = v_setzero_s32(), vs3 = v_setzero_s32();
v_int32x4 outzp = v_setall_s32(outZp), outmin = v_setall_s32(-128), outmax = v_setall_s32(127);
v_int32x4 s = v_load(biasptr + i);
v_float32x4 mult = v_load(multptr + i);
for (k = 0; k < vecsize; k += 16)
{
v_int8x16 v = v_load_aligned(sptr + k);
vs0 = v_dotprod_expand_fast(v, v_load_aligned(wptr + k), vs0);
vs1 = v_dotprod_expand_fast(v, v_load_aligned(wptr + wstep + k), vs1);
vs2 = v_dotprod_expand_fast(v, v_load_aligned(wptr + wstep * 2 + k), vs2);
vs3 = v_dotprod_expand_fast(v, v_load_aligned(wptr + wstep * 3 + k), vs3);
}
s = v_add(s, v_int32x4(v_reduce_sum(vs0), v_reduce_sum(vs1), v_reduce_sum(vs2), v_reduce_sum(vs3)));
v_int32x4 out = v_add(outzp, v_round(v_mul(v_cvt_f32(s), mult)));
v_store(dptr + i, v_min(v_max(out, outmin), outmax));
}
#endif
for (; i < nw; i++, wptr += wstep)
{
int s0 = biasptr[i];
float mult0 = multptr[i];
for (k = 0; k < vecsize; k++)
{
int8_t v = sptr[k];
s0 += (int)v * wptr[k];
}
int out0 = outZp + (int)std::round(s0 * mult0);
dptr[i] = std::min(std::max(out0, -128), 127);
}
}
ofs += nw;
}
}
const Mat *srcMat, *weights, *biasMat, *outputMultiplier;
Mat* dstMat;
int nstripes, outZp;
bool useAVX2;
bool useAVX512;
bool useLASX;
bool useRVV;
};
int inp_dims;
int num_output;
Mat weightsMat, biasMat, outputMultiplier;
};
Ptr<MatMulInt8Layer> MatMulInt8Layer::create(const MatMulInt8Params& params)
{
return Ptr<MatMulInt8Layer>(new MatMulInt8LayerImpl(params));
}
}
}
@@ -259,6 +259,25 @@ public:
output_zp = params.get<int>("zeropoints", 0);
}
Pool2Int8LayerImpl(const Pool2Int8Params& p)
{
name = p.name;
type = "Pool2Int8";
kernel_shape = p.kernel_shape;
strides = p.strides;
dilations = p.dilations;
pads = p.pads;
auto_pad = p.auto_pad;
ceil_mode = p.ceil_mode;
is_global_pooling = p.is_global_pooling;
is_max_pool = p.is_max_pool;
count_include_pad = p.count_include_pad;
input_sc = p.input_sc;
input_zp = p.input_zp;
output_sc = p.output_sc;
output_zp = p.output_zp;
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs, const int,
std::vector<MatType>& outputs,
@@ -377,5 +396,10 @@ Ptr<Pool2Int8Layer> Pool2Int8Layer::create(const LayerParams& params)
return Ptr<Pool2Int8Layer>(new Pool2Int8LayerImpl(params));
}
Ptr<Pool2Int8Layer> Pool2Int8Layer::create(const Pool2Int8Params& params)
{
return Ptr<Pool2Int8Layer>(new Pool2Int8LayerImpl(params));
}
} // namespace dnn
} // namespace cv
+7 -2
View File
@@ -269,9 +269,9 @@ void Layer::getTypes(const std::vector<MatType>&inputs,
if (preferableTarget == DNN_TARGET_CUDA_FP16 || preferableTarget == DNN_TARGET_CUDA)
CV_CheckTypeEQ(input, CV_32F, "");
else if (preferableTarget == DNN_TARGET_OPENCL_FP16)
CV_CheckType(input, input == CV_16F || input == CV_8S || input == CV_64F || input == CV_64S, "");
CV_CheckType(input, input == CV_16F || input == CV_8S || input == CV_8U || input == CV_64F || input == CV_64S, "");
else
CV_CheckType(input, input == CV_32F || input == CV_64F || input == CV_8S || input == CV_64S, "");
CV_CheckType(input, input == CV_32F || input == CV_64F || input == CV_8S || input == CV_8U || input == CV_64S, "");
}
outputs.assign(requiredOutputs, inputs[0]);
@@ -314,6 +314,11 @@ bool Layer::dynamicOutputShapes() const
return false;
}
bool Layer::isDataShuffling() const
{
return false;
}
std::ostream& Layer::dumpAttrs(std::ostream& strm, int) const
{
return strm;
+2
View File
@@ -78,6 +78,8 @@ public:
scale = params.get<float>("scales", 1.0f);
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
@@ -170,12 +170,15 @@ static void dequantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp,
CV_Assert(zpshape == scshape);
}
axis = normalize_axis(axis, ndims);
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 = inpshape[axis];
int sz_a = ndims > 0 ? inpshape[axis] : 1;
if (block_size == 0) {
size_t sc_total = scshape.total();
+2
View File
@@ -87,6 +87,8 @@ public:
backendId == DNN_BACKEND_CANN;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
+2
View File
@@ -456,6 +456,8 @@ public:
temptypes.clear();
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
virtual bool getMemoryShapes(const std::vector<MatShape>& inpshapes,
const int,
std::vector<MatShape> &outshapes,
+2
View File
@@ -147,6 +147,8 @@ public:
backendId == DNN_BACKEND_CANN;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
@@ -180,12 +180,15 @@ static void quantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp,
CV_Assert(zpshape == scshape);
}
axis = normalize_axis(axis, ndims);
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 = inpshape[axis];
int sz_a = ndims > 0 ? inpshape[axis] : 1;
if (block_size == 0) {
size_t sc_total = scshape.total();
@@ -57,6 +57,8 @@ public:
}
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
virtual bool dynamicOutputShapes() const CV_OVERRIDE
{
if (!dynamicShapeSpec)
+2
View File
@@ -239,6 +239,8 @@ public:
backendId == DNN_BACKEND_CANN;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
+2
View File
@@ -128,6 +128,8 @@ public:
return outShape;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int,
std::vector<MatShape> &outputs,
+2
View File
@@ -237,6 +237,8 @@ public:
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CANN;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
+2
View File
@@ -142,6 +142,8 @@ public:
}
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int noutputs,
std::vector<MatShape> &outputs,
+2
View File
@@ -78,6 +78,8 @@ public:
backendId == DNN_BACKEND_CUDA;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
+2
View File
@@ -81,6 +81,8 @@ public:
return outShape;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int,
std::vector<MatShape> &outputs,
+2
View File
@@ -139,6 +139,8 @@ public:
return backendId == DNN_BACKEND_OPENCV;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
virtual bool dynamicOutputShapes() const CV_OVERRIDE
{
Net::Impl* netimpl_ = getNetImpl(this);
+2
View File
@@ -38,6 +38,8 @@ public:
backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
@@ -148,6 +148,8 @@ public:
return outShape;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int,
std::vector<MatShape> &outputs,
@@ -78,6 +78,8 @@ public:
return outShape;
}
bool isDataShuffling() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int,
std::vector<MatShape> &outputs,
+269 -498
View File
@@ -266,15 +266,21 @@ protected:
void parseDequantizeLinear (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQuantizeLinear (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseCustomLayer (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQAvgPool (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQConcat (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQConv (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQEltwise (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQGemm (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQLeakyRelu (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQMatMul (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQSigmoid (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
//void parseQSoftmax (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQConv (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQMatMul (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQGemm (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQEltwise (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQLeakyRelu (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQSigmoid (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQAvgPool (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQConcat (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQSoftmax (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
Arg addDequantize(const std::string& name, const std::vector<Arg>& inputs,
const opencv_onnx::NodeProto& node_proto, int axis = -1);
void addQuantize(const std::string& name, const Arg& data,
const std::vector<Arg>& scale_zp, const std::vector<Arg>& outputs,
const opencv_onnx::NodeProto& node_proto);
std::map<std::string, int> onnx_opset_map; // map from OperatorSetIdProto
void parseOperatorSet();
@@ -2073,559 +2079,323 @@ void ONNXImporter2::parseRandomNormalLike(LayerParams& layerParams, const opencv
addLayer(layerParams, node_proto);
}
// BUG: https://github.com/opencv/opencv/issues/26310
/*void ONNXImporter2::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
// All Q* parsers decompose quantized ops into: DequantizeLinear → float op → QuantizeLinear.
// The fused Int8 layers don't work with the new graph engine (issue #26310).
Arg ONNXImporter2::addDequantize(const std::string& name, const std::vector<Arg>& inputs,
const opencv_onnx::NodeProto& node_proto, int axis)
{
LayerParams lp;
lp.name = name;
lp.type = "DequantizeLinear";
if (axis >= 0)
lp.set("axis", axis);
Arg out = net.getArg(name);
node_inputs = inputs;
node_outputs = {out};
addLayer(lp, node_proto);
return out;
}
void ONNXImporter2::addQuantize(const std::string& name, const Arg& data,
const std::vector<Arg>& scale_zp,
const std::vector<Arg>& outputs,
const opencv_onnx::NodeProto& node_proto)
{
LayerParams lp;
lp.name = name;
lp.type = "QuantizeLinear";
node_inputs = {data};
node_inputs.insert(node_inputs.end(), scale_zp.begin(), scale_zp.end());
node_outputs = outputs;
addLayer(lp, node_proto);
}
static void copyAttrs(const LayerParams& src, LayerParams& dst)
{
for (auto it = src.begin(); it != src.end(); ++it)
{
const std::string& key = it->first;
if (key != "name" && key != "type")
dst.set(key, it->second);
}
}
void ONNXImporter2::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
opencv_onnx::NodeProto node_proto = node_proto_;
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 8 || ninputs == 9);
float inp_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int inp_zp = (int)getScalarFromMat<int8_t>(getBlob(node_proto, 2));
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
if (layerParams.has("pad"))
Arg dq_x, dq_w;
dq_x = addDequantize(bn + "/dequant_x", {inp[0], inp[1], inp[2]}, node_proto);
dq_w = addDequantize(bn + "/dequant_w", {inp[3], inp[4], inp[5]}, node_proto, 0);
Arg bias_arg;
bool has_bias = (ninputs == 9);
if (has_bias)
{
bool asymmetricPadding = false;
DictValue pads = layerParams.get("pad");
const int dims = pads.size() / 2;
for (int i = 0; i < dims; ++i)
if (net.isConstArg(inp[1]) && net.isConstArg(inp[4]) && net.isConstArg(inp[8]))
{
if (pads.get<int>(i) != pads.get<int>(i + dims))
{
asymmetricPadding = true;
break;
}
Mat x_scale_mat = net.argTensor(inp[1]);
Mat w_scale_mat = net.argTensor(inp[4]);
Mat bias_int32 = net.argTensor(inp[8]);
Mat bias_float;
bias_int32.convertTo(bias_float, CV_32F);
float x_sc = x_scale_mat.at<float>(0);
if (w_scale_mat.total() == 1)
bias_float *= x_sc * w_scale_mat.at<float>(0);
else
for (int i = 0; i < (int)w_scale_mat.total(); i++)
bias_float.at<float>(i) *= x_sc * w_scale_mat.at<float>(i);
bias_arg = netimpl->newConstArg(bn + "/bias_float", bias_float);
}
if (asymmetricPadding && pads.size() == 4)
else
{
layerParams.erase("pad");
std::vector<int> paddings(4, 0);
for (int i = 0; i < dims; ++i)
{
paddings.push_back(pads.get<int>(i));
paddings.push_back(pads.get<int>(dims + i));
}
LayerParams padLp;
padLp.name = layerParams.name + "/pad";
padLp.type = "PaddingInt8";
padLp.set("paddings", DictValue::arrayInt(&paddings[0], paddings.size()));
padLp.set("depth", CV_8S);
padLp.set<double>("value", (double)inp_zp);
opencv_onnx::NodeProto proto;
proto.add_input(node_proto.input(0));
proto.add_output(padLp.name);
addLayer(padLp, proto);
node_proto.set_input(0, padLp.name);
bias_arg = addDequantize(bn + "/dequant_bias", {inp[8], inp[1]}, node_proto);
}
}
Mat weights = getBlob(node_proto, 3);
int outCn = weights.size[0];
Mat w_scale = getBlob(node_proto, 4);
CV_Assert(w_scale.total() == 1 || w_scale.total() == outCn);
bool per_channel = w_scale.total() == outCn;
Mat wt_sc = (w_scale.total() == outCn) ? w_scale : Mat(1, outCn, CV_32F, Scalar(w_scale.at<float>(0)));
LayerParams convLp;
convLp.name = bn + "/conv";
convLp.type = "Conv2";
copyAttrs(layerParams, convLp);
Arg conv_out = net.getArg(convLp.name);
node_inputs = has_bias ? std::vector<Arg>{dq_x, dq_w, bias_arg} : std::vector<Arg>{dq_x, dq_w};
node_outputs = {conv_out};
addLayer(convLp, node_proto);
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 6));
int8_t out_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 7));
Mat bias = (ninputs == 9) ? getBlob(node_proto, 8) : Mat::zeros(1, outCn, CV_32S);
Mat weights_2d = weights.reshape(1, outCn);
Mat biasFused(1, outCn, CV_32S);
Mat outputMultiplier(1, outCn, CV_32F);
for (int i = 0; i < outCn; i++)
{
biasFused.at<int>(i) = bias.at<int>(i) - inp_zp*(cv::sum(weights_2d.row(i))[0]);
outputMultiplier.at<float>(i) = (inp_sc * wt_sc.at<float>(i)) / out_sc;
}
layerParams.type = "ConvolutionInt8";
layerParams.set("num_output", outCn);
layerParams.set("input_zeropoint", inp_zp);
layerParams.set("input_scale",inp_sc);
layerParams.set("zeropoints", out_zp);
layerParams.set("scales", out_sc);
layerParams.set("per_channel", per_channel);
layerParams.blobs.push_back(weights);
layerParams.blobs.push_back(biasFused);
layerParams.blobs.push_back(outputMultiplier);
addLayer(layerParams, node_proto);
addQuantize(bn + "/quant_y", conv_out, {inp[6], inp[7]}, out, node_proto);
}
void ONNXImporter2::parseQMatMul(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 8);
CV_Assert(node_proto.input_size() == 8);
if (constBlobs.find(node_proto.input(3)) == constBlobs.end())
CV_Error(Error::StsNotImplemented, "Variable weights is not supported");
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
int firstInpDims = outShapes[node_proto.input(0)].size();
Arg dq_a, dq_b;
dq_a = addDequantize(bn + "/dequant_a", {inp[0], inp[1], inp[2]}, node_proto);
dq_b = addDequantize(bn + "/dequant_b", {inp[3], inp[4], inp[5]}, node_proto);
float inp_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t inp_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
LayerParams mmLp;
mmLp.name = bn + "/matmul";
mmLp.type = "MatMul";
Arg mm_out = net.getArg(mmLp.name);
node_inputs = {dq_a, dq_b};
node_outputs = {mm_out};
addLayer(mmLp, node_proto);
Mat weights = getBlob(node_proto, 3).t();
int outCn = weights.size[0];
int secondInpDims = weights.dims;
Mat w_scale = getBlob(node_proto, 4);
CV_Assert(w_scale.total() == 1 || w_scale.total() == outCn);
bool per_channel = w_scale.total() == outCn ? true : false;
Mat wt_sc = (w_scale.total() == outCn) ? w_scale : Mat(1, outCn, CV_32F, Scalar(w_scale.at<float>(0)));
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 6));
int8_t out_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 7));
Mat bias(1, outCn, CV_32S);
Mat outputMultiplier(1, outCn, CV_32F);
for (int i = 0; i < outCn; i++)
{
bias.at<int>(i) = -inp_zp*(cv::sum(weights.row(i))[0]);
outputMultiplier.at<float>(i) = (inp_sc * wt_sc.at<float>(i)) / out_sc;
}
layerParams.type = "InnerProductInt8";
layerParams.set("num_output", outCn);
layerParams.set("axis", firstInpDims - secondInpDims + 1);
layerParams.set("input_scale", inp_sc);
layerParams.set("input_zeropoint", inp_zp);
layerParams.set("zeropoints", out_zp);
layerParams.set("scales", out_sc);
layerParams.set("per_channel", per_channel);
layerParams.blobs.push_back(weights);
layerParams.blobs.push_back(bias);
layerParams.blobs.push_back(outputMultiplier);
addLayer(layerParams, node_proto);
addQuantize(bn + "/quant_y", mm_out, {inp[6], inp[7]}, out, node_proto);
}
// A * B + C = Y, we require that the dimension of A is [m, k], and the dimension of B is [n, k].
// And the dim of output Y is [m, n]
void ONNXImporter2::parseQGemm(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 8 || ninputs == 9);
layerParams.type = "InnerProductInt8";
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
if (constBlobs.find(node_proto.input(3)) == constBlobs.end())
CV_Error(Error::StsNotImplemented, "Variable weights is not supported");
Arg dq_a, dq_b;
dq_a = addDequantize(bn + "/dequant_a", {inp[0], inp[1], inp[2]}, node_proto);
dq_b = addDequantize(bn + "/dequant_b", {inp[3], inp[4], inp[5]}, node_proto, 0);
Mat weights = getBlob(node_proto, 3);
LayerParams gemmLp;
gemmLp.name = bn + "/gemm";
gemmLp.type = "Gemm";
copyAttrs(layerParams, gemmLp);
Arg gemm_out = net.getArg(gemmLp.name);
node_inputs = (!node_proto.input(6).empty()) ? std::vector<Arg>{dq_a, dq_b, inp[6]}
: std::vector<Arg>{dq_a, dq_b};
node_outputs = {gemm_out};
addLayer(gemmLp, node_proto);
if (!layerParams.get<int>("transB", 0))
{
transpose(weights, weights);
}
CV_Assert(layerParams.get<float>("alpha", 1) == 1.0f);
CV_Assert(layerParams.get<int>("transA", 0) == 0);
int firstInpDims = outShapes[node_proto.input(0)].size();
float inp_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t inp_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
int outCn = weights.size[0];
int secondInpDims = weights.dims;
Mat w_scale = getBlob(node_proto, 4);
CV_Assert(w_scale.total() == 1 || w_scale.total() == outCn);
bool per_channel = w_scale.total() == outCn;
Mat wt_sc = (w_scale.total() == outCn) ? w_scale : Mat(1, outCn, CV_32F, Scalar(w_scale.at<float>(0)));
Mat w_zp = getBlob(node_proto, 5);
int8_t* ptrZp = w_zp.ptr<int8_t>(0);
for (int i = 0; i < w_zp.total(); i++)
{
if (ptrZp[i] != (int8_t)0)
CV_Error(Error::StsUnsupportedFormat, "The zero-point non-zero case of W is not supported!");
}
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 7));
int8_t out_zp = ninputs == 9 ? getScalarFromMat<int8_t>(getBlob(node_proto, 8)) : 0;
Mat bias;
if (constBlobs.find(node_proto.input(6)) != constBlobs.end())
bias = getBlob(node_proto, 6);
if (bias.empty())
bias = Mat::zeros(1, outCn, CV_32S);
Mat biasFused(1, outCn, CV_32S);
Mat outputMultiplier(1, outCn, CV_32F);
for (int i = 0; i < outCn; i++)
{
biasFused.at<int>(i) = bias.at<int>(i) - inp_zp*(cv::sum(weights.row(i))[0]);
outputMultiplier.at<float>(i) = (inp_sc * wt_sc.at<float>(i)) / out_sc;
}
layerParams.type = "InnerProductInt8";
layerParams.set("num_output", outCn);
layerParams.set("axis", firstInpDims - secondInpDims + 1);
layerParams.set("input_scale", inp_sc);
layerParams.set("input_zeropoint", inp_zp);
layerParams.set("scales", out_sc);
layerParams.set("zeropoints", out_zp);
layerParams.set("per_channel", per_channel);
layerParams.blobs.push_back(weights);
layerParams.blobs.push_back(biasFused);
layerParams.blobs.push_back(outputMultiplier);
addLayer(layerParams, node_proto);
std::vector<Arg> q_sc_zp = {inp[7]};
if (ninputs == 9)
q_sc_zp.push_back(inp[8]);
addQuantize(bn + "/quant_y", gemm_out, q_sc_zp, out, node_proto);
}
void ONNXImporter2::parseQEltwise(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
void ONNXImporter2::parseQEltwise(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
opencv_onnx::NodeProto node_proto = node_proto_;
CV_Assert(node_proto.input_size() == 7 || node_proto.input_size() == 8);
std::string op = (node_proto.op_type() == "QLinearAdd") ? "sum" : "prod";
int constId = -1;
for (int i = 0; i < 4; i += 3)
{
if (constBlobs.find(node_proto.input(i)) != constBlobs.end())
constId = i;
}
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 7 || ninputs == 8);
float inp_0_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t inp_0_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
float inp_1_sc = getScalarFromMat<float>(getBlob(node_proto, 4));
int8_t inp_1_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 5));
Arg dq_a, dq_b;
dq_a = addDequantize(bn + "/dequant_a", {inp[0], inp[1], inp[2]}, node_proto);
dq_b = addDequantize(bn + "/dequant_b", {inp[3], inp[4], inp[5]}, node_proto);
// Set 2nd input as the const input
if (constId == 0)
{
cv::swap(inp_0_sc, inp_1_sc);
cv::swap(inp_0_zp, inp_1_zp);
}
LayerParams eltLp;
eltLp.name = bn + "/eltwise";
eltLp.type = "NaryEltwise";
eltLp.set("operation", (node_proto.op_type() == "QLinearAdd") ? "add" : "mul");
Arg elt_out = net.getArg(eltLp.name);
node_inputs = {dq_a, dq_b};
node_outputs = {elt_out};
addLayer(eltLp, node_proto);
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 6));
int8_t out_zp = 0;
if (node_proto.input_size() == 8)
out_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 7));
std::vector<float> inp_scales = {inp_0_sc, inp_1_sc};
std::vector<int8_t> inp_zps = {inp_0_zp, inp_1_zp};
std::vector<float> coeffs;
float offset;
if (op == "sum")
{
coeffs = {inp_scales[0]/out_sc, inp_scales[1]/out_sc};
offset = out_zp - coeffs[0]*inp_zps[0] - coeffs[1]*inp_zps[1];
}
else
{
coeffs = {inp_scales[0]/out_sc, inp_scales[1]};
offset = out_zp;
}
if (constId != -1)
{
Mat blob = getBlob(node_proto, constId);
if (blob.total() == 1)
{
float val = inp_scales[1] * (blob.at<int8_t>(0) - inp_zps[1]);
float scale = inp_scales[0] / out_sc;
if (op == "prod")
scale *= val;
float shift = out_zp - scale*inp_zps[0];
if (op == "sum")
shift += (val/out_sc);
LayerParams rescaleParams;
rescaleParams.name = layerParams.name;
rescaleParams.type = "Requantize";
rescaleParams.set("depth", CV_8S);
rescaleParams.set("scale", scale);
rescaleParams.set("shift", shift);
rescaleParams.set("isEltwise", true);
addLayer(rescaleParams, node_proto);
return;
}
else
{
MatShape inpShape = outShapes[node_proto.input(3 - constId)];
if (blob.dims == 2)
blob = blob.t();
if (shape(blob) == inpShape)
{
LayerParams constParams;
constParams.name = layerParams.name + "/const";
constParams.type = "ConstInt8";
constParams.set("depth", CV_8S);
constParams.set("scales", inp_1_sc);
constParams.set("zeropoints", inp_1_zp);
constParams.blobs.push_back(blob);
int id = net.addLayer(constParams.name, constParams.type, CV_8S, constParams);
layer_id.insert(std::make_pair(constParams.name, LayerInfo(id, 0, CV_8S)));
outShapes[constParams.name] = shape(blob);
node_proto.set_input(constId, constParams.name);
layerParams.type = "EltwiseInt8";
layerParams.set("operation", op);
layerParams.set("coeff", DictValue::arrayReal(coeffs.data(), coeffs.size()));
layerParams.set("offset", offset);
}
else
{
layerParams.type = "ScaleInt8";
layerParams.set("bias_term", op == "sum");
int axis = 1;
for (int i = 0; i < graph_proto->initializer_size(); i++)
{
opencv_onnx::TensorProto tensor_proto = graph_proto->initializer(i);
if (tensor_proto.name() == node_proto.input(constId))
{
axis = inpShape.size() - tensor_proto.dims_size();
break;
}
}
layerParams.set("axis", axis);
blob = blob.reshape(1, 1);
Mat blob_dequantized;
blob.convertTo(blob_dequantized, CV_32F, inp_scales[1], -(inp_scales[1] * inp_zps[1]));
layerParams.blobs.push_back(blob_dequantized);
}
}
}
else if (outShapes[node_proto.input(0)] == outShapes[node_proto.input(3)])
{
layerParams.type = "EltwiseInt8";
layerParams.set("operation", op);
layerParams.set("coeff", DictValue::arrayReal(coeffs.data(), coeffs.size()));
layerParams.set("offset", offset);
}
else
{
layerParams.type = "ScaleInt8";
layerParams.set("bias_term", op == "sum");
}
layerParams.set("input_scales", DictValue::arrayReal(inp_scales.data(), inp_scales.size()));
layerParams.set("input_zeropoints", DictValue::arrayInt(inp_zps.data(), inp_zps.size()));
layerParams.set("scales", out_sc);
layerParams.set("zeropoints", out_zp);
addLayer(layerParams, node_proto);
std::vector<Arg> q_sc_zp = {inp[6]};
if (ninputs == 8)
q_sc_zp.push_back(inp[7]);
addQuantize(bn + "/quant_y", elt_out, q_sc_zp, out, node_proto);
}
void ONNXImporter2::parseQLeakyRelu(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
CV_Assert(node_proto.input_size() == 4 || node_proto.input_size() == 5);
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 4 || ninputs == 5);
float slope = layerParams.get<float>("alpha");
float inp_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t inp_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 3));
int8_t out_zp = node_proto.input_size() == 4 ? 0 : getScalarFromMat<int8_t>(getBlob(node_proto, 4));
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
Mat lookUpTable(1, 256, CV_8S);
int8_t* table = lookUpTable.ptr<int8_t>();
for (int i = -128; i < 128; i++)
{
float x = inp_sc*(i - inp_zp);
float y = x >= 0.f ? x : slope*x;
int quantized = out_zp + cvRound(y/out_sc);
table[i+128] = saturate_cast<int8_t>(quantized);
}
Arg dq_x;
dq_x = addDequantize(bn + "/dequant_x", {inp[0], inp[1], inp[2]}, node_proto);
layerParams.type = "ReLUInt8";
layerParams.set("input_scale", inp_sc);
layerParams.set("input_zeropoint", inp_zp);
layerParams.set("scales", out_sc);
layerParams.set("zeropoints", out_zp);
layerParams.set("slope", slope);
layerParams.blobs.push_back(lookUpTable);
addLayer(layerParams, node_proto);
LayerParams reluLp;
reluLp.name = bn + "/leaky_relu";
reluLp.type = "ReLU";
reluLp.set("negative_slope", layerParams.get<float>("alpha", 0.01f));
Arg relu_out = net.getArg(reluLp.name);
node_inputs = {dq_x};
node_outputs = {relu_out};
addLayer(reluLp, node_proto);
std::vector<Arg> q_sc_zp = {inp[3]};
if (ninputs == 5)
q_sc_zp.push_back(inp[4]);
addQuantize(bn + "/quant_y", relu_out, q_sc_zp, out, node_proto);
}
void ONNXImporter2::parseQSigmoid(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
CV_Assert(node_proto.input_size() == 4 || node_proto.input_size() == 5);
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 4 || ninputs == 5);
float inp_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t inp_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 3));
int8_t out_zp = node_proto.input_size() == 4 ? 0 : getScalarFromMat<int8_t>(getBlob(node_proto, 4));
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
Mat lookUpTable(1, 256, CV_8S);
int8_t* table = lookUpTable.ptr<int8_t>();
for (int i = -128; i < 128; i++)
{
float x = inp_sc*(i - inp_zp);
float y = 1.f/(1.f + std::exp(-x));
int quantized = out_zp + cvRound(y/out_sc);
table[i+128] = saturate_cast<int8_t>(quantized);
}
Arg dq_x;
dq_x = addDequantize(bn + "/dequant_x", {inp[0], inp[1], inp[2]}, node_proto);
layerParams.type = "SigmoidInt8";
layerParams.set("input_scale", inp_sc);
layerParams.set("input_zeropoint", inp_zp);
layerParams.set("scales", out_sc);
layerParams.set("zeropoints", out_zp);
layerParams.blobs.push_back(lookUpTable);
addLayer(layerParams, node_proto);
LayerParams sigLp;
sigLp.name = bn + "/sigmoid";
sigLp.type = "Sigmoid";
Arg sig_out = net.getArg(sigLp.name);
node_inputs = {dq_x};
node_outputs = {sig_out};
addLayer(sigLp, node_proto);
std::vector<Arg> q_sc_zp = {inp[3]};
if (ninputs == 5)
q_sc_zp.push_back(inp[4]);
addQuantize(bn + "/quant_y", sig_out, q_sc_zp, out, node_proto);
}
void ONNXImporter2::parseQAvgPool(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
CV_Assert(node_proto.input_size() == 4 || node_proto.input_size() == 5);
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 4 || ninputs == 5);
float inp_sc = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t inp_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
float out_sc = getScalarFromMat<float>(getBlob(node_proto, 3));
int8_t out_zp = node_proto.input_size() == 4 ? 0 : getScalarFromMat<int8_t>(getBlob(node_proto, 4));
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
layerParams.type = "PoolingInt8";
layerParams.set("pool", "ave");
layerParams.set("global_pooling", node_proto.op_type() == "QLinearGlobalAveragePool");
layerParams.set("multiplier", inp_sc/out_sc);
layerParams.set("input_scale", inp_sc);
layerParams.set("input_zeropoint", inp_zp);
layerParams.set("scales", out_sc);
layerParams.set("zeropoints", out_zp);
addLayer(layerParams, node_proto);
Arg dq_x;
dq_x = addDequantize(bn + "/dequant_x", {inp[0], inp[1], inp[2]}, node_proto);
LayerParams poolLp;
poolLp.name = bn + "/avg_pool";
poolLp.type = "Pooling";
poolLp.set("pool", "ave");
poolLp.set("global_pooling", node_proto.op_type() == "QLinearGlobalAveragePool");
copyAttrs(layerParams, poolLp);
Arg pool_out = net.getArg(poolLp.name);
node_inputs = {dq_x};
node_outputs = {pool_out};
addLayer(poolLp, node_proto);
std::vector<Arg> q_sc_zp = {inp[3]};
if (ninputs == 5)
q_sc_zp.push_back(inp[4]);
addQuantize(bn + "/quant_y", pool_out, q_sc_zp, out, node_proto);
}
void ONNXImporter2::parseQConcat(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
void ONNXImporter2::parseQConcat(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
opencv_onnx::NodeProto node_proto = node_proto_;
layerParams.type = "ConcatInt8";
int num_inputs = node_proto.input_size();
int ninputs = node_proto.input_size();
CV_Assert(ninputs >= 5 && (ninputs - 2) % 3 == 0);
float out_scale = getScalarFromMat<float>(getBlob(node_proto, 0));
int8_t out_zp = getScalarFromMat<int8_t>(getBlob(node_proto, 1));
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
for (int i = 2; i < num_inputs; i += 3)
std::vector<Arg> dq_outs;
int n = (ninputs - 2) / 3;
for (int i = 0; i < n; i++)
{
float inp_scale = getScalarFromMat<float>(getBlob(node_proto, i + 1));
int8_t inp_zp = getScalarFromMat<int8_t>(getBlob(node_proto, i + 2));
if (inp_scale != out_scale || inp_zp != out_zp)
{
float scale = inp_scale/out_scale;
float shift = out_zp - scale*inp_zp;
if (constBlobs.find(node_proto.input(i)) != constBlobs.end())
{
Mat blob = getBlob(node_proto, i);
Mat blob_rescaled;
blob.convertTo(blob_rescaled, CV_8S, scale, shift);
constBlobs[node_proto.input(i)] = blob_rescaled;
}
else
{
LayerParams rescaleParams;
rescaleParams.name = node_proto.input(i) + "/rescale";
rescaleParams.type = "Requantize";
rescaleParams.set("depth", CV_8S);
rescaleParams.set("scale", scale);
rescaleParams.set("shift", shift);
rescaleParams.set("isEltwise", false);
opencv_onnx::NodeProto proto;
proto.add_input(node_proto.input(i));
proto.add_output(rescaleParams.name);
addLayer(rescaleParams, proto);
node_proto.set_input(i, rescaleParams.name);
}
}
int b = 2 + i * 3;
Arg dq = addDequantize(bn + "/dequant_" + std::to_string(i),
{inp[b], inp[b + 1], inp[b + 2]}, node_proto);
dq_outs.push_back(dq);
}
bool hasVariableInps = false;
for (int i = 2; i < num_inputs; i += 3)
{
if (layer_id.find(node_proto.input(i)) != layer_id.end())
{
hasVariableInps = true;
break;
}
}
LayerParams concatLp;
concatLp.name = bn + "/concat";
concatLp.type = "Concat";
concatLp.set("axis", layerParams.get<int>("axis", 1));
Arg concat_out = net.getArg(concatLp.name);
node_inputs = dq_outs;
node_outputs = {concat_out};
addLayer(concatLp, node_proto);
if (!hasVariableInps)
{
std::vector<Mat> inputs, concatenated;
MatShape inputShape;
for (size_t i = 2; i < num_inputs; i += 3)
{
Mat blob = getBlob(node_proto, i);
if (blob.size.dims() > inputShape.size())
{
inputShape = shape(blob);
}
inputs.push_back(blob);
}
int axis = layerParams.get<int>("axis", 1);
for (size_t i = 0; i < inputs.size(); ++i)
{
MatShape targetShape = inputShape;
targetShape[axis] = shape(inputs[i])[axis];
CV_CheckEQ(total(targetShape), total(shape(inputs[i])), "");
inputs[i] = inputs[i].reshape(0, targetShape);
}
runLayer(layerParams, inputs, concatenated);
CV_Assert(concatenated.size() == 1);
addConstant(layerParams.name, concatenated[0]);
return;
}
else
{
for (int i = 2; i < num_inputs; i += 3)
{
if (constBlobs.find(node_proto.input(i)) != constBlobs.end())
{
LayerParams constParams;
constParams.name = node_proto.input(i);
constParams.type = "ConstInt8";
constParams.blobs.push_back(getBlob(node_proto, i));
constParams.set("depth", CV_8S);
opencv_onnx::NodeProto proto;
proto.add_output(constParams.name);
addLayer(constParams, proto);
}
}
}
layerParams.set("scales", out_scale);
layerParams.set("zeropoints", out_zp);
addLayer(layerParams, node_proto);
addQuantize(bn + "/quant_y", concat_out, {inp[0], inp[1]}, out, node_proto);
}
void ONNXImporter2::parseQSoftmax(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
CV_CheckEQ(node_proto.input_size(), 5, "DNN/ONNX: QLinearSoftmax requires 5 inputs, X, X_scale, X_zero_point, Y_scale, Y_zero_point");
CV_CheckEQ(node_proto.input_size(), 5, "DNN/ONNX: QLinearSoftmax requires 5 inputs");
int opset = layerParams.get<int>("opset");
if (opset < 13) {
layerParams.set("coerced_2d", true);
std::string bn = layerParams.name;
std::vector<Arg> inp = node_inputs;
std::vector<Arg> out = node_outputs;
Arg dq_x;
dq_x = addDequantize(bn + "/dequant_x", {inp[0], inp[1], inp[2]}, node_proto);
LayerParams smLp;
smLp.name = bn + "/softmax";
smLp.type = "Softmax";
if (layerParams.has("axis"))
smLp.set("axis", layerParams.get<int>("axis"));
if (layerParams.has("opset"))
{
int opset = layerParams.get<int>("opset");
smLp.set("opset", opset);
if (opset < 13)
smLp.set("coerced_2d", true);
}
Arg sm_out = net.getArg(smLp.name);
node_inputs = {dq_x};
node_outputs = {sm_out};
addLayer(smLp, node_proto);
float x_scale = getScalarFromMat<float>(getBlob(node_proto, 1));
int8_t x_zero_point = getScalarFromMat<int8_t>(getBlob(node_proto, 2));
float y_scale = getScalarFromMat<float>(getBlob(node_proto, 3));
int8_t y_zero_point = getScalarFromMat<int8_t>(getBlob(node_proto, 4));
layerParams.type = "SoftmaxInt8";
// layerParams also has "axis" and "opset" attrs
layerParams.set("input_scale", x_scale);
layerParams.set("input_zeropoint", x_zero_point);
layerParams.set("scales", y_scale);
layerParams.set("zeropoints", y_zero_point);
addLayer(layerParams, node_proto);
}*/
addQuantize(bn + "/quant_y", sm_out, {inp[3], inp[4]}, out, node_proto);
}
void ONNXImporter2::parseRotaryEmbedding(LayerParams& params, const opencv_onnx::NodeProto& node_proto) {
int i, n_inputs = node_proto.input_size();
@@ -2819,8 +2589,8 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
// ai.onnx: opset 10+
dispatch["DequantizeLinear"] = &ONNXImporter2::parseDequantizeLinear;
dispatch["QuantizeLinear"] = &ONNXImporter2::parseQuantizeLinear;
//dispatch["QLinearConv"] = &ONNXImporter2::parseQConv;
//dispatch["QLinearMatMul"] = &ONNXImporter2::parseQMatMul;
dispatch["QLinearConv"] = &ONNXImporter2::parseQConv;
dispatch["QLinearMatMul"] = &ONNXImporter2::parseQMatMul;
// com.microsft: This operator is added for compatibility via onnx graph simplifier.
// Opset domain cannot be modified from onnx_graph_simplifier.cpp so this
@@ -2836,14 +2606,15 @@ void ONNXImporter2::buildDispatchMap_COM_MICROSOFT()
{
DispatchMap dispatch;
// BUG: https://github.com/opencv/opencv/issues/26310
//dispatch["QLinearAdd"] = dispatch["QLinearMul"] = &ONNXImporter2::parseQEltwise;
//dispatch["QLinearAveragePool"] = dispatch["QLinearGlobalAveragePool"] = &ONNXImporter2::parseQAvgPool;
//dispatch["QLinearLeakyRelu"] = &ONNXImporter2::parseQLeakyRelu;
//dispatch["QLinearSigmoid"] = &ONNXImporter2::parseQSigmoid;
//dispatch["QLinearConcat"] = &ONNXImporter2::parseQConcat;
//dispatch["QGemm"] = &ONNXImporter2::parseQGemm;
//dispatch["QLinearSoftmax"] = &ONNXImporter2::parseQSoftmax;
dispatch["QLinearAdd"] = dispatch["QLinearMul"] = &ONNXImporter2::parseQEltwise;
dispatch["QLinearAveragePool"] = dispatch["QLinearGlobalAveragePool"] = &ONNXImporter2::parseQAvgPool;
dispatch["QLinearLeakyRelu"] = &ONNXImporter2::parseQLeakyRelu;
dispatch["QLinearSigmoid"] = &ONNXImporter2::parseQSigmoid;
dispatch["QLinearConcat"] = &ONNXImporter2::parseQConcat;
dispatch["QLinearConv"] = &ONNXImporter2::parseQConv;
dispatch["QLinearMatMul"] = &ONNXImporter2::parseQMatMul;
dispatch["QGemm"] = &ONNXImporter2::parseQGemm;
dispatch["QLinearSoftmax"] = &ONNXImporter2::parseQSoftmax;
dispatch["Attention"] = &ONNXImporter2::parseAttention;
domain_dispatch_map[str_domain_com_microsoft] = dispatch;
@@ -1840,11 +1840,11 @@ CASE(test_prelu_broadcast_expanded)
CASE(test_prelu_example_expanded)
SKIP;
CASE(test_qlinearconv)
// no filter
SKIP;
CASE(test_qlinearmatmul_2D)
// no filter
SKIP;
CASE(test_qlinearmatmul_3D)
// no filter
SKIP;
CASE(test_quantizelinear)
SKIP;
CASE(test_quantizelinear_axis)
@@ -739,3 +739,6 @@
"test_eyelike_populate_off_main_diagonal",
"test_eyelike_with_dtype",
"test_eyelike_without_dtype",
"test_qlinearconv",
"test_qlinearmatmul_2D",
"test_qlinearmatmul_3D",
@@ -368,14 +368,11 @@
"test_optional_has_element_tensor_input",
"test_prelu_broadcast", // Issue::Parser:Blob slope not found in const blobs in function 'getBlob' (weights are required as inputs)
"test_prelu_example", // ---- same as above ---
"test_qlinearconv", // Issue::Parser: Blob x_scale not found in const blobs in function 'getBlob' (weights are required as inputs)
"test_qlinearmatmul_2D", // Issue:: Parser: Variable weights is not supported in function 'parseQMatMul'
"test_qlinearmatmul_2D_int8_float16",
"test_qlinearmatmul_2D_int8_float16", // Float output QLinearMatMul variants not supported
"test_qlinearmatmul_2D_int8_float32",
"test_qlinearmatmul_2D_uint8_float16",
"test_qlinearmatmul_2D_uint8_float32",
"test_qlinearmatmul_3D", // ---- same as above ---
"test_qlinearmatmul_3D_int8_float16",
"test_qlinearmatmul_3D_int8_float16", // Float output QLinearMatMul variants not supported
"test_qlinearmatmul_3D_int8_float32",
"test_qlinearmatmul_3D_uint8_float16",
"test_qlinearmatmul_3D_uint8_float32",
+11 -7
View File
@@ -2189,11 +2189,15 @@ TEST_P(Test_ONNX_layers, Gemm_External_Data)
TEST_P(Test_ONNX_layers, Quantized_MatMul_Variable_Weights)
{
// Unsupported
EXPECT_THROW(
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
testONNXModels("quantized_matmul_variable_inputs");
}, cv::Exception);
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
testONNXModels("quantized_matmul_variable_inputs", npy, 1.3, 1.3);
}
TEST_P(Test_ONNX_layers, Quantized_Eltwise)
@@ -2282,7 +2286,7 @@ TEST_P(Test_ONNX_layers, Quantized_Concat)
TEST_P(Test_ONNX_layers, Quantized_Constant)
{
testONNXModels("quantized_constant", npy, 0.008, 0.02);
testONNXModels("quantized_constant", npy, 0.02, 0.06);
}
TEST_P(Test_ONNX_layers, OutputRegistration)
@@ -2294,8 +2298,8 @@ TEST_P(Test_ONNX_layers, QLinearSoftmax)
{
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH);
testONNXModels("qlinearsoftmax_v11", npy, 0.002, 0.002); // 2D coerced
testONNXModels("qlinearsoftmax_v13", npy, 0.002, 0.002);
testONNXModels("qlinearsoftmax_v11", npy, 0.08, 0.16); // 2D coerced
testONNXModels("qlinearsoftmax_v13", npy, 0.08, 0.16);
}
TEST_P(Test_ONNX_layers, PriorBox_ONNX)