Spaces:
Runtime error
Runtime error
| # Copyright 2021 The HuggingFace Team. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import subprocess | |
| import sys | |
| import warnings | |
| from argparse import ArgumentParser | |
| from pathlib import Path | |
| from packaging import version | |
| from .. import AutoFeatureExtractor, AutoImageProcessor, AutoProcessor, AutoTokenizer | |
| from ..utils import logging | |
| from ..utils.import_utils import is_optimum_available | |
| from .convert import export, validate_model_outputs | |
| from .features import FeaturesManager | |
| from .utils import get_preprocessor | |
| MIN_OPTIMUM_VERSION = "1.5.0" | |
| ENCODER_DECODER_MODELS = ["vision-encoder-decoder"] | |
| def export_with_optimum(args): | |
| if is_optimum_available(): | |
| from optimum.version import __version__ as optimum_version | |
| parsed_optimum_version = version.parse(optimum_version) | |
| if parsed_optimum_version < version.parse(MIN_OPTIMUM_VERSION): | |
| raise RuntimeError( | |
| f"transformers.onnx requires optimum >= {MIN_OPTIMUM_VERSION} but {optimum_version} is installed. You " | |
| "can upgrade optimum by running: pip install -U optimum[exporters]" | |
| ) | |
| else: | |
| raise RuntimeError( | |
| "transformers.onnx requires optimum to run, you can install the library by running: pip install " | |
| "optimum[exporters]" | |
| ) | |
| cmd_line = [ | |
| sys.executable, | |
| "-m", | |
| "optimum.exporters.onnx", | |
| f"--model {args.model}", | |
| f"--task {args.feature}", | |
| f"--framework {args.framework}" if args.framework is not None else "", | |
| f"{args.output}", | |
| ] | |
| proc = subprocess.Popen(" ".join(cmd_line), stdout=subprocess.PIPE, shell=True) | |
| proc.wait() | |
| logger.info( | |
| "The export was done by optimum.exporters.onnx. We recommend using to use this package directly in future, as " | |
| "transformers.onnx is deprecated, and will be removed in v5. You can find more information here: " | |
| "https://huggingface.co/docs/optimum/exporters/onnx/usage_guides/export_a_model." | |
| ) | |
| def export_with_transformers(args): | |
| args.output = args.output if args.output.is_file() else args.output.joinpath("model.onnx") | |
| if not args.output.parent.exists(): | |
| args.output.parent.mkdir(parents=True) | |
| # Allocate the model | |
| model = FeaturesManager.get_model_from_feature( | |
| args.feature, args.model, framework=args.framework, cache_dir=args.cache_dir | |
| ) | |
| model_kind, model_onnx_config = FeaturesManager.check_supported_model_or_raise(model, feature=args.feature) | |
| onnx_config = model_onnx_config(model.config) | |
| if model_kind in ENCODER_DECODER_MODELS: | |
| encoder_model = model.get_encoder() | |
| decoder_model = model.get_decoder() | |
| encoder_onnx_config = onnx_config.get_encoder_config(encoder_model.config) | |
| decoder_onnx_config = onnx_config.get_decoder_config( | |
| encoder_model.config, decoder_model.config, feature=args.feature | |
| ) | |
| if args.opset is None: | |
| args.opset = max(encoder_onnx_config.default_onnx_opset, decoder_onnx_config.default_onnx_opset) | |
| if args.opset < min(encoder_onnx_config.default_onnx_opset, decoder_onnx_config.default_onnx_opset): | |
| raise ValueError( | |
| f"Opset {args.opset} is not sufficient to export {model_kind}. At least " | |
| f" {min(encoder_onnx_config.default_onnx_opset, decoder_onnx_config.default_onnx_opset)} is required." | |
| ) | |
| preprocessor = AutoFeatureExtractor.from_pretrained(args.model) | |
| onnx_inputs, onnx_outputs = export( | |
| preprocessor, | |
| encoder_model, | |
| encoder_onnx_config, | |
| args.opset, | |
| args.output.parent.joinpath("encoder_model.onnx"), | |
| ) | |
| validate_model_outputs( | |
| encoder_onnx_config, | |
| preprocessor, | |
| encoder_model, | |
| args.output.parent.joinpath("encoder_model.onnx"), | |
| onnx_outputs, | |
| args.atol if args.atol else encoder_onnx_config.atol_for_validation, | |
| ) | |
| preprocessor = AutoTokenizer.from_pretrained(args.model) | |
| onnx_inputs, onnx_outputs = export( | |
| preprocessor, | |
| decoder_model, | |
| decoder_onnx_config, | |
| args.opset, | |
| args.output.parent.joinpath("decoder_model.onnx"), | |
| ) | |
| validate_model_outputs( | |
| decoder_onnx_config, | |
| preprocessor, | |
| decoder_model, | |
| args.output.parent.joinpath("decoder_model.onnx"), | |
| onnx_outputs, | |
| args.atol if args.atol else decoder_onnx_config.atol_for_validation, | |
| ) | |
| logger.info( | |
| f"All good, model saved at: {args.output.parent.joinpath('encoder_model.onnx').as_posix()}," | |
| f" {args.output.parent.joinpath('decoder_model.onnx').as_posix()}" | |
| ) | |
| else: | |
| # Instantiate the appropriate preprocessor | |
| if args.preprocessor == "auto": | |
| preprocessor = get_preprocessor(args.model) | |
| elif args.preprocessor == "tokenizer": | |
| preprocessor = AutoTokenizer.from_pretrained(args.model) | |
| elif args.preprocessor == "image_processor": | |
| preprocessor = AutoImageProcessor.from_pretrained(args.model) | |
| elif args.preprocessor == "feature_extractor": | |
| preprocessor = AutoFeatureExtractor.from_pretrained(args.model) | |
| elif args.preprocessor == "processor": | |
| preprocessor = AutoProcessor.from_pretrained(args.model) | |
| else: | |
| raise ValueError(f"Unknown preprocessor type '{args.preprocessor}'") | |
| # Ensure the requested opset is sufficient | |
| if args.opset is None: | |
| args.opset = onnx_config.default_onnx_opset | |
| if args.opset < onnx_config.default_onnx_opset: | |
| raise ValueError( | |
| f"Opset {args.opset} is not sufficient to export {model_kind}. " | |
| f"At least {onnx_config.default_onnx_opset} is required." | |
| ) | |
| onnx_inputs, onnx_outputs = export( | |
| preprocessor, | |
| model, | |
| onnx_config, | |
| args.opset, | |
| args.output, | |
| ) | |
| if args.atol is None: | |
| args.atol = onnx_config.atol_for_validation | |
| validate_model_outputs(onnx_config, preprocessor, model, args.output, onnx_outputs, args.atol) | |
| logger.info(f"All good, model saved at: {args.output.as_posix()}") | |
| warnings.warn( | |
| "The export was done by transformers.onnx which is deprecated and will be removed in v5. We recommend" | |
| " using optimum.exporters.onnx in future. You can find more information here:" | |
| " https://huggingface.co/docs/optimum/exporters/onnx/usage_guides/export_a_model.", | |
| FutureWarning, | |
| ) | |
| def main(): | |
| parser = ArgumentParser("Hugging Face Transformers ONNX exporter") | |
| parser.add_argument( | |
| "-m", "--model", type=str, required=True, help="Model ID on huggingface.co or path on disk to load model from." | |
| ) | |
| parser.add_argument( | |
| "--feature", | |
| default="default", | |
| help="The type of features to export the model with.", | |
| ) | |
| parser.add_argument("--opset", type=int, default=None, help="ONNX opset version to export the model with.") | |
| parser.add_argument( | |
| "--atol", type=float, default=None, help="Absolute difference tolerance when validating the model." | |
| ) | |
| parser.add_argument( | |
| "--framework", | |
| type=str, | |
| choices=["pt", "tf"], | |
| default=None, | |
| help=( | |
| "The framework to use for the ONNX export." | |
| " If not provided, will attempt to use the local checkpoint's original framework" | |
| " or what is available in the environment." | |
| ), | |
| ) | |
| parser.add_argument("output", type=Path, help="Path indicating where to store generated ONNX model.") | |
| parser.add_argument("--cache_dir", type=str, default=None, help="Path indicating where to store cache.") | |
| parser.add_argument( | |
| "--preprocessor", | |
| type=str, | |
| choices=["auto", "tokenizer", "feature_extractor", "image_processor", "processor"], | |
| default="auto", | |
| help="Which type of preprocessor to use. 'auto' tries to automatically detect it.", | |
| ) | |
| parser.add_argument( | |
| "--export_with_transformers", | |
| action="store_true", | |
| help=( | |
| "Whether to use transformers.onnx instead of optimum.exporters.onnx to perform the ONNX export. It can be " | |
| "useful when exporting a model supported in transformers but not in optimum, otherwise it is not " | |
| "recommended." | |
| ), | |
| ) | |
| args = parser.parse_args() | |
| if args.export_with_transformers or not is_optimum_available(): | |
| export_with_transformers(args) | |
| else: | |
| export_with_optimum(args) | |
| if __name__ == "__main__": | |
| logger = logging.get_logger("transformers.onnx") # pylint: disable=invalid-name | |
| logger.setLevel(logging.INFO) | |
| main() | |