Prerequisites - Amazon SageMaker

Prerequisites

Note

Suivez les instructions de cette section si vous avez compilé votre modèle à l'aide du AWS SDK for Python (Boto3), de la AWS CLI ou de la console SageMaker.

Pour créer un modèle SageMaker Neo-compilé, vous avez besoin des éléments suivants :

  1. Un URI Amazon ECR d'image Docker. Vous pouvez en sélectionner un répondant à vos besoins dans cette liste.

  2. Un fichier de script de point d'entrée :

    1. Pour les modèles PyTorch et MXNet:

      Si vous avez entraîné votre modèle à l'aide de SageMaker, le script d'entraînement doit mettre en œuvre les fonctions décrites ci-dessous. Le script d'entraînement sert de script de point d'entrée pendant l'inférence. Dans l'exemple détaillé dans MNIST Training, Compilation and Deployment with MXNet Module and SageMaker Neo (Entraînement, compilation et déploiement MNIST avec le module MXNet et SageMaker Neo), le script d'entraînement (mnist.py) met en œuvre les fonctions requises.

      Si vous n'avez pas entraîné votre modèle à l'aide de SageMaker, vous devez fournir un fichier script de point d'entrée (inference.py) utilisable au moment de l'inférence. En fonction du cadre (MXNet ou Pytorch) : l'emplacement du script d'inférence doit respecter la structure de répertoire de modèles pour MxNet ou la structure de répertoire de modèles pour PyTorch du SDK SageMaker Python.

      Lorsque vous utilisez des images de conteneur optimisé pour l'inférence Neo avec PyTorch et MXNet sur les types d'instances CPU et GPU, le script d'inférence doit mettre en œuvre les fonctions suivantes :

      • model_fn : charge le modèle. (Facultatif)

      • input_fn : convertit la charge utile de demande entrante en un tableau numpy.

      • predict_fn : réalise la prédiction.

      • output_fn : convertit la sortie de la prédiction en charge utile de réponse.

      • En variante, vous pouvez définir transform_fn de sorte à combiner input_fn, predict_fn et output_fn.

      Voici quelques exemples de script inference.pydans un répertoire nommé code (code/inference.py) pour PyTorch et MXNet (Gluon et Module). Les exemples chargent d'abord le modèle, puis le servent sur des données d'image sur un GPU :

      MXNet Module
      import numpy as np import json import mxnet as mx import neomx # noqa: F401 from collections import namedtuple Batch = namedtuple('Batch', ['data']) # Change the context to mx.cpu() if deploying to a CPU endpoint ctx = mx.gpu() def model_fn(model_dir): # The compiled model artifacts are saved with the prefix 'compiled' sym, arg_params, aux_params = mx.model.load_checkpoint('compiled', 0) mod = mx.mod.Module(symbol=sym, context=ctx, label_names=None) exe = mod.bind(for_training=False, data_shapes=[('data', (1,3,224,224))], label_shapes=mod._label_shapes) mod.set_params(arg_params, aux_params, allow_missing=True) # Run warm-up inference on empty data during model load (required for GPU) data = mx.nd.empty((1,3,224,224), ctx=ctx) mod.forward(Batch([data])) return mod def transform_fn(mod, image, input_content_type, output_content_type): # pre-processing decoded = mx.image.imdecode(image) resized = mx.image.resize_short(decoded, 224) cropped, crop_info = mx.image.center_crop(resized, (224, 224)) normalized = mx.image.color_normalize(cropped.astype(np.float32) / 255, mean=mx.nd.array([0.485, 0.456, 0.406]), std=mx.nd.array([0.229, 0.224, 0.225])) transposed = normalized.transpose((2, 0, 1)) batchified = transposed.expand_dims(axis=0) casted = batchified.astype(dtype='float32') processed_input = casted.as_in_context(ctx) # prediction/inference mod.forward(Batch([processed_input])) # post-processing prob = mod.get_outputs()[0].asnumpy().tolist() prob_json = json.dumps(prob) return prob_json, output_content_type
      MXNet Gluon
      import numpy as np import json import mxnet as mx import neomx # noqa: F401 # Change the context to mx.cpu() if deploying to a CPU endpoint ctx = mx.gpu() def model_fn(model_dir): # The compiled model artifacts are saved with the prefix 'compiled' block = mx.gluon.nn.SymbolBlock.imports('compiled-symbol.json',['data'],'compiled-0000.params', ctx=ctx) # Hybridize the model & pass required options for Neo: static_alloc=True & static_shape=True block.hybridize(static_alloc=True, static_shape=True) # Run warm-up inference on empty data during model load (required for GPU) data = mx.nd.empty((1,3,224,224), ctx=ctx) warm_up = block(data) return block def input_fn(image, input_content_type): # pre-processing decoded = mx.image.imdecode(image) resized = mx.image.resize_short(decoded, 224) cropped, crop_info = mx.image.center_crop(resized, (224, 224)) normalized = mx.image.color_normalize(cropped.astype(np.float32) / 255, mean=mx.nd.array([0.485, 0.456, 0.406]), std=mx.nd.array([0.229, 0.224, 0.225])) transposed = normalized.transpose((2, 0, 1)) batchified = transposed.expand_dims(axis=0) casted = batchified.astype(dtype='float32') processed_input = casted.as_in_context(ctx) return processed_input def predict_fn(processed_input_data, block): # prediction/inference prediction = block(processed_input_data) return prediction def output_fn(prediction, output_content_type): # post-processing prob = prediction.asnumpy().tolist() prob_json = json.dumps(prob) return prob_json, output_content_type
      PyTorch 1.4 and Older
      import os import torch import torch.nn.parallel import torch.optim import torch.utils.data import torch.utils.data.distributed import torchvision.transforms as transforms from PIL import Image import io import json import pickle def model_fn(model_dir): """Load the model and return it. Providing this function is optional. There is a default model_fn available which will load the model compiled using SageMaker Neo. You can override it here. Keyword arguments: model_dir -- the directory path where the model artifacts are present """ # The compiled model is saved as "compiled.pt" model_path = os.path.join(model_dir, 'compiled.pt') with torch.neo.config(model_dir=model_dir, neo_runtime=True): model = torch.jit.load(model_path) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) # We recommend that you run warm-up inference during model load sample_input_path = os.path.join(model_dir, 'sample_input.pkl') with open(sample_input_path, 'rb') as input_file: model_input = pickle.load(input_file) if torch.is_tensor(model_input): model_input = model_input.to(device) model(model_input) elif isinstance(model_input, tuple): model_input = (inp.to(device) for inp in model_input if torch.is_tensor(inp)) model(*model_input) else: print("Only supports a torch tensor or a tuple of torch tensors") return model def transform_fn(model, request_body, request_content_type, response_content_type): """Run prediction and return the output. The function 1. Pre-processes the input request 2. Runs prediction 3. Post-processes the prediction output. """ # preprocess decoded = Image.open(io.BytesIO(request_body)) preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize( mean=[ 0.485, 0.456, 0.406], std=[ 0.229, 0.224, 0.225]), ]) normalized = preprocess(decoded) batchified = normalized.unsqueeze(0) # predict device = torch.device("cuda" if torch.cuda.is_available() else "cpu") batchified = batchified.to(device) output = model.forward(batchified) return json.dumps(output.cpu().numpy().tolist()), response_content_type
      PyTorch 1.5 and Newer

      Utilisez neopytorch pour spécifier le chemin d'accès du fichier du modèle si vous l'avez entraîné avec PyTorch version 1.5 ou une version ultérieure.

      import os import torch import neopytorch import torch.nn.parallel import torch.optim import torch.utils.data import torch.utils.data.distributed import torchvision.transforms as transforms from PIL import Image import io import json import pickle def model_fn(model_dir): """Load the model and return it. Providing this function is optional. There is a default_model_fn available, which will load the model compiled using SageMaker Neo. You can override the default here. The model_fn only needs to be defined if your model needs extra steps to load, and can otherwise be left undefined. Keyword arguments: model_dir -- the directory path where the model artifacts are present """ # The compiled model is saved as "compiled.pt" model_path = os.path.join(model_dir, 'compiled.pt') neopytorch.config(model_dir=model_dir,neo_runtime=True) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = torch.jit.load(model_path, map_location=device) model = model.to(device) # We recommend that you run warm-up inference during model load sample_input_path = os.path.join(model_dir, 'sample_input.pkl') with open(sample_input_path, 'rb') as input_file: model_input = pickle.load(input_file) if torch.is_tensor(model_input): model_input = model_input.to(device) model(model_input) elif isinstance(model_input, tuple): model_input = (inp.to(device) for inp in model_input if torch.is_tensor(inp)) model(*model_input) else: print("Only supports a torch tensor or a tuple of torch tensors") return model def transform_fn(model, request_body, request_content_type, response_content_type): """Run prediction and return the output. The function 1. Pre-processes the input request 2. Runs prediction 3. Post-processes the prediction output. """ # preprocess decoded = Image.open(io.BytesIO(request_body)) preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize( mean=[ 0.485, 0.456, 0.406], std=[ 0.229, 0.224, 0.225]), ]) normalized = preprocess(decoded) batchified = normalized.unsqueeze(0) # predict device = torch.device("cuda" if torch.cuda.is_available() else "cpu") batchified = batchified.to(device) output = model.forward(batchified) return json.dumps(output.cpu().numpy().tolist()), response_content_type
    2. Pour les instances inf1 ou les images de conteneur onnx, xgboost, keras

      Pour toutes les autres images de conteneur optimisées pour l'inférence Neo, ou les types d'instances Inferentia, le script de point d'entrée doit mettre en œuvre les fonctions suivantes pour le Runtime Deep Learning Neo :

      • neo_preprocess : convertit la charge utile de demande entrante en un tableau numpy.

      • neo_postprocess : convertit la sortie de la prédiction du Runtime Deep Learning Neo dans le corps de la réponse.

        Note

        Aucune de ces deux fonctions précédentes n'utilise des fonctionnalités de MXNet, PyTorch ou TensorFlow.

      Pour obtenir des exemples d'utilisation de ces fonctions, veuillez consulter Neo Model Compilation Sample Notebooks (Exemples de blocs-notes de compilation de modèles Neo).

    3. Pour les modèles TensorFlow

      Si votre modèle nécessite une logique de pré- et de post-traitement personnalisée avant l'envoi des données au modèle, vous devez spécifier un fichier script de point d'entrée inference.py utilisable au moment de l'inférence. Le script doit mettre en œuvre une paire de fonctions input_handler et output_handler ou une seule fonction de gestionnaire.

      Note

      Veuillez noter que si la fonction de gestionnaire est mise en œuvre, input_handler et output_handler sont ignorées.

      Voici un exemple de code de script inference.py que vous pouvez assembler avec le modèle de compilation pour effectuer un pré- et un post-traitement personnalisé sur un modèle de classification d'image. Le client SageMaker envoie le fichier image sous forme de type de contenu application/x-image à la fonction input_handler, où il est converti au format JSON. Le fichier image converti est ensuite envoyé au serveur de modèles Tensorflow (TFX) à l'aide de l'API REST.

      import json import numpy as np import json import io from PIL import Image def input_handler(data, context): """ Pre-process request input before it is sent to TensorFlow Serving REST API Args: data (obj): the request data, in format of dict or string context (Context): an object containing request and configuration details Returns: (dict): a JSON-serializable dict that contains request body and headers """ f = data.read() f = io.BytesIO(f) image = Image.open(f).convert('RGB') batch_size = 1 image = np.asarray(image.resize((512, 512))) image = np.concatenate([image[np.newaxis, :, :]] * batch_size) body = json.dumps({"signature_name": "serving_default", "instances": image.tolist()}) return body def output_handler(data, context): """Post-process TensorFlow Serving output before it is returned to the client. Args: data (obj): the TensorFlow serving response context (Context): an object containing request and configuration details Returns: (bytes, string): data to return to client, response content type """ if data.status_code != 200: raise ValueError(data.content.decode('utf-8')) response_content_type = context.accept_header prediction = data.content return prediction, response_content_type

      En l'absence de pré- ou de post-traitement personnalisé, le client SageMaker convertit le fichier image au format JSON de la même manière avant de l'envoyer au point de terminaison SageMaker.

      Pour de plus amples informations, veuillez consulter Deploying to TensorFlow Serving Endpoints in the SageMaker Python SDK (Déploiement sur des points de terminaison de service TensorFlow dans le SDK SageMaker Python).

  3. L'URI du compartiment Amazon S3 qui contient les artefacts du modèle compilé.