Skip to content

Optimum ONNX Runtime API improvement#515

Merged
michaelbenayoun merged 19 commits into
huggingface:mainfrom
michaelbenayoun:ort_quantizer_and_optimizer_export
Dec 6, 2022
Merged

Optimum ONNX Runtime API improvement#515
michaelbenayoun merged 19 commits into
huggingface:mainfrom
michaelbenayoun:ort_quantizer_and_optimizer_export

Conversation

@michaelbenayoun

@michaelbenayoun michaelbenayoun commented Nov 24, 2022

Copy link
Copy Markdown
Member

What does this PR do?

  • ORTModel, ORTModelDecoder and ORTModelForConditionalGeneration can load any ONNX model files regardless of their names, allowing to load optimized and quantized models without having to specify the file_name argument.
  • ORTModel._from_transformers now downloads and loads the model in a temporary directory instead of the cache, which was not a right place to store it.
  • ORTQuantizer saves both the model configuration and the tokenizer / processor / feature extractor, making the exported directory usable end-to-end.
  • ORTOptimizer was already saving the model configuration but not the tokenize / processor / feature extractor, it does now.
  • OnnxConfigWithPast now has two class atributes (that will be used when no value is specified in the __init__ to avoid having to overwrite the __init__ just for that) :
    • USE_PAST_FOR_INPUTS: if True, past key values will be in the inputs of the model
    • USE_PRESENT_FOR_OUTPUTS: if True, present key values will be in the outputs of the model
    • This is useful for decoder and encoder-decoder models (cc @mht-sharma)

Those features combined allow the following:

from transformers import AutoTokenizer
from optimum.onnxruntime import (
    ORTQuantizer,
    ORTOptimizer,
    ORTConfig,
    AutoQuantizationConfig,
    AutoOptimizationConfig,
    ORTModelForSequenceClassification
)
model_name = "distilbert-base-uncased-finetuned-sst-2-english" 

model = ORTModelForSequenceClassification.from_pretrained(model_name, from_transformers=True)
optimizer = ORTOptimizer.from_pretrained(model)

optimizer.optimize(
AutoOptimizationConfig.with_optimization_level("O2"), "optimized_model"
)

quantizer = ORTQuantizer.from_pretrained(model)
quantizer.quantize(
     AutoQuantizationConfig.avx512_vnni(is_static=False), "quantized_model"
)

optimized_model = ORTModelForSequenceClassification.from_pretrained("optimized_model")
quantized_model = ORTModelForSequenceClassification.from_pretrained("quantized_model")

tokenizer = AutoTokenizer.from_pretrained("optimized_model")
inputs = tokenizer("This is easier now!", return_tensors="pt")

print(model(**inputs))
print(optimized_model(**inputs))
print(quantized_model(**inputs))

This was not possible before.

Fixes: #493

@HuggingFaceDocBuilderDev

HuggingFaceDocBuilderDev commented Nov 24, 2022

Copy link
Copy Markdown

The documentation is not available anymore as the PR was closed or merged.

@michaelbenayoun
michaelbenayoun force-pushed the ort_quantizer_and_optimizer_export branch 4 times, most recently from 4ba8d37 to ff0042b Compare December 2, 2022 15:56
@michaelbenayoun
michaelbenayoun marked this pull request as ready for review December 2, 2022 15:57
@michaelbenayoun
michaelbenayoun force-pushed the ort_quantizer_and_optimizer_export branch from 2d8b31d to f503ea6 Compare December 5, 2022 09:49
@michaelbenayoun michaelbenayoun changed the title ORTQuantizer saves model config when possible Optimum ONNX Runtime API improvement Dec 5, 2022
feature_extractor.save_pretrained(args.output.parent)
except Exception:
pass
maybe_save_tokenizer_or_processor_or_feature_extractor(args.model, args.output.parent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember discussing to move this inside export, is that still the case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually do not know if we should put this inside the export itself, good question.

Comment on lines +647 to +664
task,
model_id,
subfolder=subfolder,
revision=revision,
cache_dir=cache_dir,
config=config,
use_auth_token=use_auth_token,
local_files_only=local_files_only,
force_download=force_download,
)

model_type = model.config.model_type.replace("_", "-")
model_name = getattr(model, "name", None)

