From 7ffd2a5c9e93e61317cf8ce61140c119151733b6 Mon Sep 17 00:00:00 2001 From: Xin He Date: Mon, 28 Nov 2022 19:17:01 +0800 Subject: [PATCH 1/4] new API for export --- neural_compressor/adaptor/pytorch.py | 11 + neural_compressor/adaptor/torch_utils/onnx.py | 35 ++- neural_compressor/config.py | 52 ++-- neural_compressor/model/torch_model.py | 91 ++++++- test/export/test_torch2onnx.py | 227 ++++++++++++++++++ 5 files changed, 367 insertions(+), 49 deletions(-) create mode 100644 test/export/test_torch2onnx.py diff --git a/neural_compressor/adaptor/pytorch.py b/neural_compressor/adaptor/pytorch.py index 01885ebe682..0a44fe2f5a3 100644 --- a/neural_compressor/adaptor/pytorch.py +++ b/neural_compressor/adaptor/pytorch.py @@ -2851,6 +2851,10 @@ def _pre_hook_for_qat(self, dataloader=None): quantized_ops[op[0]] = torch.quantization.default_dynamic_qconfig else: quantized_ops[op[0]] = q_cfgs + # build for fetching scale and zeropoint + op_config_dict = {} + for op in quantizable_ops: + op_config_dict[op] = {'weight': {'dtype': 'int8'}, 'activation': {'dtype': 'uint8'}} if self.version.release < Version("1.11.0").release: quantized_ops["default_qconfig"] = None else: @@ -2892,10 +2896,12 @@ def _pre_hook_for_qat(self, dataloader=None): example_inputs=example_inputs) # This is a flag for reloading self.model.q_config = { + 'calib_sampling_size': 100, # tmp arg for export API 'is_oneshot': True, 'framework': 'pytorch_fx', 'reduce_range': REDUCE_RANGE, 'quantizable_ops': quantizable_ops, + 'op': op_config_dict, 'sub_module_list': self.sub_module_list, 'approach': 'quant_aware_training' } @@ -2918,6 +2924,11 @@ def _post_hook_for_qat(self): PyTorch_FXAdaptor.convert_sub_graph(self.sub_module_list, \ self.model._model, prefix='') + if self.approach != 'post_training_dynamic_quant': + self._get_scale_zeropoint(self.model._model, self.model.q_config) + self._dump_model_op_stats(self.model._model, self.model.q_config, self.approach) + torch_utils.util.get_embedding_contiguous(self.model._model) + def train(self, model, dataloader, optimizer_tuple, criterion_tuple, hooks, **kwargs): """Execute the train process on the specified model. diff --git a/neural_compressor/adaptor/torch_utils/onnx.py b/neural_compressor/adaptor/torch_utils/onnx.py index aadcb80f810..c667281cb66 100644 --- a/neural_compressor/adaptor/torch_utils/onnx.py +++ b/neural_compressor/adaptor/torch_utils/onnx.py @@ -30,17 +30,30 @@ def __init__(self, dataloader, sample_size=100): self.datasize = self.batch_num * self.batch_size self.data = [] - for i, (input, label) in enumerate(self.dataloader): - if i * self.batch_size >= self.datasize: - break - if isinstance(input, dict) or isinstance(input, UserDict): - batch = {k: v.detach().cpu().numpy() for k, v in input.items()} - elif isinstance(input, list) or isinstance(input, tuple): - batch = {'input': [v.detach().cpu().numpy() for v in input]} - else: - batch = {'input': input.detach().cpu().numpy()} - self.data.append(batch) - self.data = iter(self.data) + try: + for i, (input, label) in enumerate(self.dataloader): + if i * self.batch_size >= self.datasize: + break + if isinstance(input, dict) or isinstance(input, UserDict): + batch = {k: v.detach().cpu().numpy() for k, v in input.items()} + elif isinstance(input, list) or isinstance(input, tuple): + batch = {'input': [v.detach().cpu().numpy() for v in input]} + else: + batch = {'input': input.detach().cpu().numpy()} + self.data.append(batch) + self.data = iter(self.data) + except: + for i, input in enumerate(self.dataloader): + if i * self.batch_size >= self.datasize: + break + if isinstance(input, dict) or isinstance(input, UserDict): + batch = {k: v.detach().cpu().numpy() for k, v in input.items()} + elif isinstance(input, list) or isinstance(input, tuple): + batch = {'input': [v.detach().cpu().numpy() for v in input]} + else: + batch = {'input': input.detach().cpu().numpy()} + self.data.append(batch) + self.data = iter(self.data) def get_next(self): return next(self.data, None) diff --git a/neural_compressor/config.py b/neural_compressor/config.py index 535eb307a28..ef8f3d53903 100644 --- a/neural_compressor/config.py +++ b/neural_compressor/config.py @@ -717,21 +717,19 @@ def __init__( self, dtype="int8", opset_version=14, - quant_mode="'QDQ'", - sample_inputs=None, + quant_format="'QDQ'", + example_inputs=None, input_names=None, output_names=None, dynamic_axes=None, - **kwargs, ): self._dtype = dtype self._opset_version = opset_version - self._quant_mode = quant_mode - self._sample_inputs = sample_inputs + self._quant_format = quant_format + self._example_inputs = example_inputs self._input_names = input_names self._output_names = output_names self._dynamic_axes = dynamic_axes - self._kwargs = kwargs @property def dtype(self): @@ -750,20 +748,20 @@ def opset_version(self, opset_version): self._opset_version = opset_version @property - def quant_mode(self): - return self._quant_mode + def quant_format(self): + return self._quant_format - @quant_mode.setter - def quant_mode(self, quant_mode): - self._quant_mode = quant_mode + @quant_format.setter + def quant_format(self, quant_format): + self._quant_format = quant_format @property - def sample_inputs(self): - return self._sample_inputs + def example_inputs(self): + return self._example_inputs - @sample_inputs.setter - def sample_inputs(self, sample_inputs): - self._sample_inputs = sample_inputs + @example_inputs.setter + def example_inputs(self, example_inputs): + self._example_inputs = example_inputs @property def input_names(self): @@ -783,7 +781,7 @@ def output_names(self, output_names): @property def dynamic_axes(self): - return self._output_names + return self._dynamic_axes @dynamic_axes.setter def dynamic_axes(self, dynamic_axes): @@ -795,8 +793,8 @@ def __init__( self, dtype="int8", opset_version=14, - quant_mode="'QDQ'", - sample_inputs=None, + quant_format="'QDQ'", + example_inputs=None, input_names=None, output_names=None, dynamic_axes=None, @@ -805,13 +803,13 @@ def __init__( super().__init__( dtype=dtype, opset_version=opset_version, - quant_mode=quant_mode, - sample_inputs=sample_inputs, + quant_format=quant_format, + example_inputs=example_inputs, input_names=input_names, output_names=output_names, dynamic_axes=dynamic_axes, - kwargs=kwargs, ) + self.kwargs = kwargs class TF2ONNXConfig(ExportConfig): @@ -819,8 +817,8 @@ def __init__( self, dtype="int8", opset_version=14, - quant_mode="'QDQ'", - sample_inputs=None, + quant_format="'QDQ'", + example_inputs=None, input_names=None, output_names=None, dynamic_axes=None, @@ -829,13 +827,13 @@ def __init__( super().__init__( dtype=dtype, opset_version=opset_version, - quant_mode=quant_mode, - sample_inputs=sample_inputs, + quant_format=quant_format, + example_inputs=example_inputs, input_names=input_names, output_names=output_names, dynamic_axes=dynamic_axes, - kwargs=kwargs, ) + self.kwargs = kwargs def set_random_seed(seed: int): diff --git a/neural_compressor/model/torch_model.py b/neural_compressor/model/torch_model.py index 06727a92a0c..a1f4d4ea5dc 100644 --- a/neural_compressor/model/torch_model.py +++ b/neural_compressor/model/torch_model.py @@ -20,7 +20,6 @@ import inspect import sys from collections import OrderedDict, UserDict -from abc import abstractmethod from ..adaptor.torch_utils.util import input2tuple from neural_compressor.utils.utility import LazyImport, compute_sparsity from neural_compressor.utils import logger @@ -46,8 +45,41 @@ def __init__(self, model, **kwargs): self.q_config = None self._workspace_path = '' self.is_quantized = False + try: + self.fp32_model = copy.deepcopy(model) + except Exception as e: # pragma: no cover + logger.warning("Fail to deep copy the model due to {}, inplace is used now.".format( + repr(e))) + self.fp32_model = model self.kwargs = kwargs if kwargs else None + def __repr__(self): + # rewirte this func to avoid printing fp32_model + from torch.nn.modules.module import _addindent + # We treat the extra repr like the sub-module, one item per line + extra_lines = [] + extra_repr = self.extra_repr() + # empty string will be split into list [''] + if extra_repr: + extra_lines = extra_repr.split('\n') + child_lines = [] + for key, module in self._modules.items(): + if key == 'fp32_model': + continue + mod_str = repr(module) + mod_str = _addindent(mod_str, 2) + child_lines.append('(' + key + '): ' + mod_str) + lines = extra_lines + child_lines + main_str = self._get_name() + '(' + if lines: + # simple one-liner info, which most builtin Modules will use + if len(extra_lines) == 1 and not child_lines: + main_str += extra_lines[0] + else: + main_str += '\n ' + '\n '.join(lines) + '\n' + main_str += ')' + return main_str + def forward(self, *args, **kwargs): return self._model(*args, **kwargs) @@ -355,13 +387,18 @@ def export_to_fp32_onnx( opset_version=14, dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}, + input_names=None, + output_names=None, do_constant_folding=True, verbose=True, fp32_model=None, ): - example_input_names = ['input'] - if isinstance(example_inputs, dict) or isinstance(example_inputs, UserDict): - example_input_names = list(example_inputs.keys()) + if input_names: + example_input_names = input_names + else: + example_input_names = ['input'] + if isinstance(example_inputs, dict) or isinstance(example_inputs, UserDict): + example_input_names = list(example_inputs.keys()) model = self.model if fp32_model: model = fp32_model @@ -371,6 +408,7 @@ def export_to_fp32_onnx( save_path, opset_version=opset_version, input_names=example_input_names, + output_names=output_names, dynamic_axes=dynamic_axes, do_constant_folding=do_constant_folding, ) @@ -386,6 +424,8 @@ def export_to_bf16_onnx(self, opset_version=14, dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}, + input_names=None, + output_names=None, do_constant_folding=True, verbose=True, ): @@ -395,6 +435,8 @@ def export_to_bf16_onnx(self, example_inputs = example_inputs, opset_version=opset_version, dynamic_axes=dynamic_axes, + input_names=input_names, + output_names=output_names, do_constant_folding=do_constant_folding, verbose=False, ) @@ -437,6 +479,8 @@ def export_to_int8_onnx( opset_version=14, dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}, + input_names=None, + output_names=None, do_constant_folding=True, quant_format='QDQ', dtype='S8S8', @@ -465,10 +509,9 @@ def export_to_int8_onnx( "No quantization configuration found, " + \ "please use the model generated by INC quantizer" if 'dynamic' in self.q_config['approach']: - op_types_to_quantize=['MatMul', 'Gather', "LSTM", 'Conv'] - pytorch_op_types_to_quantize=['Linear', 'Embedding', "LSTM", - 'Conv1d', 'Conv2d'] - addition_op_to_quantize = list(ortq.registry.IntegerOpsRegistry.keys()) + op_types_to_quantize=['MatMul', 'Gather', "LSTM"] + pytorch_op_types_to_quantize=['Linear', 'Embedding', "LSTM"] + addition_op_to_quantize = [] else: op_types_to_quantize=['MatMul', 'Gather', 'Conv'] pytorch_op_types_to_quantize=['Linear', 'Embedding', 'Conv1d', 'Conv2d'] @@ -495,6 +538,8 @@ def export_to_int8_onnx( example_inputs = example_inputs, opset_version=opset_version, dynamic_axes=dynamic_axes, + input_names=input_names, + output_names=output_names, do_constant_folding=do_constant_folding, verbose=False, fp32_model=fp32_model @@ -624,9 +669,33 @@ def export( save_path: str, conf, ): - # TODO - from neural_compressor.config import Torch2ONNXConfig - pass + if conf.dtype == 'int8': + calib_dataloader = conf.kwargs.pop("calib_dataloader", None) + self.export_to_int8_onnx( + save_path=save_path, + example_inputs=conf.example_inputs, + opset_version=conf.opset_version, + dynamic_axes=conf.dynamic_axes, + input_names=conf.input_names, + output_names=conf.output_names, + quant_format=conf.quant_format, + dtype='U8S8', + fp32_model=self.fp32_model, + calib_dataloader=calib_dataloader, + ) + elif conf.dtype == 'fp32': + self.export_to_fp32_onnx( + save_path=save_path, + example_inputs=conf.example_inputs, + opset_version=conf.opset_version, + dynamic_axes=conf.dynamic_axes, + input_names=conf.input_names, + output_names=conf.output_names, + verbose=True, + fp32_model=self.fp32_model, + ) + else: # pragma: no cover + assert False, "Not allowed dtype: {}, pleas use 'fp32' or 'int8'.".format(conf.dtype) class PyTorchFXModel(PyTorchModel): diff --git a/test/export/test_torch2onnx.py b/test/export/test_torch2onnx.py new file mode 100644 index 00000000000..01410ff0952 --- /dev/null +++ b/test/export/test_torch2onnx.py @@ -0,0 +1,227 @@ +import os +import copy +import shutil +import torch +import unittest +import numpy as np +from neural_compressor import quantization +from neural_compressor.experimental.common import Model +from neural_compressor.config import Torch2ONNXConfig +from neural_compressor.experimental.data.datasets.dataset import DATASETS +from neural_compressor import PostTrainingQuantConfig, QuantizationAwareTrainingConfig +from neural_compressor.training import prepare_compression +from neural_compressor.data import DATASETS, DATALOADERS +from transformers import AutoModelForSequenceClassification, AutoTokenizer +import torch.utils.data as data + + +def train_func_cv(compression_manager, model): + compression_manager.callbacks.on_train_begin() + optimizer = torch.optim.SGD(model.parameters(), lr=0.0001) + model.train() + input = torch.randn(1, 3, 224, 224) + output = model(input) + loss = output[0].mean() if isinstance(output, tuple) else output.mean() + optimizer.zero_grad() + loss.backward() + optimizer.step() + compression_manager.callbacks.on_train_end() + return model + +def train_func_nlp(compression_manager, model, input): + compression_manager.callbacks.on_train_begin() + optimizer = torch.optim.SGD(model.parameters(), lr=0.0001) + model.train() + output = model(**input) + loss = output.logits[0][0] + optimizer.zero_grad() + loss.backward() + optimizer.step() + compression_manager.callbacks.on_train_end() + return model + +def check_CV_onnx(model_path, dataloader): + import onnxruntime as ort + ort_session = ort.InferenceSession(model_path) + it = iter(dataloader) + input = next(it) + input_dict = {'input': input[0].detach().cpu().numpy()} + ort_session.run(None, input_dict) + return True + +def check_NLP_onnx(model_path, input): + import onnxruntime as ort + ort_session = ort.InferenceSession(model_path, None) + input_dict = {} + for k, v in input.items(): + input_dict[k] = np.array(v) + ort_session.run(None, input_dict) + return True + + +class DummyNLPDataloader(object): + def __init__(self, model_name): + self.tokenizer = AutoTokenizer.from_pretrained(model_name) + self.sequence_a = "intel-extension-for-transformers is based in SH" + self.sequence_b = "Where is intel-extension-for-transformers based? NYC or SH" + self.encoded_dict = self.tokenizer(self.sequence_a, self.sequence_b, return_tensors='pt') + self.encoded_dict['labels'] = 1 + self.batch_size = 1 + + def __iter__(self): + yield self.encoded_dict + + def __next__(self): + return self.encoded_dict + +class TestPytorch2ONNX(unittest.TestCase): + @classmethod + def setUpClass(self): + from torchvision.models.quantization import resnet18 + self.cv_model = resnet18() + self.cv_dataset = DATASETS("pytorch")["dummy"]((10, 3, 224, 224)) + self.cv_dataloader = DATALOADERS["pytorch"](self.cv_dataset) + self.nlp_model = AutoModelForSequenceClassification.from_pretrained( + "distilbert-base-uncased-finetuned-sst-2-english" + ) + self.nlp_dataloader = DummyNLPDataloader( + "distilbert-base-uncased-finetuned-sst-2-english" + ) + input = next(self.nlp_dataloader) + input.pop('labels') + self.nlp_input = input + + @classmethod + def tearDownClass(self): + shutil.rmtree('runs', ignore_errors=True) + # os.remove('fp32-cv-model.onnx') + # os.remove('int8-cv-model.onnx') + # os.remove('fp32-nlp-model.onnx') + # os.remove('int8-nlp-model.onnx') + shutil.rmtree("./saved", ignore_errors=True) + + def test_fp32_CV_models(self): + model = self.cv_model + inc_model = Model(model) + fp32_onnx_config = Torch2ONNXConfig( + dtype="fp32", + example_inputs=torch.randn(1, 3, 224, 224), + input_names=['input'], + output_names=['output'], + dynamic_axes={"input": {0: "batch_size"}, + "output": {0: "batch_size"}}, + ) + inc_model.export('fp32-cv-model.onnx', fp32_onnx_config) + check_CV_onnx('fp32-cv-model.onnx', self.cv_dataloader) + + def test_int8_CV_models(self): + for fake_yaml in ["dynamic", "qat", "static"]: + model = self.cv_model + if fake_yaml == "qat": + quant_conf = QuantizationAwareTrainingConfig(backend='pytorch_fx') + compression_manager = prepare_compression(copy.deepcopy(model), quant_conf) + q_model = train_func_cv(compression_manager, compression_manager.model) + else: + if fake_yaml == "dynamic": + quant_conf = PostTrainingQuantConfig(approach="dynamic") + elif fake_yaml == "static": + quant_conf = PostTrainingQuantConfig(approach="static", backend='pytorch_fx') + q_model = quantization.fit( + model, + quant_conf, + calib_dataloader=self.cv_dataloader if fake_yaml == "static" else None) + + if fake_yaml != "dynamic": + int8_onnx_config = Torch2ONNXConfig( + dtype="int8", + opset_version=14, + quant_format="QDQ", + example_inputs=torch.randn(1, 3, 224, 224), + input_names=['input'], + output_names=['output'], + dynamic_axes={"input": {0: "batch_size"}, + "output": {0: "batch_size"}}, + calib_dataloader=self.cv_dataloader, + ) + else: + int8_onnx_config = Torch2ONNXConfig( + dtype="int8", + opset_version=14, + quant_format="QDQ", + example_inputs=torch.randn(1, 3, 224, 224), + input_names=['input'], + output_names=['output'], + dynamic_axes={"input": {0: "batch_size"}, + "output": {0: "batch_size"}}, + ) + q_model.export('int8-cv-model.onnx', int8_onnx_config) + check_CV_onnx('int8-cv-model.onnx', self.cv_dataloader) + + def test_fp32_NLP_models(self): + symbolic_names = {0: 'batch_size', 1: 'max_seq_len'} + dynamic_axes = {k: symbolic_names for k in self.nlp_input.keys()} + + model = self.nlp_model + inc_model = Model(model) + fp32_onnx_config = Torch2ONNXConfig( + dtype="fp32", + example_inputs=tuple(self.nlp_input.values()), + input_names=list(self.nlp_input.keys()), + output_names=['labels'], + dynamic_axes=dynamic_axes, + ) + inc_model.export('fp32-nlp-model.onnx', fp32_onnx_config) + check_NLP_onnx('fp32-nlp-model.onnx', self.nlp_input) + + def test_int8_NLP_models(self): + symbolic_names = {0: 'batch_size', 1: 'max_seq_len'} + dynamic_axes = {k: symbolic_names for k in self.nlp_input.keys()} + + for fake_yaml in ["dynamic", "static", "qat"]: + model = self.nlp_model + if fake_yaml == "qat": + quant_conf = QuantizationAwareTrainingConfig(backend='pytorch_fx') + compression_manager = prepare_compression(copy.deepcopy(model), quant_conf) + q_model = train_func_nlp( + compression_manager, + compression_manager.model, + self.nlp_input + ) + else: + if fake_yaml == "dynamic": + quant_conf = PostTrainingQuantConfig(approach="dynamic") + elif fake_yaml == "static": + quant_conf = PostTrainingQuantConfig(approach="static", backend='pytorch_fx') + q_model = quantization.fit( + model, + quant_conf, + calib_dataloader=self.nlp_dataloader if fake_yaml == "static" else None) + + if fake_yaml != "dynamic": + int8_onnx_config = Torch2ONNXConfig( + dtype="int8", + opset_version=14, + quant_format="QDQ", + example_inputs=tuple(self.nlp_input.values()), + input_names=list(self.nlp_input.keys()), + output_names=['labels'], + dynamic_axes=dynamic_axes, + calib_dataloader=self.nlp_dataloader, + ) + else: + int8_onnx_config = Torch2ONNXConfig( + dtype="int8", + opset_version=14, + quant_format="QDQ", + example_inputs=tuple(self.nlp_input.values()), + input_names=list(self.nlp_input.keys()), + output_names=['labels'], + dynamic_axes=dynamic_axes, + ) + q_model.export('int8-nlp-model.onnx', int8_onnx_config) + check_NLP_onnx('int8-nlp-model.onnx', self.nlp_input) + +if __name__ == "__main__": + unittest.main() + + From 334d56f8b2afcf765e83ff76138f4792c3e24fff Mon Sep 17 00:00:00 2001 From: Xin He Date: Mon, 28 Nov 2022 20:48:22 +0800 Subject: [PATCH 2/4] fix onnxruntime 1.13.1 failure --- neural_compressor/model/torch_model.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/neural_compressor/model/torch_model.py b/neural_compressor/model/torch_model.py index a1f4d4ea5dc..6177bdc6281 100644 --- a/neural_compressor/model/torch_model.py +++ b/neural_compressor/model/torch_model.py @@ -515,11 +515,7 @@ def export_to_int8_onnx( else: op_types_to_quantize=['MatMul', 'Gather', 'Conv'] pytorch_op_types_to_quantize=['Linear', 'Embedding', 'Conv1d', 'Conv2d'] - if quant_format == 'QDQ': - addition_op_to_quantize = list(ortq.registry.QDQRegistry.keys()) - addition_op_to_quantize.remove('Relu') # ValueError: x not in list - else: - addition_op_to_quantize = list(ortq.registry.QLinearOpsRegistry.keys()) + addition_op_to_quantize = [] if 'U8S8' in dtype: op_types_to_quantize.remove('Gather') From 70b5daa4da8abc00823aa2c0fcc680c21eec4722 Mon Sep 17 00:00:00 2001 From: Xin He Date: Mon, 28 Nov 2022 20:58:36 +0800 Subject: [PATCH 3/4] support embedding for U8S8 --- neural_compressor/model/torch_model.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/neural_compressor/model/torch_model.py b/neural_compressor/model/torch_model.py index 6177bdc6281..42b5cee2d29 100644 --- a/neural_compressor/model/torch_model.py +++ b/neural_compressor/model/torch_model.py @@ -517,10 +517,6 @@ def export_to_int8_onnx( pytorch_op_types_to_quantize=['Linear', 'Embedding', 'Conv1d', 'Conv2d'] addition_op_to_quantize = [] - if 'U8S8' in dtype: - op_types_to_quantize.remove('Gather') - pytorch_op_types_to_quantize.remove('Embedding') - if quant_format == 'QDQ' and opset_version < 13: # pragma: no cover opset_version = 13 logger.warning("QDQ format requires opset_version >= 13, " + From 9ad4484206a16407ab191473a86835c03304e557 Mon Sep 17 00:00:00 2001 From: Xin He Date: Tue, 29 Nov 2022 09:27:08 +0800 Subject: [PATCH 4/4] fix typo, no need test --- neural_compressor/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neural_compressor/config.py b/neural_compressor/config.py index ef8f3d53903..371ba422963 100644 --- a/neural_compressor/config.py +++ b/neural_compressor/config.py @@ -717,7 +717,7 @@ def __init__( self, dtype="int8", opset_version=14, - quant_format="'QDQ'", + quant_format="QDQ", example_inputs=None, input_names=None, output_names=None, @@ -793,7 +793,7 @@ def __init__( self, dtype="int8", opset_version=14, - quant_format="'QDQ'", + quant_format="QDQ", example_inputs=None, input_names=None, output_names=None, @@ -817,7 +817,7 @@ def __init__( self, dtype="int8", opset_version=14, - quant_format="'QDQ'", + quant_format="QDQ", example_inputs=None, input_names=None, output_names=None,