Model Serving dans Snowpark Container Services

Note

Cette fonction est disponible dans les régions commerciales AWS et Azure. Elle n’est pas disponible dans les régions gouvernementales.

Note

La possibilité d’exécuter des modèles dans Snowpark Container Services (SPCS) décrite dans cette rubrique est disponible dans snowflake-ml-python version 1.8.0 et ultérieure.

Le Snowflake Model Registry vous permet d’exécuter des modèles soit dans un entrepôt (par défaut), soit dans un pool de calcul Snowpark Container Services (SPCS) via Model Serving. L’exécution de modèles dans un entrepôt impose quelques limitations sur la taille et les types de modèles que vous pouvez utiliser (en particulier, les modèles ayant des CPU de petite à moyenne taille dont les dépendances peuvent être satisfaites par les paquets disponibles dans le canal Snowflake conda).

L’exécution de modèles sur Snowpark Container Services (SPCS) assouplit ces restrictions, voire les élimine complètement. Vous pouvez utiliser tous les paquets de votre choix, y compris ceux du Python Package Index (PyPI) ou d’autres sources. Les grands modèles peuvent être exécutés sur des clusters distribués de GPUs. Et vous n’avez pas besoin de connaître quoi que ce soit sur les technologies de conteneurs, telles que Docker ou Kubernetes. Snowflake Model Serving s’occupe de tous les détails.

Concepts clés

Un aperçu simplifié de haut niveau de l’architecture d’inférence de Snowflake Model Serving est présenté ci-dessous.

Inférence de modèle sur l'architecture de Snowpark Container Services

Les principaux composants de l’architecture sont les suivants :

  • Serveur d’inférence : le serveur qui exécute le modèle et fournit des prédictions. Le serveur d’inférence peut utiliser plusieurs processus d’inférence pour exploiter pleinement les capacités du nœud. Les requêtes adressées au modèle sont expédiées par le contrôle d’admission, qui gère la file d’attente des requêtes entrantes pour éviter les conditions de manque de mémoire, rejetant les clients lorsque le serveur est surchargé. Aujourd’hui, Snowflake fournit un serveur d’inférence simple et flexible basé sur Python qui peut exécuter des inférences pour tous les types de modèles. Progressivement, Snowflake prévoit de proposer des serveurs d’inférence optimisés pour des types de modèles spécifiques.

  • Environnement Python spécifique au modèle : pour réduire la latence de démarrage d’un modèle, qui inclut le temps nécessaire au téléchargement des dépendances et au chargement du modèle, Snowflake crée un conteneur qui encapsule les dépendances du modèle spécifique.

  • Fonctions de service : pour communiquer avec le serveur d’inférence à partir du code exécuté dans un entrepôt, Snowflake Model Serving crée des fonctions qui ont la même signature que le modèle, mais qui appellent à la place le serveur d’inférence via le protocole de fonction externe.

  • Point de terminaison d’entrée : pour permettre aux applications extérieures à Snowflake d’appeler le modèle, Snowflake Model Serving peut provisionner un point de terminaison HTTP facultatif, accessible à l’Internet public.

Comment cela fonctionne-t-il ?

Le diagramme suivant montre comment Snowflake Model Serving déploie et sert des modèles dans un entrepôt ou sur SPCS.

Déploiement de modèles sur Snowpark Container Services

Comme vous pouvez le voir, le chemin vers le déploiement SPCS est plus complexe que le chemin vers le déploiement de l’entrepôt, mais Snowflake Model Serving fait tout le travail pour vous, y compris la création de l’image de conteneur qui contient le modèle et ses dépendances, et la création du serveur d’inférence qui exécute le modèle.

Conditions préalables

Avant de commencer, assurez-vous de disposer des éléments suivants :

  • Un compte Snowflake dans n’importe quelle région commerciale AWS ou Azure. Les régions gouvernementales ne sont pas prises en charge.

  • Version 1.6.4 ou ultérieure du paquet Python snowflake-ml-python.

  • Un modèle que vous souhaitez exécuter sur Snowpark Container Services.

  • Connaissances de Snowflake Model Registry.

  • Connaissances de Snowpark Container Services. Plus précisément, vous devez comprendre ce que sont les pools de calcul, les référentiels d’images, et les privilèges associés.

Créer un pool de calcul

Snowpark Container Services (SPCS) exécute des images de conteneurs dans des pools de calcul. Si vous ne disposez pas encore d’un pool de calcul approprié, créez-en un comme suit :

CREATE COMPUTE POOL IF NOT EXISTS mypool
    MIN_NODES = 2
    MAX_NODES = 4
    INSTANCE_FAMILY = 'CPU_X64_M'
    AUTO_RESUME = TRUE;
Copy

Voir le tableau des noms de famille pour une liste de familles d’instances valides.

