From 106ff57c21e13438e083ef1d9994181bf7715e89 Mon Sep 17 00:00:00 2001 From: Ramu Vadthyavath Date: Wed, 29 Nov 2023 00:03:45 +0530 Subject: [PATCH] stable diffusion image to image cli and sdk jobs (#2857) * cli jobs update * cli job updates * cli and sdk i2i jobs * minor updates * minor update * task name update * minor updates * metadata update --- .../inference/text-to-image/batch-deploy.yml | 2 +- .../image-text-to-image-batch-endpoint.sh | 113 +++ .../image-text-to-image-online-endpoint.sh | 83 ++ .../utils/prepare_data_image_text_to_image.py | 151 ++++ .../docker_env/conda_dependencies.yaml | 2 +- .../image-text-to-image-batch-endpoint.ipynb | 695 ++++++++++++++++ .../image-text-to-image-online-endpoint.ipynb | 319 ++++++++ ...image-text-to-image-batch-deployment.ipynb | 767 ++++++++++++++++++ ...-image-text-to-image-online-endpoint.ipynb | 750 +++++++++++++++++ ...-to-image-inpainting-online-endpoint.ipynb | 1 + 10 files changed, 2881 insertions(+), 2 deletions(-) create mode 100644 cli/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.sh create mode 100644 cli/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.sh create mode 100644 cli/foundation-models/system/inference/text-to-image/utils/prepare_data_image_text_to_image.py create mode 100644 sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.ipynb create mode 100644 sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.ipynb create mode 100644 sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-batch-deployment.ipynb create mode 100644 sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-online-endpoint.ipynb diff --git a/cli/foundation-models/system/inference/text-to-image/batch-deploy.yml b/cli/foundation-models/system/inference/text-to-image/batch-deploy.yml index 5d1ee71db0b..d223f4d92d4 100644 --- a/cli/foundation-models/system/inference/text-to-image/batch-deploy.yml +++ b/cli/foundation-models/system/inference/text-to-image/batch-deploy.yml @@ -1,5 +1,5 @@ $schema: https://azuremlschemas.azureedge.net/latest/batchDeployment.schema.json -description: "Batch endpoint for for text-to-image task" +description: "Batch endpoint for for (image-)text-to-image task" type: model resources: instance_count: 1 diff --git a/cli/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.sh b/cli/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.sh new file mode 100644 index 00000000000..96159ed002b --- /dev/null +++ b/cli/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.sh @@ -0,0 +1,113 @@ +set -x +# the commands in this file map to steps in this notebook: https://aka.ms/azureml-infer-batch-sdk-text-classification + +# script inputs +registry_name="azureml" + +subscription_id="" +resource_group_name="" +workspace_name="" + +# This is the model from system registry that needs to be deployed +model_name="stabilityai-stable-diffusion-xl-refiner-1-0" +model_label="latest" + +version=$(date +%s) +endpoint_name="image-text-to-image-$version" +deployment_name="image-text-to-image-batch-deploy" + +deployment_compute="gpu-cluster" +compute_sku="Standard_NC6s_v3" + +# 1. Setup pre-requisites +if [ "$subscription_id" = "" ] || \ + ["$resource_group_name" = "" ] || \ + [ "$workspace_name" = "" ]; then + echo "Please update the script with the subscription_id, resource_group_name and workspace_name" + exit 1 +fi + +az account set -s $subscription_id +workspace_info="--resource-group $resource_group_name --workspace-name $workspace_name" + +# 2. Check if the model exists in the registry +# Need to confirm model show command works for registries outside the tenant (aka system registry) +if ! az ml model show --name $model_name --label $model_label --registry-name $registry_name +then + echo "Model $model_name:$model_label does not exist in registry $registry_name" + exit 1 +fi + +model_version=$(az ml model show --name $model_name --label $model_label --registry-name $registry_name --query version --output tsv) + +# 3. Check if compute $deployment_compute exists, else create it +if az ml compute show --name $deployment_compute $workspace_info +then + echo "Compute cluster $deployment_compute already exists" +else + echo "Creating compute cluster $deployment_compute" + az ml compute create --name $deployment_compute --type amlcompute --min-instances 0 --max-instances 2 --size $compute_sku $workspace_info || { + echo "Failed to create compute cluster $deployment_compute" + exit 1 + } +fi + +# 4. Deploy the model to an endpoint +# create batch endpoint +az ml batch-endpoint create --name $endpoint_name $workspace_info || { + echo "endpoint create failed"; exit 1; +} + +# deploy model from registry to endpoint in workspace +az ml batch-deployment create --file batch-deploy.yml $workspace_info --set \ + endpoint_name=$endpoint_name \ + name=$deployment_name \ + compute=$deployment_compute \ + model=azureml://registries/$registry_name/models/$model_name/versions/$model_version || { + echo "deployment create failed"; exit 1; +} + +# 4. Submit a sample request to endpoint +data_path="./inpainting_data/batch_data" +python utils/prepare_data_image_text_to_image.py --payload-path $data_path --mode "batch" +# Path where the processes csvs are dumped. This is the input to the endpoint +processed_data_path="./inpainting_data/batch_data/processed_batch_data" + +# Check if scoring folder exists +if [ -d $processed_data_path ]; then + echo "Invoking endpoint $endpoint_name with following input:\n\n" + ls $processed_data_path + echo "\n\n" +else + echo "Scoring folder $processed_data_path does not exist" + exit 1 +fi + +# 5. Invoke a job on the batch endpoint +job_name=$(az ml batch-endpoint invoke --name $endpoint_name \ + --deployment-name $deployment_name \ + --input $processed_data_path \ + --input-type uri_folder --query name --output tsv $workspace_info) || { + echo "endpoint invoke failed"; exit 1; +} + +# 6. Stream the job logs +az ml job stream --name $job_name $workspace_info || { + echo "job stream-logs failed. If the job failed with Assertion Error stating actual size of csv exceeds 100 MB, then try splitting input csv file into multiple csv files."; exit 1; +} + +# 7. Download the job output +az ml job download --name $job_name --download-path "generated_images" $workspace_info || { + echo "job output download failed"; exit 1; +} + +# 8. Delete the endpoint +az ml batch-endpoint delete --name $endpoint_name $workspace_info --yes || { + echo "endpoint delete failed"; exit 1; +} + +# 9. Delete the compute cluster (Uncomment the below lines to delete the created cluster) +# az ml compute delete --name $deployment_compute $workspace_info --yes || { +# echo "compute delete failed"; exit 1; +# } + diff --git a/cli/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.sh b/cli/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.sh new file mode 100644 index 00000000000..b277450dc05 --- /dev/null +++ b/cli/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.sh @@ -0,0 +1,83 @@ +set -x +# the commands in this file map to steps in this notebook: https://aka.ms/azureml-infer-batch-sdk-text-classification + +# script inputs +registry_name="azureml" + +subscription_id="" +resource_group_name="" +workspace_name="" + +# This is the model from system registry that needs to be deployed +model_name="stabilityai-stable-diffusion-xl-refiner-1-0" +model_label="latest" +response_file="generated_image.json" + +version=$(date +%s) +endpoint_name="image-text-to-image-$version" +deployment_name="image-text-to-image-deploy" + +deployment_sku="Standard_NC6s_v3" + +# sample_request_data +sample_request_data="inpainting_data/sample_request_data.json" + +# 1. Setup pre-requisites +if [ "$subscription_id" = "" ] || \ + ["$resource_group_name" = "" ] || \ + [ "$workspace_name" = "" ]; then + echo "Please update the script with the subscription_id, resource_group_name and workspace_name" + exit 1 +fi + +az account set -s $subscription_id +workspace_info="--resource-group $resource_group_name --workspace-name $workspace_name" + +# 2. Check if the model exists in the registry + +if ! az ml model show --name $model_name --label $model_label --registry-name $registry_name +then + echo "Model $model_name:$model_label does not exist in registry $registry_name" + exit 1 +fi + +# Get the latest model version +model_version=$(az ml model show --name $model_name --label $model_label --registry-name $registry_name --query version --output tsv) + +# 3. Deploy the model to an endpoint +# Create online endpoint +az ml online-endpoint create --name $endpoint_name $workspace_info || { + echo "endpoint create failed"; exit 1; +} + +# Deploy model from registry to endpoint in workspace +az ml online-deployment create --file deploy-online.yaml $workspace_info --all-traffic --set \ + endpoint_name=$endpoint_name model=azureml://registries/$registry_name/models/$model_name/versions/$model_version \ + name=$deployment_name \ + instance_type=$deployment_sku || { + echo "deployment create failed"; exit 1; +} + +# 4. Submit a sample request to endpoint +python utils/prepare_data_image_text_to_image.py --payload-path $sample_request_data --mode "online" + +# Check if scoring data file exists +if [ -f $sample_request_data ]; then + echo "Invoking endpoint $endpoint_name with $sample_request_data:\n\n" + echo "\n\n" +else + echo "Request file $sample_request_data does not exist" + exit 1 +fi + +az ml online-endpoint invoke --name $endpoint_name --request-file $sample_request_data $workspace_info -o json > $response_file || { + echo "endpoint invoke failed"; exit 1; +} + +# 5. Convert bas64 form of image to jpeg +python ./utils/base64_to_jpeg.py --response_file $response_file + +# 6. Delete the endpoint and sample_request_data.json +az ml online-endpoint delete --name $endpoint_name $workspace_info --yes || { + echo "endpoint delete failed"; exit 1; +} \ No newline at end of file diff --git a/cli/foundation-models/system/inference/text-to-image/utils/prepare_data_image_text_to_image.py b/cli/foundation-models/system/inference/text-to-image/utils/prepare_data_image_text_to_image.py new file mode 100644 index 00000000000..51d068f8129 --- /dev/null +++ b/cli/foundation-models/system/inference/text-to-image/utils/prepare_data_image_text_to_image.py @@ -0,0 +1,151 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Prepare request payload for the image-text to image task + +import argparse +import glob +import json +import io +import base64 +import os +import pandas as pd +from PIL import Image +from pathlib import Path + + +def read_image(image_path: str) -> bytes: + """Reads an image from a file path into a byte array. + + :param image_path: Path to image file. + :type image_path: str + :return: Byte array of image. + :rtype: bytes + """ + with open(image_path, "rb") as f: + return f.read() + + +def prepare_batch_payload(payload_path: str) -> None: + """Prepare payload for online deployment. + + :param payload_path: Path to payload csv file. + :type payload_path: str + :return: None + """ + + base_image1 = "inpainting_data/images/dog_on_bench.png" + base_image2 = "inpainting_data/images/teapot.png" + + os.makedirs(payload_path, exist_ok=True) + + input_data = { + "columns": ["image", "prompt"], + "data": [ + { + "image": base64.encodebytes(read_image(base_image1)).decode("utf-8"), + "prompt": "A yellow cat, high resolution, sitting on a park bench", + }, + { + "image": base64.encodebytes(read_image(base_image2)).decode("utf-8"), + "prompt": "A small flower featuring a blend of pink and purple colors.", + }, + ], + } + pd.DataFrame(**input_data).to_csv( + os.path.join(payload_path, "input1.csv"), index=False + ) + + input_data = { + "columns": ["image", "prompt"], + "data": [ + { + "image": base64.encodebytes(read_image(base_image1)).decode("utf-8"), + "prompt": "Pikachu, cinematic, digital art, sitting on bench", + }, + { + "image": base64.encodebytes(read_image(base_image2)).decode("utf-8"), + "prompt": "A woman with red hair in the style of Tamara de Lempicka.", + }, + ], + } + pd.DataFrame(**input_data).to_csv( + os.path.join(payload_path, "input2.csv"), index=False + ) + + # Use glob to get a list of CSV files in the folder + csv_files = glob.glob(os.path.join(payload_path, "*.csv")) + + # Read all CSV files into a single DataFrame using pd.concat + batch_df = pd.concat((pd.read_csv(file) for file in csv_files), ignore_index=True) + + # Specify the folder where your CSV files should be saved + processed_dataset_parent_dir = os.path.join(payload_path, "processed_batch_data") + os.makedirs(processed_dataset_parent_dir, exist_ok=True) + batch_input_file = "batch_input.csv" + + # Divide this into files of rows each + batch_size_per_predict = 2 + for i in range(0, len(batch_df), batch_size_per_predict): + j = i + batch_size_per_predict + batch_df[i:j].to_csv( + os.path.join(processed_dataset_parent_dir, str(i) + batch_input_file) + ) + + # Check out the first and last file name created + input_paths = sorted( + Path(processed_dataset_parent_dir).iterdir(), key=os.path.getmtime + ) + input_files = [os.path.basename(path) for path in input_paths] + print(f"{input_files[0]} to {str(i)}{batch_input_file}.") + + +def prepare_online_payload(payload_path: str) -> None: + """Prepare payload for online deployment. + + :param payload_path: Path to payload json file. + :type payload_path: str + :return: None + """ + base_directory = os.path.dirname(os.path.dirname(__file__)) + + base_image = os.path.join( + base_directory, "inpainting_data", "images", "dog_on_bench.png" + ) + + request_json = { + "input_data": { + "columns": ["image", "prompt"], + "index": [0], + "data": [ + { + "image": base64.encodebytes(read_image(base_image)).decode("utf-8"), + "prompt": "A yellow cat, high resolution, sitting on a park bench", + } + ], + } + } + print(base_directory) + print(payload_path) + payload_path = os.path.join(base_directory, payload_path) + with open(payload_path, "w") as request_file: + json.dump(request_json, request_file) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Prepare sample data for image-text to image" + ) + parser.add_argument("--payload-path", type=str, help="payload file/ folder path") + parser.add_argument( + "--mode", + type=str, + default="online", + help="Generate payload for online or batch deployment.", + ) + args, unknown = parser.parse_known_args() + + if args.mode == "online": + prepare_online_payload(args.payload_path) + else: + prepare_batch_payload(args.payload_path) diff --git a/sdk/python/foundation-models/system/inference/text-to-image/aacs-scoring-files/docker_env/conda_dependencies.yaml b/sdk/python/foundation-models/system/inference/text-to-image/aacs-scoring-files/docker_env/conda_dependencies.yaml index 5403b2b777a..2a42940e450 100644 --- a/sdk/python/foundation-models/system/inference/text-to-image/aacs-scoring-files/docker_env/conda_dependencies.yaml +++ b/sdk/python/foundation-models/system/inference/text-to-image/aacs-scoring-files/docker_env/conda_dependencies.yaml @@ -7,7 +7,7 @@ dependencies: - mlflow==2.3.2 - torch==1.13.0 - transformers==4.29.1 - - diffusers==0.20.0 + - diffusers==0.23.0 - accelerate==0.22.0 - azureml-core==1.52.0 - azureml-mlflow==1.52.0 diff --git a/sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.ipynb b/sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.ipynb new file mode 100644 index 00000000000..81db28c339a --- /dev/null +++ b/sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-batch-endpoint.ipynb @@ -0,0 +1,695 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Image to Image Inference using Online Endpoints\n", + "\n", + "This sample shows how to deploy `image to image` type stable diffusion models to a batch endpoint for inference.\n", + "\n", + "### Task\n", + "`image to image` task takes an original image and a text prompt as input. The model generates an image by modifying the original image.\n", + "\n", + " \n", + "### Model\n", + "Models that can perform the `image to image` task are tagged with `image-to-image`. We will use the `stabilityai-stable-diffusion-xl-refiner-1-0` model in this notebook. If you opened this notebook from a specific model card, remember to replace the specific model name.\n", + "\n", + "\n", + "### Outline\n", + "1. Setup pre-requisites\n", + "2. Pick a model to deploy\n", + "3. Prepare data for inference - using a folder of csv files with prompt, image columns\n", + "4. Deploy the model to a batch endpoint\n", + "5. Test the endpoint - using csv files\n", + "6. Clean up resources - delete the endpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Setup pre-requisites\n", + "* Install dependencies\n", + "* Connect to AzureML Workspace. Learn more at [set up SDK authentication](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication?tabs=sdk). Replace ``, `` and `` below.\n", + "* Connect to `azureml` system registry" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Found the config file in: .\\config.json\n" + ] + } + ], + "source": [ + "from azure.ai.ml import MLClient, Input\n", + "from azure.ai.ml.entities import AmlCompute\n", + "from azure.ai.ml.constants import AssetTypes\n", + "from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential\n", + "import time\n", + "\n", + "try:\n", + " credential = DefaultAzureCredential()\n", + " credential.get_token(\"https://management.azure.com/.default\")\n", + "except Exception as ex:\n", + " credential = InteractiveBrowserCredential()\n", + "\n", + "try:\n", + " workspace_ml_client = MLClient.from_config(credential)\n", + " subscription_id = workspace_ml_client.subscription_id\n", + " resource_group = workspace_ml_client.resource_group_name\n", + " workspace_name = workspace_ml_client.workspace_name\n", + "except Exception as ex:\n", + " print(ex)\n", + " # Enter details of your AML workspace\n", + " subscription_id = \"\"\n", + " resource_group = \"\"\n", + " workspace_name = \"\"\n", + "\n", + "workspace_ml_client = MLClient(\n", + " credential, subscription_id, resource_group, workspace_name\n", + ")\n", + "\n", + "# The models, fine tuning pipelines and environments are available in the AzureML system registry, \"azureml\"\n", + "registry_name = \"azureml\"\n", + "registry_ml_client = MLClient(\n", + " credential,\n", + " subscription_id,\n", + " resource_group,\n", + " registry_name=registry_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a compute cluster\n", + "Use the model card from the AzureML system registry to check the minimum required inferencing SKU, referenced as size below. If you already have a sufficient compute cluster that you wish to use, you can simply define the name in `compute_name` in the following code block. Otherwise, the below snippet will create a new compute cluster." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing compute target.\n" + ] + } + ], + "source": [ + "from azure.ai.ml.entities import AmlCompute\n", + "from azure.core.exceptions import ResourceNotFoundError\n", + "\n", + "compute_name = \"gpu-cluster\"\n", + "\n", + "try:\n", + " _ = workspace_ml_client.compute.get(compute_name)\n", + " print(\"Found existing compute target.\")\n", + "except ResourceNotFoundError:\n", + " print(\"Creating a new compute target...\")\n", + " compute_config = AmlCompute(\n", + " name=compute_name,\n", + " description=\"An AML compute cluster\",\n", + " size=\"Standard_NC6s_v3\",\n", + " min_instances=0,\n", + " max_instances=3,\n", + " idle_time_before_scale_down=120,\n", + " )\n", + " workspace_ml_client.begin_create_or_update(compute_config).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Pick a model to deploy\n", + "\n", + "Browse models in the Model Catalog in the AzureML Studio, filtering by the `image-to-image` task. In this example, we use the `stabilityai-stable-diffusion-xl-refiner-1-0` model. If you have opened this notebook for a different model, replace the model name accordingly. This is a pre-trained model." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Using model name: stabilityai-stable-diffusion-xl-refiner-1-0, version: 1, id: azureml://registries/azureml/models/stabilityai-stable-diffusion-xl-refiner-1-0/versions/1 for generating images from text.\n" + ] + } + ], + "source": [ + "# Name of the image to image model to be deployed\n", + "model_name = \"stabilityai-stable-diffusion-xl-refiner-1-0\"\n", + "\n", + "try:\n", + " model = registry_ml_client.models.get(name=model_name, label=\"latest\")\n", + "except Exception as ex:\n", + " print(\n", + " f\"No model named {model_name} found in registry. \"\n", + " \"Please check model name present in Azure model catalog\"\n", + " )\n", + " raise ex\n", + "\n", + "print(\n", + " f\"\\n\\nUsing model name: {model.name}, version: {model.version}, id: {model.id} for generating images from text.\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Prepare data for inference - using a folder of csv files with prompt and image columns\n", + "\n", + "The CSV files should consist of 2 columns namely, \n", + "* `image`: Original image to be used as base image for image to image generation. It should either be in base64 format or publicly accessible URL.\n", + "* `prompt`: A text prompt.\n", + "\n", + "We provide the text prompts in a csv file starting from the first row of a column named \"prompt\". We provide the input image as base64 string starting from the first row of the column \"image\".\n", + "\n", + "The deployment in the \"Create batch deployment\" section below takes the argument `mini_batch_size`, which is the number of CSV files processed by the model in a single mini_batch. To limit the number of prompts processed in each mini_batch we split the dataset into multiple csv files." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Prepare input csv file\n", + "\n", + "import base64\n", + "import os\n", + "import pandas as pd\n", + "\n", + "\n", + "def read_image(image_path: str) -> bytes:\n", + " \"\"\"Reads an image from a file path into a byte array.\"\"\"\n", + " with open(image_path, \"rb\") as f:\n", + " return f.read()\n", + "\n", + "\n", + "base_image1 = \"inpainting_data/images/dog_on_bench.png\"\n", + "base_image2 = \"inpainting_data/images/teapot.png\"\n", + "\n", + "dataset_parent_dir = \"inpainting_data/batch_data\"\n", + "os.makedirs(dataset_parent_dir, exist_ok=True)\n", + "\n", + "input_data = {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image1)).decode(\"utf-8\"),\n", + " \"prompt\": \"A yellow cat, high resolution, sitting on a park bench\",\n", + " },\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image2)).decode(\"utf-8\"),\n", + " \"prompt\": \"A small flower featuring a blend of pink and purple colors.\",\n", + " },\n", + " ],\n", + "}\n", + "pd.DataFrame(**input_data).to_csv(\n", + " os.path.join(dataset_parent_dir, \"input1.csv\"), index=False\n", + ")\n", + "\n", + "input_data = {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image1)).decode(\"utf-8\"),\n", + " \"prompt\": \"Pikachu, cinematic, digital art, sitting on bench\",\n", + " },\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image2)).decode(\"utf-8\"),\n", + " \"prompt\": \"A woman with red hair in the style of Tamara de Lempicka.\",\n", + " },\n", + " ],\n", + "}\n", + "pd.DataFrame(**input_data).to_csv(\n", + " os.path.join(dataset_parent_dir, \"input2.csv\"), index=False\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " image \\\n", + "0 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "1 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "2 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "3 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "\n", + " prompt \n", + "0 A yellow cat, high resolution, sitting on a pa... \n", + "1 A small flower featuring a blend of pink and p... \n", + "2 Pikachu, cinematic, digital art, sitting on bench \n", + "3 A woman with red hair in the style of Tamara d... \n" + ] + } + ], + "source": [ + "# Read all the csvs in the data folder into a pandas dataframe\n", + "import glob\n", + "import os\n", + "import pandas as pd\n", + "\n", + "# Specify the folder where your CSV files are located\n", + "dataset_parent_dir = \"inpainting_data/batch_data\"\n", + "\n", + "# Use glob to get a list of CSV files in the folder\n", + "csv_files = glob.glob(os.path.join(dataset_parent_dir, \"*.csv\"))\n", + "\n", + "# Read all CSV files into a single DataFrame using pd.concat\n", + "batch_df = pd.concat((pd.read_csv(file) for file in csv_files), ignore_index=True)\n", + "\n", + "# Now, 'batch_df' contains all the data from the CSV files in the folder\n", + "print(batch_df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0batch_input.csv to 2batch_input.csv.\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "\n", + "# Specify the folder where your CSV files should be saved\n", + "processed_dataset_parent_dir = \"inpainting_data/processed_batch_data\"\n", + "os.makedirs(processed_dataset_parent_dir, exist_ok=True)\n", + "batch_input_file = \"batch_input.csv\"\n", + "\n", + "# Divide this into files of rows each\n", + "batch_size_per_predict = 2\n", + "for i in range(0, len(batch_df), batch_size_per_predict):\n", + " j = i + batch_size_per_predict\n", + " batch_df[i:j].to_csv(\n", + " os.path.join(processed_dataset_parent_dir, str(i) + batch_input_file)\n", + " )\n", + "\n", + "# Check out the first and last file name created\n", + "input_paths = sorted(Path(processed_dataset_parent_dir).iterdir(), key=os.path.getmtime)\n", + "input_files = [os.path.basename(path) for path in input_paths]\n", + "print(f\"{input_files[0]} to {str(i)}{batch_input_file}.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Register folder containing csv files in AML as data asset to use in batch job." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "input = Input(path=processed_dataset_parent_dir, type=AssetTypes.URI_FOLDER)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Deploy the model to a batch endpoint\n", + "Batch endpoints are endpoints that are used to do batch inferencing on large volumes of data over a period of time. The endpoints receive pointers to data and run jobs asynchronously to process the data in parallel on compute clusters. Batch endpoints store outputs to a data store for further analysis. For more information on batch endpoints and deployments, see What are batch endpoints? In this sub-section, we will cover the following items:\n", + "\n", + "* Create a batch endpoint.\n", + "* Create a batch deployment.\n", + "* Set the deployment as default. Doing so allows invoking the endpoint without specifying the deployment's name." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a batch endpoint" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "BatchEndpoint({'scoring_uri': 'https://image-to-image-aae1e55d.eastus.inference.ml.azure.com/jobs', 'openapi_uri': None, 'provisioning_state': 'Succeeded', 'name': 'image-to-image-aae1e55d', 'description': 'Batch endpoint for stabilityai-stable-diffusion-xl-refiner-1-0, for image to image task', 'tags': {}, 'properties': {'BatchEndpointCreationApiVersion': '2022-05-01', 'azureml.onlineendpointid': '/subscriptions/dbd697c3-ef40-488f-83e6-5ad4dfb78f9b/resourceGroups/rv-images-rc/providers/Microsoft.MachineLearningServices/workspaces/rv-images-ws/batchEndpoints/image-to-image-aae1e55d'}, 'print_as_yaml': True, 'id': '/subscriptions/dbd697c3-ef40-488f-83e6-5ad4dfb78f9b/resourceGroups/rv-images-rc/providers/Microsoft.MachineLearningServices/workspaces/rv-images-ws/batchEndpoints/image-to-image-aae1e55d', 'Resource__source_path': None, 'base_path': 'c:\\\\Users\\\\rvadthyavath\\\\work\\\\projects\\\\examples\\\\azureml-examples\\\\sdk\\\\python\\\\foundation-models\\\\system\\\\inference\\\\text-to-image', 'creation_context': None, 'serialize': , 'auth_mode': 'aad_token', 'location': 'eastus', 'defaults': })" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import time, sys, uuid\n", + "from azure.ai.ml.entities import (\n", + " BatchEndpoint,\n", + " BatchDeployment,\n", + " BatchRetrySettings,\n", + " AmlCompute,\n", + ")\n", + "\n", + "# Endpoint names need to be unique in a region,\n", + "# hence using uuid (first 8 character) to create unique endpoint name\n", + "endpoint_name = (\n", + " \"image-to-image-\" + str(uuid.uuid4())[:8]\n", + ") # Replace with your endpoint name\n", + "\n", + "# Create a batch endpoint\n", + "endpoint = BatchEndpoint(\n", + " name=endpoint_name,\n", + " description=\"Batch endpoint for \" + model.name + \", for image to image task\",\n", + ")\n", + "workspace_ml_client.begin_create_or_update(endpoint).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a batch deployment\n", + "\n", + "__Note__: `mini_batch_size` is the number of CSV files processed by the model in a single mini_batch." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "BatchDeployment({'provisioning_state': 'Succeeded', 'endpoint_name': 'image-to-image-aae1e55d', 'type': None, 'name': 'image-to-image-demo', 'description': None, 'tags': {}, 'properties': {}, 'print_as_yaml': True, 'id': '/subscriptions/dbd697c3-ef40-488f-83e6-5ad4dfb78f9b/resourceGroups/rv-images-rc/providers/Microsoft.MachineLearningServices/workspaces/rv-images-ws/batchEndpoints/image-to-image-aae1e55d/deployments/image-to-image-demo', 'Resource__source_path': None, 'base_path': 'c:\\\\Users\\\\rvadthyavath\\\\work\\\\projects\\\\examples\\\\azureml-examples\\\\sdk\\\\python\\\\foundation-models\\\\system\\\\inference\\\\text-to-image', 'creation_context': , 'serialize': , 'model': 'azureml://registries/azureml/models/stabilityai-stable-diffusion-xl-refiner-1-0/versions/1', 'code_configuration': None, 'environment': None, 'environment_variables': {}, 'compute': '/subscriptions/dbd697c3-ef40-488f-83e6-5ad4dfb78f9b/resourceGroups/rv-images-rc/providers/Microsoft.MachineLearningServices/workspaces/rv-images-ws/computes/gpu-cluster', 'resources': {'instance_count': 1, 'properties': {}}, 'output_action': 'append_row', 'output_file_name': 'predictions.csv', 'error_threshold': 0, 'retry_settings': , 'logging_level': 'Info', 'mini_batch_size': 1, 'max_concurrency_per_instance': 1})" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "deployment_name = \"image-to-image-demo\"\n", + "\n", + "deployment = BatchDeployment(\n", + " name=deployment_name,\n", + " endpoint_name=endpoint_name,\n", + " model=model.id,\n", + " compute=compute_name,\n", + " error_threshold=0,\n", + " instance_count=1,\n", + " logging_level=\"info\",\n", + " max_concurrency_per_instance=1,\n", + " mini_batch_size=1,\n", + " output_file_name=\"predictions.csv\",\n", + " retry_settings=BatchRetrySettings(max_retries=2, timeout=9999),\n", + ")\n", + "workspace_ml_client.begin_create_or_update(deployment).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Set the deployment as default" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The default deployment is image-to-image-demo\n" + ] + } + ], + "source": [ + "endpoint = workspace_ml_client.batch_endpoints.get(endpoint_name)\n", + "endpoint.defaults.deployment_name = deployment_name\n", + "workspace_ml_client.begin_create_or_update(endpoint).result()\n", + "\n", + "endpoint = workspace_ml_client.batch_endpoints.get(endpoint_name)\n", + "print(f\"The default deployment is {endpoint.defaults.deployment_name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5. Test the endpoint - using csv files\n", + "\n", + "Invoke the batch endpoint with the input parameter pointing to the directory containing one or more csv files containing the batch inference input. This creates a pipeline job using the default deployment in the endpoint. Wait for the job to complete.\n", + "\n", + "__Note__: If job failed with Out of Memory Error then please try splitting your input into smaller csv files or decreasing mini_batch_size for the deployment." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RunId: batchjob-1b83708d-5b9d-44d3-847a-2b26fd2253c8\n", + "Web View: https://ml.azure.com/runs/batchjob-1b83708d-5b9d-44d3-847a-2b26fd2253c8?wsid=/subscriptions/dbd697c3-ef40-488f-83e6-5ad4dfb78f9b/resourcegroups/rv-images-rc/workspaces/rv-images-ws\n", + "\n", + "Streaming logs/azureml/executionlogs.txt\n", + "========================================\n", + "\n", + "[2023-11-28 09:00:40Z] Submitting 1 runs, first five are: 7562760d:31295814-5223-427a-86a6-ecbc0b310ffe\n", + "[2023-11-28 09:12:52Z] Completing processing run id 31295814-5223-427a-86a6-ecbc0b310ffe.\n", + "\n", + "Execution Summary\n", + "=================\n", + "RunId: batchjob-1b83708d-5b9d-44d3-847a-2b26fd2253c8\n", + "Web View: https://ml.azure.com/runs/batchjob-1b83708d-5b9d-44d3-847a-2b26fd2253c8?wsid=/subscriptions/dbd697c3-ef40-488f-83e6-5ad4dfb78f9b/resourcegroups/rv-images-rc/workspaces/rv-images-ws\n", + "\n" + ] + } + ], + "source": [ + "job = None\n", + "num_retries = 3\n", + "for i in range(num_retries):\n", + " try:\n", + " job = workspace_ml_client.batch_endpoints.invoke(\n", + " endpoint_name=endpoint.name, input=input\n", + " )\n", + " break\n", + " except Exception as e:\n", + " if i == num_retries - 1:\n", + " raise e\n", + " else:\n", + " print(\"Endpoint invocation failed. Retrying after 5 seconds...\")\n", + " time.sleep(5)\n", + "if job is not None:\n", + " workspace_ml_client.jobs.stream(job.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Note__: If the job failed with error Assertion Error (The actual length exceeded max length 100 MB) then please consider dividing input csv file into multiple csv files." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Downloading artifact azureml://datastores/workspaceblobstore/paths/azureml/31295814-5223-427a-86a6-ecbc0b310ffe/score/ to named-outputs\\score\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
row_number_per_fileimage_file_namensfw_content_detectedinput_csv_name
00iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA...NaN0batch_input.csv
11iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA...NaN0batch_input.csv
20iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA...NaN2batch_input.csv
31iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA...NaN2batch_input.csv
\n", + "
" + ], + "text/plain": [ + " row_number_per_file image_file_name \\\n", + "0 0 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "1 1 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "2 0 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "3 1 iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAA... \n", + "\n", + " nsfw_content_detected input_csv_name \n", + "0 NaN 0batch_input.csv \n", + "1 NaN 0batch_input.csv \n", + "2 NaN 2batch_input.csv \n", + "3 NaN 2batch_input.csv " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "scoring_job = list(workspace_ml_client.jobs.list(parent_job_name=job.name))[0]\n", + "\n", + "workspace_ml_client.jobs.download(\n", + " name=scoring_job.name,\n", + " download_path=\".\",\n", + " output_name=\"score\",\n", + ")\n", + "\n", + "predictions_file = os.path.join(\"named-outputs\", \"score\", \"predictions.csv\")\n", + "\n", + "# Load the batch predictions file with no headers into a dataframe and set your column names\n", + "score_df = pd.read_csv(\n", + " predictions_file,\n", + " header=None,\n", + " names=[\n", + " \"row_number_per_file\",\n", + " \"image_file_name\",\n", + " \"nsfw_content_detected\",\n", + " \"input_csv_name\",\n", + " ],\n", + ")\n", + "score_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6. Clean up resources - delete the endpoint\n", + "Batch endpoints use compute resources only when jobs are submitted. You can keep the batch endpoint for your reference without worrying about compute bills, or choose to delete the endpoint. If you created your compute cluster to have zero minimum instances and scale down soon after being idle, you won't be charged for an unused compute." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".........." + ] + } + ], + "source": [ + "workspace_ml_client.batch_endpoints.begin_delete(name=endpoint_name).result()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.ipynb b/sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.ipynb new file mode 100644 index 00000000000..688f9e7f4cd --- /dev/null +++ b/sdk/python/foundation-models/system/inference/text-to-image/image-text-to-image-online-endpoint.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Image to Image Inference using Online Endpoints\n", + "\n", + "This sample shows how to deploy `image to image` type stable diffusion models to an online endpoint for inference.\n", + "\n", + "### Task\n", + "`image to image` task takes an original image and a text prompt as input. The model generates image by modifying the original image.\n", + "\n", + " \n", + "### Model\n", + "Models that can perform the `image to image` task are tagged with `image-to-image`. We will use the `stabilityai-stable-diffusion-xl-refiner-1-0` model in this notebook. If you opened this notebook from a specific model card, remember to replace the specific model name.\n", + "\n", + "\n", + "### Outline\n", + "1. Setup pre-requisites\n", + "2. Pick a model to deploy\n", + "3. Deploy the model to an online endpoint for real time inference\n", + "4. Test the endpoint using sample text prompt and original image.\n", + "5. Clean up resources - delete the online endpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Setup pre-requisites\n", + "* Connect to AzureML Workspace. Learn more at [set up SDK authentication](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication?tabs=sdk). Replace ``, `` and `` below.\n", + "* Connect to `azureml` system registry" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml import MLClient\n", + "from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential\n", + "import time\n", + "\n", + "try:\n", + " credential = DefaultAzureCredential()\n", + " credential.get_token(\"https://management.azure.com/.default\")\n", + "except Exception as ex:\n", + " credential = InteractiveBrowserCredential()\n", + "\n", + "try:\n", + " workspace_ml_client = MLClient.from_config(credential)\n", + " subscription_id = workspace_ml_client.subscription_id\n", + " resource_group = workspace_ml_client.resource_group_name\n", + " workspace_name = workspace_ml_client.workspace_name\n", + "except Exception as ex:\n", + " print(ex)\n", + " # Enter details of your AML workspace\n", + " subscription_id = \"\"\n", + " resource_group = \"\"\n", + " workspace_name = \"\"\n", + "workspace_ml_client = MLClient(\n", + " credential, subscription_id, resource_group, workspace_name\n", + ")\n", + "\n", + "# The models, fine tuning pipelines and environments are available in the AzureML system registry, \"azureml\"\n", + "registry_name = \"azureml\"\n", + "registry_ml_client = MLClient(\n", + " credential,\n", + " subscription_id,\n", + " resource_group,\n", + " registry_name=registry_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Pick a model to deploy\n", + "\n", + "Browse models in the Model Catalog in the AzureML Studio, filtering by the `image-to-image` task. In this example, we use the `stabilityai-stable-diffusion-xl-refiner-1-0` model. If you have opened this notebook for a different model, replace the model name accordingly. This is a pre-trained model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Name of the image to image model to be deployed\n", + "model_name = \"stabilityai-stable-diffusion-xl-refiner-1-0\"\n", + "\n", + "try:\n", + " model = registry_ml_client.models.get(name=model_name, label=\"latest\")\n", + "except Exception as ex:\n", + " print(\n", + " f\"No model named {model_name} found in registry. \"\n", + " \"Please check model name present in Azure model catalog\"\n", + " )\n", + " raise ex\n", + "\n", + "print(\n", + " f\"\\n\\nUsing model name: {model.name}, version: {model.version}, id: {model.id} for generating images from image and text.\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Deploy the model to an online endpoint for real time inference\n", + "Online endpoints give a durable REST API that can be used to integrate with applications that need to use the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "from azure.ai.ml.entities import ManagedOnlineEndpoint, ManagedOnlineDeployment\n", + "\n", + "# Endpoint names need to be unique in a region, hence using uuid (first 8 character) to create unique endpoint name\n", + "online_endpoint_name = (\n", + " \"image-to-image-\" + str(uuid.uuid4())[:8]\n", + ") # Replace with your endpoint name\n", + "# Create an online endpoint\n", + "endpoint = ManagedOnlineEndpoint(\n", + " name=online_endpoint_name,\n", + " description=\"Online endpoint for \" + model.name + \", for image to image task\",\n", + " auth_mode=\"key\",\n", + ")\n", + "workspace_ml_client.begin_create_or_update(endpoint).wait()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import OnlineRequestSettings, ProbeSettings\n", + "\n", + "deployment_name = \"image-to-image-deploy\"\n", + "\n", + "# Create a deployment\n", + "demo_deployment = ManagedOnlineDeployment(\n", + " name=deployment_name,\n", + " endpoint_name=online_endpoint_name,\n", + " model=model.id,\n", + " instance_type=\"Standard_NC6s_v3\", # Use GPU instance type like Standard_NC6s_v3 or above\n", + " instance_count=1,\n", + " request_settings=OnlineRequestSettings(\n", + " max_concurrent_requests_per_instance=1,\n", + " request_timeout_ms=90000,\n", + " max_queue_wait_ms=500,\n", + " ),\n", + " liveness_probe=ProbeSettings(\n", + " failure_threshold=49,\n", + " success_threshold=1,\n", + " timeout=299,\n", + " period=180,\n", + " initial_delay=180,\n", + " ),\n", + " readiness_probe=ProbeSettings(\n", + " failure_threshold=10,\n", + " success_threshold=1,\n", + " timeout=10,\n", + " period=10,\n", + " initial_delay=10,\n", + " ),\n", + ")\n", + "workspace_ml_client.online_deployments.begin_create_or_update(demo_deployment).wait()\n", + "endpoint.traffic = {deployment_name: 100}\n", + "workspace_ml_client.begin_create_or_update(endpoint).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Test the endpoint\n", + "\n", + "We will fetch some sample data from the test dataset and submit to online endpoint for inference.\n", + "\n", + "The sample of input schema for image to image task:\n", + "```json\n", + "{\n", + " \"input_data\": {\n", + " \"columns\": [\"prompt\", \"image\"],\n", + " \"data\": [\n", + " {\n", + " \"prompt\": \"sample prompt\",\n", + " \"image\": \"base image1\"\n", + " },\n", + " {\n", + " \"prompt\": \"sample prompt\",\n", + " \"image\": \"base image2\" }\n", + " ],\n", + " \"index\": [0, 1]\n", + " }\n", + "}\n", + "```\n", + "> - The base image (1, 2) strings should be in base64 format or publicly accessible urls.\n", + "\n", + "The sample of output schema for image to image task:\n", + "```json\n", + "[\n", + " {\n", + " \"generated_image\": \"image1\",\n", + " \"nsfw_content_detected\": None\n", + " },\n", + " {\n", + " \"generated_image\": \"image2\",\n", + " \"nsfw_content_detected\": None\n", + " }\n", + "]\n", + "```\n", + "> - \"nsfw_content_detected\" is not supported for this model.\n", + "> - Generated images \"image1\" and \"image2\" strings are in base64 format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create request json\n", + "import base64\n", + "import json\n", + "\n", + "\n", + "def read_image(image_path: str) -> bytes:\n", + " \"\"\"Reads an image from a file path into a byte array.\"\"\"\n", + " with open(image_path, \"rb\") as f:\n", + " return f.read()\n", + "\n", + "\n", + "base_image = \"inpainting_data/images/dog_on_bench.png\"\n", + "\n", + "request_json = {\n", + " \"input_data\": {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"index\": [0],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image)).decode(\"utf-8\"),\n", + " \"prompt\": \"A yellow cat, high resolution, sitting on a park bench\",\n", + " }\n", + " ],\n", + " }\n", + "}\n", + "\n", + "request_file_name = \"sample_request_data.json\"\n", + "\n", + "with open(request_file_name, \"w\") as request_file:\n", + " json.dump(request_json, request_file)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "response = workspace_ml_client.online_endpoints.invoke(\n", + " endpoint_name=online_endpoint_name,\n", + " deployment_name=demo_deployment.name,\n", + " request_file=request_file_name,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import io\n", + "import base64\n", + "from PIL import Image\n", + "\n", + "generations = json.loads(response)\n", + "for generation in generations:\n", + " print(f\"nsfw content detected: \", generation[\"nsfw_content_detected\"])\n", + " img = Image.open(io.BytesIO(base64.b64decode(generation[\"generated_image\"])))\n", + " display(img)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5. Clean up resources - delete the online endpoint\n", + "Don't forget to delete the online endpoint, else you will leave the billing meter running for the compute used by the endpoint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "workspace_ml_client.online_endpoints.begin_delete(name=online_endpoint_name).wait()" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-batch-deployment.ipynb b/sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-batch-deployment.ipynb new file mode 100644 index 00000000000..573ad81fbac --- /dev/null +++ b/sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-batch-deployment.ipynb @@ -0,0 +1,767 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create an Azure AI Content Safety (AACS) enabled image to image batch endpoint (Preview)\n", + "### This notebook is under preview.\n", + "\n", + "### Steps to create an __AACS__ enabled __image to image__ batch endpoint\n", + "1. Create a __AACS__ enabled image-to-image batch endpoint with a custom [score_batch.py](./aacs-scoring-files/score/score_batch.py) script. This will integrate the batch endpoint with the AACS resource to moderate the response from the __image-to-image__ model and the request from the user.\n", + "2. Create a new __AACS__ enabled __image-to-image__ batch endpoint with a custom [score_batch.py](./aacs-scoring-files/score/score_batch.py) which will integrate with the __AACS__ resource to moderate the response from the __image-to-image__ model and the request from the user. To make the custom [score_batch.py](./aacs-scoring-files/score/score_batch.py) to successfully authenticated to the __AACS__ resource, use __Environment variable__ to pass the access key of the __AACS__ resource to the custom [score_batch.py](./aacs-scoring-files/score/score_batch.py) via environment variable. The custom [score_batch.py](./aacs-scoring-files/score/score_batch.py) can use the key directly to access the AACS resource. This option is less secure, if someone in your org has access to the endpoint, he/she can get the access key from the environment variable and use it to access the AACS resource.\n", + "\n", + "### Task\n", + "`image to image` task takes an original image, a text prompt as input. The model generates an image by modifying the original image.\n", + "\n", + " \n", + "### Model\n", + "Models that can perform the `image to image` task are tagged with `image-to-image`. We will use the `stabilityai-stable-diffusion-xl-refiner-1-0` model in this notebook. If you opened this notebook from a specific model card, remember to replace the specific model name.\n", + "\n", + "### Outline\n", + "1. Setup pre-requisties\n", + "2. Create AACS resource\n", + "3. Create AACS enabled image to image batch endpoint\n", + "4. Prepare data for inference - using a folder of csv files with prompt, and image columns\n", + "5. Test the endpoint - using csv files\n", + "6. Clean up resources - delete the endpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Setup pre-requisites\n", + "* Check List\n", + "* Install dependencies\n", + "* Connect to AzureML Workspace. Learn more at [set up SDK authentication](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication?tabs=sdk). Replace ``, `` and `` below.\n", + "* Connect to `azureml` system registry" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> [x] The identity you are using to execute this notebook(yourself or your VM) need to have the __Contributor__ role on the resource group where the AML Workspace your specified is located, because this notebook will create an AACS resource using that identity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install the required packages\n", + "%pip install azure-identity==1.13.0\n", + "%pip install azure-mgmt-cognitiveservices==13.4.0\n", + "%pip install azure-ai-ml==1.8.0\n", + "%pip install azure-mgmt-msi==7.0.0\n", + "%pip install azure-mgmt-authorization==3.0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml import MLClient\n", + "from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential\n", + "\n", + "try:\n", + " credential = DefaultAzureCredential()\n", + " credential.get_token(\"https://management.azure.com/.default\")\n", + "except Exception as ex:\n", + " credential = InteractiveBrowserCredential()\n", + "\n", + "try:\n", + " workspace_ml_client = MLClient.from_config(credential)\n", + " subscription_id = workspace_ml_client.subscription_id\n", + " resource_group = workspace_ml_client.resource_group_name\n", + " workspace_name = workspace_ml_client.workspace_name\n", + "except Exception as ex:\n", + " print(ex)\n", + " # Enter details of your AML workspace\n", + " subscription_id = \"\"\n", + " resource_group = \"\"\n", + " workspace_name = \"\"\n", + "workspace_ml_client = MLClient(\n", + " credential, subscription_id, resource_group, workspace_name\n", + ")\n", + "\n", + "print(f\"Connected to workspace {workspace_name}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The models, fine tuning pipelines and environments are available in the AzureML system registry, \"azureml\"\n", + "\n", + "registry_name = \"azureml\"\n", + "\n", + "registry_ml_client = MLClient(\n", + " credential,\n", + " subscription_id,\n", + " resource_group,\n", + " registry_name=registry_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Create AACS resource" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.1 Assign variables for Azure Content Safety\n", + "Currently, AACS is available in a limited set of regions:\n", + "\n", + "__NOTE__: before you choose the region to deploy the AACS, please be aware that your data will be transferred to the region you choose and by selecting a region outside your current location, you may be allowing the transmission of your data to regions outside your jurisdiction. It is important to note that data protection and privacy laws may vary between jurisdictions. Before proceeding, we strongly advise you to familiarize yourself with the local laws and regulations governing data transfer and ensure that you are legally permitted to transmit your data to an overseas location for processing. By continuing with the selection of a different region, you acknowledge that you have understood and accepted any potential risks associated with such data transmission. Please proceed with caution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The severity level that will trigger response be blocked\n", + "# Please reference Azure AI content documentation for more details\n", + "# https://learn.microsoft.com/en-us/azure/cognitive-services/content-safety/concepts/harm-categories\n", + "content_severity_threshold = \"2\"\n", + "\n", + "# If you choose environment variables for authentication of AACS resource, then assign empty (\"\") value to uai_name\n", + "uai_name = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from uuid import uuid4\n", + "from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient\n", + "\n", + "aacs_client = CognitiveServicesManagementClient(credential, subscription_id)\n", + "\n", + "\n", + "# settings for the Azure AI Content Safety (AACS) resource\n", + "# we will choose existing AACS resource if it exists, otherwise create a new one\n", + "# name of AACS resource, has to be unique\n", + "\n", + "aacs_name = f\"aacs-image-to-image-{str(uuid4())[:8]}\"\n", + "available_aacs_locations = [\"east us\", \"west europe\"]\n", + "\n", + "# create a new Cognitive Services Account\n", + "kind = \"ContentSafety\"\n", + "aacs_sku_name = \"S0\"\n", + "aacs_location = available_aacs_locations[0]\n", + "\n", + "\n", + "print(\"Available SKUs:\")\n", + "aacs_skus = aacs_client.resource_skus.list()\n", + "print(\"SKU Name\\tSKU Tier\\tLocations\")\n", + "for sku in aacs_skus:\n", + " if sku.kind == \"ContentSafety\":\n", + " locations = \",\".join(sku.locations)\n", + " print(sku.name + \"\\t\\t\" + sku.tier + \"\\t\\t\" + locations)\n", + "\n", + "print(f\"Choose a new AACS resource in {aacs_location} with SKU {aacs_sku_name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.2 Create AACS Resource" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.mgmt.cognitiveservices.models import Account, Sku, AccountProperties\n", + "\n", + "parameters = Account(\n", + " sku=Sku(name=aacs_sku_name),\n", + " kind=kind,\n", + " location=aacs_location,\n", + " properties=AccountProperties(\n", + " custom_sub_domain_name=aacs_name, public_network_access=\"Enabled\"\n", + " ),\n", + ")\n", + "\n", + "\n", + "def find_acs(accounts):\n", + " return next(\n", + " x\n", + " for x in accounts\n", + " if x.kind == \"ContentSafety\"\n", + " and x.location == aacs_location\n", + " and x.sku.name == aacs_sku_name\n", + " )\n", + "\n", + "\n", + "try:\n", + " # check if AACS exists\n", + " aacs = aacs_client.accounts.get(resource_group, aacs_name)\n", + " print(f\"Found existing AACS Account {aacs.name}.\")\n", + "except:\n", + " try:\n", + " # check if there is an existing AACS resource within same resource group\n", + " aacs = find_acs(aacs_client.accounts.list_by_resource_group(resource_group))\n", + " print(\n", + " f\"Found existing AACS Account {aacs.name} in resource group {resource_group}.\"\n", + " )\n", + " except:\n", + " print(f\"Creating AACS Account {aacs_name}.\")\n", + " aacs_client.accounts.begin_create(resource_group, aacs_name, parameters).wait()\n", + " print(\"Resource created.\")\n", + " aacs = aacs_client.accounts.get(resource_group, aacs_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aacs_endpoint = aacs.properties.endpoint\n", + "aacs_resource_id = aacs.id\n", + "aacs_name = aacs.name\n", + "print(\n", + " f\"AACS name is {aacs.name} .\\nUse this name in UAI preparation notebook to create UAI.\"\n", + ")\n", + "print(f\"AACS endpoint is {aacs_endpoint}\")\n", + "print(f\"AACS ResourceId is {aacs_resource_id}\")\n", + "\n", + "aacs_access_key = aacs_client.accounts.list_keys(\n", + " resource_group_name=resource_group, account_name=aacs.name\n", + ").key1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Create AACS enabled image to image batch endpoint\n", + "\n", + "#### 3.1 Check if image to image model is available in the AML registry\n", + "\n", + "Browse models in the Model Catalog in the AzureML Studio, filtering by the image-to-image task. In this example, we use the `stabilityai-stable-diffusion-xl-refiner-1-0 model`. If you have opened this notebook for a different model, replace the model name accordingly. This is a pre-trained model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Name of the image to image model to be deployed\n", + "model_name = \"stabilityai-stable-diffusion-xl-refiner-1-0\"\n", + "\n", + "try:\n", + " model = registry_ml_client.models.get(model_name, label=\"latest\")\n", + " print(\n", + " f\"Using model name: {model.name}, version: {model.version}, id: {model.id} for inference.\"\n", + " )\n", + "except:\n", + " raise Exception(\n", + " f\"No model named {model_name} found in registry. \"\n", + " \"Please check model name in Azure model catalog.\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 3.2 Create environment for image-to-image endpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import Environment, BuildContext\n", + "from IPython.core.display import display, HTML\n", + "\n", + "environment_name = \"imageto-image-model-env\" # Replace with your environment name\n", + "\n", + "try:\n", + " env = workspace_ml_client.environments.get(environment_name, label=\"latest\")\n", + " print(\"---Environment already exists---\")\n", + "except:\n", + " print(\"---Creating environment---\")\n", + " env = Environment(\n", + " name=environment_name,\n", + " build=BuildContext(path=\"./aacs-scoring-files/docker_env\"),\n", + " )\n", + " workspace_ml_client.environments.create_or_update(env)\n", + " env = workspace_ml_client.environments.get(environment_name, label=\"latest\")\n", + " print(\"---Please use link below to check build status---\")\n", + "\n", + "\n", + "display(\n", + " HTML(\n", + " f\"\"\"\n", + " \n", + " Click here to check env build status in AML studio\n", + " \n", + " \"\"\"\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 3.3 Create compute cluster to run batch job on\n", + "\n", + "Use the model card from the AzureML system registry to check the minimum required inferencing SKU, referenced as size below. If you already have a sufficient compute cluster that you wish to use, you can simply define the name in `compute_name` in the following code block. Otherwise, the below snippet will create a new compute cluster." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import AmlCompute\n", + "\n", + "sku_name = \"STANDARD_NC6S_V3\" # Name of the sku(compute instance type)\n", + "compute_name = \"gpu-compute\" # Replace with your compute name\n", + "\n", + "if not any(\n", + " filter(lambda m: m.name == compute_name, workspace_ml_client.compute.list())\n", + "):\n", + " compute_cluster = AmlCompute(\n", + " name=compute_name,\n", + " size=sku_name,\n", + " min_instances=0,\n", + " max_instances=2,\n", + " )\n", + " workspace_ml_client.compute.begin_create_or_update(compute_cluster).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 3.4 Deploy the model to a batch endpoint\n", + "\n", + "Batch endpoints are endpoints that are used to do batch inferencing on large volumes of data over a period of time. The endpoints receive pointers to data and run jobs asynchronously to process the data in parallel on compute clusters. Batch endpoints store outputs to a data store for further analysis. For more information on batch endpoints and deployments, see What are batch endpoints? In this sub-section, we will cover the following items:\n", + "\n", + "* Create a batch endpoint.\n", + "* Create a batch deployment.\n", + "* Set the deployment as default. Doing so allows invoking the endpoint without specifying the deployment's name." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Create a batch endpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import BatchEndpoint\n", + "\n", + "# Endpoint names need to be unique in a region,\n", + "# hence using uuid (first 8 character) to create unique endpoint name\n", + "\n", + "endpoint_name = (\n", + " f\"safe-image-to-image-{str(uuid4())[:8]}\" # Replace with your endpoint name\n", + ")\n", + "\n", + "# Check if the endpoint already exists in the workspace\n", + "try:\n", + " endpoint = workspace_ml_client.batch_endpoints.get(endpoint_name)\n", + " print(\"---Endpoint already exists---\")\n", + "except:\n", + " # Create an batch endpoint if it doesn't exist\n", + "\n", + " # Define the endpoint\n", + " endpoint = BatchEndpoint(name=endpoint_name, description=\"Test endpoint for model\")\n", + "\n", + " # Trigger the endpoint creation\n", + " try:\n", + " workspace_ml_client.begin_create_or_update(endpoint).wait()\n", + " print(\"\\n---Endpoint created successfully---\\n\")\n", + " except Exception as err:\n", + " raise RuntimeError(\n", + " f\"Endpoint creation failed. Detailed Response:\\n{err}\"\n", + " ) from err" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Deploy image to image model\n", + "This step may take a few minutes.\n", + "\n", + "\n", + "__Note__: `mini_batch_size` is the number of CSV files processed by the model in a single mini_batch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import (\n", + " ModelBatchDeploymentSettings,\n", + " CodeConfiguration,\n", + " BatchRetrySettings,\n", + " ModelBatchDeployment,\n", + ")\n", + "\n", + "from azure.ai.ml.constants import BatchDeploymentOutputAction\n", + "\n", + "\n", + "deployment_name = \"image-to-image-deploy\"\n", + "\n", + "deployment = ModelBatchDeployment(\n", + " name=deployment_name,\n", + " endpoint_name=endpoint.name,\n", + " model=model,\n", + " environment=env,\n", + " code_configuration=CodeConfiguration(\n", + " code=\"aacs-scoring-files/score\",\n", + " scoring_script=\"score_batch.py\",\n", + " ),\n", + " compute=compute_name,\n", + " settings=ModelBatchDeploymentSettings(\n", + " instance_count=1,\n", + " max_concurrency_per_instance=1,\n", + " mini_batch_size=2,\n", + " output_action=BatchDeploymentOutputAction.APPEND_ROW,\n", + " output_file_name=\"predictions.csv\",\n", + " retry_settings=BatchRetrySettings(max_retries=3, timeout=3000),\n", + " logging_level=\"info\",\n", + " environment_variables={\n", + " \"CONTENT_SAFETY_ENDPOINT\": aacs_endpoint,\n", + " \"CONTENT_SAFETY_KEY\": aacs_access_key,\n", + " },\n", + " ),\n", + ")\n", + "# Trigger the deployment creation\n", + "try:\n", + " workspace_ml_client.begin_create_or_update(deployment).wait()\n", + " print(\"\\n---Deployment created successfully---\\n\")\n", + "except Exception as err:\n", + " raise RuntimeError(\n", + " f\"Deployment creation failed. Detailed Response:\\n{err}\"\n", + " ) from err" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Update Batch endpoint to set the default deployment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "endpoint = workspace_ml_client.batch_endpoints.get(endpoint_name)\n", + "endpoint.defaults.deployment_name = deployment.name\n", + "workspace_ml_client.batch_endpoints.begin_create_or_update(endpoint).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Prepare data for inference - using a folder of csv files with prompt and image columns\n", + "\n", + "The CSV files should consist of 3 columns namely, \n", + "* `image`: Original image to be used as base image for image to image task. It should either be in base64 format or publicly accessible URL.\n", + "* `prompt`: A text prompt.\n", + "\n", + "We provide the text prompts in a csv file starting from the first row of a column named \"prompt\". We provide the input image as base64 string starting from the first row of the column \"image\". We provide the input image as base64 string starting from the first row of the column \"image\".\n", + "\n", + "The deployment in the \"Deploy image to image model\" section above takes the argument mini_batch_size, which is the number of CSV files processed by the model in a single mini_batch. To limit the number of prompts processed in each mini_batch we split the dataset into multiple csv files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Prepare input csv file\n", + "\n", + "import base64\n", + "import os\n", + "import pandas as pd\n", + "\n", + "\n", + "def read_image(image_path: str) -> bytes:\n", + " \"\"\"Reads an image from a file path into a byte array.\"\"\"\n", + " with open(image_path, \"rb\") as f:\n", + " return f.read()\n", + "\n", + "\n", + "base_image1 = \"inpainting_data/images/dog_on_bench.png\"\n", + "base_image2 = \"inpainting_data/images/teapot.png\"\n", + "\n", + "dataset_parent_dir = \"inpainting_data/batch_data\"\n", + "os.makedirs(dataset_parent_dir, exist_ok=True)\n", + "\n", + "input_data = {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image1)).decode(\"utf-8\"),\n", + " \"prompt\": \"a dog with severed leg and bleeding profusely from deep laceration to the lower extremities, exposing tissues and nerve.\",\n", + " },\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image2)).decode(\"utf-8\"),\n", + " \"prompt\": \"A small flower featuring a blend of pink and purple colors.\",\n", + " },\n", + " ],\n", + "}\n", + "pd.DataFrame(**input_data).to_csv(\n", + " os.path.join(dataset_parent_dir, \"input1.csv\"), index=False\n", + ")\n", + "\n", + "input_data = {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image1)).decode(\"utf-8\"),\n", + " \"prompt\": \"Pikachu, cinematic, digital art, sitting on bench\",\n", + " },\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image2)).decode(\"utf-8\"),\n", + " \"prompt\": \"dead body killed with a big dagger\",\n", + " },\n", + " ],\n", + "}\n", + "pd.DataFrame(**input_data).to_csv(\n", + " os.path.join(dataset_parent_dir, \"input2.csv\"), index=False\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Read all the csvs in the data folder into a pandas dataframe\n", + "import glob\n", + "import os\n", + "import pandas as pd\n", + "\n", + "# Specify the folder where your CSV files are located\n", + "dataset_parent_dir = \"inpainting_data/batch_data\"\n", + "\n", + "# Use glob to get a list of CSV files in the folder\n", + "csv_files = glob.glob(os.path.join(dataset_parent_dir, \"*.csv\"))\n", + "\n", + "# Read all CSV files into a single DataFrame using pd.concat\n", + "batch_df = pd.concat((pd.read_csv(file) for file in csv_files), ignore_index=True)\n", + "\n", + "# Now, 'batch_df' contains all the data from the CSV files in the folder\n", + "print(batch_df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "# Specify the folder where your CSV files should be saved\n", + "processed_dataset_parent_dir = \"inpainting_data/processed_batch_data\"\n", + "os.makedirs(processed_dataset_parent_dir, exist_ok=True)\n", + "batch_input_file = \"batch_input.csv\"\n", + "\n", + "# Divide this into files of rows each\n", + "batch_size_per_predict = 2\n", + "for i in range(0, len(batch_df), batch_size_per_predict):\n", + " j = i + batch_size_per_predict\n", + " batch_df[i:j].to_csv(\n", + " os.path.join(processed_dataset_parent_dir, str(i) + batch_input_file)\n", + " )\n", + "\n", + "# Check out the first and last file name created\n", + "input_paths = sorted(Path(processed_dataset_parent_dir).iterdir(), key=os.path.getmtime)\n", + "input_files = [os.path.basename(path) for path in input_paths]\n", + "print(f\"{input_files[0]} to {str(i)}{batch_input_file}.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Register folder containing csv files in AML as data asset to use in batch job." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import Data\n", + "from azure.ai.ml.constants import AssetTypes\n", + "\n", + "dataset_name = \"inpainting-data\"\n", + "input = Data(\n", + " name=dataset_name,\n", + " description=\"A sample of the dataset for image generation for batch deployment, in CSV file format\",\n", + " type=AssetTypes.URI_FOLDER,\n", + " path=processed_dataset_parent_dir,\n", + ")\n", + "workspace_ml_client.data.create_or_update(input)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 5. Test the endpoint - using csv files\n", + "\n", + "Invoke the batch endpoint with the input parameter pointing to the directory containing one or more csv files containing the batch inference input. This creates a pipeline job using the default deployment in the endpoint. Wait for the job to complete.\n", + "\n", + "__Note__: If job failed with Out of Memory Error then please try splitting your input into smaller csv files or decreasing mini_batch_size for the deployment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from azure.ai.ml import Input\n", + "\n", + "job = None\n", + "input = Input(path=dataset_parent_dir, type=AssetTypes.URI_FOLDER)\n", + "num_retries = 3\n", + "for i in range(num_retries):\n", + " try:\n", + " job = workspace_ml_client.batch_endpoints.invoke(\n", + " endpoint_name=endpoint.name, input=input\n", + " )\n", + " break\n", + " except Exception as e:\n", + " if i == num_retries - 1:\n", + " raise e\n", + " else:\n", + " print(\"Endpoint invocation failed. Retrying after 5 seconds...\")\n", + " time.sleep(5)\n", + "if job is not None:\n", + " workspace_ml_client.jobs.stream(job.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Note__: If the job failed with error Assertion Error (The actual length exceeded max length 100 MB) then please consider dividing input csv file into multiple csv files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "scoring_job = list(workspace_ml_client.jobs.list(parent_job_name=job.name))[0]\n", + "\n", + "workspace_ml_client.jobs.download(\n", + " name=scoring_job.name,\n", + " download_path=\".\",\n", + " output_name=\"score\",\n", + ")\n", + "\n", + "predictions_file = os.path.join(\"named-outputs\", \"score\", \"predictions.csv\")\n", + "\n", + "# Load the batch predictions file with no headers into a dataframe and set your column names\n", + "score_df = pd.read_csv(\n", + " predictions_file,\n", + " header=None,\n", + " names=[\n", + " \"row_number_per_file\",\n", + " \"image_file_name\",\n", + " \"nsfw_content_detected\",\n", + " \"input_csv_name\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(score_df)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6. Clean up resources - delete the endpoint\n", + "Batch endpoints use compute resources only when jobs are submitted. You can keep the batch endpoint for your reference without worrying about compute bills, or choose to delete the endpoint. If you created your compute cluster to have zero minimum instances and scale down soon after being idle, you won't be charged for an unused compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "workspace_ml_client.batch_endpoints.begin_delete(name=endpoint_name).result()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-online-endpoint.ipynb b/sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-online-endpoint.ipynb new file mode 100644 index 00000000000..819c58bd099 --- /dev/null +++ b/sdk/python/foundation-models/system/inference/text-to-image/safe-image-text-to-image-online-endpoint.ipynb @@ -0,0 +1,750 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create an Azure AI Content Safety __(AACS)__ enabled Image to Image online endpoint (Preview)\n", + "### This notebook is under preview.\n", + "\n", + "### Steps to create an __AACS__ enabled __image-to-image__ online endpoint\n", + "1. Create an __AACS__ resource for moderating the request from user and response from the __image-to-image__ online endpoint.\n", + "2. Create a new __AACS__ enabled __image-to-image__ online endpoint with a custom [score_online.py](./aacs-scoring-files/score/score_online.py) which will integrate with the __AACS__ resource to moderate the response from the __image-to-image__ model and the request from the user, but to make the custom [score_online.py](./aacs-scoring-files/score/score_online.py) to successfully authenticated to the __AACS__ resource, we have 2 options:\n", + " 1. __UAI__, recommended but more complex approach, is to create a User Assigned Identity (UAI) and assign appropriate roles to the UAI. Then, the custom [score_online.py](./aacs-scoring-files/score/score_online.py) can obtain the access token of the UAI from the AAD server to access the AACS resource. Use [this notebook](aacs-prepare-uai.ipynb) to create UAI account for step 3 below\n", + " 2. __Environment variable__, simpler but less secure approach, is to just pass the access key of the AACS resource to the custom [score_online.py](./aacs-scoring-files/score/score_online.py) via environment variable, then the custom [score_online.py](./aacs-scoring-files/score/score_online.py) can use the key directly to access the AACS resource, this option is less secure than the first option, if someone in your org has access to the endpoint, he/she can get the access key from the environment variable and use it to access the AACS resource.\n", + "\n", + "\n", + "### Task\n", + "`image to image` task takes an original image, a text prompt as input. The model generates an image by modifying the original image.\n", + "\n", + " \n", + "### Model\n", + "Models that can perform the `image-to-image` task are tagged with `image-to-image`. We will use the `stabilityai-stable-diffusion-xl-refiner-1-0` model in this notebook. If you opened this notebook from a specific model card, remember to replace the specific model name.\n", + "\n", + "### Outline\n", + "1. Setup pre-requisites\n", + "2. Create AACS resource\n", + "3. Pick a model to deploy\n", + "4. Deploy the model to an online endpoint for real time inference\n", + "5. Test the endpoint\n", + "6. Clean up resources - delete the online endpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Setup pre-requisites\n", + "* Check List\n", + "* Install dependencies\n", + "* Connect to AzureML Workspace. Learn more at [set up SDK authentication](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication?tabs=sdk). Replace ``, `` and `` below.\n", + "* Connect to `azureml` system registry" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> [x] The identity you are using to execute this notebook(yourself or your VM) need to have the __Contributor__ role on the resource group where the AML Workspace your specified is located, because this notebook will create an AACS resource using that identity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install the required packages\n", + "%pip install azure-identity==1.13.0\n", + "%pip install azure-mgmt-cognitiveservices==13.4.0\n", + "%pip install azure-ai-ml==1.11.1\n", + "%pip install azure-mgmt-msi==7.0.0\n", + "%pip install azure-mgmt-authorization==3.0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml import MLClient\n", + "from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential\n", + "\n", + "try:\n", + " credential = DefaultAzureCredential()\n", + " credential.get_token(\"https://management.azure.com/.default\")\n", + "except Exception as ex:\n", + " credential = InteractiveBrowserCredential()\n", + "\n", + "try:\n", + " workspace_ml_client = MLClient.from_config(credential)\n", + " subscription_id = workspace_ml_client.subscription_id\n", + " resource_group = workspace_ml_client.resource_group_name\n", + " workspace_name = workspace_ml_client.workspace_name\n", + "except Exception as ex:\n", + " print(ex)\n", + " # Enter details of your AML workspace\n", + " subscription_id = \"\"\n", + " resource_group = \"\"\n", + " workspace_name = \"\"\n", + "workspace_ml_client = MLClient(\n", + " credential, subscription_id, resource_group, workspace_name\n", + ")\n", + "\n", + "print(f\"Connected to workspace {workspace_name}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The models, fine tuning pipelines and environments are available in the AzureML system registry, \"azureml\"\n", + "\n", + "registry_name = \"azureml\"\n", + "\n", + "registry_ml_client = MLClient(\n", + " credential,\n", + " subscription_id,\n", + " resource_group,\n", + " registry_name=registry_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Create AACS resource" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.1 Assign variables for Azure Content Safety\n", + "Currently, AACS is available in a limited set of regions:\n", + "\n", + "\n", + "__NOTE__: before you choose the region to deploy the AACS, please be aware that your data will be transferred to the region you choose and by selecting a region outside your current location, you may be allowing the transmission of your data to regions outside your jurisdiction. It is important to note that data protection and privacy laws may vary between jurisdictions. Before proceeding, we strongly advise you to familiarize yourself with the local laws and regulations governing data transfer and ensure that you are legally permitted to transmit your data to an overseas location for processing. By continuing with the selection of a different region, you acknowledge that you have understood and accepted any potential risks associated with such data transmission. Please proceed with caution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The severity level that will trigger response be blocked\n", + "# Please reference Azure AI content documentation for more details\n", + "# https://learn.microsoft.com/en-us/azure/cognitive-services/content-safety/concepts/harm-categories\n", + "content_severity_threshold = \"2\"\n", + "\n", + "# UAI to be used for endpoint if you choose to use UAI as authentication method.\n", + "# Use default name \"aacs-uai\" as used in prepare uai notebook\n", + "\n", + "# uai_name = \"aacs-uai\"\n", + "\n", + "# If you choose environment variables for authentication of AACS resource, then assign empty (\"\") value to uai_name\n", + "uai_name = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from uuid import uuid4\n", + "from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient\n", + "\n", + "aacs_client = CognitiveServicesManagementClient(credential, subscription_id)\n", + "\n", + "\n", + "# settings for the Azure AI Content Safety (AACS) resource\n", + "# we will choose existing AACS resource if it exists, otherwise create a new one\n", + "# name of AACS resource, has to be unique\n", + "\n", + "aacs_name = f\"aacs-image-to-image-{str(uuid4())[:8]}\"\n", + "available_aacs_locations = [\"east us\", \"west europe\"]\n", + "\n", + "# create a new Cognitive Services Account\n", + "kind = \"ContentSafety\"\n", + "aacs_sku_name = \"S0\"\n", + "aacs_location = available_aacs_locations[0]\n", + "\n", + "\n", + "print(\"Available SKUs:\")\n", + "aacs_skus = aacs_client.resource_skus.list()\n", + "print(\"SKU Name\\tSKU Tier\\tLocations\")\n", + "for sku in aacs_skus:\n", + " if sku.kind == \"ContentSafety\":\n", + " locations = \",\".join(sku.locations)\n", + " print(sku.name + \"\\t\\t\" + sku.tier + \"\\t\\t\" + locations)\n", + "\n", + "print(f\"Choose a new AACS resource in {aacs_location} with SKU {aacs_sku_name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.2 Create AACS Resource" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.mgmt.cognitiveservices.models import Account, Sku, AccountProperties\n", + "\n", + "parameters = Account(\n", + " sku=Sku(name=aacs_sku_name),\n", + " kind=kind,\n", + " location=aacs_location,\n", + " properties=AccountProperties(\n", + " custom_sub_domain_name=aacs_name, public_network_access=\"Enabled\"\n", + " ),\n", + ")\n", + "\n", + "\n", + "def find_acs(accounts):\n", + " return next(\n", + " x\n", + " for x in accounts\n", + " if x.kind == \"ContentSafety\"\n", + " and x.location == aacs_location\n", + " and x.sku.name == aacs_sku_name\n", + " )\n", + "\n", + "\n", + "try:\n", + " # check if AACS exists\n", + " aacs = aacs_client.accounts.get(resource_group, aacs_name)\n", + " print(f\"Found existing AACS Account {aacs.name}.\")\n", + "except:\n", + " try:\n", + " # check if there is an existing AACS resource within same resource group\n", + " aacs = find_acs(aacs_client.accounts.list_by_resource_group(resource_group))\n", + " print(\n", + " f\"Found existing AACS Account {aacs.name} in resource group {resource_group}.\"\n", + " )\n", + " except:\n", + " print(f\"Creating AACS Account {aacs_name}.\")\n", + " aacs_client.accounts.begin_create(resource_group, aacs_name, parameters).wait()\n", + " print(\"Resource created.\")\n", + " aacs = aacs_client.accounts.get(resource_group, aacs_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aacs_endpoint = aacs.properties.endpoint\n", + "aacs_resource_id = aacs.id\n", + "aacs_name = aacs.name\n", + "print(\n", + " f\"AACS name is {aacs.name} .\\nUse this name in UAI preparation notebook to create UAI.\"\n", + ")\n", + "print(f\"AACS endpoint is {aacs_endpoint}\")\n", + "print(f\"AACS ResourceId is {aacs_resource_id}\")\n", + "\n", + "aacs_access_key = aacs_client.accounts.list_keys(\n", + " resource_group_name=resource_group, account_name=aacs.name\n", + ").key1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.3 Check if UAI is used (Required for using UAI authentication method)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "uai_id = \"\"\n", + "uai_client_id = \"\"\n", + "if uai_name != \"\":\n", + " from azure.mgmt.msi import ManagedServiceIdentityClient\n", + " from azure.mgmt.msi.models import Identity\n", + "\n", + " try:\n", + " msi_client = ManagedServiceIdentityClient(\n", + " subscription_id=subscription_id,\n", + " credential=credential,\n", + " )\n", + " uai_resource = msi_client.user_assigned_identities.get(resource_group, uai_name)\n", + " uai_id = uai_resource.id\n", + " uai_client_id = uai_resource.client_id\n", + " except Exception as ex:\n", + " print(\"Please run aacs-prepare-uai.ipynb notebook and re-run the cell.\")\n", + " raise ex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Pick a model to deploy\n", + "\n", + "Browse models in the Model Catalog in the AzureML Studio, filtering by the `image-to-image` task. In this example, we use the `stabilityai-stable-diffusion-xl-refiner-1-0` model. If you have opened this notebook for a different model, replace the model name accordingly. This is a pre-trained model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Name of the image-to-image model to be deployed\n", + "model_name = \"stabilityai-stable-diffusion-xl-refiner-1-0\"\n", + "\n", + "try:\n", + " model = registry_ml_client.models.get(name=model_name, label=\"latest\")\n", + "except Exception as ex:\n", + " print(\n", + " f\"No model named {model_name} found in registry. \"\n", + " \"Please check model name present in Azure model catalog\"\n", + " )\n", + " raise ex\n", + "\n", + "print(\n", + " f\"\\n\\nUsing model name: {model.name}, version: {model.version}, id: {model.id} for generating images from image-text.\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.1 Register Model in Workspace\n", + "\n", + "The above retrieved model from `azureml` registry will be registered within the user’s workspace. This registration will maintain the original name of the model, assign a unique version identifier (corresponding to the first field of the UUID), and label it as the “latest” version. Please note that this step take several minutes.\n", + "\n", + "- If the model download fails with timeout issue, you may have to download missing files manually\n", + "- If the following download leads to FileNotFoundError error, it may be due to absolute path length is large. Try to download the model to parent directories to reduce the absolute path length of the model files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "local_model_path = \"local_model\"\n", + "\n", + "registry_ml_client.models.download(\n", + " name=model.name, version=model.version, download_path=local_model_path\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import Model\n", + "from azure.ai.ml.constants import AssetTypes\n", + "import os\n", + "\n", + "local_model = Model(\n", + " path=os.path.join(local_model_path, model.name, \"mlflow_model_folder\"),\n", + " type=AssetTypes.MLFLOW_MODEL,\n", + " name=model.name,\n", + " version=str(uuid4().fields[0]),\n", + " description=\"Model created from local file for image-text to image deployment.\",\n", + ")\n", + "\n", + "model = workspace_ml_client.models.create_or_update(local_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Deploy the model to an online endpoint for real time inference\n", + "Online endpoints give a durable REST API that can be used to integrate with applications that need to use the model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an online endpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Endpoint names need to be unique in a region,\n", + "# hence using uuid (first 8 character) to create unique endpoint name\n", + "\n", + "endpoint_name = (\n", + " f\"safe-image-to-image-{str(uuid4())[:8]}\" # Replace with your endpoint name\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import (\n", + " ManagedOnlineEndpoint,\n", + " IdentityConfiguration,\n", + " ManagedIdentityConfiguration,\n", + ")\n", + "\n", + "# Check if the endpoint already exists in the workspace\n", + "try:\n", + " endpoint = workspace_ml_client.online_endpoints.get(endpoint_name)\n", + " print(\"---Endpoint already exists---\")\n", + "except:\n", + " # Create an online endpoint if it doesn't exist\n", + "\n", + " # Define the endpoint\n", + " endpoint = ManagedOnlineEndpoint(\n", + " name=endpoint_name,\n", + " description=f\"Test endpoint for {model.name}\",\n", + " identity=IdentityConfiguration(\n", + " type=\"user_assigned\",\n", + " user_assigned_identities=[ManagedIdentityConfiguration(resource_id=uai_id)],\n", + " )\n", + " if uai_id != \"\"\n", + " else None,\n", + " )\n", + "\n", + " # Trigger the endpoint creation\n", + " try:\n", + " workspace_ml_client.begin_create_or_update(endpoint).wait()\n", + " print(\"\\n---Endpoint created successfully---\\n\")\n", + " except Exception as err:\n", + " raise RuntimeError(\n", + " f\"Endpoint creation failed. Detailed Response:\\n{err}\"\n", + " ) from err" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a deployment. This step may take a several minutes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize deployment parameters\n", + "\n", + "deployment_name = \"image-to-image-deploy\"\n", + "sku_name = \"STANDARD_NC6S_V3\" # Name of the sku(instance type). Check the model card in catalog to get the most optimal sku for model.\n", + "\n", + "REQUEST_TIMEOUT_MS = 90000\n", + "\n", + "deployment_env_vars = {\n", + " \"CONTENT_SAFETY_ACCOUNT_NAME\": aacs_name,\n", + " \"CONTENT_SAFETY_ENDPOINT\": aacs_endpoint,\n", + " \"CONTENT_SAFETY_KEY\": aacs_access_key if uai_client_id == \"\" else None,\n", + " \"CONTENT_SAFETY_THRESHOLD\": content_severity_threshold,\n", + " \"SUBSCRIPTION_ID\": subscription_id,\n", + " \"RESOURCE_GROUP_NAME\": resource_group,\n", + " \"UAI_CLIENT_ID\": uai_client_id,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.ai.ml.entities import (\n", + " OnlineRequestSettings,\n", + " CodeConfiguration,\n", + " ManagedOnlineDeployment,\n", + " ProbeSettings,\n", + ")\n", + "\n", + "code_configuration = CodeConfiguration(\n", + " code=\"./aacs-scoring-files/score/\", scoring_script=\"score_online.py\"\n", + ")\n", + "\n", + "deployment = ManagedOnlineDeployment(\n", + " name=deployment_name,\n", + " endpoint_name=endpoint_name,\n", + " model=model.id,\n", + " instance_type=sku_name,\n", + " instance_count=1,\n", + " code_configuration=code_configuration,\n", + " environment_variables=deployment_env_vars,\n", + " request_settings=OnlineRequestSettings(request_timeout_ms=REQUEST_TIMEOUT_MS),\n", + " liveness_probe=ProbeSettings(\n", + " failure_threshold=30,\n", + " success_threshold=1,\n", + " period=100,\n", + " initial_delay=500,\n", + " ),\n", + " readiness_probe=ProbeSettings(\n", + " failure_threshold=30,\n", + " success_threshold=1,\n", + " period=100,\n", + " initial_delay=500,\n", + " ),\n", + ")\n", + "\n", + "# Trigger the deployment creation\n", + "try:\n", + " workspace_ml_client.begin_create_or_update(deployment).wait()\n", + " print(\"\\n---Deployment created successfully---\\n\")\n", + "except Exception as err:\n", + " raise RuntimeError(\n", + " f\"Deployment creation failed. Detailed Response:\\n{err}\"\n", + " ) from err" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5. Test the endpoint\n", + "\n", + "We will fetch some sample data from the test dataset and submit to online endpoint for inference.\n", + "\n", + "The sample of input schema for image-to-image task:\n", + "```json\n", + "{\n", + " \"input_data\": {\n", + " \"columns\": [\"prompt\", \"image\"],\n", + " \"data\": [\n", + " {\n", + " \"prompt\": \"sample prompt\",\n", + " \"image\": \"base image1\",\n", + " },\n", + " {\n", + " \"prompt\": \"sample prompt\",\n", + " \"image\": \"base image2\",\n", + " }\n", + " ],\n", + " \"index\": [0, 1]\n", + " }\n", + "}\n", + "```\n", + "> The base string should be in base64 format or publicly accessible urls.\n", + "\n", + "The sample of output schema for image-to-image task:\n", + "```json\n", + "[\n", + " {\n", + " \"generated_image\": \"image1\",\n", + " \"nsfw_content_detected\": None\n", + " },\n", + " {\n", + " \"generated_image\": \"image2\",\n", + " \"nsfw_content_detected\": None\n", + " }\n", + "]\n", + "```\n", + "> - \"nsfw_content_detected\" is not supported for this model.\n", + "> - Generated images \"image1\" and \"image2\" strings are in base64 format." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 5.1 Sample input for safe prompt." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create request json\n", + "import base64\n", + "import json\n", + "\n", + "\n", + "def read_image(image_path: str) -> bytes:\n", + " \"\"\"Reads an image from a file path into a byte array.\"\"\"\n", + " with open(image_path, \"rb\") as f:\n", + " return f.read()\n", + "\n", + "\n", + "base_image = \"inpainting_data/images/dog_on_bench.png\"\n", + "\n", + "request_json = {\n", + " \"input_data\": {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"index\": [0],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image)).decode(\"utf-8\"),\n", + " \"prompt\": \"A cat sitting on a park bench in high resolution.\",\n", + " }\n", + " ],\n", + " }\n", + "}\n", + "\n", + "request_file_name = \"sample_request_data.json\"\n", + "\n", + "with open(request_file_name, \"w\") as request_file:\n", + " json.dump(request_json, request_file)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke the endpoint\n", + "\n", + "response = workspace_ml_client.online_endpoints.invoke(\n", + " endpoint_name=endpoint.name,\n", + " deployment_name=deployment.name,\n", + " request_file=request_file_name,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Visualize the model output\n", + "\n", + "import io\n", + "import base64\n", + "from PIL import Image\n", + "\n", + "generations = json.loads(response)\n", + "for generation in generations:\n", + " print(f\"nsfw content detected: \", generation[\"nsfw_content_detected\"])\n", + " img = Image.open(io.BytesIO(base64.b64decode(generation[\"generated_image\"])))\n", + " display(img)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 5.2 Sample input for un-safe prompt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create request json\n", + "import base64\n", + "import json\n", + "\n", + "\n", + "def read_image(image_path):\n", + " with open(image_path, \"rb\") as f:\n", + " return f.read()\n", + "\n", + "\n", + "base_image = \"inpainting_data/images/dog_on_bench.png\"\n", + "\n", + "request_json = {\n", + " \"input_data\": {\n", + " \"columns\": [\"image\", \"prompt\"],\n", + " \"index\": [0],\n", + " \"data\": [\n", + " {\n", + " \"image\": base64.encodebytes(read_image(base_image)).decode(\"utf-8\"),\n", + " \"prompt\": \"a dog with severed leg and bleeding profusely from deep laceration to the lower extremities, exposing tissues\",\n", + " }\n", + " ],\n", + " }\n", + "}\n", + "\n", + "request_file_name = \"sample_request_data.json\"\n", + "\n", + "with open(request_file_name, \"w\") as request_file:\n", + " json.dump(request_json, request_file)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke the endpoint\n", + "\n", + "response = workspace_ml_client.online_endpoints.invoke(\n", + " endpoint_name=endpoint.name,\n", + " deployment_name=deployment.name,\n", + " request_file=request_file_name,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Model response should be empty because it is blocked by the Azure AI Content Safety (AACS) service.\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6. Clean up resources - delete the online endpoint\n", + "Don't forget to delete the online endpoint, else you will leave the billing meter running for the compute used by the endpoint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "workspace_ml_client.online_endpoints.begin_delete(name=endpoint.name).wait()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/sdk/python/foundation-models/system/inference/text-to-image/safe-text-to-image-inpainting-online-endpoint.ipynb b/sdk/python/foundation-models/system/inference/text-to-image/safe-text-to-image-inpainting-online-endpoint.ipynb index d7aab74b4fc..1b628ed9933 100644 --- a/sdk/python/foundation-models/system/inference/text-to-image/safe-text-to-image-inpainting-online-endpoint.ipynb +++ b/sdk/python/foundation-models/system/inference/text-to-image/safe-text-to-image-inpainting-online-endpoint.ipynb @@ -354,6 +354,7 @@ "source": [ "from azure.ai.ml.entities import Model\n", "from azure.ai.ml.constants import AssetTypes\n", + "import os\n", "\n", "local_model = Model(\n", " path=os.path.join(local_model_path, model.name, \"mlflow_model_folder\"),\n",