Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support exporting > 2Gb transformer models #1514

Merged
merged 14 commits into from
May 11, 2023
18 changes: 17 additions & 1 deletion src/sparseml/pytorch/utils/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,23 @@ def export_onnx(

# clean up graph from any injected / wrapped operations
_delete_trivial_onnx_adds(onnx_model)
onnx.save(onnx_model, file_path)
dbogunowicz marked this conversation as resolved.
Show resolved Hide resolved

try:
# alternatively we can just check the size of the onnx file on disk
dbogunowicz marked this conversation as resolved.
Show resolved Hide resolved
onnx.save(onnx_model, file_path)

except Exception as e:
_LOGGER.info(
"Attempted to save ONNX model without external data."
f"Results in error: {e}. Saving with external data instead."
)
onnx.save(
onnx_model,
file_path,
location=os.path.basename(file_path).replace("onnx", "data"),
save_as_external_data=True,
all_tensors_to_one_file=True,
)

if convert_qat and is_quant_module:
# overwrite exported model with fully quantized version
Expand Down
26 changes: 22 additions & 4 deletions src/sparseml/transformers/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,14 @@
__all__ = ["export_transformer_to_onnx", "load_task_model"]

MODEL_ONNX_NAME = "model.onnx"
dbogunowicz marked this conversation as resolved.
Show resolved Hide resolved
DEPLOYMENT_FILES: List[str] = [
EXTERNAL_ONNX_DATA_NAME = "model.data"
dbogunowicz marked this conversation as resolved.
Show resolved Hide resolved
MANDATORY_DEPLOYMENT_FILES: List[str] = [
MODEL_ONNX_NAME,
"tokenizer.json",
EXTERNAL_ONNX_DATA_NAME,
"tokenizer_config.json",
"config.json",
]
OPTIONAL_DEPLOYMENT_FILES: List[str] = [EXTERNAL_ONNX_DATA_NAME, "tokenizer.json"]

_LOGGER = logging.getLogger(__name__)

Expand All @@ -113,7 +115,12 @@ def load_task_model(task: str, model_path: str, config: Any) -> Module:
return SparseAutoModel.question_answering_from_pretrained(
model_name_or_path=model_path,
config=config,
dbogunowicz marked this conversation as resolved.
Show resolved Hide resolved
model_type="model",
)
if task == "text-generation" or task == "codegen" or task == "opt":
if task == "codegen":
raise NotImplementedError()
return SparseAutoModel.text_generation_from_pretrained(
model_name_or_path=model_path, config=config
)

if (
Expand Down Expand Up @@ -403,7 +410,9 @@ def create_deployment_folder(

if deployment_files is None:
# set deployment files to default values
deployment_files = copy.deepcopy(DEPLOYMENT_FILES)
deployment_files = copy.deepcopy(
MANDATORY_DEPLOYMENT_FILES + OPTIONAL_DEPLOYMENT_FILES
)
if onnx_file_name != MODEL_ONNX_NAME:
# replace the default onnx model name with the custom one
deployment_files[deployment_files.index(MODEL_ONNX_NAME)] = onnx_file_name
Expand All @@ -418,6 +427,12 @@ def create_deployment_folder(
expected_file_path = os.path.join(training_directory, file_name)
deployment_file_path = os.path.join(deployment_folder_dir, file_name)
if not os.path.exists(expected_file_path):
if file_name in OPTIONAL_DEPLOYMENT_FILES:
_LOGGER.warning(
f"Optional file {file_name} not found in {training_directory}. "
f"Skipping copying to deployment folder."
)
continue
raise ValueError(
f"Attempting to copy {file_name} file from {expected_file_path},"
f"but the file does not exits. Make sure that {training_directory} "
Expand All @@ -426,6 +441,9 @@ def create_deployment_folder(
if file_name == MODEL_ONNX_NAME:
# moving onnx file from training to deployment directory
shutil.move(expected_file_path, deployment_file_path)
elif file_name == EXTERNAL_ONNX_DATA_NAME:
# moving external onnx tensors from training to deployment directory
shutil.move(expected_file_path, deployment_file_path)
else:
# copying remaining `deployment_files` from training to deployment directory
shutil.copyfile(expected_file_path, deployment_file_path)
Expand Down
Binary file not shown.
13 changes: 13 additions & 0 deletions src/sparseml/transformers/utils/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import torch
from torch.nn import Module
from transformers import (
AutoModelForCausalLM,
AutoModelForMaskedLM,
AutoModelForQuestionAnswering,
AutoModelForSequenceClassification,
Expand Down Expand Up @@ -111,6 +112,18 @@ def masked_language_modeling_from_pretrained_distil(

return model, teacher

@staticmethod
def text_generation_from_pretrained(
model_name_or_path: str,
config: Optional[Any] = None,
**kwargs,
) -> Module:
""" """
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path, config=config, **kwargs
)
return model

@staticmethod
def question_answering_from_pretrained(
model_name_or_path: str,
Expand Down
3 changes: 3 additions & 0 deletions src/sparseml/yolov8/trainers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from sparseml.pytorch.utils import ModuleExporter
from sparseml.pytorch.utils.helpers import download_framework_model_by_recipe_type
from sparseml.pytorch.utils.logger import LoggerManager, PythonLogger, WANDBLogger
from sparseml.yolov8.utils import check_coco128_segmentation
from sparseml.yolov8.utils.export_samples import export_sample_inputs_outputs
from sparseml.yolov8.validators import (
SparseClassificationValidator,
Expand Down Expand Up @@ -683,6 +684,8 @@ def val(self, data=None, **kwargs):
# use trained imgsz unless custom value is passed
args.imgsz = self.ckpt["train_args"]["imgsz"]
args.imgsz = check_imgsz(args.imgsz, max_dim=1)
if args.task == "segment":
check_coco128_segmentation(args)

validator = self.ValidatorClass(args=args)
validator(model=self.model)
Expand Down
1 change: 1 addition & 0 deletions src/sparseml/yolov8/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
# limitations under the License.
# flake8: noqa
from .export_samples import *
from .helpers import *
38 changes: 38 additions & 0 deletions src/sparseml/yolov8/utils/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (c) 2021 - present / Neuralmagic, Inc. 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 os
import warnings
from argparse import Namespace


__all__ = ["check_coco128_segmentation"]


def check_coco128_segmentation(args: Namespace) -> Namespace:
"""
Checks if the argument 'data' is coco128.yaml and if so,
replaces it with coco128-seg.yaml.
:param args: arguments to check
:return: the updated arguments
"""
if args.data == "coco128.yaml":
dataset_name, dataset_extension = os.path.splitext(args.data)
dataset_yaml = dataset_name + "-seg" + dataset_extension
warnings.warn(
f"Dataset yaml {dataset_yaml} is not supported for segmentation. "
f"Attempting to use {dataset_yaml} instead."
)
args.data = dataset_yaml
return args