Assurez-vous que le rôle qui exécutera le modèle est le propriétaire du pool de calcul ou qu’il dispose des privilèges USAGE ou OPERATE sur le pool.

Créer un référentiel d’images

Snowflake Model Serving crée une image de conteneur qui contient le modèle et ses dépendances. Pour stocker cette image, vous avez besoin d’un référentiel d’images. Si vous n’en avez pas déjà un, créez-en un comme suit :

CREATE IMAGE REPOSITORY IF NOT EXISTS my_inference_images
Copy

Si vous utilisez un référentiel d’images dont vous n’êtes pas propriétaire, assurez-vous que le rôle qui créera l’image du conteneur dispose des privilèges READ WRITE, SERVICE READ, SERVICE et WRITE sur le référentiel. Accordez ces privilèges comme suit :

GRANT READ ON IMAGE REPOSITORY my_inference_images TO ROLE myrole;
GRANT WRITE ON IMAGE REPOSITORY my_inference_images TO ROLE myrole;
GRANT SERVICE READ ON IMAGE REPOSITORY my_inference_images TO ROLE myrole;
GRANT SERVICE WRITE ON IMAGE REPOSITORY my_inference_images TO ROLE myrole;
Copy

Privilèges requis

Modeling Serving s’exécute sur Snowpark Container Services. Pour utiliser Model Serving, un utilisateur doit disposer des privilèges suivants :

  • USAGE ou OWNERSHIP sur un pool de calcul où le service sera exécuté.

  • Un référentiel d’images avec les quatre privilèges : READ, WRITE, SERVICE READ et SERVICE WRITE. Si l’utilisateur est propriétaire du référentiel d’images, tous les privilèges lui sont automatiquement accordés.

  • Si un point de terminaison d’entrée est souhaité sur Model Serving, l’utilisateur doit avoir le privilège BIND SERVICE ENDPOINT sur le compte.

  • Seuls les propriétaires de modèles peuvent déployer le modèle sur Serving. Pour permettre aux non-propriétaires d’accéder à l’inférence, les propriétaires doivent déployer le service, puis accorder le rôle de service INFERENCE_SERVICE_FUNCTION_USAGE pour partager les fonctions du service et ALL_ENDPOINTS_USAGE pour partager les points terminaison d’entrée.

Limitations

Les limites suivantes s’appliquent à Model Serving dans Snowpark Container Services.

  • Seul le propriétaire d’un modèle peut le déployer sur Snowpark Container Services.

  • La taille du cluster de calcul ne s’adapte pas automatiquement. Vous pouvez modifier manuellement le nombre d’instances lors de l’exécution à l’aide de ALTER SERVICE myservice MIN_INSTANCES = n. Dans certains cas, cela entraîne la défaillance des nœuds existants.

  • La mise à l’échelle des services et des pools de calcul est plus lente que prévu.

  • Les services d’inférence pour lesquels l’entrée est activée ne peuvent pas être suspendus.

  • La construction d’une image échoue si elle dure plus d’une heure.

  • Les fonctions de table ne sont pas prises en charge. Les modèles sans fonction régulière ne peuvent actuellement pas être déployés sur Snowpark Container Services.

Déploiement d’un modèle sur SPCS

Soit vous enregistrez une nouvelle version du modèle (en utilisant reg.log_model) soit vous obtenez une référence à une version de modèle existante (reg.get_model(...).version(...)). Dans les deux cas, vous vous retrouvez avec une référence à un objet ModelVersion.

Dépendances et éligibilité du modèle

Les dépendances d’un modèle déterminent s’il peut s’exécuter dans un entrepôt, dans un service SPCS, ou les deux. Vous pouvez, si nécessaire, spécifier intentionnellement des dépendances pour rendre un modèle inéligible à l’exécution dans l’un de ces environnements.

Le canal Snowflake conda est disponible uniquement dans les entrepôts et constitue la seule source de dépendances d’entrepôt. Par défaut, les dépendances de conda pour les modèles SPCS obtiennent leurs dépendances de conda-forge.

Lorsque vous enregistrez une version de modèle, les dépendances conda du modèle sont validées par rapport au canal conda Snowflake. Si toutes les dépendances conda du modèle y sont disponibles, le modèle est considéré comme éligible pour être exécuté dans un entrepôt. Il peut également être éligible pour se présenter à un service SPCS si toutes ses dépendances sont disponibles depuis conda-forge, bien que cela ne soit pas vérifié tant que vous n’avez pas créé un service.

Les modèles connectés avec les dépendances PyPI doivent être exécutés sur SPCS. Spécifier au moins une dépendance PyPI est un moyen de rendre un modèle inéligible à l’exécution dans un entrepôt. Si votre modèle n’a que des dépendances conda, spécifiez-en au moins une avec un canal explicite (même conda-forge), comme indiqué dans l’exemple suivant.

