Déployer des modèles pour l’inférence en temps réel (REST API)

Note

Généralement disponible depuis la version 1.25.0 de snowflake-ml-python.

Utilisez l’inférence en temps réel pour les workflows interactifs qui nécessitent une faible latence. Vous pouvez déployer n’importe quel modèle à partir du registre des modèles de Snowflake en tant que service géré avec un point de terminaison HTTP dédié. Les services gérés disposent d’une mise à l’échelle automatique et sont entièrement intégrés à l’écosystème Snowflake, offrant une observabilité complète.

Utilisez l’inférence en ligne pour votre flux de travail lorsque :

  • Votre application nécessite une faible latence pour des réponses immédiates

  • Votre modèle sert de backend pour une application Web ou mobile orientée utilisateur.

  • L’entrée de votre modèle peut s’insérer dans la charge utile HTTP de la requête.

  • Le service doit automatiquement être mis à l’échelle horizontalement pour gérer des volumes de requêtes fluctuants.

Fonctionnement

Snowflake simplifie le pipeline de déploiement en hébergeant votre modèle sous forme de serveur HTTP dans Snowpark Container Services (SPCS). Cette architecture vous permet de :

  • Complexité abstraite : déployez des modèles sophistiqués sans gérer les images Docker ou les clusters Kubernetes.

  • Performance à l’échelle : exécuter des modèles à grande échelle sur des clusters GPU distribués pour les exigences de hautes performances.

  • ** Assurez la fiabilité : ** utilisez l’observabilité intégrée, la répartition du trafic et les déploiements shadow (fantôme)/canary (canari) pour des mises à niveau aisées des modèles.

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, Azure ou Google Cloud. Les régions gouvernementales ne sont pas prises en charge.

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

  • Modèle connecté au Registre des modèles de Snowflake.

  • Compréhension des pools de calcul et des privilèges associés sur SPCS.

Privilèges requis

Le service de modèle s’exécute au-dessus de Snowpark Container Services. Vous avez besoin des privilèges suivants pour utiliser le service de modèle :

  • USAGE ou OWNERSHIP sur le pool de calcul où le service s’exécute. Vous pouvez également utiliser les pools de calcul système par défaut.

  • Privilège BIND SERVICE ENDPOINT sur le compte pour pouvoir créer un point de terminaison public.

  • Privilège OWNER ou READ sur le modèle

Limitations

Les limites suivantes s’appliquent au service de modèle en ligne dans Snowpark Container Services.

  • Les fonctions de table ne sont pas prises en charge. Votre modèle doit avoir une fonction de table pour être déployée dans Snowflake.

  • Les modèles développés à l’aide des classes de modélisation ML Snowpark ne peuvent pas être déployés dans des environnements dotés d’un GPU. Pour contourner le problème, vous pouvez extraire le modèle natif et le déployer. Pour plus d’informations, voir Déployer un modèle pour l’inférence en ligne.

Déployer un modèle pour l’inférence en ligne

Snowflake ML utilise un objet de version de modèle pour créer un service de modèle qui gère les requêtes d’inférence. Pour créer un objet de version de modèle, vous pouvez soit connecter une nouvelle version de modèle, soit obtenir une référence à une version de modèle existante. Après avoir obtenu votre objet de version de modèle, vous pouvez utiliser le code Python suivant pour créer un service de modèle et déployer ce service dans SPCS :

# reg is a snowflake.ml.registry.Registry object
example_mv_object = reg.get_model("mymodel_name").version("version_name") # a snowflake.ml.model.ModelVersion object

example_mv_object.create_service(service_name="myservice",
                  service_compute_pool="my_compute_pool",
                  ingress_enabled=True,
                  gpu_requests=None)
Copy

create_service requiert les arguments suivants :

  • service_name : le nom du service que vous créez. Ce nom doit être unique au sein de votre compte Snowflake.

  • service_compute_pool : le nom du pool de calcul que vous utilisez pour exécuter le modèle. Le pool de calcul doit déjà exister. Si le modèle s’intègre bien dans les pools de calcul du système, vous pouvez les utiliser (SYSTEM_COMPUTE_POOL_GPU ou SYSTEM_COMPUTE_POOL_CPU) aussi.

  • ingress_enabled : la valeur doit être True pour appeler une inférence en ligne depuis l’extérieur de Snowflake.

  • gpu_requests : Une chaîne spécifiant le nombre de GPUs. Pour un modèle qui peut être exécuté sur un CPU ou plusieurs GPUs, 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 fonctionner que sur un CPU (comme les modèles scikit-learn), la création de l’image échoue si vous demandez des GPUs. Si vous déployez un nouveau modèle, la création du service peut prendre jusqu’à 10 minutes pour les modèles alimentés par CPU et 20 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.

