diff --git a/how-to-use-azureml/azure-databricks/production-deployment-to-AKS-from-Databricks.ipynb b/how-to-use-azureml/azure-databricks/production-deployment-to-AKS-from-Databricks.ipynb new file mode 100644 index 000000000..f04600352 --- /dev/null +++ b/how-to-use-azureml/azure-databricks/production-deployment-to-AKS-from-Databricks.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"markdown","source":["# Production deployment to AKS from Databricks using Azure ML SDK\n## Train, register model in Databricks, Azure ML, Deploy Azure ML model to AKS\n\nGoal of this notebook is to show the steps involved in deploying the model once it is built, although the example used is using scikit learn the same methodology can be applied to other machine learning models. This python notebook is tested on Databricks runtime 7.3 LTS ML | Spark 3.0.1 | Scala 2.12\n\n## Steps:\n* Train a model (In this notebook scikit learn using sample dataset)\n* Use MLflow to log the model in Databricks.\n* Download MLflow to the local Databricks environment.\n* Register MLflow model to Azure ML workspace\n* Deploy registered model to AKS. \n\n## Setup\n* If you are using a cluster running Databricks Runtime, you must install mlflow library from PyPI. See Cmd 3.\n* If you are using a cluster running Databricks Runtime ML, mlflow library is already installed.\n* Create Azure ML workspace"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"528c6d2b-3c5a-4236-ac61-fa8cc8d4d323"}}},{"cell_type":"markdown","source":["Install the mlflow library. \nThis is required for Databricks Runtime clusters only. If you are using a cluster running Databricks Runtime ML, skip to Cmd 4."],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"9596a0fc-f582-4e66-a457-f9549a700228"}}},{"cell_type":"code","source":["# If you are running Databricks Runtime version 7.1 or above, uncomment this line and run this cell:\n# %pip install mlflow\n# %pip install azureml-sdk[databricks]\n# %pip install azureml-mlflow\n\n# If you are running Databricks Runtime version 6.4 to 7.0, uncomment this line and run this cell:\n# dbutils.library.installPyPI(\"mlflow\")\n# dbutils.library.installPyPI(\"azureml-sdk[databricks]\")\n# dbutils.library.installPyPI(\"azureml-mlflow\")"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"4504cb32-8df6-4114-8e4c-a7ca5feb29ee"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["# %pip install msrest==0.6.21\n# Install this library if you see error/warnings during azureml-sdk import"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"0a7a8ca5-479a-459c-b00c-00341a3060d8"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Import the required libraries."],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"6b1f2b42-7670-4ac6-9f14-40e29cc9c122"}}},{"cell_type":"code","source":["import mlflow\nimport mlflow.sklearn\nfrom mlflow.tracking.client import MlflowClient\nfrom mlflow.entities import ViewType\n\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom numpy import savetxt\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.datasets import load_diabetes\n\nfrom sklearn.ensemble import RandomForestRegressor\nfrom sklearn.metrics import mean_squared_error"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"cec61d6d-ea1a-4d2f-9ee6-625393a24aa5"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["* API reference: [Workspace Class](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.workspace.workspace?view=azure-ml-py)\n* NOTE: To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code received in the output cell to authenticate"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"c3077da7-b452-4f92-8f12-ada71fb3da3b"}}},{"cell_type":"code","source":["import azureml\nfrom azureml.core import Workspace\n\nsubscription_id = \"xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx\" #you should be owner or contributor\nresource_group = \"xxxxxx\" #you should be owner or contributor\nworkspace_name = \"xxxxx\" #your workspace name\nworkspace_region = \"southeastasia\" #your region (if workspace need to be created)\n\nworkspace = Workspace.create(name = workspace_name,\n location = workspace_region,\n resource_group = resource_group,\n subscription_id = subscription_id,\n exist_ok=True)\n# workspace.write_config()"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"d9c8a1be-ad97-4a1d-b0d5-79dafd93bc84"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Import the dataset from scikit-learn and create the training and test datasets.\n* Docs link [sklearn diabetes dataset](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_diabetes.html)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"b3b8e9ea-2046-48e0-bb26-d71c6669e66e"}}},{"cell_type":"code","source":["db = load_diabetes()\nX = db.data\ny = db.target\nX_train, X_test, y_train, y_test = train_test_split(X, y)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"e835da93-ac8c-4250-ab85-608ba2f280a8"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Create a random forest model and log parameters, metrics, and the model using `mlflow.sklearn.autolog()`.\n* Enable autolog() For details about what information is logged with `autolog()`, refer to the [MLflow documentation](https://mlflow.org/docs/latest/index.html)."],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"8123df40-67fa-43d0-97c1-6f608c9f7d61"}}},{"cell_type":"code","source":["mlflow.sklearn.autolog()\n\n# With autolog() enabled, all model parameters, a model score, and the fitted model are automatically logged. \nwith mlflow.start_run():\n \n # Set the model parameters. \n n_estimators = 100\n max_depth = 6\n max_features = 3\n \n # Create and train model.\n rf = RandomForestRegressor(n_estimators = n_estimators, max_depth = max_depth, max_features = max_features)\n rf.fit(X_train, y_train)\n \n # Use the model to make predictions on the test dataset.\n predictions = rf.predict(X_test)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"70fefb47-9af8-49c8-932d-49a0727c1428"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Get the current notebook path using scala dbutils api and pass it to widget. This widget value can then be retrived from python"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"6ffdee56-555e-4ec5-b131-3e439ce4fc90"}}},{"cell_type":"code","source":["%scala\nval notebookPath = dbutils.notebook.getContext.notebookPath.getOrElse(\"\")\ndbutils.widgets.text(\"Current_Notebook_Path\",notebookPath)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"9f643833-410b-4196-8c42-6af35c65d611"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["nb_path = dbutils.widgets.get(\"Current_Notebook_Path\")\nprint(nb_path)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"331b8781-ecee-4076-91aa-dd03a6185014"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["# Get current experiment ID, this is based on the notebook path\nnotebook_path = nb_path\nexp_id = mlflow.get_experiment_by_name(notebook_path).experiment_id\n\nbest_run = MlflowClient().search_runs(\n experiment_ids=exp_id,\n run_view_type=ViewType.ACTIVE_ONLY,\n max_results=1,\n order_by=[\"metrics.areaUnderROC DESC\"]\n)[0]"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"191c74ed-2492-4747-9e0f-66e0c855f049"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["best_run"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"167926ff-8fcc-491c-809f-186d9ceb3823"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["model_name = \"my-model-ss\" # Can be anything \nartifact_path = \"model\" # Folder where the model is saved, Recommended to keep it as model\nmodel_stage = 'Staging' # model stage, default is None, this example we are going to move the model to staging\nmodel_uri = f\"runs:/{best_run.info.run_id}/{artifact_path}\" # use the run_id from the best run\n# artifact_uri = best_run.info.artifact_uri\n# image_name = f\"{model_name}-image\""],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"38cdb950-5ff7-49c4-84cd-ea23cb5ae6d4"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["import mlflow.azureml\nfrom mlflow.azureml import mlflow_register_model\nfrom azureml.core.model import Model\nfrom mlflow.store.artifact.models_artifact_repo import ModelsArtifactRepository\nfrom mlflow.tracking.client import MlflowClient\nimport os\n\nclient = MlflowClient()\n\n# Register the model in Databricks, NOTE: It might take upto 300 sec for it to be register.\ndatabricks_mlflow_model = mlflow_register_model(name=model_name, model_uri=model_uri)\n\n# Moving the model to Staging in Databricks MLFlow\nclient.transition_model_version_stage(\n name=databricks_mlflow_model.name,\n version=databricks_mlflow_model.version,\n stage=model_stage,\n) \n\nmodel_path = f\"models:/{model_name}/{model_stage}\"\n\nos.makedirs(\"model\", exist_ok=True)\n\nlocal_path = ModelsArtifactRepository(model_path).download_artifacts(\"\",dst_path=\"model\") # Do not change the dst_path\n\nazureml_model =Model.register(workspace=workspace,\n model_path=local_path,\n model_name=model_name,\n description=\"Test model registry\")"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"282d8be4-0523-41c0-a1ef-e6be2776b88c"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Un-comment the below cell to create a new AKS cluster"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"71f86965-7822-4617-8c1c-293a132a45d3"}}},{"cell_type":"code","source":["# Create new AKS compute\n# from azureml.core.compute import AksCompute, ComputeTarget\n# prov_config = AksCompute.provisioning_configuration()\n# aks_name = 'aks-mlflow'\n\n# # Create the cluster\n# aks_target = ComputeTarget.create(workspace=workspace, \n# name=aks_name, \n# provisioning_configuration=prov_config)\n\n# aks_target.wait_for_completion(show_output = True)\n\n# print(aks_target.provisioning_state)\n# print(aks_target.provisioning_errors)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"56611ba7-d33f-4497-b9a5-96b276a769f3"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Use existing AKS cluster (NOTE: you need to add this AKS cluster as inference target in Azure ML)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"bbf3492a-d249-420f-8f93-cc49c7492af9"}}},{"cell_type":"code","source":["# Connect to existing AKS\nfrom azureml.core.compute import AksCompute, ComputeTarget\n\n# Give the cluster a local name\naks_compute_name = \"aks-mlflow\"\n\naks_target = AksCompute(workspace,aks_compute_name)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"9a054a6b-47a2-454e-86bc-619e6d4bec13"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Sample `score.py` script for the entry point to the container"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"58933c14-a3e8-4c79-b8ac-2299dde286b4"}}},{"cell_type":"code","source":["score_py = \"\"\"import joblib\nimport numpy as np\nimport os\n\nfrom inference_schema.schema_decorators import input_schema, output_schema\nfrom inference_schema.parameter_types.numpy_parameter_type import NumpyParameterType\n\n\n# The init() method is called once, when the web service starts up.\n#\n# Typically you would deserialize the model file, as shown here using joblib,\n# and store it in a global variable so your run() method can access it later.\ndef init():\n global model\n\n # The AZUREML_MODEL_DIR environment variable indicates\n # a directory containing the model file you registered.\n model_filename = 'model.pkl'\n model_path = os.path.join(os.environ['AZUREML_MODEL_DIR'],'model',model_filename)\n model = joblib.load(model_path)\n\n\n# The run() method is called each time a request is made to the scoring API.\n#\n# Shown here are the optional input_schema and output_schema decorators\n# from the inference-schema pip package. Using these decorators on your\n# run() method parses and validates the incoming payload against\n# the example input you provide here. This will also generate a Swagger\n# API document for your web service.\n@input_schema('data', NumpyParameterType(np.array([[0.1, 1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8, 8.9, 9.0]])))\n@output_schema(NumpyParameterType(np.array([4429.929236457418])))\ndef run(data):\n # Use the model object loaded by init().\n result = model.predict(data)\n\n # You can return any JSON-serializable object.\n return result.tolist()\n\"\"\"\n\nwith open('score.py','w') as f:\n f.write(score_py)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"e7fa577c-8a93-403c-89df-1249d564b77c"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Deployment to AKS can take upto 10 min, if this step take longer than that troubleshoot using the api [link](https://aka.ms/debugimage#dockerlog) or from Azure ML deploymnet logs"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"948b2f94-d218-4342-b24b-1e0892ef454a"}}},{"cell_type":"code","source":["from azureml.core.model import InferenceConfig\nfrom azureml.core.webservice import Webservice, AksWebservice\nfrom azureml.core.environment import Environment,CondaDependencies\nfrom azureml.core.model import InferenceConfig, Model\nfrom azureml.core.webservice.aks import AksServiceDeploymentConfiguration\n\n# Choosing AzureML-Minimal and customizing as required ( see the last cell to display available environments)\nenv = Environment.get(workspace=workspace, name=\"AzureML-Minimal\")\ncurated_clone = env.clone(\"customize_curated\")\n\nconda_dep_pkgs=['joblib','scikit-learn']\npip_pkgs=['azureml-defaults', 'inference-schema']\nconda_dep = CondaDependencies()\n\n# Install additional packages as required\nfor conda_dep_pkg in conda_dep_pkgs:\n conda_dep.add_conda_package(conda_package=conda_dep_pkg)\n\nfor pip_pkg in pip_pkgs:\n conda_dep.add_pip_package(pip_package=pip_pkg)\n\ncurated_clone.python.conda_dependencies=conda_dep\n\nprod_webservice_name = \"diabetes-model-prod\"\nprod_webservice_deployment_config = AksWebservice.deploy_configuration()\n\n# NOTE: score.py is created in the previous cell and save to driver local path\ninference_config = InferenceConfig(entry_script='score.py', environment=curated_clone)\n\nservice = Model.deploy(workspace=workspace,\n name=prod_webservice_name,\n models=[azureml_model],\n inference_config=inference_config,\n deployment_config=prod_webservice_deployment_config,\n deployment_target = aks_target,\n overwrite=True)\n\nservice.wait_for_deployment(show_output=True)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"5347ca65-d6f6-4203-ae4b-d190e7daa5b7"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["print(service.get_logs())"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"5b8d22c6-7e27-4e68-b812-6ce804e3a60a"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"markdown","source":["Print the scoring url ( NOTE: This is also available from Azure ML studio under Endpoints section)"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"9918652f-3170-4ab6-ac41-3722674c5336"}}},{"cell_type":"code","source":["service.scoring_uri"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"a53aae37-c066-44c2-b411-bec3eb97220f"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["# envs = Environment.list(workspace=workspace)\n# # Print list of environments available in Azure ML\n# for env in envs:\n# if env.startswith(\"AzureML\"):\n# print(\"Name\",env)\n# print(\"packages\", envs[env].python.conda_dependencies.serialize_to_string())"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"d2aa93d9-228a-4484-9ea0-b5790eb52b25"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0},{"cell_type":"code","source":["# Remove the widgets\ndbutils.widgets.removeAll()"],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"cb6fe12a-f238-4a45-b52a-ca879b9b193d"}},"outputs":[{"output_type":"display_data","metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}},"output_type":"display_data","data":{"text/html":[""]}}],"execution_count":0}],"metadata":{"application/vnd.databricks.v1+notebook":{"notebookName":"production-deployment-to-AKS-from-Databricks","dashboards":[],"notebookMetadata":{"pythonIndentUnit":2,"experimentId":"2379837548404309"},"language":"python","widgets":{},"notebookOrigID":2379837548404309}},"nbformat":4,"nbformat_minor":0} diff --git a/how-to-use-azureml/deployment/production-deploy-r-model-to-aks/README.md b/how-to-use-azureml/deployment/production-deploy-r-model-to-aks/README.md new file mode 100644 index 000000000..0c47b4bb7 --- /dev/null +++ b/how-to-use-azureml/deployment/production-deploy-r-model-to-aks/README.md @@ -0,0 +1,39 @@ +# Deploy R model using AzureML and AzureML Python SDK on Databricks + +## 1. Solution Overview + +Sample notebook in this repo `notebook.ipynb` provide a simple way to deploy R model using AzureML and AzureML Python SDK on Databrick or other Azure ML compute. + +## 1.1 Scope + +The scope of this notebook is to deploy R model using AzureML and AzureML Python SDK on Databricks.This notebook assume you have R model is already trained and ready to deploy. This notebook assumes you have pre-processing R script and R model file ( rds ). `rpy2` python package is leveraged to read incoming http requests and respond to the request. Autenthication to AzureML is done using interactive login, but Service priciple can also be used [reference](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.authentication.serviceprincipalauthentication?view=azure-ml-py) + +### 1.2 Tools/Libraries used + +1. AzureML Create & use software environments in Azure Machine Learning [Link](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-use-environments) + +2. Deploy a model to an Azure Kubernetes Service cluster [Link](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-deploy-azure-kubernetes-service?tabs=python) + +3. R and pandas data frames [Link](https://rpy2.github.io/doc/v3.0.x/html/generated_rst/pandas.html) + +## 2. How to use this notebook + +Import this `notebook.ipynb` file to Databricks and update relevant portions highlighted in the notebook to match your environment. + +### 2.1 Prerequisites + +This notebook assumes the environment is setup as follows: + +1. Databricks workspace is deployed and a cluster to run the notebook is created. + +2. R model is trained either on Datbricks cluster or on Azure ML compute. + +3. AzureML workspace is created + +4. AKS cluster is created and linked to AzureML workspace + +5. Service principal details with permission to access AzureML workspace and any other data sources. + +### 3. Next Steps + +Provide steps to deploy using AzureML CLI diff --git a/how-to-use-azureml/deployment/production-deploy-r-model-to-aks/production-deploy-r-model-to-aks.ipynb b/how-to-use-azureml/deployment/production-deploy-r-model-to-aks/production-deploy-r-model-to-aks.ipynb new file mode 100644 index 000000000..fd808113c --- /dev/null +++ b/how-to-use-azureml/deployment/production-deploy-r-model-to-aks/production-deploy-r-model-to-aks.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":null,"source":["%pip install azureml-sdk[databricks]\n","%pip install azureml-mlflow\n","%pip install rpy2"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"0a5e8e30-f7a1-43ed-9331-6179a063784c"}}},{"cell_type":"code","execution_count":null,"source":["import pandas as pd\n","import numpy as np\n","import mlflow\n","import mlflow.azureml\n","from azureml.core import Workspace\n","from azureml.core.authentication import InteractiveLoginAuthentication\n","from azureml.core.compute import AksCompute, ComputeTarget\n","from azureml.core.webservice import AksWebservice\n","from random import randint\n","from azureml.core.environment import Environment,CondaDependencies\n","from azureml.core.webservice import Webservice, AksWebservice\n","from azureml.core.model import InferenceConfig, Model\n","from azureml.core.webservice.aks import AksServiceDeploymentConfiguration\n","from azureml.core.resource_configuration import ResourceConfiguration\n","\n","interactive_auth = InteractiveLoginAuthentication(tenant_id=\"xxxxx\")\n","subscription_id = \"xxxxx\" #you should be owner or contributor\n","resource_group = \"xxxxx\" #Resource group name. NOTE: you should be owner or contributor\n","workspace_name = \"xxxxx\" #AzureML workspace name\n","aks_compute_name = \"xxxxx\"\n","experiment_name = \"xxxxx\" # Cab be any name and will be displayed in the Azure ML UI\n","workspace_region = 'xxxxx'\n","\n","workspace = Workspace.get(name = workspace_name,\n"," location = workspace_region,\n"," resource_group = resource_group,\n"," subscription_id = subscription_id,\n"," auth=interactive_auth)\n","\n","mlflow.set_tracking_uri(workspace.get_mlflow_tracking_uri())\n","mlflow.set_experiment(experiment_name)\n","aks_target = AksCompute(workspace,aks_compute_name)"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"7fc9116b-809e-494b-9eea-650dfa16cd2d"}}},{"cell_type":"code","execution_count":null,"source":["# Sample R function to use be used as part of scoring function\n","# -----------------------------------------------------------\n","# Replace this function with your R pre-processing steps\n","# -----------------------------------------------------------\n","\n","r_init_code = \"\"\"fahrenheit_to_celsius <- function(temp_F) {\n"," temp_C <- (temp_F - 32) * 5 / 9\n"," return(temp_C)\n","}\"\"\"\n","\n","with open('score.R','w') as f:\n"," f.write(r_init_code)"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"a2bb4176-3865-47ed-ab94-be801db239e2"}}},{"cell_type":"code","execution_count":null,"source":["# Specify customer Dockerfile to build environment with R. Note R package needs to be compiled with --enable-R-shlib option\n","dockerfile = r\"\"\"FROM mcr.microsoft.com/azureml/openmpi3.1.2-ubuntu18.04\n","RUN mkdir /usr/share/man/man1 && apt-get update && apt-get install --no-install-recommends -y openjdk-8-jdk\n","ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/\n","ENV DEBIAN_FRONTEND=noninteractive\n","RUN apt-get update \\\n"," && apt-get install --yes \\\n"," libssl-dev \\\n"," libfuse-dev \\\n"," python3 python3-pip \\\n"," wget \\\n"," openjdk-8-jdk \\\n"," build-essential gfortran libreadline-dev libxml2-dev libcurl4-openssl-dev libpcre2-dev libbz2-dev liblzma-dev \\\n"," && apt-get clean \\\n"," && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n","# Conda Environment\n","ENV MINICONDA_VERSION py37_4.9.2\n","ENV PATH /opt/miniconda/bin:$PATH\n","RUN wget -qO /tmp/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh && \\\n"," bash /tmp/miniconda.sh -bf -p /opt/miniconda && \\\n"," conda clean -ay && \\\n"," rm -rf /opt/miniconda/pkgs && \\\n"," rm /tmp/miniconda.sh && \\\n"," find / -type d -name __pycache__ | xargs rm -rf\n","RUN wget -qO R-4.0.4.tar.gz https://cran.r-project.org/src/base/R-4/R-4.0.4.tar.gz && \\\n"," tar -xvf R-4.0.4.tar.gz && \\\n"," cd R-4.0.4 && \\\n"," ./configure --enable-R-shlib --with-x=no --without-recommended-packages && \\\n"," make -j4 && make install && rm -rf /R-4.0.4.tar.gz\n","ENV LD_LIBRARY_PATH=\"/usr/local/lib/R/lib:$LD_LIBRARY_PATH\"\n","RUN ldconfig\n","RUN R -e \"install.packages(c('dplyr'), repos = 'https://cloud.r-project.org/')\"\n","RUN R -e \"install.packages(c('conflicted'), repos = 'https://cloud.r-project.org/')\"\n","\"\"\"\n","with open('Dockerfile', 'w') as f:\n"," f.write(dockerfile)"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"d0677cbb-0832-4d72-abb4-8e23079a7424"}}},{"cell_type":"code","execution_count":null,"source":["# Python entry script to translate pandas df to R df and vice-versa\n","# -----------------------------------------------------------\n","# Replace run function with your python pre-processing steps. e.g. parse input json etc\n","# Note: both R pre-process script and R rds file are loaded as models\n","# -----------------------------------------------------------\n","score_py = \"\"\"import readline\n","import joblib\n","import os\n","import json\n","import numpy as np\n","import pandas as pd\n","import rpy2\n","import rpy2.robjects as ro\n","from rpy2.robjects.packages import importr\n","from rpy2.robjects import pandas2ri\n","from rpy2.robjects.conversion import localconverter\n","from inference_schema.schema_decorators import input_schema, output_schema\n","from inference_schema.parameter_types.numpy_parameter_type import NumpyParameterType\n","from azureml.core.model import Model\n","\n","# The init() method is called once, when the web service starts up.\n","#\n","# Typically you would deserialize the model file, as shown here using joblib,\n","# and store it in a global variable so your run() method can access it later.\n","def init():\n"," global r_model_path,r_entry_script, score_function_r\n","\n"," # The AZUREML_MODEL_DIR environment variable indicates\n"," # a directory containing the model file you registered.\n"," r_model_path = Model.get_model_path(model_name='my-r-model') \n"," r_entry_script = Model.get_model_path(model_name='my-r-script')\n"," \n"," # Defining the R script and loading the instance in Python\n"," r = ro.r\n"," r['source'](r_entry_script)\n"," # Loading the function we have defined in R.\n"," score_function_r = ro.globalenv['fahrenheit_to_celsius']\n","\n","# The run() method is called each time a request is made to the scoring API.\n","#\n","# Shown here are the optional input_schema and output_schema decorators\n","# from the inference-schema pip package. Using these decorators on your\n","# run() method parses and validates the incoming payload against\n","# the example input you provide here. This will also generate a Swagger\n","# API document for your web service.\n","@input_schema('data', NumpyParameterType(np.array([100])))\n","@output_schema(NumpyParameterType(np.array([1])))\n","def run(data):\n"," # sample data\n"," pd_df = pd.DataFrame(np.array([100]))\n"," with localconverter(ro.default_converter + pandas2ri.converter):\n"," r_from_pd_df = ro.conversion.py2rpy(pd_df)\n","\n"," # r_from_pd_df\n"," df_result_r = score_function_r(r_from_pd_df)\n","\n"," with localconverter(ro.default_converter + pandas2ri.converter):\n"," pd_from_r_df = ro.conversion.rpy2py(df_result_r)\n","\n"," return pd_from_r_df.to_json()\"\"\"\n","\n","with open('score.py','w') as f:\n"," f.write(score_py)"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"c6ce1e4b-4d43-4069-8835-92c3477854e2"}}},{"cell_type":"code","execution_count":null,"source":["%r\n","# Generating random data (3000) to be loaded as part of the scoring\n","# -----------------------------------------------------------\n","# Create this class.rds file from your existing R model\n","# -----------------------------------------------------------\n","RandomNum <- runif(3000, 1, 5000)\n","save(RandomNum,file=\"/databricks/driver/class.rds\")"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"906cf3e0-e96b-46ef-915e-491cd48d64c8"}}},{"cell_type":"code","execution_count":null,"source":["# Testing of python rpy2 package\n","# -----------------------------------------------------------\n","# Test locally to check the R pre-process script works as expected\n","# -----------------------------------------------------------\n","import pandas as pd\n","import rpy2.robjects as ro\n","from rpy2.robjects.packages import importr\n","from rpy2.robjects import pandas2ri\n","import json\n","from rpy2.robjects.conversion import localconverter\n","\n","from azureml.core.model import Model\n","r = ro.r\n","r['source'](\"score.R\")\n","# Loading the function we have defined in R.\n","score_function_r = ro.globalenv['fahrenheit_to_celsius']\n","\n","# Create pandas dataframe\n","pd_df = pd.DataFrame(np.array([100]))\n","\n","# Create R datafram from pandas dataframe\n","with localconverter(ro.default_converter + pandas2ri.converter):\n"," r_from_pd_df = ro.conversion.py2rpy(pd_df)\n","\n","# pass R dataframe to R scoring function \n","df_result_r = score_function_r(r_from_pd_df)\n","\n","# Create pandas dataframe from R dataframe\n","with localconverter(ro.default_converter + pandas2ri.converter):\n"," pd_from_r_df = ro.conversion.rpy2py(df_result_r)\n","\n","pd_from_r_df.to_json()"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"4c867f00-323a-4c4b-aa3c-4e8e99fa0ee6"}}},{"cell_type":"code","execution_count":null,"source":["# Create, register and build a custom R image from custome Docker image\n","myrenv_py = Environment(name=\"custom_env_r\")\n","myrenv_py.inferencing_stack_version='latest'\n","myrenv_py.docker.enabled = True\n","myrenv_py.docker.base_image = None\n","myrenv_py.docker.base_dockerfile = \"./Dockerfile\"\n","myrenv_py.register(workspace=workspace)\n","\n","conda_dep_pkgs=['joblib','pandas','tzlocal']\n","pip_pkgs=['azureml-defaults', 'inference-schema','rpy2==3.4.5']\n","conda_dep = CondaDependencies()\n","\n","# Install additional conda packages as required\n","for conda_dep_pkg in conda_dep_pkgs:\n"," conda_dep.add_conda_package(conda_package=conda_dep_pkg)\n","\n","# Install additional pip packages as required\n","for pip_pkg in pip_pkgs:\n"," conda_dep.add_pip_package(pip_package=pip_pkg)\n","\n","myrenv_py.python.conda_dependencies=conda_dep"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"b92cfe46-ca1e-4dc5-83a0-35d0a4c7e80d"}}},{"cell_type":"code","execution_count":null,"source":["# Create webservice and deploy the model to AKS\n","prod_webservice_name = \"r-model-prod-01\"\n","prod_webservice_deployment_config = AksWebservice.deploy_configuration(cpu_cores = 1, memory_gb = 1)\n","inference_config = InferenceConfig(entry_script='score.py',environment=myrenv_py)\n","\n","azureml_r_model = Model.register(workspace=workspace,\n"," model_name='my-r-model', # Name of the registered model in your workspace.\n"," model_path='./class.rds', # Local file to upload and register as a model.\n"," description='R Model RDS',\n"," tags={'area': 'azureml', 'type': 'databricks notebook', 'language': 'R'})\n","\n","azureml_r_entry_script = Model.register(workspace=workspace,\n"," model_name='my-r-script', # Name of the registered model in your workspace.\n"," model_path='./score.R', # Local file to upload and register as a model.\n"," description='R model scoring scrpt',\n"," tags={'area': 'azureml', 'type': 'databricks notebook', 'language': 'R'})\n","\n","service = Model.deploy(workspace=workspace,\n"," name=prod_webservice_name,\n"," models=[azureml_r_model,azureml_r_entry_script],\n"," inference_config=inference_config,\n"," deployment_config=prod_webservice_deployment_config,\n"," deployment_target = aks_target,\n"," overwrite=True)"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"bd749f59-a2b7-4fab-ad28-099c14faa919"}}},{"cell_type":"code","execution_count":null,"source":["# Check the status of the deployment, note first deployment can take upto 20 min, since it includes building a custom docker image\n","print(service.get_logs())"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"ead9be81-39d7-45f9-b0d5-03d4dca54bea"}}},{"cell_type":"code","execution_count":null,"source":["import json\n","import requests\n","\n","scoring_uri = service.scoring_uri\n","key, _ = service.get_keys()\n","headers = {\"Content-Type\": \"application/json\"}\n","headers[\"Authorization\"] = f\"Bearer {key}\"\n","data = {\"data\":[100]}\n","data = json.dumps(data)\n","resp = requests.post(scoring_uri, data=data, headers=headers)\n","print(resp.text)"],"outputs":[{"output_type":"display_data","data":{"text/html":[""]},"metadata":{"application/vnd.databricks.v1+output":{"data":"","errorSummary":"","metadata":{},"errorTraceType":null,"type":"ipynbError","arguments":{}}}}],"metadata":{"application/vnd.databricks.v1+cell":{"title":"","showTitle":false,"inputWidgets":{},"nuid":"a09afdcf-5c36-4986-9858-42ce5f9cf92f"}}}],"metadata":{"application/vnd.databricks.v1+notebook":{"notebookName":"deploy_R_model","dashboards":[],"notebookMetadata":{"pythonIndentUnit":2},"language":"python","widgets":{},"notebookOrigID":3412160021845754},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":2} \ No newline at end of file