| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- # 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(cmd_line, stdout=subprocess.PIPE)
- 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()
|