L’exemple précédent ne montre que les arguments obligatoires et les plus couramment utilisés. Pour une liste complète des arguments, voir la référence API ModelVersion.

Configuration de service par défaut

Le serveur exécutant le modèle que vous avez déployé utilise des valeurs par défaut qui fonctionnent pour la plupart des cas d’utilisation :

  • Nombre de threads de travail : pour un modèle alimenté par CPU, le nombre de processus utilisés par le serveur est deux fois le nombre de CPUs plus un. Les modèles alimentés par GPU utilisent un seul processus de travail. Vous pouvez remplacer cela en utilisant l’argument num_workers dans l’appel create_service. Il est recommandé de spécifier le plus petit nœud GPU où le modèle s’insère dans la mémoire. Dimensionnez en augmentant le nombre d’instances. Par exemple, si le modèle s’inscrit dans le type d’instance GPU_NV_S (GPU_NV_SM sur Azure), utilisez gpu_requests=1 et mettez à l’échelle en augmentant max_instances. Cependant, si le plus petit nœud disponible a 4  GPUs et que vous n’avez besoin que de 2, utilisez num_workers=2 (c’est-à-dire gpu disponible / gpu nécessaires au modèle).

  • 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é inférence et utilise le port 5000. Ceux-ci ne peuvent pas être personnalisés. Pour une utilisation optimale des ressources, indiquez le plus petit nœud GPU qui peut ajuster le modèle dans la mémoire. Augmentez le nombre d’instances pour l’adapter à votre charge de travail. Par exemple, si le modèle s’inscrit dans le type d’instance GPU_NV_S (GPU_NV_SM sur Azure), utilisez gpu_requests=1 et mettez à l’échelle en augmentant max_instances.

Comportement de création d’image de conteneur

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.

