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

Merge pull request #28595 from abhishek-gola:qdq_support

* added qdq fusion

* Added VNNI optimizations
This commit is contained in:
Abhishek Gola
2026-03-31 17:46:09 +05:30
committed by GitHub
parent 44bf6a2db3
commit e59506bbf5
13 changed files with 1715 additions and 98 deletions
@@ -1153,6 +1153,10 @@ CV__DNN_INLINE_NS_BEGIN
class CV_EXPORTS ActivationLayerInt8 : public ActivationLayer
{
public:
int input_zp, output_zp;
float input_sc, output_sc;
Mat activationLUT;
static Ptr<ActivationLayerInt8> create(const LayerParams &params);
};
@@ -1200,6 +1204,13 @@ CV__DNN_INLINE_NS_BEGIN
class CV_EXPORTS EltwiseLayerInt8 : public Layer
{
public:
std::vector<float> coeffs;
std::vector<int> zeropoints;
std::vector<float> scales;
float output_sc;
int output_zp;
float offset;
static Ptr<EltwiseLayerInt8> create(const LayerParams &params);
};
+816
View File
@@ -0,0 +1,816 @@
// 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 "net_impl.hpp"
namespace cv { namespace dnn {
CV__DNN_INLINE_NS_BEGIN
using std::vector;
using std::string;
typedef std::pair<int, int> int_pair;
typedef std::pair<int, Arg> int_arg_pair;
struct ModelFusionQDQ
{
ModelFusionQDQ(Net::Impl* netimpl_) : netimpl(netimpl_) {}
void fuse()
{
int i, niter = 10;
netimpl->useCounts(usecounts);
for (i = 0; i < niter; i++) {
bool fused_any = fuseGraph(netimpl->mainGraph);
if (!fused_any)
break;
}
}
template<typename _LayerType> _LayerType*
getLayer(std::vector<Ptr<Layer> >& newprog, int op_idx) const
{
return op_idx >= 0 ? dynamic_cast<_LayerType*>(newprog.at(op_idx).get()) : 0;
}
LayerParams makeLayerParamsFromOriginal(const Layer* layer, const String& newType) const
{
LayerParams params;
int lid = netimpl->getLayerId(layer->name);
if (lid >= 0)
params = netimpl->getLayerData(lid).params;
params.name = layer->name;
params.type = newType;
return params;
}
Ptr<Layer> createFusedLayer(const LayerParams& src) const
{
LayerParams params = src;
return LayerFactory::createLayerInstance(params.type, params);
}
template<typename LayerT>
bool getQdqPatternContext(Layer* layer_ptr,
size_t ninputs,
const std::vector<Arg>& inputs,
const std::vector<int>& producer_of,
std::vector<Ptr<Layer> >& newprog,
Arg& q_data_in,
Arg& out_scale,
Arg& out_zp,
int& mid_layer_idx,
LayerT*& mid_layer) const
{
QuantizeLinearLayer* ql = dynamic_cast<QuantizeLinearLayer*>(layer_ptr);
if (!(ql && ninputs == 3 && usecounts.at(inputs[0].idx) == 1))
return false;
q_data_in = inputs[0];
out_scale = inputs[1];
out_zp = inputs[2];
mid_layer_idx = producer_of.at(q_data_in.idx);
mid_layer = getLayer<LayerT>(newprog, mid_layer_idx);
return mid_layer != 0;
}
bool fuseGraph(Ptr<Graph>& graph)
{
vector<Arg> removed_args;
bool modified = false;
const std::vector<Ptr<Layer> >& prog = graph->prog();
size_t i, nargs = netimpl->args.size(), nops = prog.size();
std::vector<int> producer_of(nargs, -1);
std::vector<Ptr<Layer> > newprog;
std::vector<Arg> fused_inputs;
for (i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
Layer* layer_ptr = (Layer*)layer.get();
int fused_layer_idx = -1;
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
if (subgraphs) {
for (Ptr<Graph>& g: *subgraphs) {
if (fuseGraph(g))
modified = true;
}
}
const std::vector<Arg>& inputs = layer->inputs;
const std::vector<Arg>& outputs = layer->outputs;
size_t ninputs = inputs.size();
removed_args.clear();
fused_inputs.clear(); // leave it empty to re-use original fused node inputs as-is.
for(;;) {
Arg q_data_in, out_scale, out_zp;
int add_layer_idx = -1;
NaryEltwiseLayer* add = 0;
if (getQdqPatternContext<NaryEltwiseLayer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in, out_scale, out_zp,
add_layer_idx, add) &&
add && add->inputs.size() >= 2) {
vector<DequantizeLinearLayer*> dq_ptrs;
vector<int> dq_prog_indices;
vector<Arg> int8_inputs;
for (size_t k = 0; k < add->inputs.size(); k++) {
const Arg& add_inp = add->inputs[k];
int dq_idx = producer_of.at(add_inp.idx);
DequantizeLinearLayer* dq =
getLayer<DequantizeLinearLayer>(newprog, dq_idx);
if (!dq || dq->inputs.size() < 3 ||
usecounts.at(add_inp.idx) != 1) {
break;
}
dq_ptrs.push_back(dq);
dq_prog_indices.push_back(dq_idx);
int8_inputs.push_back(dq->inputs[0]); // int8 quantized tensor
}
const int eltwise_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
const bool eltwise_out_int8 = (eltwise_out_type == CV_8S || eltwise_out_type == CV_8U);
if (dq_ptrs.size() == add->inputs.size() && eltwise_out_int8) {
// Read per-input scale and zero-point from the DQ const args.
vector<float> in_scales(2);
vector<int> in_zps(2);
bool eltwise_in_int8 = true;
for (int k = 0; k < 2; k++) {
int inp_type = netimpl->argData(dq_ptrs[k]->inputs[0]).type;
eltwise_in_int8 = eltwise_in_int8 && (inp_type == CV_8S || inp_type == CV_8U);
in_scales[k] = netimpl->argTensor(dq_ptrs[k]->inputs[1]).at<float>(0);
const Mat& zp_m = netimpl->argTensor(dq_ptrs[k]->inputs[2]);
in_zps[k] = zp_m.depth() == CV_8U
? (int)zp_m.at<uint8_t>(0)
: (int)zp_m.at<int8_t>(0);
}
if (!eltwise_in_int8)
break;
float out_scale_val = netimpl->argTensor(out_scale).at<float>(0);
const Mat& elt_out_zp_m = netimpl->argTensor(out_zp);
int out_zp_val = elt_out_zp_m.depth() == CV_8U
? (int)elt_out_zp_m.at<uint8_t>(0)
: (int)elt_out_zp_m.at<int8_t>(0);
LayerParams eltwiseParams = makeLayerParamsFromOriginal(add, "EltwiseInt8");
eltwiseParams.blobs.clear();
Ptr<Layer> eltwiseInt8 = createFusedLayer(eltwiseParams);
if (!eltwiseInt8.empty()) {
auto* elt = dynamic_cast<EltwiseLayerInt8*>(eltwiseInt8.get());
CV_Assert(elt);
elt->scales = in_scales;
elt->zeropoints = in_zps;
elt->output_sc = out_scale_val;
elt->output_zp = out_zp_val;
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>();
break;
}
}
}
int relu_layer_idx = -1;
ReLULayer* relu = 0;
if (getQdqPatternContext<ReLULayer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in, out_scale, out_zp,
relu_layer_idx, relu) &&
relu->inputs.size() == 1) {
Arg relu_in = relu->inputs[0];
int dq_idx = producer_of.at(relu_in.idx);
DequantizeLinearLayer* dq = getLayer<DequantizeLinearLayer>(newprog, dq_idx);
const int relu_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
const bool relu_out_int8 = (relu_out_type == CV_8S || relu_out_type == CV_8U);
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 &&
relu_in_int8 && relu_out_int8 &&
usecounts.at(relu_in.idx) == 1) {
const float inp_sc = netimpl->argTensor(dq->inputs[1]).at<float>(0);
const Mat& relu_zp_m = netimpl->argTensor(dq->inputs[2]);
const int inp_zp = relu_zp_m.depth() == CV_8U
? (int)relu_zp_m.at<uint8_t>(0)
: (int)relu_zp_m.at<int8_t>(0);
const float out_sc = netimpl->argTensor(out_scale).at<float>(0);
const Mat& out_zp_relu_m = netimpl->argTensor(out_zp);
const int out_zp_i = out_zp_relu_m.depth() == CV_8U
? (int)out_zp_relu_m.at<uint8_t>(0)
: (int)out_zp_relu_m.at<int8_t>(0);
if (inp_sc > 0.f && out_sc > 0.f) {
const bool isU8 = (relu_in_type == CV_8U);
Mat lookUpTable(1, 256, isU8 ? CV_8U : CV_8S);
if (isU8) {
uint8_t* table = lookUpTable.ptr<uint8_t>();
for (int t = 0; t < 256; t++) {
float x = inp_sc * (t - inp_zp);
float y = std::max(0.0f, x);
int quantized = out_zp_i + cvRound(y / out_sc);
table[t] = saturate_cast<uint8_t>(quantized);
}
} else {
int8_t* table = lookUpTable.ptr<int8_t>();
for (int t = -128; t < 128; t++) {
float x = inp_sc * (t - inp_zp);
float y = std::max(0.0f, x);
int quantized = out_zp_i + cvRound(y / out_sc);
table[t + 128] = saturate_cast<int8_t>(quantized);
}
}
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;
}
}
}
}
// Compound pattern: DQ, DQ -> Add -> ReLU -> QuantizeLinear
// Common in ResNet residual blocks. Fuses into EltwiseInt8 with activation LUT.
{
int relu_layer_idx2 = -1;
ReLULayer* relu2 = 0;
Arg q_data_in2, out_scale2, out_zp2;
if (getQdqPatternContext<ReLULayer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in2, out_scale2, out_zp2,
relu_layer_idx2, relu2) &&
relu2->inputs.size() == 1) {
Arg relu_in2 = relu2->inputs[0];
int add_idx2 = producer_of.at(relu_in2.idx);
NaryEltwiseLayer* add2 = getLayer<NaryEltwiseLayer>(newprog, add_idx2);
if (add2 && add2->inputs.size() >= 2 &&
usecounts.at(relu_in2.idx) == 1) {
vector<DequantizeLinearLayer*> dq_ptrs2;
vector<int> dq_prog_indices2;
vector<Arg> int8_inputs2;
for (size_t k = 0; k < add2->inputs.size(); k++) {
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 &&
usecounts.at(add_inp.idx) == 1) {
dq_ptrs2.push_back(dq2);
dq_prog_indices2.push_back(dq_idx2);
int8_inputs2.push_back(dq2->inputs[0]);
} else {
int arg_type = netimpl->argData(add_inp).type;
if (arg_type == CV_8S || arg_type == CV_8U) {
dq_ptrs2.push_back(nullptr);
dq_prog_indices2.push_back(-1);
int8_inputs2.push_back(add_inp);
} else {
break; // not int8, can't fuse
}
}
}
const int elt_out_type2 = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
const bool elt_out_int82 = (elt_out_type2 == CV_8S || elt_out_type2 == CV_8U);
if (int8_inputs2.size() == add2->inputs.size() && elt_out_int82) {
vector<float> in_scales2(2);
vector<int> in_zps2(2);
bool elt_in_int82 = true;
for (int k = 0; k < 2; k++) {
if (dq_ptrs2[k]) {
int it = netimpl->argData(dq_ptrs2[k]->inputs[0]).type;
elt_in_int82 = elt_in_int82 && (it == CV_8S || it == CV_8U);
in_scales2[k] = netimpl->argTensor(dq_ptrs2[k]->inputs[1]).at<float>(0);
const Mat& zp_m2 = netimpl->argTensor(dq_ptrs2[k]->inputs[2]);
in_zps2[k] = zp_m2.depth() == CV_8U
? (int)zp_m2.at<uint8_t>(0)
: (int)zp_m2.at<int8_t>(0);
} else {
int prod_idx = producer_of.at(add2->inputs[k].idx);
Layer* prod = prod_idx >= 0 && !newprog[prod_idx].empty()
? newprog[prod_idx].get() : nullptr;
ConvolutionLayerInt8* ci = prod ? dynamic_cast<ConvolutionLayerInt8*>(prod) : nullptr;
EltwiseLayerInt8* ei = prod ? dynamic_cast<EltwiseLayerInt8*>(prod) : nullptr;
InnerProductLayerInt8* fi = prod ? dynamic_cast<InnerProductLayerInt8*>(prod) : nullptr;
if (ci) { in_scales2[k] = ci->output_sc; in_zps2[k] = ci->output_zp; }
else if (ei) { in_scales2[k] = ei->output_sc; in_zps2[k] = ei->output_zp; }
else if (fi) { in_scales2[k] = fi->output_sc; in_zps2[k] = fi->output_zp; }
else { elt_in_int82 = false; }
}
}
if (elt_in_int82) {
float out_sc2 = netimpl->argTensor(out_scale2).at<float>(0);
const Mat& out_zp_m2 = netimpl->argTensor(out_zp2);
int out_zp_val2 = out_zp_m2.depth() == CV_8U
? (int)out_zp_m2.at<uint8_t>(0)
: (int)out_zp_m2.at<int8_t>(0);
if (out_sc2 > 0.f) {
LayerParams eltParams = makeLayerParamsFromOriginal(add2, "EltwiseInt8");
eltParams.blobs.clear();
Ptr<Layer> eltInt8 = createFusedLayer(eltParams);
if (!eltInt8.empty()) {
auto* elt = dynamic_cast<EltwiseLayerInt8*>(eltInt8.get());
CV_Assert(elt);
elt->scales = in_scales2;
elt->zeropoints = in_zps2;
elt->output_sc = out_sc2;
elt->output_zp = out_zp_val2;
const bool isU8 = (elt_out_type2 == CV_8U);
Mat lut(1, 256, isU8 ? CV_8U : CV_8S);
if (isU8) {
uint8_t* tbl = lut.ptr<uint8_t>();
for (int t = 0; t < 256; t++) {
float x = out_sc2 * (t - out_zp_val2);
float y = std::max(0.0f, x);
tbl[t] = saturate_cast<uint8_t>(out_zp_val2 + cvRound(y / out_sc2));
}
} else {
int8_t* tbl = lut.ptr<int8_t>();
for (int t = -128; t < 128; t++) {
float x = out_sc2 * (t - out_zp_val2);
float y = std::max(0.0f, x);
tbl[t + 128] = saturate_cast<int8_t>(out_zp_val2 + cvRound(y / out_sc2));
}
}
LayerParams reluActParams;
reluActParams.name = relu2->name;
reluActParams.type = "ReLUInt8";
Ptr<Layer> reluAct = createFusedLayer(reluActParams);
if (!reluAct.empty()) {
auto* reluActLayer = dynamic_cast<ActivationLayerInt8*>(reluAct.get());
if (reluActLayer) {
reluActLayer->input_sc = out_sc2;
reluActLayer->input_zp = out_zp_val2;
reluActLayer->output_sc = out_sc2;
reluActLayer->output_zp = out_zp_val2;
reluActLayer->activationLUT = lut;
eltInt8->setActivation(reluAct.dynamicCast<ActivationLayer>());
}
}
fused_layer_idx = add_idx2;
newprog[add_idx2] = eltInt8;
newprog[relu_layer_idx2] = Ptr<Layer>();
fused_inputs.swap(int8_inputs2);
removed_args.push_back(q_data_in2);
removed_args.push_back(relu_in2);
for (const Arg& add_inp : add2->inputs)
removed_args.push_back(add_inp);
for (int dq_prog_idx : dq_prog_indices2) {
if (dq_prog_idx >= 0)
newprog[dq_prog_idx] = Ptr<Layer>();
}
break;
}
}
}
}
}
}
}
Arg out_scale_arg, out_zp_arg;
int conv_layer_idx = -1;
Conv2Layer* conv = 0;
if (getQdqPatternContext<Conv2Layer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in, out_scale_arg, out_zp_arg,
conv_layer_idx, conv) &&
(conv->inputs.size() == 2 || conv->inputs.size() == 3)) {
const Arg conv_x = conv->inputs[0];
const Arg conv_w = conv->inputs[1];
const int dq_x_idx = producer_of.at(conv_x.idx);
const int dq_w_idx = producer_of.at(conv_w.idx);
DequantizeLinearLayer* dq_x = getLayer<DequantizeLinearLayer>(newprog, dq_x_idx);
DequantizeLinearLayer* dq_w = getLayer<DequantizeLinearLayer>(newprog, dq_w_idx);
// Allow usecounts > 1 for conv input (shared DQ output at stage transitions)
// The int8 data (DQ's input[0]) can be shared safely.
if (dq_x && dq_w &&
dq_x->inputs.size() >= 3 && dq_w->inputs.size() >= 3 &&
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);
int inp_zp = 0, out_zp = 0;
const Mat& x_zp_m_read = netimpl->argTensor(dq_x->inputs[2]);
if (x_zp_m_read.depth() == CV_8S)
inp_zp = (int)x_zp_m_read.at<int8_t>(0);
else
inp_zp = (int)x_zp_m_read.at<uint8_t>(0);
const Mat& out_zp_m = netimpl->argTensor(out_zp_arg);
if (out_zp_m.depth() == CV_8S)
out_zp = (int)out_zp_m.at<int8_t>(0);
else if (out_zp_m.depth() == CV_8U)
out_zp = (int)out_zp_m.at<uint8_t>(0);
else
out_zp = out_zp_m.at<int>(0);
if (!(inp_sc > 0.f && out_sc > 0.f))
break;
Mat w_q = netimpl->argTensor(dq_w->inputs[0]);
Mat w_sc_m = netimpl->argTensor(dq_w->inputs[1]);
Mat w_zp_m = netimpl->argTensor(dq_w->inputs[2]);
const Mat& x_zp_m = netimpl->argTensor(dq_x->inputs[2]);
if (!w_q.empty() && w_q.depth() == CV_8S && w_q.dims >= 3) {
const int outCn = w_q.size[0];
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;
bool all_wzp_zero = true;
if (w_zp_m.total() > 1 && w_zp_m.total() != (size_t)outCn)
all_wzp_zero = false;
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;
}
bool symmetric_pads = true;
size_t npads = conv->pads.size();
size_t ndims_pad = npads / 2;
for (size_t d = 0; d < ndims_pad && symmetric_pads; d++) {
if (conv->pads[d] != conv->pads[d + ndims_pad])
symmetric_pads = false;
}
if (all_wzp_zero && symmetric_pads) {
Mat bias = Mat::zeros(1, outCn, CV_32S);
bool biasOk = true;
int dq_bias_idx = -1;
if (conv->inputs.size() == 3) {
if (netimpl->isConstArg(conv->inputs[2])) {
Mat b = netimpl->argTensor(conv->inputs[2]);
if (b.empty() || b.total() != (size_t)outCn) {
biasOk = false;
} else if (b.depth() == CV_32S) {
bias = b.reshape(1, 1);
} else if (b.depth() == CV_32F || b.depth() == CV_64F) {
Mat b1 = b.reshape(1, 1);
for (int oc = 0; oc < outCn; oc++) {
const float b_real = b1.depth() == CV_32F
? b1.at<float>(oc)
: (float)b1.at<double>(oc);
const float denom = inp_sc * wt_sc.at<float>(oc);
if (std::abs(denom) < 1e-12f) { biasOk = false; break; }
bias.at<int>(oc) = cvRound(b_real / denom);
}
} else {
biasOk = false;
}
} else {
dq_bias_idx = producer_of.at(conv->inputs[2].idx);
DequantizeLinearLayer* dq_b =
getLayer<DequantizeLinearLayer>(newprog, dq_bias_idx);
if (!dq_b || dq_b->inputs.size() < 2 ||
usecounts.at(conv->inputs[2].idx) != 1 ||
!netimpl->isConstArg(dq_b->inputs[0])) {
biasOk = false;
} else {
Mat bq = netimpl->argTensor(dq_b->inputs[0]);
if (bq.empty() || bq.total() != (size_t)outCn || bq.depth() != CV_32S)
biasOk = false;
else
bias = bq.reshape(1, 1);
}
}
}
if (!biasOk)
break;
const bool inputIsU8 =
(netimpl->argData(dq_x->inputs[0]).type == CV_8U) ||
(x_zp_m.depth() == CV_8U);
const int inp_zp_kernel = inputIsU8 ? (inp_zp - 128) : inp_zp;
Mat weights_2d = w_q.reshape(1, outCn);
Mat biasFused(1, outCn, CV_32S);
Mat outputMultiplier(1, outCn, CV_32F);
for (int oc = 0; oc < outCn; oc++) {
biasFused.at<int>(oc) = bias.at<int>(oc) - inp_zp_kernel * (int)cv::sum(weights_2d.row(oc))[0];
outputMultiplier.at<float>(oc) = (inp_sc * wt_sc.at<float>(oc)) / out_sc;
}
LayerParams convInt8Params = makeLayerParamsFromOriginal(conv, "ConvolutionInt8");
{
int kndims = w_q.dims - 2;
std::vector<int> ksize(kndims);
for (int d = 0; d < kndims; d++)
ksize[d] = w_q.size[d + 2];
convInt8Params.set("kernel_size", DictValue::arrayInt(ksize.data(), kndims));
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()));
}
convInt8Params.set("num_output", outCn);
convInt8Params.set("group", conv->ngroups);
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<ConvolutionLayerInt8*>(convInt8.get());
CV_Assert(convInt8Layer);
convInt8Layer->input_zp = inp_zp;
convInt8Layer->input_sc = inp_sc;
convInt8Layer->output_zp = out_zp;
convInt8Layer->output_sc = out_sc;
convInt8Layer->per_channel = per_channel;
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;
}
}
}
}
}
int mm_layer_idx = -1;
MatMulLayer* mm = 0;
if (getQdqPatternContext<MatMulLayer>(layer_ptr, ninputs, inputs, producer_of,
newprog, q_data_in, out_scale, out_zp,
mm_layer_idx, mm) &&
mm->inputs.size() == 2) {
const Arg mm_x = mm->inputs[0];
const Arg mm_w = mm->inputs[1];
int dq_x_idx = producer_of.at(mm_x.idx);
int dq_w_idx = producer_of.at(mm_w.idx);
DequantizeLinearLayer* dq_x = getLayer<DequantizeLinearLayer>(newprog, dq_x_idx);
DequantizeLinearLayer* dq_w = getLayer<DequantizeLinearLayer>(newprog, dq_w_idx);
float inp_sc = 0.f, out_sc = 0.f;
int inp_zp = 0, out_zp_i = 0;
const int fc_out_type = !outputs.empty() ? netimpl->argData(outputs[0]).type : -1;
const bool fc_out_int8 = (fc_out_type == CV_8S || fc_out_type == CV_8U);
const int fc_in_type = (dq_x && !dq_x->inputs.empty()) ? netimpl->argData(dq_x->inputs[0]).type : -1;
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 &&
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);
const Mat& fc_zp_m = netimpl->argTensor(dq_x->inputs[2]);
inp_zp = fc_zp_m.depth() == CV_8U
? (int)fc_zp_m.at<uint8_t>(0)
: (int)fc_zp_m.at<int8_t>(0);
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<int8_t>(0);
if (!(inp_sc > 0.f && out_sc > 0.f))
break;
Mat w_q = netimpl->argTensor(dq_w->inputs[0]);
Mat w_sc_m = netimpl->argTensor(dq_w->inputs[1]);
Mat w_zp_m = netimpl->argTensor(dq_w->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) {
Mat weights = w_q.t();
int outCn = weights.size[0];
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;
Mat bias(1, outCn, CV_32S);
Mat outputMultiplier(1, outCn, CV_32F);
for (int ioc = 0; ioc < outCn; ioc++) {
bias.at<int>(ioc) = -inp_zp * (int)cv::sum(weights.row(ioc))[0];
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->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;
}
}
}
}
}
int pool_layer_idx = -1;
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, "PoolingInt8");
poolInt8Params.blobs.clear();
Ptr<Layer> poolInt8 = createFusedLayer(poolInt8Params);
if (!poolInt8.empty()) {
auto* poolInt8Layer = dynamic_cast<PoolingLayerInt8*>(poolInt8.get());
CV_Assert(poolInt8Layer);
const String poolInt8Type = static_cast<Layer&>(*poolInt8Layer).type;
std::vector<Mat> poolInt8Blobs = poolInt8Layer->blobs;
static_cast<PoolingLayer&>(*poolInt8Layer) = *pool;
static_cast<Layer&>(*poolInt8Layer).type = poolInt8Type;
poolInt8Layer->blobs = poolInt8Blobs;
poolInt8Layer->input_sc = inp_sc;
poolInt8Layer->input_zp = inp_zp;
poolInt8Layer->output_sc = out_sc;
poolInt8Layer->output_zp = out_zp_i;
fused_layer_idx = pool_layer_idx;
newprog[pool_layer_idx] = poolInt8;
fused_inputs.assign(1, dq->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(pool_in);
newprog[dq_idx] = Ptr<Layer>();
break;
}
}
}
}
{
ActivationLayerInt8* activ_int8 = dynamic_cast<ActivationLayerInt8*>(layer_ptr);
if (activ_int8 && ninputs == 1 &&
usecounts.at(inputs[0].idx) == 1) {
Arg activ_inp = inputs[0];
int producer_idx = producer_of.at(activ_inp.idx);
if (producer_idx >= 0 && !newprog[producer_idx].empty()) {
Layer* producer_layer = newprog[producer_idx].get();
Ptr<ActivationLayer> activ_layer = layer.dynamicCast<ActivationLayer>();
ConvolutionLayerInt8* conv_int8 =
dynamic_cast<ConvolutionLayerInt8*>(producer_layer);
if (conv_int8 && conv_int8->output_sc == activ_int8->input_sc &&
conv_int8->output_zp == activ_int8->input_zp) {
if (newprog[producer_idx]->setActivation(activ_layer)) {
conv_int8->output_sc = activ_int8->output_sc;
conv_int8->output_zp = activ_int8->output_zp;
fused_layer_idx = producer_idx;
removed_args.push_back(activ_inp);
break;
}
}
InnerProductLayerInt8* fc_int8 =
dynamic_cast<InnerProductLayerInt8*>(producer_layer);
if (fc_int8 && fc_int8->output_sc == activ_int8->input_sc &&
fc_int8->output_zp == activ_int8->input_zp) {
if (newprog[producer_idx]->setActivation(activ_layer)) {
fc_int8->output_sc = activ_int8->output_sc;
fc_int8->output_zp = activ_int8->output_zp;
fused_layer_idx = producer_idx;
removed_args.push_back(activ_inp);
break;
}
}
EltwiseLayerInt8* elt_int8 =
dynamic_cast<EltwiseLayerInt8*>(producer_layer);
if (elt_int8 && elt_int8->output_sc == activ_int8->input_sc &&
elt_int8->output_zp == activ_int8->input_zp) {
if (newprog[producer_idx]->setActivation(activ_layer)) {
elt_int8->output_sc = activ_int8->output_sc;
elt_int8->output_zp = activ_int8->output_zp;
fused_layer_idx = producer_idx;
removed_args.push_back(activ_inp);
break;
}
}
}
}
}
break;
}
if (fused_layer_idx >= 0) {
modified = true;
Layer* fused_layer = newprog[fused_layer_idx];
fused_layer->outputs = outputs;
if (!fused_inputs.empty())
fused_layer->inputs = fused_inputs;
for (Arg new_out: outputs)
producer_of[new_out.idx] = fused_layer_idx;
for (Arg old_out: removed_args) {
usecounts.at(old_out.idx) = 0;
producer_of.at(old_out.idx) = -1;
}
} else {
for (auto out: outputs)
producer_of[out.idx] = (int)newprog.size();
newprog.push_back(layer);
}
}
if (modified) {
size_t i, j = 0, newops = newprog.size();
for (i = 0; i < newops; i++) {
if (!newprog[i].empty()) {
if (j < i)
newprog[j] = newprog[i];
j++;
}
}
newprog.resize(j);
graph->setProg(newprog);
}
return modified;
}
Net::Impl* netimpl;
vector<int> usecounts;
};
void Net::Impl::fuseQDQ()
{
ModelFusionQDQ qdqFusion(this);
qdqFusion.fuse();
}
CV__DNN_INLINE_NS_END
}} // cv::dnn
+148 -54
View File
@@ -14,6 +14,7 @@
#include <iostream>
#include <numeric>
namespace cv
{
namespace dnn
@@ -42,16 +43,32 @@ public:
BaseConvolutionLayerInt8Impl(const LayerParams &params)
{
setParamsFrom(params);
getConvolutionKernelParams(params, kernel_size, pads_begin, pads_end, strides, dilations, padMode, adjust_pads, useWinograd);
const bool hasKernelParams =
params.has("kernel") || params.has("kernel_size") || params.has("kernel_h") || params.has("kernel_w");
if (hasKernelParams)
{
getConvolutionKernelParams(params, kernel_size, pads_begin, pads_end, strides, dilations, padMode, adjust_pads, useWinograd);
}
else
{
kernel_size.clear();
pads_begin.clear();
pads_end.clear();
strides.clear();
dilations.clear();
adjust_pads.clear();
padMode = params.get<String>("pad_mode", "");
}
numOutput = params.get<int>("num_output");
numOutput = params.get<int>("num_output", blobs.empty() ? 0 : blobs[0].size[0]);
int ngroups = params.get<int>("group", 1);
CV_Assert(numOutput % ngroups == 0);
if (numOutput > 0)
CV_Assert(numOutput % ngroups == 0);
input_sc = params.get<float>("input_scale");
input_zp = params.get<int>("input_zeropoint");
output_zp = params.get<int>("zeropoints");
output_sc = params.get<float>("scales");
input_sc = params.get<float>("input_scale", 1.0f);
input_zp = params.get<int>("input_zeropoint", 0);
output_zp = params.get<int>("zeropoints", 0);
output_sc = params.get<float>("scales", 1.0f);
per_channel = params.get<bool>("per_channel", true);
if (kernel_size.size() == 2) {
@@ -100,7 +117,8 @@ public:
}
const Mat &input = inputs[0];
CV_Assert(((input.dims == 3 && kernel_size.size() == 1) || input.dims == 4 || input.dims == 5) && input.type() == CV_8S);
CV_Assert(((input.dims == 3 && kernel_size.size() == 1) || input.dims == 4 || input.dims == 5) &&
(input.type() == CV_8S || input.type() == CV_8U));
for (size_t i = 0; i < outputs.size(); i++)
{
CV_Assert(inputs[i].type() == input.type());
@@ -126,6 +144,19 @@ public:
}
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>& outputs,
std::vector<MatType>& internals) const CV_OVERRIDE
{
CV_Assert(!inputs.empty());
for (auto t : inputs)
CV_CheckType(t, t == CV_8S || t == CV_8U, "");
outputs.assign(requiredOutputs, inputs[0]);
internals.assign(requiredInternals, CV_32S);
}
virtual MatShape computeColRowShape(const MatShape &inpShape, const MatShape &outShape) const = 0;
bool is1x1() const
{
@@ -163,9 +194,11 @@ public:
enum { VEC_ALIGN = 32, DFT_TYPE = CV_8S };
Mat weightsMat;
std::vector<int> biasvec;
std::vector<int> biasvecVNNI; // Bias adjusted for VNNI uint8 direct path
std::vector<float> outputMultiplier;
Mat activationLUT;
Ptr<ActivationLayerInt8> activ;
Mat outputInt32Buf_; // pre-allocated scratch buffer for int32 accumulation
ConvolutionLayerInt8Impl(const LayerParams &params) : BaseConvolutionLayerInt8Impl(params){}
@@ -247,13 +280,16 @@ public:
std::vector<Mat> inputs;
inputs_arr.getMatVector(inputs);
const int outCn = numOutput > 0 ? numOutput : blobs[0].size[0];
CV_Assert(outCn > 0);
numOutput = outCn;
// prepare weightsMat where each row is aligned and has enough zero padding on the right to
// use vectorized (i.e. with intrinsics) loops without tail processing
Mat wm = blobs[0].reshape(1, numOutput);
Mat wm = blobs[0].reshape(1, outCn);
if( wm.step1() % VEC_ALIGN != 0 )
{
int newcols = (int)alignSize(wm.step1(), VEC_ALIGN);
Mat wm_buffer = Mat(numOutput, newcols, wm.type());
Mat wm_buffer = Mat(outCn, newcols, wm.type());
Mat wm_padding = wm_buffer.colRange(wm.cols, newcols);
wm_padding.setTo(Scalar::all(0));
Mat wm_aligned = wm_buffer.colRange(0, wm.cols);
@@ -263,11 +299,11 @@ public:
weightsMat = wm;
Mat biasMat = blobs[1];
biasvec.resize(numOutput+2);
biasvec.resize(outCn + 2);
Mat outMult = blobs[2];
outputMultiplier.resize(numOutput+2);
for(int i = 0; i < numOutput; i++ )
outputMultiplier.resize(outCn + 2);
for(int i = 0; i < outCn; i++ )
{
biasvec[i] = biasMat.at<int>(i);
outputMultiplier[i] = outMult.at<float>(i);
@@ -687,7 +723,7 @@ public:
class ParallelConv : public cv::ParallelLoopBody
{
public:
enum { BLK_SIZE = 32, BLK_SIZE_CN = 64 };
enum { BLK_SIZE = 64, BLK_SIZE_CN = 64 };
const Mat* input_;
const Mat* weights_;
@@ -704,14 +740,16 @@ public:
bool useAVX512;
bool useLASX;
bool useRVV;
bool useVNNI;
int blk_size_cn;
int inpZp, outZp;
const std::vector<float>* multiplier;
Mat* directU8Out_;
ParallelConv()
: input_(0), weights_(0), output_(0), ngroups_(0), nstripes_(0),
biasvec_(0), activLUT_(0), activ_(0), is1x1_(false), useAVX2(false), useAVX512(false), useLASX(false), useRVV(false)
, blk_size_cn(0), inpZp(0), outZp(0), multiplier(0)
biasvec_(0), activLUT_(0), activ_(0), is1x1_(false), useAVX2(false), useAVX512(false), useLASX(false), useRVV(false), useVNNI(false)
, blk_size_cn(0), inpZp(0), outZp(0), multiplier(0), directU8Out_(0)
{}
static void run( const Mat& input, Mat& output, const Mat& weights, const std::vector<float>& multipliers,
@@ -719,7 +757,8 @@ public:
const std::vector<size_t>& kernel_size, const std::vector<size_t>& strides,
const std::vector<size_t>& pads_begin, const std::vector<size_t>& pads_end,
const std::vector<size_t>& dilations,
const ActivationLayerInt8* activ, int ngroups, int nstripes, int inp_Zp, int out_Zp)
const ActivationLayerInt8* activ, int ngroups, int nstripes, int inp_Zp, int out_Zp,
Mat* directU8Out = nullptr)
{
size_t karea = std::accumulate(kernel_size.begin(), kernel_size.end(),
1, std::multiplies<size_t>());
@@ -731,9 +770,9 @@ public:
input.size[0] == output.size[0],
weights.rows == output.size[1],
weights.cols == (input.size[1]/ngroups)*karea,
input.type() == CV_8SC1,
(input.type() == CV_8SC1 || input.type() == CV_8UC1),
output.type() == CV_32SC1,
input.type() == weights.type(),
weights.type() == CV_8SC1,
input.isContinuous(),
output.isContinuous(),
biasvec.size() == (size_t)output.size[1]+2);
@@ -765,7 +804,10 @@ public:
p.useAVX2 = checkHardwareSupport(CPU_AVX2) && isConv2D;
p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX && isConv2D;
#if CV_AVXVNNI_AVAILABLE
p.useVNNI = p.useAVX2 && checkHardwareSupport(CPU_AVX_VNNI) && isConv2D &&
input.type() == CV_8UC1;
#endif
p.useLASX = checkHardwareSupport(CPU_LASX) && isConv2D;
p.useRVV = checkHardwareSupport(CPU_RVV) && isConv2D;
@@ -818,6 +860,7 @@ public:
p.biasvec_ = &biasvec;
p.activLUT_ = &activLUT;
p.activ_ = !activLUT.empty() ? activ : 0;
p.directU8Out_ = directU8Out;
parallel_for_(Range(0, nstripes), p, nstripes);
}
@@ -898,27 +941,18 @@ public:
const float* multptr_ = &multiplier->at(0);
const int* lutptr_ = !activLUT_->empty() ? activLUT_->ptr<int>() : 0;
int* data_out0_ = output_->ptr<int>();
AutoBuffer<int8_t> rowbuf0_;
int8_t* rowbuf0 = 0;
bool use_rowbuf = !depthWiseConvolution;
int blk_size = depthWiseConvolution ? outPlaneSize : min((int)BLK_SIZE, stripeSize);
// im2row buffer is not used for depth-wise convolution
// Use thread_local to avoid repeated allocation across layers
if(use_rowbuf)
{
size_t rowbufsz = alignSize(karea*blk_size_cn, valign)*min((int)BLK_SIZE, blk_size);
//printf("karea=%d, blk_size_cn=%d, rowbufsz=%d, stripeSize=%d\n", karea, blk_size_cn, (int)rowbufsz, stripeSize);
rowbuf0_.allocate(rowbufsz + valign);
rowbuf0 = alignPtr(rowbuf0_.data(), (int)(valign*sizeof(int8_t)));
// we clear the buffer once; ultimately, it lets us to avoid
// tail processing after running the unrolled/vectorized loop.
// the main idea is to make sure that the tail (a.k.a. padding) of each row
// (i.e. the elements with indices between vsz=karea*ncn and vsz_a)
// does not contain NaNs or Infs. Because the padding in the weights
// matrix is explicitly initialized with 0's, we handle all other
// cases nicely, i.e. we can skip expliciting re-initialization
// of the padding - we just retain elements from the previous iteration
// of the loop over channels (cn0).
thread_local AutoBuffer<int8_t> rowbuf0_tls;
rowbuf0_tls.allocate(rowbufsz + valign);
rowbuf0 = alignPtr(rowbuf0_tls.data(), (int)(valign*sizeof(int8_t)));
memset(rowbuf0, (int8_t)inpZp, rowbufsz*sizeof(rowbuf0[0]) );
}
@@ -1346,8 +1380,12 @@ public:
}
}
}
// now compute dot product of the weights
// and im2row-transformed part of the tensor
#if CV_AVXVNNI_AVAILABLE
if(useVNNI)
opt_AVX2::fastConvVNNI(wptr, wstep, biasptr, (const uint8_t*)rowbuf0, data_out0 + ofs0,
outShape, bsz, vsz, vsz_a, outZp, multptr, cn0 == 0, cn1 == inpCn);
else
#endif
#if CV_TRY_AVX512_SKX
if(useAVX512)
opt_AVX2::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0,
@@ -1492,6 +1530,34 @@ public:
activ_->forwardSlice(data_out0 + stripeStart, lutptr_,
data_out0 + stripeStart, (int)(stripeEnd - stripeStart),
outPlaneSize, startOutCn, startOutCn + outCn);
// Fused int32 → uint8 conversion (cache-hot, avoids separate convertTo pass)
if( directU8Out_ )
{
uint8_t* u8base = directU8Out_->ptr<uint8_t>() + subsampleIdx*outPlaneSize*outCn;
for( int c = 0; c < outCn; c++ )
{
const int* src = data_out0 + c*outPlaneSize + stripeStart;
uint8_t* dst = u8base + c*outPlaneSize + stripeStart;
int len = stripeEnd - stripeStart;
int j = 0;
#if CV_SSE2
__m128i voffset = _mm_set1_epi32(128);
for( ; j <= len - 8; j += 8 )
{
__m128i v0 = _mm_loadu_si128((const __m128i*)(src + j));
__m128i v1 = _mm_loadu_si128((const __m128i*)(src + j + 4));
v0 = _mm_add_epi32(v0, voffset);
v1 = _mm_add_epi32(v1, voffset);
__m128i p16 = _mm_packs_epi32(v0, v1);
__m128i p8 = _mm_packus_epi16(p16, p16);
_mm_storel_epi64((__m128i*)(dst + j), p8);
}
#endif
for( ; j < len; j++ )
dst[j] = (uint8_t)std::min(std::max(src[j] + 128, 0), 255);
}
}
}
}
};
@@ -1512,19 +1578,6 @@ public:
inputs_arr.getMatVector(inputs);
outputs_arr.getMatVector(outputs);
/*if (inputs[0].dims > 3) {
printf("conv %s: input (%d x %d x %d x %d), kernel (%d x %d), pad (%d x %d), stride (%d x %d), dilation (%d x %d)\n",
name.c_str(), inputs[0].size[0], inputs[0].size[1], inputs[0].size[2], inputs[0].size[3],
kernel.width, kernel.height, pad.width, pad.height,
stride.width, stride.height, dilation.width, dilation.height);
}
else {
printf("conv %s: input (%d x %d x %d), kernel (%d x %d), pad (%d x %d), stride (%d x %d), dilation (%d x %d)\n",
name.c_str(), inputs[0].size[0], inputs[0].size[1], inputs[0].size[2],
kernel.width, kernel.height, pad.width, pad.height,
stride.width, stride.height, dilation.width, dilation.height);
}*/
int inpGroupCn = blobs[0].size[1];
CV_Assert_N(inputs.size() == (size_t)1, inputs[0].size[1] % inpGroupCn == 0,
outputs.size() == 1, inputs[0].data != outputs[0].data);
@@ -1533,12 +1586,53 @@ public:
CV_Assert(outputs[0].size[1] % ngroups == 0);
int nstripes = std::max(getNumThreads(), 1);
Mat outputInt32 = Mat(shape(outputs[0]), CV_32S);
ParallelConv::run(inputs[0], outputInt32, weightsMat, outputMultiplier, biasvec, activationLUT, kernel_size, strides,
pads_begin, pads_end, dilations, activ.get(), ngroups, nstripes, input_zp, output_zp);
outputInt32.convertTo(outputs[0], CV_8S);
outputInt32Buf_.fit(shape(outputs[0]), CV_32S);
Mat outputInt32 = outputInt32Buf_;
if (inputs[0].type() == CV_8U)
{
#if CV_AVXVNNI_AVAILABLE
// AVX-VNNI path: pass uint8 input directly, skip u8→s8 conversion.
if (checkHardwareSupport(CPU_AVX_VNNI) && inputs[0].dims == 4) {
if (biasvecVNNI.empty() && !biasvec.empty()) {
int outCn = weightsMat.rows;
biasvecVNNI.resize(biasvec.size());
for (int oc = 0; oc < outCn; oc++) {
const int8_t* wrow = weightsMat.ptr<int8_t>(oc);
int colsum = 0;
for (int j = 0; j < weightsMat.cols; j++)
colsum += (int)wrow[j];
biasvecVNNI[oc] = biasvec[oc] - 128 * colsum;
}
for (size_t oc = outCn; oc < biasvec.size(); oc++)
biasvecVNNI[oc] = biasvecVNNI[outCn > 0 ? outCn - 1 : 0];
}
const int outZpS8 = output_zp - 128;
ParallelConv::run(inputs[0], outputInt32, weightsMat, outputMultiplier,
biasvecVNNI, activationLUT, kernel_size, strides,
pads_begin, pads_end, dilations, activ.get(),
ngroups, nstripes, input_zp, outZpS8,
&outputs[0]);
}
else
#endif
{
Mat inpS8;
inputs[0].convertTo(inpS8, CV_8S, 1.0, -128.0);
const int inpZpS8 = input_zp - 128;
const int outZpS8 = output_zp - 128;
ParallelConv::run(inpS8, outputInt32, weightsMat, outputMultiplier, biasvec, activationLUT, kernel_size, strides,
pads_begin, pads_end, dilations, activ.get(), ngroups, nstripes, inpZpS8, outZpS8);
Mat outS8;
outputInt32.convertTo(outS8, CV_8S);
outS8.convertTo(outputs[0], CV_8U, 1.0, 128.0);
}
}
else
{
ParallelConv::run(inputs[0], outputInt32, weightsMat, outputMultiplier, biasvec, activationLUT, kernel_size, strides,
pads_begin, pads_end, dilations, activ.get(), ngroups, nstripes, input_zp, output_zp);
outputInt32.convertTo(outputs[0], outputs[0].type());
}
#if CV_SSE3
_MM_SET_FLUSH_ZERO_MODE(ftzMode);
@@ -18,8 +18,6 @@ namespace dnn
class ActivationLayerInt8Impl CV_FINAL : public ActivationLayerInt8
{
public:
int input_zp, output_zp;
float input_sc, output_sc;
float slope = 0.0f;
#ifdef HAVE_TIMVX
@@ -30,10 +28,10 @@ public:
setParamsFrom(params);
activationLUT = !blobs.empty() ? blobs[0] : Mat();
input_zp = params.get<int>("input_zeropoint");
input_sc = params.get<float>("input_scale");
output_zp = params.get<int>("zeropoints");
output_sc = params.get<float>("scales");
input_zp = params.get<int>("input_zeropoint", 0);
input_sc = params.get<float>("input_scale", 1.0f);
output_zp = params.get<int>("zeropoints", 0);
output_sc = params.get<float>("scales", 1.0f);
if (params.has("slope"))
{
@@ -355,7 +353,6 @@ public:
}
Mat activationLUT;
};
Ptr<ActivationLayerInt8> ActivationLayerInt8::create(const LayerParams& params)
+197 -16
View File
@@ -7,6 +7,8 @@
#include "../op_timvx.hpp"
#include "../ie_ngraph.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include "opencv2/core/hal/hal.hpp"
#include "opencv2/core/hal/intrin.hpp"
namespace cv
{
@@ -22,12 +24,6 @@ public:
SUM = 1,
MAX = 2
} op;
std::vector<float> coeffs;
std::vector<int> zeropoints;
std::vector<float> scales;
int output_zp;
float output_sc;
enum OutputChannelsMode
{
@@ -101,8 +97,8 @@ public:
}
}
output_zp = params.get<int>("zeropoints");
output_sc = params.get<float>("scales");
output_zp = params.get<int>("zeropoints", 0);
output_sc = params.get<float>("scales", 1.0f);
channelsModeInput = ELTWISE_CHANNNELS_SAME;
if (params.has("output_channels_mode"))
@@ -142,6 +138,56 @@ public:
return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
}
void ensureQuantizationParams()
{
if (!coeffs.empty())
return;
if (scales.empty())
return;
CV_CheckEQ(scales.size(), zeropoints.size(), "EltwiseInt8: scales and zeropoints sizes must match");
CV_Assert(output_sc > 0.0f);
if (op == SUM)
{
coeffs.resize(scales.size());
offset = (float)output_zp;
for (size_t i = 0; i < scales.size(); i++)
{
coeffs[i] = scales[i] / output_sc;
offset -= coeffs[i] * zeropoints[i];
}
}
else if (op == PROD)
{
coeffs.resize(scales.size());
coeffs[0] = scales[0] / output_sc;
for (size_t i = 1; i < scales.size(); i++)
coeffs[i] = scales[i];
offset = (float)output_zp;
}
else if (op == MAX)
{
for (size_t i = 0; i < scales.size(); i++)
{
const bool sameQuant =
std::abs(scales[i] - output_sc) < 1e-6f &&
zeropoints[i] == output_zp;
if (!sameQuant)
{
CV_Error(Error::StsBadArg,
"EltwiseInt8 'max' requires identical quantization "
"(same scale and zero-point) for all inputs and output.");
}
}
}
else
{
CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation");
}
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
@@ -248,6 +294,7 @@ public:
bool isLast) CV_OVERRIDE
{
#ifdef HAVE_TIMVX
ensureQuantizationParams();
// tvGraph Initialization.
if (inputsWrapper.size() != 2)
return Ptr<BackendNode>();
@@ -374,6 +421,7 @@ public:
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> > &inputs,
const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
{
ensureQuantizationParams();
CV_Assert(nodes.size() >= 2);
std::vector<ov::Output<ov::Node>> ieInpNodes(nodes.size());
for (size_t i = 0; i < nodes.size(); i++)
@@ -544,6 +592,7 @@ public:
size_t dstIdx = delta + (sampleIdx*channels + c)*planeSize;
int8_t* dstptr = dstptr0 + dstIdx;
float* bufptr = bufptr0 + dstIdx;
bool directOutput = false;
// process first two inputs
{
@@ -589,7 +638,16 @@ public:
}
else if (op == MAX)
{
for (int j = 0; j < blockSize; j++)
int j = 0;
#if CV_SIMD128
for (; j <= blockSize - 16; j += 16)
{
v_int8x16 va = v_load(srcptr0 + j);
v_int8x16 vb = v_load(srcptrI + j);
v_store(dstptr + j, v_max(va, vb));
}
#endif
for (; j < blockSize; j++)
{
dstptr[j] = std::max(srcptr0[j], srcptrI[j]);
}
@@ -598,9 +656,86 @@ public:
{
float c0 = coeffsptr[0];
float c1 = coeffsptr[1];
for (int j = 0; j < blockSize; j++)
if (nsrcs == 2)
{
bufptr[j] = c0*srcptr0[j] + c1*srcptrI[j];
directOutput = true;
int j = 0;
#if CV_SIMD128
{
v_float32x4 vc0 = v_setall_f32(c0);
v_float32x4 vc1 = v_setall_f32(c1);
v_float32x4 voffs = v_setall_f32(offset);
for (; j <= blockSize - 16; j += 16)
{
v_int8x16 va = v_load(srcptr0 + j);
v_int8x16 vb = v_load(srcptrI + j);
v_int16x8 va_lo, va_hi, vb_lo, vb_hi;
v_expand(va, va_lo, va_hi);
v_expand(vb, vb_lo, vb_hi);
v_int32x4 va0, va1, va2, va3, vb0, vb1, vb2, vb3;
v_expand(va_lo, va0, va1);
v_expand(va_hi, va2, va3);
v_expand(vb_lo, vb0, vb1);
v_expand(vb_hi, vb2, vb3);
v_float32x4 r0 = v_add(v_add(v_mul(v_cvt_f32(va0), vc0),
v_mul(v_cvt_f32(vb0), vc1)), voffs);
v_float32x4 r1 = v_add(v_add(v_mul(v_cvt_f32(va1), vc0),
v_mul(v_cvt_f32(vb1), vc1)), voffs);
v_float32x4 r2 = v_add(v_add(v_mul(v_cvt_f32(va2), vc0),
v_mul(v_cvt_f32(vb2), vc1)), voffs);
v_float32x4 r3 = v_add(v_add(v_mul(v_cvt_f32(va3), vc0),
v_mul(v_cvt_f32(vb3), vc1)), voffs);
v_store(dstptr + j, v_pack(v_pack(v_round(r0), v_round(r1)),
v_pack(v_round(r2), v_round(r3))));
}
}
#endif
for (; j < blockSize; j++)
{
dstptr[j] = saturate_cast<int8_t>(std::round(c0*srcptr0[j] + c1*srcptrI[j] + offset));
}
}
else
{
int j = 0;
#if CV_SIMD128
{
v_float32x4 vc0 = v_setall_f32(c0);
v_float32x4 vc1 = v_setall_f32(c1);
for (; j <= blockSize - 16; j += 16)
{
v_int8x16 va = v_load(srcptr0 + j);
v_int8x16 vb = v_load(srcptrI + j);
v_int16x8 va_lo, va_hi, vb_lo, vb_hi;
v_expand(va, va_lo, va_hi);
v_expand(vb, vb_lo, vb_hi);
v_int32x4 va0, va1, va2, va3, vb0, vb1, vb2, vb3;
v_expand(va_lo, va0, va1);
v_expand(va_hi, va2, va3);
v_expand(vb_lo, vb0, vb1);
v_expand(vb_hi, vb2, vb3);
v_store(bufptr + j, v_add(v_mul(v_cvt_f32(va0), vc0),
v_mul(v_cvt_f32(vb0), vc1)));
v_store(bufptr + j + 4, v_add(v_mul(v_cvt_f32(va1), vc0),
v_mul(v_cvt_f32(vb1), vc1)));
v_store(bufptr + j + 8, v_add(v_mul(v_cvt_f32(va2), vc0),
v_mul(v_cvt_f32(vb2), vc1)));
v_store(bufptr + j + 12, v_add(v_mul(v_cvt_f32(va3), vc0),
v_mul(v_cvt_f32(vb3), vc1)));
}
}
#endif
for (; j < blockSize; j++)
{
bufptr[j] = c0*srcptr0[j] + c1*srcptrI[j];
}
}
}
else
@@ -628,7 +763,14 @@ public:
}
else if (op == MAX)
{
for (int j = 0; j < blockSize; j++)
int j = 0;
#if CV_SIMD128
for (; j <= blockSize - 16; j += 16)
{
v_store(dstptr + j, v_max(v_load(dstptr + j), v_load(srcptrI + j)));
}
#endif
for (; j < blockSize; j++)
{
dstptr[j] = std::max(dstptr[j], srcptrI[j]);
}
@@ -636,7 +778,31 @@ public:
else if (op == SUM)
{
float cI = coeffsptr[inputIdx];
for (int j = 0; j < blockSize; j++)
int j = 0;
#if CV_SIMD128
{
v_float32x4 vcI = v_setall_f32(cI);
for (; j <= blockSize - 16; j += 16)
{
v_int8x16 vi = v_load(srcptrI + j);
v_int16x8 vi_lo, vi_hi;
v_expand(vi, vi_lo, vi_hi);
v_int32x4 vi0, vi1, vi2, vi3;
v_expand(vi_lo, vi0, vi1);
v_expand(vi_hi, vi2, vi3);
v_store(bufptr + j, v_add(v_load(bufptr + j),
v_mul(v_cvt_f32(vi0), vcI)));
v_store(bufptr + j + 4, v_add(v_load(bufptr + j + 4),
v_mul(v_cvt_f32(vi1), vcI)));
v_store(bufptr + j + 8, v_add(v_load(bufptr + j + 8),
v_mul(v_cvt_f32(vi2), vcI)));
v_store(bufptr + j + 12, v_add(v_load(bufptr + j + 12),
v_mul(v_cvt_f32(vi3), vcI)));
}
}
#endif
for (; j < blockSize; j++)
{
bufptr[j] += cI * srcptrI[j];
}
@@ -646,9 +812,24 @@ public:
}
// add offset and saturate cast to int8
if (op == SUM || op == PROD)
if ((op == SUM || op == PROD) && !directOutput)
{
for (int j = 0; j < blockSize; j++)
int j = 0;
#if CV_SIMD128
{
v_float32x4 voffs = v_setall_f32(offset);
for (; j <= blockSize - 16; j += 16)
{
v_float32x4 r0 = v_add(v_load(bufptr + j), voffs);
v_float32x4 r1 = v_add(v_load(bufptr + j + 4), voffs);
v_float32x4 r2 = v_add(v_load(bufptr + j + 8), voffs);
v_float32x4 r3 = v_add(v_load(bufptr + j + 12), voffs);
v_store(dstptr + j, v_pack(v_pack(v_round(r0), v_round(r1)),
v_pack(v_round(r2), v_round(r3))));
}
}
#endif
for (; j < blockSize; j++)
{
dstptr[j] = saturate_cast<int8_t>(std::round(bufptr[j] + offset));
}
@@ -667,6 +848,7 @@ public:
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
ensureQuantizationParams();
std::vector<Mat> inputs, outputs;
inputs_arr.getMatVector(inputs);
@@ -749,7 +931,6 @@ public:
private:
bool hasVecInput;
float offset;
};
Ptr<EltwiseLayerInt8> EltwiseLayerInt8::create(const LayerParams& params)
@@ -22,10 +22,10 @@ public:
{
setParamsFrom(params);
input_sc = params.get<float>("input_scale");
input_zp = params.get<int>("input_zeropoint");
output_zp = params.get<int>("zeropoints");
output_sc = params.get<float>("scales");
input_sc = params.get<float>("input_scale", 1.0f);
input_zp = params.get<int>("input_zeropoint", 0);
output_zp = params.get<int>("zeropoints", 0);
output_sc = params.get<float>("scales", 1.0f);
axis = params.get<int>("axis", 1);
per_channel = params.get<bool>("per_channel", true);
@@ -4,6 +4,16 @@
#include "opencv2/core/hal/intrin.hpp"
#if !defined(CV_AVXVNNI_AVAILABLE)
#if (CV_TRY_AVX2 || CV_AVX2) && \
((defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 11) || \
(defined(__clang__) && !defined(__apple_build_version__) && __clang_major__ >= 12))
#define CV_AVXVNNI_AVAILABLE 1
#else
#define CV_AVXVNNI_AVAILABLE 0
#endif
#endif
namespace cv {
namespace dnn {
CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN
@@ -27,6 +37,17 @@ void fastGEMM1T( const int8_t* vec, const int8_t* weights,
size_t wstep, const int* bias, const float* multiplier,
int* dst, int nvecs, int vecsize, int outZp );
#if CV_AVXVNNI_AVAILABLE
// AVX-VNNI variants: take uint8 input directly, use _mm256_dpbusd_epi32
void fastConvVNNI( const int8_t* weights, size_t wstep, const int* bias,
const uint8_t* rowbuf, int* output, const int* outShape,
int blockSize, int vecsize, int vecsize_aligned, int outZp,
const float* multiplier, bool initOutput, bool finalOutput );
void fastGEMM1TVNNI( const uint8_t* vec, const int8_t* weights,
size_t wstep, const int* bias, const float* multiplier,
int* dst, int nvecs, int vecsize, int outZp );
#endif
#if !defined(CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY) && CV_AVX2
#define OPENCV_FMADD_EPI8(_Tpvec, func) \
inline _Tpvec _##func##_fmaddepi8_epi32(const _Tpvec& a, const _Tpvec& b, const _Tpvec& c) \
@@ -328,6 +349,231 @@ void fastConv( const int8_t* weights, size_t wstep, const int* bias,
_mm256_zeroupper();
}
#if CV_AVXVNNI_AVAILABLE
// AVX-VNNI optimized convolution: uses _mm256_dpbusd_epi32 (unsigned input × signed weights)
__attribute__((target("avxvnni")))
void fastConvVNNI( const int8_t* weights, size_t wstep, const int* bias,
const uint8_t* rowbuf, int* output, const int* outShape,
int blockSize, int vecsize, int vecsize_aligned, int outZp,
const float* multiplier, bool initOutput, bool finalOutput )
{
int outCn = outShape[1];
size_t outPlaneSize = outShape[2]*outShape[3];
int CV_DECL_ALIGNED(16) maskbuf[FASCONV_BASE_VECSZ] = {0};
int rsz = blockSize % FASCONV_BASE_VECSZ;
for( int i = 0; i < rsz; i++ )
maskbuf[FASCONV_BASE_VECSZ - i - 1] = -1;
__m128 mask = _mm_loadu_ps((const float*)maskbuf);
for( int i = 0; i < outCn; i += 3 )
{
const int8_t* wptr0 = weights + i*wstep;
const int8_t* wptr1 = wptr0 + wstep;
const int8_t* wptr2 = wptr1 + wstep;
int* outptr0 = output + i*outPlaneSize;
int* outptr1 = outptr0 + outPlaneSize;
int* outptr2 = outptr1 + outPlaneSize;
int bias0 = bias[i], bias1 = bias[i+1], bias2 = bias[i+2];
float mult0 = multiplier[i], mult1 = multiplier[i+1], mult2 = multiplier[i+2];
if( i+2 >= outCn )
{
wptr2 = wptr1;
outptr2 = outptr1;
bias2 = bias1;
mult2 = mult1;
if( i+1 >= outCn )
{
wptr2 = wptr1 = wptr0;
outptr2 = outptr1 = outptr0;
bias2 = bias1 = bias0;
mult2 = mult1 = mult0;
}
}
int j = 0;
for( ; j < blockSize; j += FASCONV_BASE_VECSZ )
{
bool tail = false;
if (j + FASCONV_BASE_VECSZ > blockSize)
{
if (j == 0)
break;
j = blockSize - FASCONV_BASE_VECSZ;
tail = true;
}
int k = 0;
const uint8_t* rptr = rowbuf + j*vecsize_aligned;
__m256i vs00 = _mm256_setzero_si256(), vs01 = _mm256_setzero_si256(),
vs02 = _mm256_setzero_si256(), vs03 = _mm256_setzero_si256(),
vs10 = _mm256_setzero_si256(), vs11 = _mm256_setzero_si256(),
vs12 = _mm256_setzero_si256(), vs13 = _mm256_setzero_si256(),
vs20 = _mm256_setzero_si256(), vs21 = _mm256_setzero_si256(),
vs22 = _mm256_setzero_si256(), vs23 = _mm256_setzero_si256();
for (; k < vecsize; k += 32, rptr += 32 )
{
__m256i w0 = _mm256_load_si256((const __m256i*)(wptr0 + k));
__m256i w1 = _mm256_load_si256((const __m256i*)(wptr1 + k));
__m256i w2 = _mm256_load_si256((const __m256i*)(wptr2 + k));
__m256i r0 = _mm256_load_si256((const __m256i*)rptr);
vs00 = _mm256_dpbusd_epi32(vs00, r0, w0);
vs10 = _mm256_dpbusd_epi32(vs10, r0, w1);
vs20 = _mm256_dpbusd_epi32(vs20, r0, w2);
r0 = _mm256_load_si256((const __m256i*)(rptr + vecsize_aligned));
vs01 = _mm256_dpbusd_epi32(vs01, r0, w0);
vs11 = _mm256_dpbusd_epi32(vs11, r0, w1);
vs21 = _mm256_dpbusd_epi32(vs21, r0, w2);
r0 = _mm256_load_si256((const __m256i*)(rptr + vecsize_aligned*2));
vs02 = _mm256_dpbusd_epi32(vs02, r0, w0);
vs12 = _mm256_dpbusd_epi32(vs12, r0, w1);
vs22 = _mm256_dpbusd_epi32(vs22, r0, w2);
r0 = _mm256_load_si256((const __m256i*)(rptr + vecsize_aligned*3));
vs03 = _mm256_dpbusd_epi32(vs03, r0, w0);
vs13 = _mm256_dpbusd_epi32(vs13, r0, w1);
vs23 = _mm256_dpbusd_epi32(vs23, r0, w2);
}
__m256i t0 = _mm256_hadd_epi32(_mm256_hadd_epi32(vs00, vs01), _mm256_hadd_epi32(vs02, vs03));
__m256i t1 = _mm256_hadd_epi32(_mm256_hadd_epi32(vs10, vs11), _mm256_hadd_epi32(vs12, vs13));
__m256i t2 = _mm256_hadd_epi32(_mm256_hadd_epi32(vs20, vs21), _mm256_hadd_epi32(vs22, vs23));
t0 = _mm256_add_epi32(t0, _mm256_permute2x128_si256(t0, t0, 1));
t1 = _mm256_add_epi32(t1, _mm256_permute2x128_si256(t1, t1, 1));
t2 = _mm256_add_epi32(t2, _mm256_permute2x128_si256(t2, t2, 1));
__m128i s0, s1, s2;
if( initOutput )
{
s0 = _mm_set1_epi32(bias0);
s1 = _mm_set1_epi32(bias1);
s2 = _mm_set1_epi32(bias2);
}
else
{
s0 = _mm_loadu_si128((__m128i*)(outptr0 + j));
s1 = _mm_loadu_si128((__m128i*)(outptr1 + j));
s2 = _mm_loadu_si128((__m128i*)(outptr2 + j));
}
s0 = _mm_add_epi32(s0, _mm256_castsi256_si128(t0));
s1 = _mm_add_epi32(s1, _mm256_castsi256_si128(t1));
s2 = _mm_add_epi32(s2, _mm256_castsi256_si128(t2));
if( finalOutput )
{
__m128i voutzp = _mm_set1_epi32(outZp);
__m128i outmin = _mm_set1_epi32(-128), outmax = _mm_set1_epi32(127);
s0 = _mm_add_epi32(voutzp, _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(s0), _mm_set1_ps(mult0))));
s1 = _mm_add_epi32(voutzp, _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(s1), _mm_set1_ps(mult1))));
s2 = _mm_add_epi32(voutzp, _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(s2), _mm_set1_ps(mult2))));
s0 = _mm_min_epi32(_mm_max_epi32(s0, outmin), outmax);
s1 = _mm_min_epi32(_mm_max_epi32(s1, outmin), outmax);
s2 = _mm_min_epi32(_mm_max_epi32(s2, outmin), outmax);
}
if( tail )
{
s0 = _mm_castps_si128(_mm_blendv_ps(_mm_loadu_ps((const float*)outptr0 + j), _mm_castsi128_ps(s0), mask));
s1 = _mm_castps_si128(_mm_blendv_ps(_mm_loadu_ps((const float*)outptr1 + j), _mm_castsi128_ps(s1), mask));
s2 = _mm_castps_si128(_mm_blendv_ps(_mm_loadu_ps((const float*)outptr2 + j), _mm_castsi128_ps(s2), mask));
}
_mm_storeu_si128((__m128i*)(outptr0 + j), s0);
_mm_storeu_si128((__m128i*)(outptr1 + j), s1);
_mm_storeu_si128((__m128i*)(outptr2 + j), s2);
}
for( ; j <= blockSize - 2; j += 2 )
{
const uint8_t* rptr0 = rowbuf + j*vecsize_aligned;
const uint8_t* rptr1 = rowbuf + (j+1)*vecsize_aligned;
int s00, s01, s10, s11, s20, s21;
if( initOutput )
{
s00 = s01 = bias0;
s10 = s11 = bias1;
s20 = s21 = bias2;
}
else
{
s00 = outptr0[j]; s01 = outptr0[j+1];
s10 = outptr1[j]; s11 = outptr1[j+1];
s20 = outptr2[j]; s21 = outptr2[j+1];
}
for( int k = 0; k < vecsize; k++ )
{
int8_t w0 = wptr0[k], w1 = wptr1[k], w2 = wptr2[k];
int r = (int)rptr0[k];
s00 += (int)w0*r; s10 += (int)w1*r; s20 += (int)w2*r;
r = (int)rptr1[k];
s01 += (int)w0*r; s11 += (int)w1*r; s21 += (int)w2*r;
}
if( finalOutput )
{
s00 = std::min(std::max(outZp + (int)std::round(s00*mult0), -128), 127);
s01 = std::min(std::max(outZp + (int)std::round(s01*mult0), -128), 127);
s10 = std::min(std::max(outZp + (int)std::round(s10*mult1), -128), 127);
s11 = std::min(std::max(outZp + (int)std::round(s11*mult1), -128), 127);
s20 = std::min(std::max(outZp + (int)std::round(s20*mult2), -128), 127);
s21 = std::min(std::max(outZp + (int)std::round(s21*mult2), -128), 127);
}
outptr0[j] = s00;
outptr0[j+1] = s01;
outptr1[j] = s10;
outptr1[j+1] = s11;
outptr2[j] = s20;
outptr2[j+1] = s21;
}
for( ; j < blockSize; j++ )
{
const uint8_t* rptr0 = rowbuf + j*vecsize_aligned;
int s00, s10, s20;
if( initOutput )
{
s00 = bias0;
s10 = bias1;
s20 = bias2;
}
else
{
s00 = outptr0[j];
s10 = outptr1[j];
s20 = outptr2[j];
}
for( int k = 0; k < vecsize; k++ )
{
int8_t w0 = wptr0[k], w1 = wptr1[k], w2 = wptr2[k];
int r = (int)rptr0[k];
s00 += (int)w0*r; s10 += (int)w1*r; s20 += (int)w2*r;
}
if( finalOutput )
{
s00 = std::min(std::max(outZp + (int)std::round(s00*mult0), -128), 127);
s10 = std::min(std::max(outZp + (int)std::round(s10*mult1), -128), 127);
s20 = std::min(std::max(outZp + (int)std::round(s20*mult2), -128), 127);
}
outptr0[j] = s00;
outptr1[j] = s10;
outptr2[j] = s20;
}
}
_mm256_zeroupper();
}
#endif // CV_AVXVNNI_AVAILABLE (fastConvVNNI)
static inline void _mm256_expand_mul_add(const __m256i& a, const __m256i& b,
__m256i& out0, __m256i& out1, __m256i& out2, __m256i& out3)
{
@@ -631,6 +877,81 @@ void fastGEMM1T( const int8_t* vec, const int8_t* weights,
_mm256_zeroupper();
}
#if CV_AVXVNNI_AVAILABLE
// AVX-VNNI variant of fastGEMM1T: uses _mm256_dpbusd_epi32 (unsigned input × signed weights)
__attribute__((target("avxvnni")))
void fastGEMM1TVNNI( const uint8_t* vec, const int8_t* weights,
size_t wstep, const int* bias, const float* multiplier,
int* dst, int nvecs, int vecsize, int outZp )
{
int i = 0;
for( ; i <= nvecs - 8; i += 8 )
{
const int8_t* wptr = weights + i*wstep;
__m256i vs0 = _mm256_setzero_si256(), vs1 = _mm256_setzero_si256(),
vs2 = _mm256_setzero_si256(), vs3 = _mm256_setzero_si256(),
vs4 = _mm256_setzero_si256(), vs5 = _mm256_setzero_si256(),
vs6 = _mm256_setzero_si256(), vs7 = _mm256_setzero_si256();
__m128i voutzp = _mm_set1_epi32(outZp);
__m128i outmin = _mm_set1_epi32(-128), outmax = _mm_set1_epi32(127);
for( int k = 0; k < vecsize; k += 32, wptr += 32 )
{
__m256i v = _mm256_load_si256((const __m256i*)(vec + k));
vs0 = _mm256_dpbusd_epi32(vs0, v, _mm256_load_si256((const __m256i*)wptr));
vs1 = _mm256_dpbusd_epi32(vs1, v, _mm256_load_si256((const __m256i*)(wptr + wstep)));
vs2 = _mm256_dpbusd_epi32(vs2, v, _mm256_load_si256((const __m256i*)(wptr + wstep*2)));
vs3 = _mm256_dpbusd_epi32(vs3, v, _mm256_load_si256((const __m256i*)(wptr + wstep*3)));
vs4 = _mm256_dpbusd_epi32(vs4, v, _mm256_load_si256((const __m256i*)(wptr + wstep*4)));
vs5 = _mm256_dpbusd_epi32(vs5, v, _mm256_load_si256((const __m256i*)(wptr + wstep*5)));
vs6 = _mm256_dpbusd_epi32(vs6, v, _mm256_load_si256((const __m256i*)(wptr + wstep*6)));
vs7 = _mm256_dpbusd_epi32(vs7, v, _mm256_load_si256((const __m256i*)(wptr + wstep*7)));
}
__m256i s0 = _mm256_hadd_epi32(_mm256_hadd_epi32(vs0, vs1), _mm256_hadd_epi32(vs2, vs3));
__m256i s1 = _mm256_hadd_epi32(_mm256_hadd_epi32(vs4, vs5), _mm256_hadd_epi32(vs6, vs7));
s0 = _mm256_add_epi32(s0, _mm256_permute2x128_si256(s0, s0, 1));
s1 = _mm256_add_epi32(s1, _mm256_permute2x128_si256(s1, s1, 1));
__m128i t0 = _mm_add_epi32(_mm256_castsi256_si128(s0), _mm_loadu_si128((__m128i*)(bias + i)));
__m128i t1 = _mm_add_epi32(_mm256_castsi256_si128(s1), _mm_loadu_si128((__m128i*)(bias + i + 4)));
t0 = _mm_add_epi32(voutzp, _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(t0), _mm_loadu_ps(multiplier + i))));
t1 = _mm_add_epi32(voutzp, _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(t1), _mm_loadu_ps(multiplier + i + 4))));
t0 = _mm_min_epi32(_mm_max_epi32(t0, outmin), outmax);
t1 = _mm_min_epi32(_mm_max_epi32(t1, outmin), outmax);
_mm_storeu_si128((__m128i*)(dst + i), t0);
_mm_storeu_si128((__m128i*)(dst + i + 4), t1);
}
for( ; i < nvecs; i++ )
{
const int8_t* wptr = weights + i*wstep;
__m256i vs0 = _mm256_setzero_si256();
for( int k = 0; k < vecsize; k += 32, wptr += 32 )
{
__m256i v = _mm256_load_si256((const __m256i*)(vec + k));
vs0 = _mm256_dpbusd_epi32(vs0, v, _mm256_load_si256((const __m256i*)wptr));
}
__m256i s0 = _mm256_hadd_epi32(_mm256_hadd_epi32(vs0, vs0), vs0);
s0 = _mm256_add_epi32(s0, _mm256_permute2x128_si256(s0, s0, 1));
int temp = _mm_extract_epi32(_mm256_castsi256_si128(s0), 0);
dst[i] = outZp + (int)std::round((temp + bias[i]) * multiplier[i]);
}
_mm256_zeroupper();
}
#endif // CV_AVXVNNI_AVAILABLE (fastGEMM1TVNNI)
#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
+22 -15
View File
@@ -30,16 +30,13 @@ public:
isGlobalPooling = std::vector<bool>(3, false);
output_zp = params.get<int>("zeropoints", 0);
input_zp = params.get<int>("input_zeropoint", output_zp);
multiplier = params.get<float>("multiplier", 1.f);
output_sc = params.get<float>("scales", 1.f);
input_sc = multiplier * output_sc;
input_sc = params.get<float>("input_scale", params.get<float>("multiplier", 1.f) * output_sc);
hasDynamicShapes = params.get<bool>("has_dynamic_shapes", false);
shapesInitialized = !hasDynamicShapes;
if (params.has("pool") || params.has("kernel_size") ||
params.has("kernel_w") || params.has("kernel_h"))
if (params.has("pool"))
{
String pool = toLowerCase(params.get<String>("pool", "max"));
if (pool == "max")
@@ -50,12 +47,21 @@ public:
type = SUM;
else
CV_Error(Error::StsBadArg, "Unknown pooling type \"" + pool + "\"");
}
else
{
type = MAX;
}
const bool hasKernelOrGlobalSpec =
params.has("kernel_size") || params.has("kernel_w") || params.has("kernel_h") || params.has("kernel_d") ||
params.has("global_pooling") || params.has("global_pooling_d") ||
params.has("global_pooling_h") || params.has("global_pooling_w");
if (hasKernelOrGlobalSpec)
{
getPoolingKernelParams(params, kernel_size, isGlobalPooling, pads_begin, pads_end, strides, padMode);
globalPooling = isGlobalPooling[0] || isGlobalPooling[1] || isGlobalPooling[2];
}
else
CV_Error(Error::StsBadArg, "Cannot determine pooling type");
setParamsFrom(params);
ceilMode = params.get<bool>("ceil_mode", true);
spatialScale = params.get<float>("spatial_scale", 1);
@@ -357,7 +363,7 @@ public:
int nstripes, inpZp, outZp;
std::vector<int> ofsbuf;
int poolingType;
float multiplier;
float inputScaleRatio;
float spatialScale;
std::vector<size_t> pads_begin, pads_end;
@@ -366,13 +372,13 @@ public:
PoolingInvoker() : src(0), rois(0), dst(0), pad_l(0), pad_t(0), pad_r(0), pad_b(0),
avePoolPaddedArea(false), nstripes(0), inpZp(0), outZp(0),
poolingType(MAX), multiplier(1), spatialScale(0){}
poolingType(MAX), inputScaleRatio(1.f), spatialScale(0){}
static void run(const Mat& src, const Mat& rois, Mat& dst,
std::vector<size_t> kernel_size, std::vector<size_t> strides,
std::vector<size_t> pads_begin, std::vector<size_t> pads_end,
bool avePoolPaddedArea, int poolingType, float spatialScale,
float multiplier, int inpZp, int outZp, int nstripes)
float inputScaleRatio, int inpZp, int outZp, int nstripes)
{
CV_Assert_N(
src.isContinuous(), dst.isContinuous(),
@@ -405,7 +411,7 @@ public:
p.outZp = outZp;
p.poolingType = poolingType;
p.spatialScale = spatialScale;
p.multiplier = multiplier;
p.inputScaleRatio = inputScaleRatio;
int height = isPool1D ? 1 : src.size[src.dims - 2];
int width = src.size[src.dims - 1];
@@ -610,7 +616,7 @@ public:
int bias = (avePoolPaddedArea ? (padded_kernel_area - real_kernel_area) * inpZp : 0)
- (inpZp * kernel_area);
float inv_kernel_area = poolingType == AVE ? multiplier / kernel_area : multiplier;
float inv_kernel_area = poolingType == AVE ? inputScaleRatio / kernel_area : inputScaleRatio;
#if CV_SIMD128
if( isPool2D && xstart > 0 && x0 + 15 < x1 && (x0 + 15) * stride_w - pad_l + kernel_w < inp_width )
{
@@ -680,16 +686,18 @@ public:
{
const int nstripes = getNumThreads();
Mat rois;
const float inputScaleRatio = input_sc / std::max(output_sc, 1e-12f);
PoolingInvoker::run(src, rois, dst, kernel_size, strides, pads_begin, pads_end, avePoolPaddedArea, type,
spatialScale, multiplier, input_zp, output_zp, nstripes);
spatialScale, inputScaleRatio, input_zp, output_zp, nstripes);
}
void avePooling(Mat &src, Mat &dst)
{
const int nstripes = getNumThreads();
Mat rois;
const float inputScaleRatio = input_sc / std::max(output_sc, 1e-12f);
PoolingInvoker::run(src, rois, dst, kernel_size, strides, pads_begin, pads_end, avePoolPaddedArea, type,
spatialScale, multiplier, input_zp, output_zp, nstripes);
spatialScale, inputScaleRatio, input_zp, output_zp, nstripes);
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
@@ -787,7 +795,6 @@ private:
};
bool hasDynamicShapes;
bool shapesInitialized;
float multiplier;
};
Ptr<PoolingLayerInt8> PoolingLayerInt8::create(const LayerParams& params)
@@ -11,6 +11,42 @@ namespace cv
namespace dnn
{
#if CV_SIMD || CV_SIMD_SCALABLE
static void dequantizeLinearChunk_u8_f32(const uint8_t* src, float* dst,
float scale, int zp, int64_t len)
{
const int vlanes = VTraits<v_float32>::vlanes();
v_float32 vscale = vx_setall_f32(scale);
v_int32 vzp = vx_setall_s32(zp);
int64_t j = 0;
for (; j <= len - vlanes; j += vlanes) {
v_int32 vi = v_reinterpret_as_s32(vx_load_expand_q(src + j));
vi = v_sub(vi, vzp);
v_float32 vf = v_mul(v_cvt_f32(vi), vscale);
v_store(dst + j, vf);
}
for (; j < len; j++)
dst[j] = (float)(src[j] - zp) * scale;
}
static void dequantizeLinearFast_u8_f32(const uint8_t* 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_u8_f32(inp + ofs, out + ofs, scale, zp, len);
}
});
}
#endif
/*
DequantizeLinear layer, as defined in ONNX specification:
https://onnx.ai/onnx/operators/onnx__DequantizeLinear.html
@@ -165,6 +201,19 @@ static void dequantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp,
}
}
// Fast path: per-tensor dequantization uint8→float with universal intrinsics
#if CV_SIMD || CV_SIMD_SCALABLE
if (block_size == 0 && sz_a == 1 && inptype == CV_8U && outtype == CV_32F && sctype == CV_32F) {
float sc = reinterpret_cast<const float*>(scale.data)[0];
int zpval = zp.empty() ? 0 : (int)reinterpret_cast<const uint8_t*>(zp.data)[0];
int64_t total = nslices * slice_size;
dequantizeLinearFast_u8_f32(reinterpret_cast<const uint8_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),
+74 -2
View File
@@ -11,6 +11,7 @@
#include "../op_vkcom.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include "opencv2/core/hal/intrin.hpp"
#include <algorithm>
#include <iterator>
@@ -468,13 +469,70 @@ public:
}
}
#if CV_SIMD
// Fast path: fully contiguous float Add → flatten + SIMD + parallel_for_
bool is_add = (this->op == OPERATION::SUM || this->op == OPERATION::ADD);
if (is_add && std::is_same<T, float>::value && std::is_same<RESULT_T, float>::value &&
dp1 == 1 && dp2 == 1 && dp == 1 && ndims >= 1) {
bool contiguous = true;
for (int k = ndims - 2; k >= 0; k--) {
if (shape[k] <= 1) continue; // size-1 dims have stride 0, skip
size_t expected = (size_t)shape[k + 1] * step1[k + 1];
if (step1[k] != expected || step2[k] != expected || step[k] != expected) {
contiguous = false;
break;
}
}
if (contiguous) {
int64_t total = (int64_t)nplanes * plane_size;
const float* p1 = (const float*)data1;
const float* p2 = (const float*)data2;
float* po = (float*)data;
const int64_t chunk = 1024;
int64_t nchunks = (total + chunk - 1) / chunk;
parallel_for_(Range(0, (int)nchunks), [&](const Range& r) {
for (int c = r.start; c < r.end; c++) {
int64_t start = c * chunk;
int64_t end = std::min(start + chunk, total);
int64_t i = start;
for (; i <= end - (int64_t)VTraits<v_float32>::nlanes * 4; i += VTraits<v_float32>::nlanes * 4) {
v_store(po + i, v_add(vx_load(p1 + i), vx_load(p2 + i)));
v_store(po + i + VTraits<v_float32>::nlanes, v_add(vx_load(p1 + i + VTraits<v_float32>::nlanes), vx_load(p2 + i + VTraits<v_float32>::nlanes)));
v_store(po + i + VTraits<v_float32>::nlanes*2, v_add(vx_load(p1 + i + VTraits<v_float32>::nlanes*2), vx_load(p2 + i + VTraits<v_float32>::nlanes*2)));
v_store(po + i + VTraits<v_float32>::nlanes*3, v_add(vx_load(p1 + i + VTraits<v_float32>::nlanes*3), vx_load(p2 + i + VTraits<v_float32>::nlanes*3)));
}
for (; i < end; i++)
po[i] = p1[i] + p2[i];
}
});
return;
}
}
#endif
if (nplanes == 1) { // parallelize within the plane
const T* ptr1 = (const T*)data1;
const T* ptr2 = (const T*)data2;
RESULT_T* ptr = (RESULT_T*)data;
auto worker = [&](const Range &r) {
if (dp1 == 1 && dp2 == 1 && dp == 1) {
for(int i = r.start; i < r.end; i++) {
int i = r.start;
#if CV_SIMD
if (is_add && std::is_same<T, float>::value && std::is_same<RESULT_T, float>::value) {
const float* p1 = (const float*)(const void*)&ptr1[r.start];
const float* p2 = (const float*)(const void*)&ptr2[r.start];
float* po = (float*)(void*)&ptr[r.start];
int len = r.end - r.start, j = 0;
for (; j <= len - VTraits<v_float32>::nlanes * 4; j += VTraits<v_float32>::nlanes * 4) {
v_store(po + j, v_add(vx_load(p1 + j), vx_load(p2 + j)));
v_store(po + j + VTraits<v_float32>::nlanes, v_add(vx_load(p1 + j + VTraits<v_float32>::nlanes), vx_load(p2 + j + VTraits<v_float32>::nlanes)));
v_store(po + j + VTraits<v_float32>::nlanes*2, v_add(vx_load(p1 + j + VTraits<v_float32>::nlanes*2), vx_load(p2 + j + VTraits<v_float32>::nlanes*2)));
v_store(po + j + VTraits<v_float32>::nlanes*3, v_add(vx_load(p1 + j + VTraits<v_float32>::nlanes*3), vx_load(p2 + j + VTraits<v_float32>::nlanes*3)));
}
i = r.start + j;
}
#endif
for(; i < r.end; i++) {
ptr[i] = op(ptr1[i], ptr2[i]);
}
} else if (dp1 == 1 && dp2 == 0 && dp == 1){
@@ -516,7 +574,21 @@ public:
const T* ptr2 = (const T*)ptr2_;
RESULT_T* ptr = (RESULT_T*)ptr_;
if (dp1 == 1 && dp2 == 1 && dp == 1) {
for(int i = 0; i < plane_size; i++) {
int i = 0;
#if CV_SIMD
if (is_add && std::is_same<T, float>::value && std::is_same<RESULT_T, float>::value) {
const float* p1 = (const float*)(const void*)ptr1;
const float* p2 = (const float*)(const void*)ptr2;
float* po = (float*)(void*)ptr;
for (; i <= plane_size - VTraits<v_float32>::nlanes * 4; i += VTraits<v_float32>::nlanes * 4) {
v_store(po + i, v_add(vx_load(p1 + i), vx_load(p2 + i)));
v_store(po + i + VTraits<v_float32>::nlanes, v_add(vx_load(p1 + i + VTraits<v_float32>::nlanes), vx_load(p2 + i + VTraits<v_float32>::nlanes)));
v_store(po + i + VTraits<v_float32>::nlanes*2, v_add(vx_load(p1 + i + VTraits<v_float32>::nlanes*2), vx_load(p2 + i + VTraits<v_float32>::nlanes*2)));
v_store(po + i + VTraits<v_float32>::nlanes*3, v_add(vx_load(p1 + i + VTraits<v_float32>::nlanes*3), vx_load(p2 + i + VTraits<v_float32>::nlanes*3)));
}
}
#endif
for(; i < plane_size; i++) {
ptr[i] = op(ptr1[i], ptr2[i]);
}
} else if (dp1 == 1 && dp2 == 0 && dp == 1){
@@ -7,11 +7,63 @@
#include "layers_common.hpp"
#include "../net_impl.hpp"
#if defined(__x86_64__) || defined(_M_X64)
#include <immintrin.h>
#endif
namespace cv
{
namespace dnn
{
// Fast path for per-tensor quantization: float → uint8 with AVX2
#if defined(__x86_64__) || defined(_M_X64)
#if defined(__GNUC__) || defined(__clang__)
__attribute__((target("avx2")))
#endif
static void quantizeLinearChunk_f32_u8_avx2(const float* src, uint8_t* dst,
float inv_scale, float zp_f,
int64_t len)
{
__m256 vscale = _mm256_set1_ps(inv_scale);
__m256 vzp = _mm256_set1_ps(zp_f);
__m256 vmin = _mm256_setzero_ps();
__m256 vmax = _mm256_set1_ps(255.f);
int64_t j = 0;
for (; j <= len - 8; j += 8) {
__m256 v = _mm256_loadu_ps(src + j);
v = _mm256_add_ps(_mm256_mul_ps(v, vscale), vzp);
v = _mm256_min_ps(_mm256_max_ps(v, vmin), vmax);
__m256i vi = _mm256_cvtps_epi32(v);
__m128i lo = _mm256_castsi256_si128(vi);
__m128i hi = _mm256_extracti128_si256(vi, 1);
__m128i packed16 = _mm_packs_epi32(lo, hi);
__m128i packed8 = _mm_packus_epi16(packed16, packed16);
_mm_storel_epi64((__m128i*)(dst + j), packed8);
}
for (; j < len; j++)
dst[j] = saturate_cast<uint8_t>(src[j] * inv_scale + zp_f);
}
static void quantizeLinearFast_f32_u8_avx2(const float* inp, uint8_t* out,
float inv_scale, float zp_f,
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);
quantizeLinearChunk_f32_u8_avx2(inp + ofs, out + ofs, inv_scale, zp_f, len);
}
});
}
#endif
/*
QuantizeLinear layer, as defined in ONNX specification:
https://onnx.ai/onnx/operators/onnx__QuantizeLinear.html
@@ -172,6 +224,20 @@ static void quantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp,
}
}
// Fast path: per-tensor quantization float→uint8 with AVX2 + proper parallelism
#if defined(__x86_64__) || defined(_M_X64)
if (block_size == 0 && sz_a == 1 && inptype == CV_32F && outtype == CV_8U && sctype == CV_32F
&& checkHardwareSupport(CV_CPU_AVX2)) {
float inv_scale = 1.f / reinterpret_cast<const float*>(scale.data)[0];
float zp_f = zp.empty() ? 0.f : (float)reinterpret_cast<const uint8_t*>(zp.data)[0];
int64_t total = nslices * slice_size;
quantizeLinearFast_f32_u8_avx2(reinterpret_cast<const float*>(inp.data),
reinterpret_cast<uint8_t*>(out.data),
inv_scale, zp_f, total);
return;
}
#endif
if (outtype == CV_8U && sctype == CV_32F && inptype == CV_32F)
quantizeLinear(reinterpret_cast<const float*>(inp.data),
reinterpret_cast<const float*>(scale.data),
+2
View File
@@ -450,6 +450,8 @@ struct Net::Impl : public detail::NetImplBase
// fuse batch norm, add bias and activation to convolution
void fuseBasic();
// replace constant sub-expressions with their results
void fuseQDQ();
void constFold();
// make some operations (activation, batch norm, convolution) unary if
// all their arguments except for the 1st one are constant.
+1
View File
@@ -497,6 +497,7 @@ void Net::Impl::prepareForInference()
#endif
if (!prepared) {
fuseQDQ();
constFold();
constArgs();
useBlockLayout();