# reg is a snowflake.ml.registry.Registry object
reg.log_model(
    model_name="my_model",
    version_name="v1",
    model=model,
    conda_dependencies=["conda-forge::scikit-learn"])
Copy

Pour les modèles déployés avec SPCS, les dépendances conda, le cas échéant, sont installées en premier, puis toutes les dépendances PyPI sont installées dans l’environnement conda à l’aide de pip.

Créez un service

Pour créer un service SPCS et y déployer le modèle, appelez la méthode create_service de la version du modèle, comme illustré dans l’exemple suivant.

# mv is a snowflake.ml.model.ModelVersion object
mv.create_service(service_name="myservice",
                  service_compute_pool="my_compute_pool",
                  image_repo="mydb.myschema.my_image_repo",
                  ingress_enabled=True,
                  gpu_requests=None)
Copy

Voici les arguments requis pour create_service :

  • service_name : le nom du service à créer. Ce nom doit être unique au sein du compte.

  • service_compute_pool : le nom du Compute Pool à utiliser pour exécuter le modèle. Le pool de calcul doit déjà exister.

  • image_repo : le nom du référentiel d’images à utiliser pour stocker l’image du conteneur. Le référentiel doit déjà exister et l’utilisateur doit avoir le privilège SERVICE WRITE sur celui-ci (ou OWNERSHIP).

  • ingress_enabled : si True, le service est rendu accessible via un point de terminaison HTTP. Pour créer le point de terminaison, l’utilisateur doit disposer du privilège BIND SERVICE ENDPOINT.

  • gpu_requests : une chaîne spécifiant le nombre de GPUs. Pour un modèle qui peut être exécuté sur l’un ou l’autre CPU ou GPU, cet argument détermine si le modèle sera exécuté sur le CPU ou sur les GPUs. Si le modèle est d’un type connu qui ne peut être exécuté que sur CPU (par exemple, les modèles scikit-learn), la création de l’image échoue si des GPUs sont demandés.

Si vous déployez un nouveau modèle, la création du service peut prendre 5 minutes pour les modèles alimentés par CPU et 10 minutes pour les modèles alimentés par GPU. Si le pool de calcul est inactif ou nécessite un redimensionnement, la création du service peut prendre plus de temps.

Cet exemple montre uniquement les arguments obligatoires et les plus couramment utilisés. Voir la référence d’API ModelVersion pour une liste complète des arguments.

Configuration de service par défaut

Dans sa version initiale, le serveur d’inférence utilise des paramètres par défaut faciles à utiliser qui conviennent à la plupart des cas d’utilisation. Ces paramètres sont les suivants :

  • Nombre de threads de travail : pour un modèle alimenté par CPU, le serveur utilise deux fois le nombre de CPUs plus un processus de travail. Les modèles alimentés par GPU utilisent un seul processus de travail. Vous pouvez passer outre en utilisant l’argument num_workers dans l’appel create_service.

  • Thread safety : certains modèles ne sont pas sécurisés au niveau du filetage. Par conséquent, le service charge une copie distincte du modèle pour chaque processus de travail. Cela peut entraîner un épuisement des ressources pour les grands modèles.

  • Utilisation du nœud : par défaut, une instance de serveur d’inférence sollicite l’ensemble du nœud en demandant la totalité du CPU et de la mémoire du nœud sur lequel elle s’exécute. Pour personnaliser l’allocation des ressources par instance, utilisez des arguments tels que cpu_requests, memory_requests, et gpu_requests.

  • Point de terminaison : le point de terminaison de l’inférence est nommé inference et utilise le port 5000. Ceux-ci ne peuvent pas être personnalisés.

  • Suspension automatique : Les services d’inférence sont automatiquement suspendus après trente minutes d’inactivité, sauf s’ils disposent d’un point de terminaison d’entrée. Un service suspendu reprend automatiquement lorsqu’il reçoit une requête.

Comportement de création d’image de conteneur

Par défaut, Snowflake Model Serving crée l’image du conteneur à l’aide du même pool de calcul qui sera utilisé pour exécuter le modèle. Ce pool de calcul d’inférence est probablement surpuissant pour cette tâche (par exemple, les GPUs ne sont pas utilisés dans la création d’images de conteneurs). Dans la plupart des cas, cela n’aura pas d’impact significatif sur les coûts de calcul, mais si cela constitue un problème, vous pouvez choisir un pool de calcul moins puissant pour créer des images en spécifiant l’argument image_build_compute_pool.

create_service est une fonction idempotente. L’appeler plusieurs fois ne déclenche pas la création d’image à chaque fois. Cependant, les images de conteneurs peuvent être reconstruites en fonction des mises à jour du service d’inférence, y compris des correctifs pour les vulnérabilités des paquets dépendants. Lorsque cela se produit, create_service déclenche automatiquement une reconstruction de l’image.