Par défaut, le service de modèle Snowflake crée l’image du conteneur à l’aide du même pool de calcul qui est utilisé pour exécuter le modèle. Le pool de calcul est probablement surpuissant pour le processus de création d’images (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. Cependant, si vous êtes préoccupé, vous pouvez spécifier un pool de calcul moins puissant pour créer des images avec l’argument image_build_compute_pool.

Appeler plusieurs fois create_service() ne déclenche pas une construction à chaque fois que vous l’appelez.

Cependant, les images des conteneurs peuvent être reconstruites si Snowflake a mis à jour le 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.

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.

Invocation du modèle déployé

Points de terminaison HTTP

Chaque service dispose d’un nom DNS interne. Le déploiement d’un service avec ingress_enabled crée également un point de terminaison HTTP public disponible en dehors de Snowflake. L’un ou l’autre des points de terminaison peut être utilisé pour appeler un service.

Vous pouvez trouver le point de terminaison HTTP public d’un service avec l’entrée activée à l’aide de la commande SHOW ENDPOINTS. La sortie contient une colonne ingress_url, qui contient une entrée au format unique-service-id-account-id.snowflakecomputing.app. Il s’agit du point de terminaison HTTP disponible publiquement pour votre service. Pour les utilisateurs de lien privé, utilisez privatelink_ingress_url au lieu de ingress_url.

Pour obtenir le nom DNS interne sur Snowflake, utilisez la commande DESCRIBE SERVICE. La colonne dns_name de sortie de cette commande contient le nom DNS interne d’un service. Pour trouver le port de votre service, utilisez la commande SHOW ENDPOINTS IN SERVICE. La colonne port ou port_range contient le port utilisé par un service. Vous pouvez effectuer des appels internes à votre service par l’intermédiaire de l’URL https://dns_name:port.

Pour appeler des méthodes particulières du modèle, utilisez le nom de la méthode comme chemin vers l’URL (par ex. https://unique-service-id-account-id.snowflakecomputing.app/method-name ou http://dns_name:port/<method-name>). Dans une URL, les traits de soulignement (_) dans le nom de la méthode sont remplacés par des tirets (-) dans l’URL. Par exemple, le nom du service prediction_prob est changé en prediction-proba dans l’URL.

Pour simplifier les choses, en Python, l’API list_services() peut être appelée sur l’objet ModelVersion :

# mv: snowflake.ml.model.ModelVersion
mv.list_services()
Copy

Elle génère à la fois le point de terminaison public (inference_endpoint) et le point de terminaison interne (internal_endpoint).

Authentification

Snowflake prend en charge plusieurs protocoles d’authentification. Le plus simple de tous est d’utiliser Jeton d’accès programmatiques (PAT) où le jeton peut être transmis simplement à l’en-tête de la requête comme Authorization: Snowflake Token="your_pat_token"

Note

Tous les échecs d’autorisation tels qu’un jeton incorrect ou l’absence de route réseau vers le service entraînent une erreur 404. À ce jour, il n’existe aucun moyen de distinguer les erreurs d’authentification des URLs invalides.

Autorisation

Par défaut, seuls les propriétaires du service peuvent utiliser le point de terminaison. Pour permettre à un autre rôle d’accéder au point de terminaison, les propriétaires du service peuvent accorder le rôle de service ALL_ENDPOINTS_USAGE.

Corps de requête (ou protocole ou format de données)

Snowflake prend en charge deux types de formats de données pour les requêtes REST. Ils sont basées sur le dataframe Pandas, notamment parce qu’ils sont bien connues dans l’industrie et vérifiables par les clients utilisant des scripts Python simples avec un dataframe Pandas.

Astuce

Mappage méthode-à-URL : lors de la construction de votre URL de requête, notez que les traits de soulignement (_) dans les noms de méthodes de votre modèle sont automatiquement remplacés par des tirets (-). Par exemple, si votre méthode de modèle est predict_proba, l’URL du point de terminaison devient /predict-proba.

Voici les détails relatifs aux formats

  1. dataframe_split est une représentation compacte, index/colonnes/données.

  • Une représentation qui reflète pandas_df.to_json(orient="split").

  1. dataframe_records est une représentation clé/valeur (orientée enregistrement).

  • Une représentation qui reflète df.to_json(orient="records").

Il est recommandé d’utiliser le format dataframe_split. Étant donné que dataframe_records répète les noms de colonnes pour chaque ligne, cela produit généralement un corps de requête plus grand que dataframe_split. Cela peut avoir un impact sur les performances pour les lots importants ou les appels fréquents.

Les points de terminaison des modèles continuent de renvoyer un format de sortie unique, quel que soit le format d’entrée que vous utilisez.

  1. Format :code:`dataframe_split` (recommandé)

Cela correspond à la structure produite par l’orientation « split » de Pandas. Le corps de la requête englobe la structure suivante sous une clé dataframe_split :

  • index : Une liste d’indices de ligne.

  • columns : Une liste de noms de colonnes.

  • data : Une liste de lignes, où chaque ligne est une liste de valeurs alignées sur les colonnes.

Exemple de requête cURL

curl -X POST "<endpoint_url>" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -w "\n\n=== RESULT ===\nHTTP Status: %{http_code}\nTotal Time: %{time_total}s\nConnect Time: %{time_connect}s\nServer Processing: %{time_starttransfer}s\nResponse Size: %{size_download} bytes\nRequest Size: %{size_upload} bytes\n" \
 -d '{
       "dataframe_split": {
         "index": [0, 1],
         "columns": ["customer_id", "age", "monthly_spend"],
         "data": [
            [101, 32, 85.5],
            [102, 45, 120.0],
         ]
       }
     }'
Copy
  1. Format dataframe_records

dataframe_records correspond à la structure produite par l’orientation des enregistrements Pandas :

  • Une liste d’enregistrements, où chaque enregistrement est un dictionnaire mappant noms de colonnes à valeurs.

Le corps de la requête englobe cette liste sous la clé:code:dataframe_records :

Exemple de requête cURL

curl -X POST "<endpoint_url>" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -w "\n\n=== RESULT ===\nHTTP Status: %{http_code}\nTotal Time: %{time_total}s\nConnect Time: %{time_connect}s\nServer Processing: %{time_starttransfer}s\nResponse Size: %{size_download} bytes\nRequest Size: %{size_upload} bytes\n" \
 -d '{
       "dataframe_records": [
          {
            "customer_id": 101,
            "age": 32,
            "monthly_spend": 85.5,
          },
          {
            "customer_id": 102,
            "age": 45,
            "monthly_spend": 120.0,
          },
        ]
     }'
