Tests de charge et surveillance des modèles AI Platform

Ce document explique comment tester et surveiller les performances de diffusion en ligne des modèles de machine learning (ML) déployés sur AI Platform Prediction. Ce document utilise Locust, un outil Open Source permettant de tester la charge.

Ce document est destiné aux data scientists et aux ingénieurs MLOps qui souhaitent surveiller la charge de travail de service, la latence et l'utilisation des ressources de leurs modèles de ML en production.

Dans le présent document, nous partons du principe que vous avez une certaine expérience de Google Cloud, TensorFlow, AI Platform Prediction, Cloud Monitoring et des notebooks Jupyter.

Le document est accompagné d'un Dépôt GitHub qui comprend le code et un guide de déploiement pour implémenter le système décrites dans ce document. Les tâches sont intégrées aux notebooks Jupyter.

Coûts

Les notebooks que vous utilisez dans ce document utilisent les composants facturables suivants de Google Cloud :

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût.

Avant de commencer

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  5. Make sure that billing is enabled for your Google Cloud project.

Présentation de l'architecture

Le schéma suivant illustre l'architecture système permettant de déployer le modèle de ML pour la prédiction en ligne, d'exécuter le test de charge, et de collecter et analyser les métriques pour les performances de diffusion du modèle de ML.

Architecture permettant de déployer le modèle, et de collecter et analyser les performances du modèle