Note

Les modèles développés à l’aide des classes de modélisation Snowpark ML ne peuvent pas être déployés dans des environnements dotés d’une GPU. Pour contourner le problème, vous pouvez extraire le modèle natif et le déployer. Par exemple :

# Train a model using Snowpark ML
from snowflake.ml.modeling.xgboost import XGBRegressor
regressor = XGBRegressor(...)
regressor.fit(training_df)

# Extract the native model
xgb_model = regressor.to_xgboost()
# Test the model with pandas dataframe
pandas_test_df = test_df.select(['FEATURE1', 'FEATURE2', ...]).to_pandas()
xgb_model.predict(pandas_test_df)

# Log the model in Snowflake Model Registry
mv = reg.log_model(xgb_model,
                   model_name="my_native_xgb_model",
                   sample_input_data=pandas_test_df,
                   comment = 'A native XGB model trained from Snowflake Modeling API',
                   )
# Now we should be able to deploy to a GPU compute pool on SPCS
mv.create_service(
    service_name="my_service_gpu",
    service_compute_pool="my_gpu_pool",
    image_repo="my_repo",
    max_instances=1,
    gpu_requests="1",
)
Copy

Interface utilisateur

Vous pouvez gérer les modèles déployés dans l’UI de Model Registry Snowsight. Pour plus d’informations, voir Services d’inférence de modèles.

Utilisation d’un modèle déployé dans SPCS

Vous pouvez appeler les méthodes d’un modèle en utilisant SQL, Python, ou un point de terminaison HTTP.

SQL

Snowflake Model Serving crée des fonctions de service lors du déploiement d’un modèle dans SPCS. Ces fonctions servent de passerelle entre SQL et le modèle exécuté dans le pool de calcul du SPCS. Une fonction de service est créée pour chaque méthode du modèle, et elles sont nommées comme service_name!method_name. Par exemple, si le modèle a deux méthodes nommées PREDICT et EXPLAIN est qu’il est en cours de déploiement sur un service nommé MY_SERVICE, les fonctions de service résultantes sont MY_SERVICE!PREDICT et MY_SERVICE!EXPLAIN.

Note

Les fonctions de service sont contenues dans le service. Pour cette raison, elles ne disposent que d’un seul point de contrôle d’accès, le service. Vous ne pouvez pas avoir différents privilèges de contrôle d’accès pour différentes fonctions dans un même service.

L’appel des fonctions de service d’un modèle en SQL se fait en utilisant un code comme celui-ci :

-- See signature of the inference function in SQL.
SHOW FUNCTIONS IN MODEL my_native_xgb_model VERSION ...;

-- Call the inference function in SQL following the same signature (from `arguments` column of the above query)
SELECT MY_SERVICE!PREDICT(feature1, feature2, ...) FROM input_data_table;
Copy

Python

Appeler les méthodes d’un service à l’aide de la méthode run d’un objet de version de modèle, y compris l’argument service_name pour spécifier le service où la méthode sera exécutée. Par exemple :

# Get signature of the inference function in Python
# mv is a snowflake.ml.model.ModelVersion object
mv.show_functions()
# Call the function in Python
service_prediction = mv.run(
    test_df,
    function_name="predict",
    service_name="my_service")
Copy

Si vous n’incluez pas l’argument service_name, le modèle s’exécute dans un entrepôt.

Point de terminaison HTTP

Le déploiement d’un service avec l’entrée activée crée un point de terminaison HTTP par lequel vous pouvez appeler le service. Vous pouvez trouver le point de terminaison en utilisant la commande SHOW ENDPOINTS IN SERVICE.

SHOW ENDPOINTS IN SERVICE my_service;
Copy

Prenez note de la colonne ingress_url, qui devrait ressembler à random_str-account-id.snowflakecomputing.app. Les limites du nom DNS s’appliquent. Dans une URL, un trait de soulignement (_) dans le nom de la méthode est remplacé par un tiret (-) (par exemple, l’URL de predict_proba est url/predict-proba).

Les utilisateurs peuvent appeler le service en utilisant le point de terminaison public de manière programmatique. Les applications utilisent l’authentification par paire de clés pour authentifier les requêtes adressées au point de terminaison public. Générez un jeton Web JSON (JWT) à partir de la paire de clés, échangez le jeton JWT avec Snowflake contre un jeton OAuth et utilisez le jeton OAuth pour authentifier les requêtes adressées au point de terminaison public d’un service. Pour un exemple, voir Accéder au point de terminaison public de manière programmatique.

Pour plus d’informations sur le format de données requis, voir Formats des données d’entrée et de sortie des services à distance.