Copy

Transmission de paramètres

Si la signature du modèle comprend des paramètres définis avec des ParamSpec, vous pouvez transmettre les valeurs des paramètres en incluant une clé params de premier niveau dans le corps de requête JSON à côté de dataframe_split ou dataframe_records. N’incluez que les paramètres que vous souhaitez redéfinir ; les paramètres non spécifiés utilisent leurs valeurs par défaut issues de la signature.

Exemple de requête cURL avec paramètres :

curl -X POST "<endpoint_url>/predict" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -d '{
        "dataframe_split": {
            "index": [0],
            "columns": ["input_text"],
            "data": [["Hello, world!"]]
        },
        "params": {"temperature": 0.9, "max_tokens": 512}
      }'
Copy

La clé params fonctionne de la même manière avec le format dataframe_records :

curl -X POST "<endpoint_url>/predict" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -d '{
        "dataframe_records": [
            {"input_text": "Hello, world!"}
        ],
        "params": {"temperature": 0.9, "max_tokens": 512}
      }'
Copy

Exemples Python

  1. Format dataframe_split

Snowflake recommande de générer la charge utile en utilisant la sérialisation JSON Pandas, puis de désérialiser avec json.loads avant d’envoyer la requête. Cela garantit que les types de données sont traités de manière cohérente.

import json
import pandas as pd
import requests

# Example DataFrame
df = pd.DataFrame(
    {
        "customer_id": [101, 102],
        "age": [32, 45],
        "monthly_spend": [85.5, 120.0],
    }
)

ENDPOINT_URL = "<your endpoint URL>"
HEADERS = {
    "Authorization": f'Snowflake Token="{PAT}"',
    "Content-Type": "application/json"
}

# Use Pandas to generate the JSON, then load it back to a Python dict
split_obj = json.loads(df.to_json(orient="split"))

payload = {
    "dataframe_split": split_obj
}

response = requests.post(
    ENDPOINT_URL,
    headers=HEADERS,
    json=payload,
    timeout=30,
)

result = response.json()
Copy

Points clés :

  • Utilisez pd.Dataframe.to_json (par ex. df.to_json(orient="split") ) pour gérer correctement les types tels que les horodatages, les virgules flottantes, les valeurs nulles, les catégories, etc., avec lesquelles le sérialiseur json natif n’est pas familier.

  • json.loads(...) convertit la chaîne JSON vers un dictionnaire Python afin que nous puissions construire correctement la charge utile.

  • requests.post(..., json=payload) resérialise le dictionnaire vers JSON pour la requête HTTP.

Pour inclure des paramètres, ajoutez une clé params au dictionnaire de la charge utile :

payload = {
    "dataframe_split": split_obj,
    "params": {"temperature": 0.9, "max_tokens": 512}
}
Copy
  1. Format dataframe_records

Comme avec dataframe_split, utilisez la sérialisation JSON Pandas et json.loads :

import json
import pandas as pd
import requests

df = pd.DataFrame(
    {
        "customer_id": [101, 102],
        "age": [32, 45],
        "monthly_spend": [85.5, 120.0],
    }
)

ENDPOINT_URL = "<your endpoint invoke URL>"
HEADERS = {
    "Authorization": "Bearer <your token>",
    "Content-Type": "application/json",
}

records_obj = json.loads(df.to_json(orient="records"))

payload = {
    "dataframe_records": records_obj
}

response = requests.post(
    ENDPOINT_URL,
    headers=HEADERS,
    json=payload,
    timeout=30,
)

response.raise_for_status()
result = response.json()
Copy

Prochaines étapes

Explorez ces guides détaillés pour optimiser et gérer vos services d’inférence :

  • Exemples de flux de travail : Voir le code de bout en bout pour les modèles XGBoost (CPU), Hugging Face (GPU) et PyTorch.

  • Gestion des services et mise à l’échelle : découvrez la mise à l’échelle automatique, la suspension manuelle et la configuration matérielle.

  • Points de terminaison stables et référence API : analyse approfondie de la passerelle Snowflake, de l’authentification et des protocoles de données (dataframe_split).

  • Journaux d’inférence de capture automatique : configurez la journalisation automatique pour la surveillance des modèles.

  • Dépannage : corrections courantes des conflits de paquets, erreurs OOM et échecs de construction.