Ce flux est représenté dans le diagramme suivant :

  1. Votre modèle entraîné peut se trouver dans Cloud Storage : exemple, un modèle TensorFlow SavedModel ou scikit-learn joblib. Vous pouvez aussi l'intégrer à un conteneur de livraison personnalisé Container Registry : pour exemple : TorchServe permettant de diffuser des modèles PyTorch.
  2. Le modèle est déployé AI Platform Prediction en tant qu'API REST. AI Platform Prediction est un service entièrement géré pour d'inférence du modèle, différents types de machines, compatible avec autoscaling en fonction de l'utilisation des ressources Accélérateurs de GPU.
  3. Locust est utilisé pour implémenter une tâche de test (comportement de l'utilisateur). Pour ce faire, il appelle le modèle de ML déployé sur AI Platform Prediction et l'exécute à grande échelle sur Google Kubernetes Engine (GKE). Cette opération simule de nombreux appels utilisateur simultanés pour tester le service de prédiction de modèles. Vous pouvez suivre la progression des tests à l'aide de l'interface Web Locust.
  4. Les journaux Locust testent les statistiques dans Cloud Logging. Les entrées de journal créées par le test Locust permettent de définir ensemble de Métriques basées sur les journaux dans Cloud Monitoring : Ces métriques complètent les statistiques standards d'AI Platform Prediction.
  5. Les métriques AI Platform et Locust personnalisées sont disponibles pour une visualisation en temps réel dans un tableau de bord Cloud Monitoring. Une fois le test terminé, les métriques sont également collectées par programmation afin que vous puissiez les analyser et les visualiser dans Notebooks Vertex AI Workbench gérés par l'utilisateur.

Notebooks Jupyter pour ce scénario

Toutes les tâches de préparation et de déploiement du modèle, d'exécution du test Locust, ainsi que de collecte et d'analyse des résultats du test, sont codées dans les notebooks Jupyter suivants. Pour ce faire, vous devez exécuter la séquence de cellules de chaque notebook.

  1. 01-prepare-and-deploy.ipynb. Vous exécutez ce notebook pour préparer un fichier SavedModel TensorFlow pour la diffusion et pour déployer le modèle sur AI Platform Prediction.
  2. 02-perf-testing.ipynb. Vous allez exécuter ce notebook pour créer des métriques basées sur les journaux dans Cloud Monitoring pour le test Locust, et pour déployer le test Locust sur GKE et l'exécuter.
  3. 03-analyze-results.ipynb. Vous allez exécuter ce notebook pour collecter et analyser les résultats du test de charge Locust à partir des métriques standards d'AI Platform créées par Cloud Monitoring et à partir de métriques Locust personnalisées.

Initialiser l'environnement

Comme décrit dans le fichier README.md du dépôt GitHub associé, vous devez effectuer les étapes suivantes pour préparer l'environnement d'exécution des notebooks :

  1. Dans votre projet Google Cloud, créez un bucket Cloud Storage, nécessaire pour stocker le modèle entraîné et la configuration de test Locust. Notez le nom que vous utilisez pour le bucket, car vous en aurez besoin plus tard.
  2. Créer un espace de travail Cloud Monitoring dans votre projet.
  3. Créer un cluster Google Kubernetes Engine disposant des processeurs requis. Le pool de nœuds doit avoir accès aux API Cloud.
  4. Créer une instance de notebooks gérés par l'utilisateur dans Vertex AI Workbench qui utilise TensorFlow 2. Pour ce tutoriel, vous n'avez pas besoin de GPU, car vous n'entraînez pas le modèle. (Les GPU peuvent être utiles dans d'autres scénarios, en particulier pour accélérer l'entraînement de vos modèles.)

Ouvrir JupyterLab

Pour parcourir les tâches du scénario, vous devez ouvrir l'environnement JupyterLab et récupérer les notebooks.

  1. Dans Google Cloud Console, accédez à la page Notebooks.

    Accéder à Notebooks

  2. Dans l'onglet Notebooks gérés par l'utilisateur, cliquez sur Ouvrir Jupyterlab à côté de l'environnement de notebook que vous avez créé.

    Cela ouvre l'environnement JupyterLab dans votre navigateur.

  3. Pour lancer un onglet de terminal, cliquez sur l'icône Terminal dans l'onglet Launcher (Lanceur d'applications).

  4. Dans le terminal, clonez le dépôt GitHub mlops-on-gcp :

    git clone https://github.com/GoogleCloudPlatform/mlops-on-gcp.git
    

    Une fois la commande terminée, le dossier mlops-on-gcp s'affiche dans le navigateur de fichiers. Dans ce dossier, les notebooks avec lesquels vous travaillez dans ce document s'affichent.

Configurer les paramètres du notebook

Dans cette section, vous allez définir des variables dans le notebook avec des valeurs propres à votre contexte, et préparer l'environnement pour exécuter le code du scénario.

  1. Accédez au répertoire model_serving/caip-load-testing :
  2. Pour chacun des trois notebooks, procédez comme suit :
    1. Ouvrez le notebook.
    2. Exécutez les cellules sous Configurer les paramètres de l'environnement Google Cloud.

Les sections suivantes mettent en évidence les parties clés du processus, et expliquent les aspects de la conception et du code.

Diffuser le modèle pour la prédiction en ligne

Le modèle de ML utilisé dans ce document utilise le modèle de classification d'images ResNet V2 101 pré-entraîné fourni par TensorFlow Hub. Toutefois, vous pouvez adapter les techniques et les modèles de conception système de ce document à d'autres domaines et à d'autres types de modèles.

Le code de préparation et de diffusion du modèle ResNet 101 se trouve dans le notebook 01-prepare-and-deploy.ipynb. Vous exécutez les cellules du notebook pour effectuer les tâches suivantes :

  1. Téléchargez et exécutez le modèle ResNet à partir de TensorFlow Hub.
  2. Créez des signatures de diffusion pour le modèle.
  3. Exportez le modèle en tant que modèle SavedModel.
  4. Déployez le modèle SavedModel dans AI Platform Prediction.
  5. Validez le modèle déployé.

Les sections suivantes de ce document fournissent des informations détaillées sur la préparation du modèle ResNet et son déploiement.

Préparer le modèle ResNet pour le déploiement

Le modèle ResNet de TensorFlow Hub n'a pas de signature de diffusion, car il est optimisé pour la recomposition et les réglages précis. Par conséquent, vous devez créer des signatures de diffusion pour le modèle afin de pouvoir le diffuser pour les prédictions en ligne.

En outre, pour diffuser le modèle, nous vous recommandons d'intégrer la logique d'ingénierie des caractéristiques dans l'interface de diffusion. Cela garantit l'affinité entre le prétraitement et la diffusion du modèle, au lieu de dépendre de l'application cliente pour prétraiter les données au format requis. Vous devez également inclure le post-traitement dans l'interface de diffusion, par exemple la conversion d'un ID de classe en libellé de classe.

Pour rendre le modèle ResNet diffusable, vous devez mettre en œuvre des signatures de diffusion décrivant les méthodes d'inférence du modèle. Par conséquent, le code du notebook ajoute deux signatures :

  • Signature par défaut. Cette signature expose la méthode predict par défaut du modèle ResNet V2 101 ; la méthode par défaut n'utilise pas de logique de prétraitement ou de post-traitement.
  • Signature de prétraitement et de post-traitement. Les entrées attendues dans cette interface nécessitent un prétraitement relativement complexe, y compris l'encodage, le scaling et la normalisation de l'image. Par conséquent, le modèle expose également une signature alternative qui intègre la logique de prétraitement et de post-traitement. Cette signature accepte les images brutes non traitées et renvoie la liste des libellés de classe classées et les probabilités de libellé associées.

Les signatures sont créées dans une classe de module personnalisé. La classe est dérivée de la classe de base tf.Module qui encapsule le modèle ResNet. La classe personnalisée étend la classe de base à l'aide d'une méthode qui met en œuvre la logique de prétraitement et de sortie du post-traitement des images. La méthode par défaut du module personnalisé est mappée sur la méthode par défaut du modèle ResNet de base pour maintenir l'interface analogue. Le module personnalisé est exporté en tant que SavedModel, qui inclut le modèle d'origine, la logique de prétraitement et deux signatures de diffusion.

La mise en œuvre de la classe de module personnalisé est illustrée dans l'extrait de code suivant :

LABELS_KEY = 'labels'
PROBABILITIES_KEY = 'probabilities'
NUM_LABELS = 5

class ServingModule(tf.Module):
    """
    A custom tf.Module that adds image preprocessing and output post processing to
    a base TF 2 image classification model from TensorFlow Hub.
    """

    def __init__(self, base_model, input_size, output_labels):
        super(ServingModule, self).__init__()
        self._model = base_model
        self._input_size = input_size
        self._output_labels = tf.constant(output_labels, dtype=tf.string)

    def _decode_and_scale(self, raw_image):
        """
        Decodes, crops, and resizes a single raw image.
        """

        image = tf.image.decode_image(raw_image, dtype=tf.dtypes.uint8, expand_animations=False)
        image_shape = tf.shape(image)
        image_height = image_shape[0]
        image_width = image_shape[1]
        crop_size = tf.minimum(image_height, image_width)
        offset_height = ((image_height - crop_size) + 1) // 2
        offset_width = ((image_width - crop_size) + 1) // 2

        image = tf.image.crop_to_bounding_box(image, offset_height, offset_width, crop_size, crop_size)
        image = tf.image.resize(image, [self._input_size, self._input_size])
        image = tf.cast(image, tf.uint8)

        return image

    def _preprocess(self, raw_inputs):
        """
        Preprocesses raw inputs as sent by the client.
        """

        # A mitigation for https://github.com/tensorflow/tensorflow/issues/28007
        with tf.device('/cpu:0'):
            images = tf.map_fn(self._decode_and_scale, raw_inputs, dtype=tf.uint8)
        images = tf.image.convert_image_dtype(images, tf.float32)

        return images

    def _postprocess(self, model_outputs):
        """
        Postprocess outputs returned by the base model.
        """

        probabilities = tf.nn.softmax(model_outputs)
        indices = tf.argsort(probabilities, axis=1, direction='DESCENDING')

        return {
            LABELS_KEY: tf.gather(self._output_labels, indices, axis=-1)[:,:NUM_LABELS],
            PROBABILITIES_KEY: tf.sort(probabilities, direction='DESCENDING')[:,:NUM_LABELS]
        }

    @tf.function(input_signature=[tf.TensorSpec([None, 224, 224, 3], tf.float32)])
    def __call__(self, x):
        """
        A pass-through to the base model.
        """

        return self._model(x)

    @tf.function(input_signature=[tf.TensorSpec([None], tf.string)])
    def predict_labels(self, raw_images):
        """
        Preprocesses inputs, calls the base model
        and postprocess outputs from the base model.
        """

        # Call the preprocessing handler
        images = self._preprocess(raw_images)

        # Call the base model
        logits = self._model(images)

        # Call the postprocessing handler
        outputs = self._postprocess(logits)

        return outputs

serving_module = ServingModule(model, 224, imagenet_labels)

L'extrait de code suivant montre comment le modèle est exporté en tant que SavedModel avec les signatures de diffusion définies précédemment :

...

default_signature = serving_module.__call__.get_concrete_function()
preprocess_signature = serving_module.predict_labels.get_concrete_function()
signatures = {
    'serving_default': default_signature,
    'serving_preprocess': preprocess_signature
}

tf.saved_model.save(serving_module, model_path, signatures=signatures)

Déployer le modèle sur AI Platform Prediction

Lorsque le modèle est exporté en tant que SavedModel, les tâches suivantes sont effectuées :

  • Le modèle est importé dans Cloud Storage.
  • Un objet de modèle est créé dans AI Platform Prediction.
  • Une version de modèle est créée pour le SavedModel.

L'extrait de code suivant du notebook montre les commandes qui effectuent ces tâches.

gcloud storage cp {model_path} {GCS_MODEL_LOCATION} --recursive

gcloud ai-platform models create {MODEL_NAME} \
    --project {PROJECT_ID} \
    --regions {REGION}

MACHINE_TYPE='n1-standard-8'
ACCELERATOR='count=1,type=nvidia-tesla-p4'

gcloud beta ai-platform versions create {MODEL_VERSION} \
    --model={MODEL_NAME} \
    --origin={GCS_MODEL_LOCATION} \
    --runtime-version=2.1 \
    --framework=TENSORFLOW \
    --python-version=3.7 \
    --machine-type={MACHINE_TYPE} \
    --accelerator={ACCELERATOR} \
    --project={PROJECT_ID}

La commande crée un type de machine n1-standard-8 pour le service de prédiction de modèles et un accélérateur GPU nvidia-tesla-p4.

Après avoir exécuté les cellules du notebook contenant ces commandes, vous pouvez vérifier que la version du modèle est déployée en l'affichant sur la page Modèles AI Platform de la console Google Cloud. Le résultat ressemble à ce qui suit :

Vérification du déploiement du modèle via la console Google Cloud.

Créer des métriques Cloud Monitoring

Une fois le modèle configuré pour la diffusion, vous pouvez configurer des métriques qui vous permettent de surveiller les performances de diffusion. Le code de configuration des métriques se trouve dans le notebook 02-perf-testing.ipynb.

La première partie du 02-perf-testing.ipynb un notebook crée des métriques personnalisées basées sur les journaux dans Cloud Monitoring à l'aide du SDK Cloud Logging pour Python. Les métriques sont basées sur les entrées de journal générées par la tâche Locust. La log_stats écrit les entrées de journal dans un journal Cloud Logging nommé locust.

Chaque entrée de journal inclut un ensemble de paires clé-valeur au format JSON, comme indiqué dans le tableau suivant. Les métriques sont basées sur le sous-ensemble de clés de l'entrée de journal.

Clé Description de la valeur Utilisation
test_id ID d'un test Filtrage
des attributs
model Nom du modèle AI Platform Prediction
model_version Version du modèle AI Platform Prediction
latency Temps de réponse du 95e centile, calculé sur une fenêtre glissante de 10 secondes Valeurs
de la métrique
num_requests Nombre total de requêtes depuis le début du test
num_failures Nombre total d'échecs depuis le début du test
user_count Nombre d'utilisateurs simulés
rps Requêtes par seconde

L'extrait de code suivant montre la fonction create_locust_metric du notebook qui crée une métrique personnalisée basée sur les journaux.

def create_locust_metric(
    metric_name:str,
    log_path:str,
    value_field:str,
    bucket_bounds:List[int]):

    metric_path = logging_client.metric_path(PROJECT_ID, metric_name)
    log_entry_filter = 'resource.type=global AND logName={}'.format(log_path)

    metric_descriptor = {
        'metric_kind': 'DELTA',
        'value_type': 'DISTRIBUTION',
        'labels': [{'key': 'test_id', 'value_type': 'STRING'},
                   {'key': 'signature', 'value_type': 'STRING'}]}

    bucket_options = {
        'explicit_buckets': {'bounds': bucket_bounds}}

    value_extractor = 'EXTRACT(jsonPayload.{})'.format(value_field)
    label_extractors = {
        'test_id': 'EXTRACT(jsonPayload.test_id)',
        'signature': 'EXTRACT(jsonPayload.signature)'}

    metric = logging_v2.types.LogMetric(
        name=metric_name,
        filter=log_entry_filter,
        value_extractor=value_extractor,
        bucket_options=bucket_options,
        label_extractors=label_extractors,
        metric_descriptor=metric_descriptor,
    )

    try:
        logging_client.get_log_metric(metric_path)
        print('Metric: {} already exists'.format(metric_path))
    except:
        logging_client.create_log_metric(parent, metric)
        print('Created metric {}'.format(metric_path))

L'extrait de code suivant montre comment la méthode create_locust_metric est appelée dans le notebook pour créer les quatre métriques Locust personnalisées qui sont affichées dans le tableau précédent.

# user count metric
metric_name = 'locust_users'
value_field = 'user_count'
bucket_bounds = [1, 16, 32, 64, 128]

create_locust_metric(metric_name, log_path, value_field, bucket_bounds)

# latency metric
metric_name = 'locust_latency'
value_field = 'latency'
bucket_bounds = [1, 50, 100, 200, 500]

create_locust_metric(metric_name, log_path, value_field, bucket_bounds)

# failure count metric
metric_name = 'num_failures'
value_field = 'num_failures'
bucket_bounds = [1, 1000]

create_locust_metric(metric_name, log_path, value_field, bucket_bounds)

# request count metric
metric_name = 'num_requests'
value_field = 'num_requests'
bucket_bounds = [1, 1000]

create_locust_metric(metric_name, log_path, value_field, bucket_bounds)

Le notebook crée un tableau de bord Cloud Monitoring personnalisé appelé AI Platform Prediction et Locust. Le tableau de bord combine les métriques AI Platform Prediction standards et les métriques personnalisées créées à partir des journaux Locust.

Pour en savoir plus, consultez la documentation de l'API Cloud Logging.

Ce tableau de bord et ses graphiques peuvent être créés manuellement. Le notebook fournit toutefois un moyen programmatique de le créer à l'aide de la classe monitoring-template.json modèle JSON. Le code utilise le DashboardsServiceClient pour charger le modèle JSON et créer le tableau de bord dans Cloud Monitoring, comme indiqué dans l'extrait de code suivant:

parent = 'projects/{}'.format(PROJECT_ID)

dashboard_template_file = 'monitoring-template.json'
with open(dashboard_template_file) as f:
    dashboard_template = json.load(f)

dashboard_proto = Dashboard()
dashboard_proto = ParseDict(dashboard_template, dashboard_proto)
dashboard = dashboard_service_client.create_dashboard(parent, dashboard_proto)

Une fois le tableau de bord créé, il apparaît dans la liste des tableaux de bord Cloud Monitoring de la console Google Cloud :

Page de la console Google Cloud affichant la liste des tableaux de bord Monitoring.

Vous pouvez cliquer sur le tableau de bord pour l'ouvrir et afficher les graphiques. Chaque graphique affiche une métrique à partir d'AI Platform Prediction ou des journaux Locust, comme illustré dans les captures d'écran suivantes.

Divers graphiques affichant des métriques basées sur les données des journaux

Déployer le test Locust sur le cluster GKE

Avant de déployer le système Locust sur GKE, vous devez créer le Image de conteneur Docker qui contient la logique de test intégrée au fichier task.py. L'image est dérivée de l'image baseline locust.io et est utilisée pour le maître et les pods de nœuds de calcul Locust.

La logique de création et de déploiement se trouve dans le notebook sous 3.  Déployer Locust sur un cluster GKE. L'image est créée à l'aide du code suivant :

image_uri = 'gcr.io/{}/locust'.format(PROJECT_ID)

!gcloud builds submit --tag {image_uri} locust/locust-image

Le processus de déploiement décrit dans le notebook a été défini à l'aide de Kustomize. La Fichiers manifestes de déploiement Locust Kustomize définissez les fichiers suivants qui définissent les composants:

  • locust-master. Ce fichier définit un déploiement qui héberge une interface Web dans laquelle vous démarrez le test et consultez les statistiques en direct.
  • locust-worker. Ce fichier définit un déploiement qui exécute une tâche pour tester votre service de prédiction de modèle de ML. En règle générale, plusieurs nœuds de calcul sont créés pour simuler l'effet de plusieurs utilisateurs simultanés effectuant des appels vers votre API de service de prédiction.
  • locust-worker-service. Ce fichier définit un service qui accède à l'interface Web dans locust-master via un équilibreur de charge HTTP.

Vous devez mettre à jour le fichier manifeste par défaut avant de déployer le cluster. La le fichier manifeste par défaut kustomization.yaml et patch.yaml files; vous devez apporter des modifications dans les deux fichiers.

Dans le fichier kustomization.yaml, procédez comme suit :

  • Définissez le nom de l'image personnalisée Locust. Définissez le champ newName de la section images sur le nom de l'image personnalisée que vous avez créée précédemment.
  • Vous pouvez également définir le nombre de pods de nœuds de calcul. La configuration par défaut déploie 32 pods de nœuds de calcul. Pour modifier le nombre, modifiez le champ count dans la section replicas. Assurez-vous que votre cluster GKE dispose d'un nombre suffisant de processeurs pour les nœuds de calcul Locust.
  • Définissez le bucket Cloud Storage pour les fichiers de configuration de test et de charge utile. Dans la section configMapGenerator, assurez-vous que les éléments suivants sont définis :
    • LOCUST_TEST_BUCKET Définissez ce paramètre sur le nom du bucket Cloud Storage que vous avez créé précédemment.
    • LOCUST_TEST_CONFIG Saisissez le nom du fichier de configuration de test. Dans le fichier YAML, ce paramètre est défini sur test-config.json, mais vous pouvez le modifier si vous souhaitez utiliser un autre nom.
    • LOCUST_TEST_PAYLOAD Définissez ce paramètre sur le nom du fichier de charge utile de test. Dans le fichier YAML, ce paramètre est défini sur test-payload.json, mais vous pouvez le modifier si vous souhaitez utiliser un autre nom.

Dans le fichier patch.yaml, procédez comme suit :

  • Vous pouvez également modifier le pool de nœuds qui héberge le maître et les nœuds de calcul Locust. Si vous déployez la charge de travail Locust sur un pool de nœuds autre que default-pool, recherchez la section matchExpressions, puis, sous values, mettez à jour le nom du pool de nœuds sur lequel la charge de travail Locust sera déployée.

Une fois ces modifications effectuées, vous pouvez créer vos personnalisations dans les fichiers manifestes Kustomize et appliquer le déploiement Locust (locust-master, locust-worker etlocust-master-service) sur le cluster GKE. La commande suivante dans le notebook effectue ces tâches :

!kustomize build locust/manifests | kubectl apply -f -

Vous pouvez vérifier les charges de travail déployées dans la console Google Cloud. Le résultat ressemble à ce qui suit :

Console GKE affichant les charges de travail

Mettre en œuvre le test de charge Locust

La tâche de test pour Locust consiste à appeler le modèle déployé sur AI Platform Prediction.

Cette tâche est implémentée dans la classe AIPPClient du module task.py situé dans le dossier /locust/locust-image/. L'extrait de code suivant montre la mise en œuvre de la classe.

class AIPPClient(object):
   """
   A convenience wrapper around AI Platform Prediction REST API.
   """

   def __init__(self, service_endpoint):
       logging.info(
         "Setting the AI Platform Prediction service endpoint: {}".format(service_endpoint))
       credentials, _ = google.auth.default()
       self._authed_session = AuthorizedSession(credentials)
       self._service_endpoint = service_endpoint

   def predict(self, project_id, model, version, signature, instances):
       """
       Invokes the predict method on the specified signature.
       """

       url = '{}/v1/projects/{}/models/{}/versions/{}:predict'.format(
           self._service_endpoint, project_id, model, version)

       request_body = {
           'signature_name': signature,
           'instances': instances
       }

       response = self._authed_session.post(url, data=json.dumps(request_body))
       return response

La classe AIPPUser du fichier task.py hérite de la classe locust.User pour simuler le comportement des utilisateurs pour l'appel du modèle AI Platform Prediction. Ce comportement est mis en œuvre dans la méthode predict_task. La méthode on_start de la classe AIPPUser télécharge les fichiers suivants à partir d'un bucket Cloud Storage spécifié dans la variable LOCUST_TEST_BUCKET du fichier task.py :

  • test-config.json Ce fichier JSON inclut les configurations suivantes pour le test : test_id, project_id, model et version.
  • test-payload.json Ce fichier JSON inclut, avec la signature cible, les instances de données au format attendu par AI Platform Prediction.

Le code permettant de préparer les données et la configuration de test est inclus dans le fichier 02-perf-testing.ipynb bloc-notes sous 4. Configurer un test Locust.

Les configurations de test et les instances de données sont utilisées comme paramètres pour la méthode predict dans la classe AIPPClient afin de tester le modèle cible à l'aide des données de test requises. La classe AIPPUser simule un temps d'attente de 1 à 2 secondes entre les appels d'un seul utilisateur.

Exécuter le test Locust

Après avoir exécuté les cellules de notebook pour déployer la charge de travail Locust sur le cluster GKE, créé, puis importé les fichiers test-config.json et test-payload.json dans Cloud Storage, vous pouvez démarrer, arrêter et configurer un nouveau test de charge Locust à l'aide de son interface Web. Le code du notebook récupère l'URL de l'équilibreur de charge externe qui expose l'interface Web à l'aide de la commande suivante :

%%bash
IP_ADDRESS=$(kubectl get service locust-master | awk -v  col=4 'FNR==2{print $col}')
echo http://$IP_ADDRESS:8089

Pour effectuer le test, procédez comme suit :

  1. Dans un navigateur, saisissez l'URL que vous avez récupérée.
  2. Pour simuler votre charge de travail de test à l'aide de configurations différentes, saisissez des valeurs dans l'interface Locust, semblable à ce qui suit :

    Interface Locust pour le démarrage d'une exécution de test Locust

    La capture d'écran précédente montre les valeurs de configuration suivantes :

    • Nombre total d'utilisateurs à simuler : 150
    • Taux d'apparition: 1
    • Hôte : http://ml.googleapis.com
    • Nombre d'utilisateurs à augmenter par étape : 10
    • Durée de l'étape : 2m

Lors de l'exécution du test, vous pouvez le surveiller en examinant les graphiques Locust. Les captures d'écran suivantes montrent comment les valeurs s'affichent.

Un graphique indique le nombre total de requêtes par seconde :

Graphique Locust affichant le nombre de requêtes par seconde

Un autre graphique indique le temps de réponse en millisecondes :

Graphique Locust montrant le temps de réponse en millisecondes

Comme indiqué précédemment, ces statistiques sont également consignées dans Cloud Logging afin que vous puissiez créer des métriques personnalisées basées sur les journaux Cloud Monitoring.

Collecter et analyser les résultats du test

La tâche suivante consiste à collecter et à analyser les métriques Cloud Monitoring sont calculées à partir des journaux de résultats Objet DataFrame pandas afin que vous puissiez visualiser et analyser les résultats dans le notebook. Le code permettant d'effectuer cette tâche se trouve dans le notebook 03-analyze-results.ipynb.

Le code utilise le SDK Python Cloud Monitoring Query pour filtrer et récupérer les valeurs de métrique, en fonction des valeurs transmises dans les paramètres project_id, test_id, start_time, end_time, model, model_version et log_name.

L'extrait de code suivant montre les méthodes de récupération des métriques AI Platform Prediction et des métriques personnalisées basées sur les journaux Locust.

import pandas as pd
from google.cloud.monitoring_v3.query import Query

def _get_aipp_metric(metric_type: str, labels: List[str]=[], metric_name=None)-> pd.DataFrame:
    """
    Retrieves a specified AIPP metric.
    """
    query = Query(client, project_id, metric_type=metric_type)
    query = query.select_interval(end_time, start_time)
    query = query.select_resources(model_id=model)
    query = query.select_resources(version_id=model_version)

    if metric_name:
        labels = ['metric'] + labels
    df = query.as_dataframe(labels=labels)

    if not df.empty:
        if metric_name:
            df.columns.set_levels([metric_name], level=0, inplace=True)
        df = df.set_index(df.index.round('T'))
        return df

def _get_locust_metric(metric_type: str, labels: List[str]=[],
                       metric_name=None)-> pd.DataFrame:
    """
     Retrieves a specified custom logs-based metric.
     """
     query = Query(client, project_id, metric_type=metric_type)
     query = query.select_interval(end_time, start_time)
     query = query.select_metrics(log=log_name)
     query = query.select_metrics(test_id=test_id)

     if metric_name:
         labels = ['metric'] + labels
     df = query.as_dataframe(labels=labels)

     if not df.empty:
        if metric_name:
            df.columns.set_levels([metric_name], level=0, inplace=True)
        df = df.apply(lambda row: [metric.mean for metric in row])
        df = df.set_index(df.index.round('T'))

     return df

Les données de métriques sont récupérées en tant qu'objet DataFrame pandas pour chaque métrique. Les trames de données individuelles sont ensuite fusionnées en un seul objet DataFrame. L'objet DataFrame final avec les résultats fusionnés ressemble à ce qui suit dans votre notebook :

Affichage dans le notebook d'une trame pandas avec des objets DataFrame fusionnés

L'objet DataFrame récupéré utilise l'indexation hiérarchique des noms de colonnes. En effet, certaines métriques contiennent plusieurs séries temporelles. Par exemple, la métrique GPU duty_cycle inclut une série temporelle de mesures pour chaque GPU utilisé dans le déploiement, indiqué par replica_id. Le niveau supérieur de l'index de colonne indique le nom d'une métrique individuelle. Le deuxième niveau correspond à un ID d'instance dupliquée. Le troisième niveau correspond à la signature d'un modèle. Toutes les métriques sont alignées sur la même chronologie.

Les graphiques suivants illustrent l'utilisation du GPU, l'utilisation du processeur et la latence telle qu'elles apparaissent dans le notebook.

Utilisation GPU :

Graphique en courbes illustrant l'utilisation GPU pendant la période

Utilisation du processeur :

Graphique en courbes illustrant l'utilisation GPU pendant la période

Latence :

Graphique en courbes indiquant la latence pendant la période

Les graphiques montrent le comportement et la séquence suivants :

  1. À mesure que la charge de travail (nombre d'utilisateurs) augmente, l'utilisation du processeur et GPU augmente. Par conséquent, la latence augmente et la différence entre la latence du modèle et la latence totale augmente jusqu'à atteindre un pic autour de 20h40.
  2. À 20h40, l'utilisation GPU atteint 100 %, tandis que le graphique du processeur indique que l'utilisation atteint 4 processeurs. L'exemple utilise une machine n1-standard-8 dans ce test, dotée de huit processeurs. Ainsi, l'utilisation du processeur atteint 50 %.
  3. À ce stade, l'autoscaling ajoute de la capacité : un nouveau nœud de diffusion est ajouté avec une instance dupliquée GPU supplémentaire. La première utilisation de l'instance dupliquée GPU diminue, et l'utilisation de la deuxième est plus importante.
  4. La latence diminue à mesure que la nouvelle instance dupliquée commence à diffuser des prédictions, convergeant à environ 200 millisecondes.
  5. L'utilisation du processeur converge à environ 250 % pour chaque instance dupliquée, c'est-à-dire que 2,5 processeurs sont utilisés sur huit. Cette valeur indique que vous pouvez utiliser une machine n1-standard-4 au lieu d'une machine n1-standard-8.

Nettoyer

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et les ressources individuelles.

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Si vous souhaitez conserver le projet Google Cloud tout en supprimant les ressources que vous avez créé, supprimez le cluster Google Kubernetes Engine et le modèle AI Platform déployé.

Étape suivante