Voir Déployer un transformateur de phrases Hugging Face pour inférence alimentée par GPU pour un exemple d’utilisation d’un point de terminaison de service de modèle HTTP.

Gestion des services

Snowpark Container Services propose une interface SQL de gestion des services. Vous pouvez utiliser les commandes DESCRIBE SERVICE et ALTER SERVICE avec les services SPCS créés par Snowflake Model Serving comme vous le feriez pour gérer n’importe quel autre service SPCS. Par exemple, vous pouvez :

  • Changer MIN_INSTANCES et d’autres propriétés d’un service

  • Supprimer un service

  • Partager un service avec un autre compte

  • Changer la propriété d’un service (le nouveau propriétaire doit avoir accès au modèle)

Note

Si le propriétaire d’un service perd l’accès au modèle sous-jacent pour une raison quelconque, le service cesse de fonctionner après un redémarrage. Il continuera à fonctionner jusqu’à ce qu’il soit redémarré.

Pour garantir la reproductibilité et le débogage, vous ne pouvez pas modifier la spécification d’un service d’inférence existant. Vous pouvez cependant copier la spécification, la personnaliser et utiliser la spécification personnalisée pour créer votre propre service pour héberger le modèle. Cependant, cette méthode ne protège pas le modèle sous-jacent contre la suppression. En outre, il ne suit pas la lignée. Il est préférable de permettre à Snowflake Model Serving de créer des services.

Suspension de services

Si vous utilisez le service uniquement à partir de SQL via des fonctions de service, ne définissez pas le paramètre ingress_enabled sur true. L’activation de l’entrée implique que le service doit rester en activité à tout moment afin de pouvoir répondre aux requêtes HTTP entrantes. Lorsque l’entrée n’est pas activée, le service d’inférence est suspendu automatiquement après trente minutes d’inactivité et reprend automatiquement lorsqu’il reçoit une requête.

Pour suspendre manuellement un service, utilisez la commande ALTER SERVICE.

ALTER SERVICE my_service SUSPEND;
Copy

La reprise automatique du service à la réception d’une nouvelle requête est soumise à des délais de planification et de démarrage. Le délai de planification dépend de la disponibilité du pool de calcul et le délai de démarrage dépend de la taille du modèle.

Suppression de modèles

Vous pouvez gérer les modèles et les versions de modèles comme d’habitude avec l’interface SQL ou l’API Python, à la condition qu’un modèle ou une version de modèle utilisé(e) par un service (qu’il soit en cours d’exécution ou suspendu) ne puisse pas être supprimé(e). Pour supprimer un modèle ou une version de modèle, supprimez d’abord le service.

Exemples

Ces exemples supposent que vous avez déjà créé un pool de calcul, un référentiel d’images et que vous avez accordé les privilèges nécessaires. Voir Conditions préalables pour plus de détails.

Déployer un modèle XGBoost pour une inférence alimentée par CPU

Le code suivant illustre les étapes clés du déploiement d’un modèle XGBoost pour l’inférence dans SPCS, puis en utilisant le modèle déployé pour l’inférence. Un notebook pour cet exemple est disponible.

from snowflake.ml.registry import registry
from snowflake.ml.utils.connection_params import SnowflakeLoginOptions
from snowflake.snowpark import Session

from xgboost import XGBRegressor

# your model training code here output of which is a trained xgb_model

# Open model registry
reg = registry.Registry(session=session, database_name='my_registry_db', schema_name='my_registry_schema')

# Log the model in Snowflake Model Registry
model_ref = reg.log_model(
    model_name="my_xgb_forecasting_model",
    version_name="v1",
    model=xgb_model,
    conda_dependencies=["scikit-learn","xgboost"],
    sample_input_data=pandas_test_df,
    comment="XGBoost model for forecasting customer demand"
)

# Deploy the model to SPCS
model_ref.create_service(
    service_name="forecast_model_service",
    service_compute_pool="my_cpu_pool",
    image_repo="my_image_repo",
    ingress_enabled=True)

# See all services running a model
model_ref.list_services()

# Run on SPCS
model_ref.run(pandas_test_df, function_name="predict", service_name="forecast_model_service")

# Delete the service
model_ref.delete_service("forecast_model_service")
Copy

Étant donné que l’entrée est activée sur ce modèle, vous pouvez appeler son point de terminaison HTTP comme suit.

import json
import numpy as np
from pprint import pprint
import requests
import snowflake.connector

# Generate headers including authorization.
# This example uses session token authorization. Ideally key-pair authentication is used for API access.
def initiate_snowflake_connection():
    connection_parameters = SnowflakeLoginOptions("connection_name")
    connection_parameters["session_parameters"] = {"PYTHON_CONNECTOR_QUERY_RESULT_FORMAT": "json"}
    snowflake_conn = snowflake.connector.connect(**connection_parameters)
    return snowflake_conn

