1. Introduction
Présentation
Cloud Run a récemment ajouté la compatibilité avec les GPU. Elle est disponible en version Preview publique sur liste d'attente. Si vous souhaitez tester cette fonctionnalité, remplissez ce formulaire pour vous inscrire sur la liste d'attente. Cloud Run est une plate-forme de conteneurs sur Google Cloud qui vous permet d'exécuter facilement votre code dans un conteneur, sans avoir à gérer de cluster.
Aujourd'hui, les GPU que nous mettons à disposition sont des GPU Nvidia L4 avec 24 Go de VRAM. Un GPU est associé à chaque instance Cloud Run, et l'autoscaling Cloud Run s'applique toujours. Cela inclut le scaling horizontal jusqu'à cinq instances (avec augmentation de quota disponible), ainsi que le scaling à la baisse jusqu'à zéro instance en l'absence de requêtes.
Dans cet atelier de programmation, vous allez créer et déployer une application TorchServe qui utilise stable diffusion XL pour générer des images à partir d'une requête textuelle. L'image générée est renvoyée à l'appelant sous forme de chaîne encodée en base64.
Cet exemple est basé sur Exécuter le modèle Stable Diffusion à l'aide de diffuseurs Huggingface dans Torchserve. Cet atelier de programmation vous explique comment modifier cet exemple pour qu'il fonctionne avec Cloud Run.
Points abordés
- Exécuter un modèle Stable Diffusion XL sur Cloud Run à l'aide de GPU
2. Activer les API et définir des variables d'environnement
Avant de pouvoir utiliser cet atelier de programmation, vous devez activer plusieurs API. Cet atelier de programmation nécessite d'utiliser les API suivantes. Vous pouvez activer ces API en exécutant la commande suivante:
gcloud services enable run.googleapis.com \ storage.googleapis.com \ cloudbuild.googleapis.com \
Vous pouvez ensuite définir les variables d'environnement qui seront utilisées tout au long de cet atelier de programmation.
PROJECT_ID=<YOUR_PROJECT_ID> REPOSITORY=repo NETWORK_NAME=default REGION=us-central1 IMAGE=us-central1-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/gpu-torchserve
3. Créer l'application Torchserve
Commencez par créer un répertoire pour le code source et accédez-y avec la commande cd.
mkdir stable-diffusion-codelab && cd $_
Créez un fichier config.properties
. Il s'agit du fichier de configuration de TorchServe.
inference_address=http://0.0.0.0:8080 enable_envvars_config=true min_workers=1 max_workers=1 default_workers_per_model=1 default_response_timeout=1000 load_models=all max_response_size=655350000 # to enable authorization, see https://github.com/pytorch/serve/blob/master/docs/token_authorization_api.md#how-to-set-and-disable-token-authorization disable_token_authorization=true
Notez que dans cet exemple, l'adresse d'écoute http://0.0.0.0 est utilisée pour fonctionner sur Cloud Run. Le port par défaut de Cloud Run est le port 8080.
Créez un fichier requirements.txt
.
python-dotenv accelerate transformers diffusers numpy google-cloud-storage nvgpu
Créez un fichier appelé stable_diffusion_handler.py
.
from abc import ABC import base64 import datetime import io import logging import os from diffusers import StableDiffusionXLImg2ImgPipeline from diffusers import StableDiffusionXLPipeline from google.cloud import storage import numpy as np from PIL import Image import torch from ts.torch_handler.base_handler import BaseHandler logger = logging.getLogger(__name__) def image_to_base64(image: Image.Image) -> str: """Convert a PIL image to a base64 string.""" buffer = io.BytesIO() image.save(buffer, format="JPEG") image_str = base64.b64encode(buffer.getvalue()).decode("utf-8") return image_str class DiffusersHandler(BaseHandler, ABC): """Diffusers handler class for text to image generation.""" def __init__(self): self.initialized = False def initialize(self, ctx): """In this initialize function, the Stable Diffusion model is loaded and initialized here. Args: ctx (context): It is a JSON Object containing information pertaining to the model artifacts parameters. """ logger.info("Initialize DiffusersHandler") self.manifest = ctx.manifest properties = ctx.system_properties model_dir = properties.get("model_dir") model_name = os.environ["MODEL_NAME"] model_refiner = os.environ["MODEL_REFINER"] self.bucket = None logger.info( "GPU device count: %s", torch.cuda.device_count(), ) logger.info( "select the GPU device, cuda is available: %s", torch.cuda.is_available(), ) self.device = torch.device( "cuda:" + str(properties.get("gpu_id")) if torch.cuda.is_available() and properties.get("gpu_id") is not None else "cpu" ) logger.info("Device used: %s", self.device) # open the pipeline to the inferenece model # this is generating the image logger.info("Donwloading model %s", model_name) self.pipeline = StableDiffusionXLPipeline.from_pretrained( model_name, variant="fp16", torch_dtype=torch.float16, use_safetensors=True, ).to(self.device) logger.info("done donwloading model %s", model_name) # open the pipeline to the refiner # refiner is used to remove artifacts from the image logger.info("Donwloading refiner %s", model_refiner) self.refiner = StableDiffusionXLImg2ImgPipeline.from_pretrained( model_refiner, variant="fp16", torch_dtype=torch.float16, use_safetensors=True, ).to(self.device) logger.info("done donwloading refiner %s", model_refiner) self.n_steps = 40 self.high_noise_frac = 0.8 self.initialized = True # Commonly used basic negative prompts. logger.info("using negative_prompt") self.negative_prompt = ("worst quality, normal quality, low quality, low res, blurry") # this handles the user request def preprocess(self, requests): """Basic text preprocessing, of the user's prompt. Args: requests (str): The Input data in the form of text is passed on to the preprocess function. Returns: list : The preprocess function returns a list of prompts. """ logger.info("Process request started") inputs = [] for _, data in enumerate(requests): input_text = data.get("data") if input_text is None: input_text = data.get("body") if isinstance(input_text, (bytes, bytearray)): input_text = input_text.decode("utf-8") logger.info("Received text: '%s'", input_text) inputs.append(input_text) return inputs def inference(self, inputs): """Generates the image relevant to the received text. Args: input_batch (list): List of Text from the pre-process function is passed here Returns: list : It returns a list of the generate images for the input text """ logger.info("Inference request started") # Handling inference for sequence_classification. image = self.pipeline( prompt=inputs, negative_prompt=self.negative_prompt, num_inference_steps=self.n_steps, denoising_end=self.high_noise_frac, output_type="latent", ).images logger.info("Done model") image = self.refiner( prompt=inputs, negative_prompt=self.negative_prompt, num_inference_steps=self.n_steps, denoising_start=self.high_noise_frac, image=image, ).images logger.info("Done refiner") return image def postprocess(self, inference_output): """Post Process Function converts the generated image into Torchserve readable format. Args: inference_output (list): It contains the generated image of the input text. Returns: (list): Returns a list of the images. """ logger.info("Post process request started") images = [] response_size = 0 for image in inference_output: # Save image to GCS if self.bucket: image.save("temp.jpg") # Create a blob object blob = self.bucket.blob( datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".jpg" ) # Upload the file blob.upload_from_filename("temp.jpg") # to see the image, encode to base64 encoded = image_to_base64(image) response_size += len(encoded) images.append(encoded) logger.info("Images %d, response size: %d", len(images), response_size) return images
Créez un fichier nommé start.sh
. Ce fichier est utilisé comme point d'entrée dans le conteneur pour démarrer TorchServe.
#!/bin/bash echo "starting the server" # start the server. By default torchserve runs in backaround, and start.sh will immediately terminate when done # so use --foreground to keep torchserve running in foreground while start.sh is running in a container torchserve --start --ts-config config.properties --models "stable_diffusion=${MAR_FILE_NAME}.mar" --model-store ${MAR_STORE_PATH} --foreground
Exécutez ensuite la commande suivante pour le rendre exécutable.
chmod 755 start.sh
Créez un objet dockerfile
.
# pick a version of torchserve to avoid any future breaking changes # docker pull pytorch/torchserve:0.11.1-cpp-dev-gpu FROM pytorch/torchserve:0.11.1-cpp-dev-gpu AS base USER root WORKDIR /home/model-server COPY requirements.txt ./ RUN pip install --upgrade -r ./requirements.txt # Stage 1 build the serving container. FROM base AS serve-gcs ENV MODEL_NAME='stabilityai/stable-diffusion-xl-base-1.0' ENV MODEL_REFINER='stabilityai/stable-diffusion-xl-refiner-1.0' ENV MAR_STORE_PATH='/home/model-server/model-store' ENV MAR_FILE_NAME='model' RUN mkdir -p $MAR_STORE_PATH COPY config.properties ./ COPY stable_diffusion_handler.py ./ COPY start.sh ./ # creates the mar file used by torchserve RUN torch-model-archiver --force --model-name ${MAR_FILE_NAME} --version 1.0 --handler stable_diffusion_handler.py -r requirements.txt --export-path ${MAR_STORE_PATH} # entrypoint CMD ["./start.sh"]
4. Configurer Cloud NAT
Cloud NAT vous permet d'avoir une bande passante plus élevée pour accéder à Internet et télécharger le modèle depuis HuggingFace, ce qui accélère considérablement les délais de déploiement.
Pour utiliser Cloud NAT, exécutez la commande suivante afin d'activer une instance Cloud NAT:
gcloud compute routers create nat-router --network $NETWORK_NAME --region us-central1 gcloud compute routers nats create vm-nat --router=nat-router --region=us-central1 --auto-allocate-nat-external-ips --nat-all-subnet-ip-ranges
5. Créer et déployer le service Cloud Run
Envoyez votre code à Cloud Build.
gcloud builds submit --tag $IMAGE
Déployer sur Cloud Run
gcloud beta run deploy gpu-torchserve \ --image=$IMAGE \ --cpu=8 --memory=32Gi \ --gpu=1 --no-cpu-throttling --gpu-type=nvidia-l4 \ --allow-unauthenticated \ --region us-central1 \ --project $PROJECT_ID \ --execution-environment=gen2 \ --max-instances 1 \ --network $NETWORK_NAME \ --vpc-egress all-traffic
6. Tester le service
Vous pouvez tester le service en exécutant les commandes suivantes:
PROMPT_TEXT="a cat sitting in a magnolia tree" SERVICE_URL=$(gcloud run services describe gpu-torchserve --region $REGION --format 'value(status.url)') time curl $SERVICE_URL/predictions/stable_diffusion -d "data=$PROMPT_TEXT" | base64 --decode > image.jpg
Le fichier image.jpg
s'affiche dans votre répertoire actuel. Vous pouvez ouvrir l'image dans l'éditeur Cloud Shell pour voir un chat assis dans un arbre.
7. Félicitations !
Félicitations ! Vous avez terminé cet atelier de programmation.
Nous vous recommandons de consulter la documentation sur les GPU Cloud Run.
Points abordés
- Exécuter un modèle Stable Diffusion XL sur Cloud Run à l'aide de GPU
8. Effectuer un nettoyage
Pour éviter les frais involontaires (par exemple, si cette tâche Cloud Run est appelée par inadvertance plus de fois que votre quota mensuel d'appels Cloud Run dans le niveau sans frais), vous pouvez supprimer la tâche Cloud Run ou le projet que vous avez créé à l'étape 2.
Pour supprimer le job Cloud Run, accédez à la console Cloud Run à l'adresse https://console.cloud.google.com/run/, puis supprimez le service gpu-torchserve
.
Vous devez également supprimer votre configuration Cloud NAT.
Si vous choisissez de supprimer l'ensemble du projet, accédez à https://console.cloud.google.com/cloud-resource-manager, sélectionnez le projet que vous avez créé à l'étape 2, puis choisissez "Supprimer". Si vous supprimez le projet, vous devrez modifier les projets dans votre SDK Cloud. Vous pouvez afficher la liste de tous les projets disponibles en exécutant gcloud projects list
.