onnx_config_constructor = TasksManager.get_exporter_config_constructor(
model_type, "onnx", task=task, model_name=model_name
)
onnx_config = onnx_config_constructor(model.config, use_present_in_outputs=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this being repeated in multiple files. Would it make sense to have a helper function for these lines?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried implementing it, but end up with circular imports!

Comment thread optimum/exporters/onnx/base.py
Comment thread optimum/onnxruntime/modeling_seq2seq.py Outdated
Comment thread optimum/modeling_base.py
os.makedirs(save_directory, exist_ok=True)

self.config.save_pretrained(save_directory)
for preprocessor in self._preprocessors:

@fxmarty fxmarty Dec 5, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PreTrainedModel does not save preprocessors with save_pretrained, why would we introduce this asymmetry in Optimum? Personnally I don't understand the motivation for this self._preprocessors. What's the issue with fully dissociating preprocessing and modeling, like it's done in transformers?

I also think it would be better to break down these huge PRs in smaller ones

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is not there, we do not do anything, otherwise we save it. The reason for this is because let's say I export a model with the ORTModel classes, I want to be able to use the save directory as a model repo end-to-end. Same thing for graph optimization / quantization. Otherwise we would need to provide a different name for the ORTModel.from_pretrained and the AutoTokenizer.from_pretrained.

Comment thread optimum/onnxruntime/modeling_decoder.py Outdated
Comment thread optimum/onnxruntime/modeling_decoder.py Outdated
Comment thread optimum/onnxruntime/modeling_decoder.py Outdated
Comment thread optimum/onnxruntime/modeling_decoder.py Outdated
os.path.join(model_id, subfolder, decoder_with_past_file_name) if use_cache else None
model_path = Path(model_id)

def validate_filename(filename):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be better out of _from_pretrained. Otherwise this is redefined every time _from_pretrained is called, am I correct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, which is not a big deal for functions with a one-time usage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have it as a function in the first time then?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use it multiple time inside this function, but never outside. We could make that a method, but that is not needed specifically. While it is true that there is redundancy between this and modeling_seq2seq.py, I wanted to keep the function simple (making it a method woulrd require more arguments and so on).

Comment thread optimum/onnxruntime/modeling_decoder.py Outdated
succeeded = False
return succeeded

def infer_filename(pattern: str, argument_name: str, fail_if_not_found: bool = True) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above (Can we add a short comment as to what this function does + wouldn't it be better out)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this is needed, this is basically a temporary function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the name is enough, I renamed the first one since the name was not explicit.

Comment thread optimum/onnxruntime/modeling_ort.py Outdated
from ..exporters import TasksManager
from ..exporters.onnx import export
from ..modeling_base import FROM_PRETRAINED_START_DOCSTRING, OptimizedModel
from ..utils.save_utils import maybe_load_preprocessors, maybe_save_tokenizer_or_processor_or_feature_extractor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first glance I don't understand what the maybe means in the functions name

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it might load / save or not. If nothing is found, it won't do anything.

Comment thread optimum/onnxruntime/modeling_decoder.py
):
io_binding = self.session.io_binding()

if "TensorrtExecutionProvider" in self.providers and self.use_io_binding:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this removed? I could not find it elsewhere. If removed, why?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is in ORTModel, now ORTModelForConditionalGeneration uses the base class initialiazer, so we do not need that anymore here.

Comment thread optimum/onnxruntime/quantization.py
Comment thread optimum/utils/save_utils.py Outdated
Comment thread optimum/onnxruntime/modeling_decoder.py
Comment thread optimum/onnxruntime/modeling_decoder.py
@michaelbenayoun
michaelbenayoun merged commit 2a4b790 into huggingface:main Dec 6, 2022
@michaelbenayoun
michaelbenayoun deleted the ort_quantizer_and_optimizer_export branch December 6, 2022 09:13
@fxmarty

fxmarty commented Dec 6, 2022

Copy link
Copy Markdown
Contributor

Why was it merged? I would have liked to rereview and I don't think all my points were addressed.

@michaelbenayoun

Copy link
Copy Markdown
Member Author

Which point do you want to discuss? We can discuss them, and I will make the PR for those changes (if needed).

@fxmarty

fxmarty commented Dec 6, 2022

Copy link
Copy Markdown
Contributor

#515 (comment) => I still don't understand why we introduce self._preprocessors that does not exist in transformers.

#515 (comment) => could not find a description

#515 (comment) => same, I disagree it is not needed, it would help readability

#515 (comment) => could not find a documentation, but could have missed it

@michaelbenayoun

Copy link
Copy Markdown
Member Author
  • The idea behind self._preprocessors is to be able to save all the model's preprocessors when possible, this will make using the saved model much easier.

Example: You load bert-base-uncased and quantize it. Now you have a bert_quantized directory.

With self._preprocessors we are able to save the preprocessors during quantization, meaning that you ca do:

model_name = "bert_quantized"
tok = AutoTokenizer.from_pretrained(model_name)
quantized_model = ORTModel.from_pretrained(model_name)

instead of:

tok = AutoTokenizer.from_pretrained("bert-base-uncased")
quantized_model = ORTModel.from_pretrained("bert_quantized")

This makes things a lot easier in terms of usage, especially if we think about production.

  • I am not really in favor of documenting inner functions, especially this one, it does what the name implies: it infers the filename of the onnx file. But I will document it if you think this is needed here.
  • Same comment, validate_file_exists is quite explicit it terms of naming, it checks that the file called "filename" exists. I will put a comment if you think this is needed too.
  • You mean that you want to say in the docstring that we use a regex to find potential files?

@fxmarty

fxmarty commented Dec 7, 2022

Copy link
Copy Markdown
Contributor

@michaelbenayoun What is the difference between the two code samples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ORTQuantizer does not save config and tokenizer / feature extractor

5 participants