def get_headers(snowflake_conn):
    token = snowflake_conn._rest._token_request('ISSUE')
    headers = {'Authorization': f'Snowflake Token=\"{token["data"]["sessionToken"]}\"'}
    return headers

headers = get_headers(initiate_snowflake_connection())

# Put the endpoint url with method name `predict`
# The endpoint url can be found with `show endpoints in service <service_name>`.
URL = 'https://<random_str>-<organization>-<account>.snowflakecomputing.app/predict'

# Prepare data to be sent
data = {"data": np.column_stack([range(pandas_test_df.shape[0]), pandas_test_df.values]).tolist()}

# Send over HTTP
def send_request(data: dict):
    output = requests.post(URL, json=data, headers=headers)
    assert (output.status_code == 200), f"Failed to get response from the service. Status code: {output.status_code}"
    return output.content

# Test
results = send_request(data=data)
pprint(json.loads(results))
Copy

Déployer un transformateur de phrases Hugging Face pour inférence alimentée par GPU

Le code suivant entraîne et déploie un transformateur de phrases Hugging Face, y compris un point de terminaison HTTP.

Cet exemple exige le paquet sentence-transformers, un pool de calcul GPU et un référentiel d’images.

from snowflake.ml.registry import registry
from snowflake.ml.utils.connection_params import SnowflakeLoginOptions
from snowflake.snowpark import Session
from sentence_transformers import SentenceTransformer

session = Session.builder.configs(SnowflakeLoginOptions("connection_name")).create()
reg = registry.Registry(session=session, database_name='my_registry_db', schema_name='my_registry_schema')

# Take an example sentence transformer from HF
embed_model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

# Have some sample input data
input_data = [
    "This is the first sentence.",
    "Here's another sentence for testing.",
    "The quick brown fox jumps over the lazy dog.",
    "I love coding and programming.",
    "Machine learning is an exciting field.",
    "Python is a popular programming language.",
    "I enjoy working with data.",
    "Deep learning models are powerful.",
    "Natural language processing is fascinating.",
    "I want to improve my NLP skills.",
]

# Log the model with pip dependencies
pip_model = reg.log_model(
    embed_model,
    model_name="sentence_transformer_minilm",
    version_name="pip",
    sample_input_data=input_data,  # Needed for determining signature of the model
    pip_requirements=["sentence-transformers", "torch", "transformers"], # If you want to run this model in the Warehouse, you can use conda_dependencies instead
)

# Force Snowflake to not try to check warehouse
conda_forge_model = reg.log_model(
    embed_model,
    model_name="sentence_transformer_minilm",
    version_name="conda_forge_force",
    sample_input_data=input_data,
    # setting any package from conda-forge is sufficient to know that it can't be run in warehouse
    conda_dependencies=["sentence-transformers", "conda-forge::pytorch", "transformers"]
)

# Deploy the model to SPCS
pip_model.create_service(
    service_name="my_minilm_service",
    service_compute_pool="my_gpu_pool",  # Using GPU_NV_S - smallest GPU node that can run the model
    image_repo="my_image_repo",
    ingress_enabled=True,
    gpu_requests="1", # Model fits in GPU memory; only needed for GPU pool
    max_instances=4, # 4 instances were able to run 10M inferences from an XS warehouse
)

# See all services running a model
pip_model.list_services()

# Run on SPCS
pip_model.run(input_data, function_name="encode", service_name="my_minilm_service")

# Delete the service
pip_model.delete_service("my_minilm_service")
Copy

Dans SQL, vous pouvez appeler la fonction de service comme suit :

SELECT my_minilm_service!encode('This is a test sentence.');
Copy

De même, vous pouvez appeler son point de terminaison HTTP comme suit.

import json
from pprint import pprint
import requests

# Put the endpoint url with method name `encode`
URL='https://<random_str>-<account>.snowflakecomputing.app/encode'

# Prepare data to be sent
data = {
    'data': []
}
for idx, x in enumerate(input_data):
    data['data'].append([idx, x])

# Send over HTTP
def send_request(data: dict):
    output = requests.post(URL, json=data, headers=headers)
    assert (output.status_code == 200), f"Failed to get response from the service. Status code: {output.status_code}"
    return output.content

# Test
results = send_request(data=data)
pprint(json.loads(results))
Copy

Déployer un modèle PyTorch pour l’inférence alimentée par GPU

Voir ce démarrage rapide pour un exemple d’entraînement et de déploiement d’un modèle de recommandation d’apprentissage profond PyTorch (DLRM) dans SPCS pour une inférence par GPU.

Meilleures pratiques

Partage d’un référentiel d’images

Il est courant que plusieurs utilisateurs ou rôles utilisent le même modèle. L’utilisation d’un référentiel d’images unique permet de créer l’image une seule fois et de la réutiliser par tous les utilisateurs, ce qui permet d’économiser du temps et de l’argent. Tous les rôles qui utiliseront le référentiel ont besoin de privilèges SERVICE READ, SERVICE WRITE, READ, et WRITE sur le référentiel. Étant donné que l’image peut avoir besoin d’être reconstruite pour mettre à jour les dépendances, vous devez conserver les privilèges d’écriture ; ne les révoquez pas une fois l’image initialement créée.

Mise à l’échelle du service d’inférence

La mise à l’échelle automatique de Snowpark Container Services est très conservatrice et ne s’adapte pas assez rapidement à la plupart des charges de travail de ML. Pour cette raison, Snowflake vous recommande de définir les deux MIN_INSTANCES et MAX_INSTANCES sur la même valeur, en choisissant ces valeurs pour obtenir les performances dont vous avez besoin pour vos charges de travail typiques. Utilisez SQL comme ce qui suit :

ALTER SERVICE myservice
    SET MIN_INSTANCES = <new_num>
        MAX_INSTANCES = <new_num>;
Copy

Pour la même raison, lors de la création initiale du service à l’aide de l’API Python, la méthode create_service accepte uniquement max_instances et utilise cette même valeur pour min_instances.

Choix du type de nœud et du nombre d’instances

Utilisez le plus petit nœud de GPU où le modèle s’insère dans la mémoire. Dimensionnez en augmentant le nombre d’instances, plutôt qu’en augmentant num_workers dans un nœud de GPU plus grand. Par exemple, si le modèle correspond au type d’instance GPU_NV_S (GPU_NV_SM sur Azure), utilisez gpu_requests=1 et effectuez une évolution en augmentant max_instances plutôt que d’utiliser une combinaison de gpu_requests et num_workers sur une instance GPU plus grande.

Sélection d’une taille d’entrepôt

Plus l’entrepôt est grand, plus les requêtes parallèles sont envoyées aux serveurs d’inférence. L’inférence est une opération coûteuse, utilisez donc un entrepôt plus petit lorsque cela est possible. L’utilisation d’une taille d’entrepôt supérieure à la moyenne n’accélère pas les performances des requêtes et entraîne des coûts supplémentaires.

Résolution des problèmes

Surveiller les déploiements de SPCS

Vous pouvez surveiller le déploiement en inspectant les services lancés à l’aide de la requête SQL suivante.

SHOW SERVICES IN COMPUTE POOL my_compute_pool;
Copy

Deux tâches sont lancées :

  • MODEL_BUILD_xxxxx : les derniers caractères du nom sont choisis de façon aléatoire pour éviter les conflits de noms. Cette tâche construit l’image et se termine une fois l’image construite. Si une image existe déjà, la tâche est ignorée.

    Les connexions sont utiles pour résoudre des problèmes tels que des conflits dans les dépendances des paquets. Pour voir les journaux de cette tâche, exécutez la commande SQL ci-dessous, en veillant à utiliser les mêmes caractères finaux :

    CALL SYSTEM$GET_SERVICE_LOGS('MODEL_BUILD_xxxxx', 0, 'model-build');
    
    Copy
  • MYSERVICE : le nom du service tel que spécifié dans l’appel à create_service. Cette tâche est démarrée si la tâche MODEL_BUILD aboutit ou est ignorée. Pour voir les journaux de cette tâche, exécutez la commande SQL ci-dessous :

    CALL SYSTEM$GET_SERVICE_LOGS('MYSERVICE', 0, 'model-inference');
    
    Copy

    Si les journaux ne sont pas disponibles via SYSTEM$GET_SERVICE_LOG car la tâche de construction ou le service a été supprimé, vous pouvez consulter la table des événements (si elle est activée) pour voir les journaux :

    SELECT RESOURCE_ATTRIBUTES, VALUE
    FROM <EVENT_TABLE_NAME>
    WHERE true
        AND timestamp > dateadd(day, -1, current_timestamp())  -- choose appropriate timestamp range
        AND RESOURCE_ATTRIBUTES:"snow.database.name" = '<db of the service>'
        AND RESOURCE_ATTRIBUTES:"snow.schema.name" = '<schema of the service>'
        AND RESOURCE_ATTRIBUTES:"snow.service.name" = '<Job or Service name>'
    AND RESOURCE_ATTRIBUTES:"snow.service.container.instance" = '0'  -- choose all instances or one particular
    AND RESOURCE_ATTRIBUTES:"snow.service.container.name" != 'snowflake-ingress' --skip logs from internal sidecar
    ORDER BY timestamp ASC;
    
    Copy

Conflits de paquets

Deux systèmes dictent les paquets installés dans le conteneur de services : le modèle lui-même et le serveur d’inférence. Pour réduire les conflits avec les dépendances de votre modèle, le serveur d’inférence ne nécessite que les paquets suivants :

  • gunicorn<24.0.0

  • starlette<1.0.0

  • uvicorn-standard<1.0.0

Assurez-vous que les dépendances de votre modèle, ainsi que celles ci-dessus, peuvent être résolues par pip ou conda, quel que soit votre choix.

Si un modèle possède à la fois conda_dependencies et pip_requirements, elles seront installées comme suit via conda :

Canaux :

  • conda-forge

  • nodefaults

Dépendances :

  • all_conda_packages

  • pip :
    • all_pip_packages

Snowflake récupère les paquets Anaconda à partir de conda-forge lors de la construction d’images de conteneurs car le canal conda de Snowflake n’est disponible que dans les entrepôts, et le canal defaults exige que les utilisateurs acceptent les conditions d’utilisation d’Anaconda, ce qui n’est pas possible lors d’une construction automatisée. Pour obtenir des paquets provenant d’un canal différent, tel que defaults, spécifiez chaque paquet avec le nom du canal, comme dans defaults::pkg_name.

Note

Si vous spécifiez à la fois conda_dependencies et pip_requirements, l’image du conteneur se construit avec succès même si les deux ensembles de dépendances ne sont pas compatibles, ce qui peut faire que l’image du conteneur qui en résulte ne fonctionne pas comme prévu. Snowflake recommande d’utiliser uniquement conda_dependencies ou uniquement pip_requirements, et non les deux.

Service à court de mémoire

Certains modèles ne sont pas compatibles avec les threads, Snowflake charge donc une copie distincte du modèle en mémoire pour chaque processus de travail. Cela peut entraîner des conditions de pénurie de mémoire pour les grands modèles avec un nombre plus élevé de workers. Essayez de réduire num_workers.

Performances de requête insatisfaisantes

En général, l’inférence est limitée par le nombre d’instances dans le service d’inférence. Essayez de transférer une valeur plus élevée pour max_instances lorsque vous déployez le modèle.

Impossible de modifier les spécifications du service

Les spécifications des services de création de modèles et d’inférence ne peuvent pas être modifiées à l’aide de ALTER SERVICE. Vous ne pouvez modifier que des attributs tels que TAG, MIN_INSTANCES, et ainsi de suite. Cependant, étant donné que l’image est publiée dans le référentiel d’images, vous pouvez copier la spécification, la modifier et créer un nouveau service à partir de celle-ci, que vous pouvez démarrer manuellement.

Paquet non trouvé

Le déploiement du modèle a échoué pendant la phase de construction de l’image. Les journaux model-build suggèrent qu’un paquet demandé n’a pas été trouvé. (Cette étape utilise conda-forge par défaut si le paquet est mentionné dans conda_dependencies.)

L’installation d’un paquet peut échouer pour l’une des raisons suivantes :

  • Le nom ou la version du paquet n’est pas valide. Vérifiez l’orthographe et la version du paquet.

  • La version demandée du paquet n’existe pas dans conda-forge. Vous pouvez essayer de supprimer la spécification de la version pour obtenir la dernière version disponible dans conda-forge, ou utiliser pip_requirements à la place. Vous pouvez consulter tous les paquets disponibles ici.

  • Il peut arriver que vous ayez besoin d’un paquet provenant d’un canal spécial (par exemple pytorch). Ajoutez un préfixe channel_name:: à la dépendance, par exemple pytorch::torch.

Inadéquation de la version de Hugging Face Hub

Un service d’inférence du modèle Hugging Face peut échouer avec le message d’erreur suivant :

ImportError: huggingface-hub>=0.30.0,<1.0 is required for a normal functioning of this module, but found huggingface-hub==0.25.2

Cela est dû au fait que le paquet transformers ne spécifie pas les dépendances correctes dans huggingface-hub, mais qu’il les vérifie dans le code. Pour résoudre ce problème, connectez à nouveau le modèle, en spécifiant cette fois explicitement la version requise de huggingface-hub dans conda_dependencies ou pip_requirements.

Torch n’est pas compilé avec CUDA activé

La cause typique de cette erreur est que vous avez spécifié à la fois conda_dependencies et pip_requirements. Comme indiqué dans la section Conflits de paquets, conda est le gestionnaire de paquets utilisé pour construire l’image du conteneur. Anaconda ne résout pas les paquets de conda_dependencies et pip_requirements ensemble et donne la priorité aux paquets conda. Cela peut conduire à une situation où les paquets conda ne sont pas compatibles avec les paquets pip. Vous avez peut-être spécifié torch dans pip_requirements, et non dans conda_dependencies. Envisagez de consolider les dépendances dans conda_dependencies ou pip_requirements. Si cela n’est pas possible, préférez spécifier les paquets les plus importants dans conda_dependencies.