From 31512d3d6ec25e64d4195939b0ac036ce74c9b1a Mon Sep 17 00:00:00 2001 From: Jenny So Date: Wed, 9 Dec 2020 14:23:10 -0800 Subject: [PATCH 01/13] Update docs (#339) * Update getting_started.md * Update custom_model.md --- docs/custom_model.md | 2 +- docs/getting_started.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/custom_model.md b/docs/custom_model.md index a554f376..5c7f8f4a 100644 --- a/docs/custom_model.md +++ b/docs/custom_model.md @@ -80,7 +80,7 @@ To disable the evaluation step, either: ## Customize the build agent environment -The DevOps pipeline definitions in the MLOpsPython template run several steps in a Docker container that contains the dependencies required to work through the Getting Started guide. If additional dependencies are required to run your unit tests or generate your Azure ML pipeline, there are a few options: +The DevOps pipeline definitions in the MLOpsPython template run several steps in a Docker container that contains the dependencies required to work through the Getting Started guide. These dependencies may change over time and may not suit your project's needs. To manage your own dependencies, there are a few options: * Add a pipeline step to install dependencies required by unit tests to `.pipelines/code-quality-template.yml`. Recommended if you only have a small number of test dependencies. * Create a new Docker image containing your dependencies. See [docs/custom_container.md](custom_container.md). Recommended if you have a larger number of dependencies, or if the overhead of installing additional dependencies on each run is too high. diff --git a/docs/getting_started.md b/docs/getting_started.md index 7a311cf8..52bb04d6 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -389,6 +389,7 @@ To remove the resources created for this project, use the [/environment_setup/ia ## Next Steps: Integrating your project - The [custom model](custom_model.md) guide includes information on bringing your own code to this repository template. +- We recommend using a [custom container](custom_model.md#customize-the-build-agent-environment) to manage your pipeline environment and dependencies. The container provided with the getting started guide may not be suitable or up to date with your project needs. - Consider using [Azure Pipelines self-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&tabs=browser#install) to speed up your Azure ML pipeline execution. The Docker container image for the Azure ML pipeline is sizable, and having it cached on the agent between runs can trim several minutes from your runs. ### Additional Variables and Configuration From aba8aefc4b340f89707f0abcc82528d5b032c92d Mon Sep 17 00:00:00 2001 From: "SATO Naoki (Neo)" Date: Wed, 17 Feb 2021 06:20:23 +0900 Subject: [PATCH 02/13] development_setup.md update (#349) * development_setup.md update development_setup.md updated to use install_requirements.sh. See #158: > Use conda rather than pip packages when possible (as recommended in AML docs). > Dev environment is hence also constrained to conda (no more pip install -r requirements.txt). * Content of install_requirements.sh deleted * build_train_pipeline.py filename fixed * build_train_pipeline.py filename fixed --- docs/development_setup.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/development_setup.md b/docs/development_setup.md index 68e6b6bf..1c8c2479 100644 --- a/docs/development_setup.md +++ b/docs/development_setup.md @@ -10,19 +10,12 @@ In order to configure the project locally, create a copy of `.env.example` in th [Install the Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). The Azure CLI will be used to log you in interactively. -Create a virtual environment using [venv](https://docs.python.org/3/library/venv.html), [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv). +Install [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html). -Here is an example for setting up and activating a `venv` environment with Python 3: +Install the required Python modules. [`install_requirements.sh`](https://github.com/microsoft/MLOpsPython/blob/master/environment_setup/install_requirements.sh) creates and activates a new conda environment with required Python modules. ``` -python3 -mvenv .venv -source .venv/bin/activate -``` - -Install the required Python modules in your virtual environment. - -``` -pip install -r environment_setup/requirements.txt +. environment_setup/install_requirements.sh ``` ### Running local code @@ -30,11 +23,11 @@ pip install -r environment_setup/requirements.txt To run your local ML pipeline code on Azure ML, run a command such as the following (in bash, all on one line): ``` -export BUILD_BUILDID=$(uuidgen); python ml_service/pipelines/build_train_pipeline.py && python ml_service/pipelines/run_train_pipeline.py +export BUILD_BUILDID=$(uuidgen); python ml_service/pipelines/diabetes_regression_build_train_pipeline.py && python ml_service/pipelines/run_train_pipeline.py ``` BUILD_BUILDID is a variable used to uniquely identify the ML pipeline between the -`build_train_pipeline.py` and `run_train_pipeline.py` scripts. In Azure DevOps it is +`diabetes_regression_build_train_pipeline.py` and `run_train_pipeline.py` scripts. In Azure DevOps it is set to the current build number. In a local environment, we can use a command such as `uuidgen` so set a different random identifier on each run, ensuring there are no collisions. From 771c8ef1de199cbb77eb5fd95ebf2ad0ec3ca256 Mon Sep 17 00:00:00 2001 From: "SATO Naoki (Neo)" Date: Fri, 19 Feb 2021 04:27:36 +0900 Subject: [PATCH 03/13] fix TRAIN_SCRIPT_PATH value in .env.example (#348) TRAIN_SCRIPT_PATH value updated from 'training/train.py' to 'training/train_aml.py'. This is aligned with /.pipelines/diabetes_regression-variables-template.yml. --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 3c63d969..47311d1e 100644 --- a/.env.example +++ b/.env.example @@ -25,7 +25,7 @@ AML_CLUSTER_PRIORITY = 'lowpriority' # Training Config MODEL_NAME = 'diabetes_regression_model.pkl' MODEL_VERSION = '1' -TRAIN_SCRIPT_PATH = 'training/train.py' +TRAIN_SCRIPT_PATH = 'training/train_aml.py' # AML Pipeline Config @@ -78,4 +78,4 @@ SCORING_DATASTORE_INPUT_FILENAME = 'diabetes_scoring_input.csv' SCORING_DATASTORE_OUTPUT_CONTAINER = 'output' SCORING_DATASTORE_OUTPUT_FILENAME = 'diabetes_scoring_output.csv' SCORING_DATASET_NAME = 'diabetes_scoring_ds' -SCORING_PIPELINE_NAME = 'diabetes-scoring-pipeline' \ No newline at end of file +SCORING_PIPELINE_NAME = 'diabetes-scoring-pipeline' From 2f905b25eb06de673b000450f58520c754577d1a Mon Sep 17 00:00:00 2001 From: Daniel Heinze Date: Thu, 11 Mar 2021 19:22:11 +0100 Subject: [PATCH 04/13] Added AKS compute name details (#355) Added a clarification, that the AKS compute name comes from the inference cluster, which needs to be created to continue. --- docs/getting_started.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/getting_started.md b/docs/getting_started.md index 52bb04d6..7a2c25f0 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -313,15 +313,15 @@ Keep the Azure Container Instances deployment active because it's a lightweight In the Variables tab, edit your variable group (`devopsforai-aml-vg`). In the variable group definition, add these variables: -| Variable Name | Suggested Value | -| ------------------- | --------------- | -| AKS_COMPUTE_NAME | aks | -| AKS_DEPLOYMENT_NAME | mlops-aks | - -Set **AKS_COMPUTE_NAME** to the _Compute name_ of the Inference Cluster that references the Azure Kubernetes Service cluster in your Azure ML Workspace. +| Variable Name | Suggested Value | Description | +| ------------------- | --------------- | ----------- | +| AKS_COMPUTE_NAME | aks | The Compute name of the inference cluster, created in the Azure ML Workspace (ml.azure.com). This connection has to be created manually before setting the value! | +| AKS_DEPLOYMENT_NAME | mlops-aks | The name of the deployed aks cluster in your subscripttion. | After successfully deploying to Azure Container Instances, the next stage will deploy the model to Kubernetes and run a smoke test. +Set **AKS_COMPUTE_NAME** to the _Compute name_ of the Inference Cluster that references the Azure Kubernetes Service cluster in your Azure ML Workspace. + ![build](./images/multi-stage-aci-aks.png) Consider enabling [manual approvals](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/approvals) before the deployment stages. From 2892680a58e49806478f045016ae2b61415c754d Mon Sep 17 00:00:00 2001 From: Jenny So Date: Thu, 11 Mar 2021 10:38:53 -0800 Subject: [PATCH 05/13] Pin AzureML SDK > 1.18.0 for pyyaml fix (#356) * Update conda_dependencies_scoring.yml * Update conda_dependencies_scorecopy.yml * Update conda_dependencies_scoring.yml --- diabetes_regression/conda_dependencies_scorecopy.yml | 2 +- diabetes_regression/conda_dependencies_scoring.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/diabetes_regression/conda_dependencies_scorecopy.yml b/diabetes_regression/conda_dependencies_scorecopy.yml index dffafd08..2d73f4fe 100644 --- a/diabetes_regression/conda_dependencies_scorecopy.yml +++ b/diabetes_regression/conda_dependencies_scorecopy.yml @@ -25,7 +25,7 @@ dependencies: - pip: # Base AzureML SDK - - azureml-sdk==1.6.* + - azureml-sdk>1.18.0 # Score copying deps - azure-storage-blob diff --git a/diabetes_regression/conda_dependencies_scoring.yml b/diabetes_regression/conda_dependencies_scoring.yml index 60c45c44..748cf6a5 100644 --- a/diabetes_regression/conda_dependencies_scoring.yml +++ b/diabetes_regression/conda_dependencies_scoring.yml @@ -25,7 +25,7 @@ dependencies: - pip: # Base AzureML SDK - - azureml-sdk==1.6.* + - azureml-sdk>1.18.0 # Scoring deps - scikit-learn From ae60e489f0c658ba313e6e0020c61b40ffe3bdc9 Mon Sep 17 00:00:00 2001 From: Jenny So Date: Thu, 13 May 2021 01:17:44 -0700 Subject: [PATCH 06/13] Pin azureml version to 1.27.* (#361) * Update ci_dependencies.yml * Update diabetes_regression-cd.yml * Update diabetes_regression-package-model-template.yml * Update diabetes_regression-publish-model-artifact-template.yml * Update conda_dependencies.yml * Update conda_dependencies_scorecopy.yml * Update conda_dependencies_scoring.yml --- .pipelines/diabetes_regression-cd.yml | 4 ++-- .pipelines/diabetes_regression-package-model-template.yml | 2 +- .../diabetes_regression-publish-model-artifact-template.yml | 2 +- diabetes_regression/ci_dependencies.yml | 2 +- diabetes_regression/conda_dependencies.yml | 4 ++-- diabetes_regression/conda_dependencies_scorecopy.yml | 2 +- diabetes_regression/conda_dependencies_scoring.yml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pipelines/diabetes_regression-cd.yml b/.pipelines/diabetes_regression-cd.yml index 8dd35e47..a691cc47 100644 --- a/.pipelines/diabetes_regression-cd.yml +++ b/.pipelines/diabetes_regression-cd.yml @@ -49,7 +49,7 @@ stages: azureSubscription: '$(WORKSPACE_SVC_CONNECTION)' scriptLocation: inlineScript workingDirectory: $(Build.SourcesDirectory) - inlineScript: 'az extension add -n azure-cli-ml' + inlineScript: 'az extension add --source https://azurecliext.blob.core.windows.net/release/azure_cli_ml-1.27.0-py3-none-any.whl --yes' - task: AzureCLI@1 displayName: "Deploy to ACI (CLI)" inputs: @@ -95,7 +95,7 @@ stages: azureSubscription: '$(WORKSPACE_SVC_CONNECTION)' scriptLocation: inlineScript workingDirectory: $(Build.SourcesDirectory) - inlineScript: 'az extension add -n azure-cli-ml' + inlineScript: 'az extension add --source https://azurecliext.blob.core.windows.net/release/azure_cli_ml-1.27.0-py3-none-any.whl --yes' - task: AzureCLI@1 displayName: "Deploy to AKS (CLI)" inputs: diff --git a/.pipelines/diabetes_regression-package-model-template.yml b/.pipelines/diabetes_regression-package-model-template.yml index 7725b19c..16fc1c1d 100644 --- a/.pipelines/diabetes_regression-package-model-template.yml +++ b/.pipelines/diabetes_regression-package-model-template.yml @@ -17,7 +17,7 @@ steps: azureSubscription: '$(WORKSPACE_SVC_CONNECTION)' scriptLocation: inlineScript workingDirectory: $(Build.SourcesDirectory) - inlineScript: 'az extension add -n azure-cli-ml' + inlineScript: 'az extension add --source https://azurecliext.blob.core.windows.net/release/azure_cli_ml-1.27.0-py3-none-any.whl --yes' - task: AzureCLI@1 displayName: 'Create model package and set IMAGE_LOCATION variable' inputs: diff --git a/.pipelines/diabetes_regression-publish-model-artifact-template.yml b/.pipelines/diabetes_regression-publish-model-artifact-template.yml index 00e45105..d666750d 100644 --- a/.pipelines/diabetes_regression-publish-model-artifact-template.yml +++ b/.pipelines/diabetes_regression-publish-model-artifact-template.yml @@ -6,7 +6,7 @@ steps: azureSubscription: '$(WORKSPACE_SVC_CONNECTION)' scriptLocation: inlineScript workingDirectory: $(Build.SourcesDirectory) - inlineScript: 'az extension add -n azure-cli-ml' + inlineScript: 'az extension add --source https://azurecliext.blob.core.windows.net/release/azure_cli_ml-1.27.0-py3-none-any.whl --yes' - task: AzureCLI@1 inputs: azureSubscription: '$(WORKSPACE_SVC_CONNECTION)' diff --git a/diabetes_regression/ci_dependencies.yml b/diabetes_regression/ci_dependencies.yml index 542db0c4..73086471 100644 --- a/diabetes_regression/ci_dependencies.yml +++ b/diabetes_regression/ci_dependencies.yml @@ -17,7 +17,7 @@ dependencies: - pip: # dependencies with versions aligned with conda_dependencies.yml. - - azureml-sdk + - azureml-sdk==1.27.* # Additional pip dependencies for the CI environment. - pytest==5.4.* diff --git a/diabetes_regression/conda_dependencies.yml b/diabetes_regression/conda_dependencies.yml index 49405c47..e214c7b2 100644 --- a/diabetes_regression/conda_dependencies.yml +++ b/diabetes_regression/conda_dependencies.yml @@ -23,11 +23,11 @@ dependencies: - pip: # Base AzureML SDK - - azureml-sdk + - azureml-sdk==1.27.* # Must match AzureML SDK version. # https://docs.microsoft.com/en-us/azure/machine-learning/concept-environments - - azureml-defaults + - azureml-defaults==1.27.* # Training deps - scikit-learn diff --git a/diabetes_regression/conda_dependencies_scorecopy.yml b/diabetes_regression/conda_dependencies_scorecopy.yml index 2d73f4fe..9ed22ccd 100644 --- a/diabetes_regression/conda_dependencies_scorecopy.yml +++ b/diabetes_regression/conda_dependencies_scorecopy.yml @@ -25,7 +25,7 @@ dependencies: - pip: # Base AzureML SDK - - azureml-sdk>1.18.0 + - azureml-sdk==1.27.* # Score copying deps - azure-storage-blob diff --git a/diabetes_regression/conda_dependencies_scoring.yml b/diabetes_regression/conda_dependencies_scoring.yml index 748cf6a5..e744b369 100644 --- a/diabetes_regression/conda_dependencies_scoring.yml +++ b/diabetes_regression/conda_dependencies_scoring.yml @@ -25,7 +25,7 @@ dependencies: - pip: # Base AzureML SDK - - azureml-sdk>1.18.0 + - azureml-sdk==1.27.* # Scoring deps - scikit-learn From c321a263297d9109860e0371c6c90549dc8306b3 Mon Sep 17 00:00:00 2001 From: Katzmann1983 Date: Tue, 14 Dec 2021 15:15:02 +0100 Subject: [PATCH 07/13] update documentation (#379) Co-authored-by: Jens Humrich --- docs/code_description.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/code_description.md b/docs/code_description.md index d30295e9..81abc78f 100644 --- a/docs/code_description.md +++ b/docs/code_description.md @@ -52,8 +52,8 @@ The repository provides a template with folders structure suitable for maintaini - `.pipelines/code-quality-template.yml` : a pipeline template used by the CI and PR pipelines. It contains steps performing linting, data and unit testing. - `.pipelines/diabetes_regression-ci-image.yml` : a pipeline building a scoring image for the diabetes regression model. - `.pipelines/diabetes_regression-ci.yml` : a pipeline triggered when the code is merged into **master**. It performs linting, data integrity testing, unit testing, building and publishing an ML pipeline. -- `.pipelines/diabetes_regression-cd.yml` : a pipeline triggered when the code is merged into **master** and the `.pipelines/diabetes_regression-ci.yml` completes. It performs linting, data integrity testing, unit testing, building and publishing an ML pipeline. -- `.pipelines/diabetes_regression-package-model-template.yml` : a pipeline triggered when the code is merged into **master**. It deploys the registered model to a target. +- `.pipelines/diabetes_regression-cd.yml` : a pipeline triggered when the code is merged into **master** and the `.pipelines/diabetes_regression-ci.yml` completes. Deploys the model to ACI, AKS or Webapp. +- `.pipelines/diabetes_regression-package-model-template.yml` : Pipeline template that creates a model package and adds the package location to the environment for subsequent tasks to use. - `.pipelines/diabetes_regression-get-model-id-artifact-template.yml` : a pipeline template used by the `.pipelines/diabetes_regression-cd.yml` pipeline. It takes the model metadata artifact published by the previous pipeline and gets the model ID. - `.pipelines/diabetes_regression-publish-model-artifact-template.yml` : a pipeline template used by the `.pipelines/diabetes_regression-ci.yml` pipeline. It finds out if a new model was registered and publishes a pipeline artifact containing the model metadata. - `.pipelines/helm-*.yml` : pipeline templates used by the `.pipelines/abtest.yml` pipeline. @@ -84,11 +84,11 @@ The repository provides a template with folders structure suitable for maintaini ### Evaluation Step -- `diabetes_regression/evaluate/evaluate_model.py` : an evaluating step of an ML training pipeline which registers a new trained model if evaluation shows the new model is more performant than the previous one. +- `diabetes_regression/evaluate/evaluate_model.py` : an evaluating step which cancels the pipeline in case of non-improvement. ### Registering Step -- `diabetes_regression/evaluate/register_model.py` : registers a new trained model if evaluation shows the new model is more performant than the previous one. +- `diabetes_regression/register/register_model.py` : registers a new trained model if evaluation shows the new model is more performant than the previous one. ### Scoring From fa03633cdb562f4d17f478f8ad11d21840e158aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Martins?= Date: Tue, 14 Dec 2021 16:54:49 +0100 Subject: [PATCH 08/13] Revision of getting started guide up to Batch scoring. Also new diagam and fix to ARM template to remove region restrictions. (#387) Co-authored-by: Joao Pedro Martins --- docs/getting_started.md | 84 +++++++++++------- docs/images/aci-in-azure-portal.png | Bin 0 -> 52224 bytes .../arm-templates/cloud-environment.json | 10 --- 3 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 docs/images/aci-in-azure-portal.png diff --git a/docs/getting_started.md b/docs/getting_started.md index 7a2c25f0..3cd1f263 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -36,13 +36,13 @@ If you already have an Azure DevOps organization, create a new project using the ### Install the Azure Machine Learning extension -Install the **Azure Machine Learning** extension to your Azure DevOps organization from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-air-aiagility.vss-services-azureml). +Install the **Azure Machine Learning** extension to your Azure DevOps organization from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-air-aiagility.vss-services-azureml) by clicking "Get it free" and following the steps. The UI will tell you if try to add it and it's already installed. -This extension contains the Azure ML pipeline tasks and adds the ability to create Azure ML Workspace service connections. +This extension contains the Azure ML pipeline tasks and adds the ability to create Azure ML Workspace service connections. The documentation page on the marketplace includes detailed instructions with screenshots on what capabilities it includes. ## Get the code -We recommend using the [repository template](https://github.com/microsoft/MLOpsPython/generate), which effectively forks the repository to your own GitHub location and squashes the history. You can use the resulting repository for this guide and for your own experimentation. +We recommend using the [repository template](https://github.com/microsoft/MLOpsPython/generate), which effectively forks this repository to your own GitHub location and squashes the history. You can use the resulting repository for this guide and for your own experimentation. ## Create a Variable Group for your Pipeline @@ -59,15 +59,14 @@ The variable group should contain the following required variables. **Azure reso | Variable Name | Suggested Value | Short description | | ------------------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | BASE_NAME | [your project name] | Unique naming prefix for created resources - max 10 chars, letters and numbers only | -| LOCATION | centralus | [Azure location](https://azure.microsoft.com/en-us/global-infrastructure/locations/), no spaces | +| LOCATION | centralus | [Azure location](https://azure.microsoft.com/en-us/global-infrastructure/locations/), no spaces. You can list all the region codes by running `az account list-locations -o table` in the Azure CLI | | RESOURCE_GROUP | mlops-RG | Azure Resource Group name | | WORKSPACE_NAME | mlops-AML-WS | Azure ML Workspace name | | AZURE_RM_SVC_CONNECTION | azure-resource-connection | [Azure Resource Manager Service Connection](#create-an-azure-devops-service-connection-for-the-azure-resource-manager) name | | WORKSPACE_SVC_CONNECTION | aml-workspace-connection | [Azure ML Workspace Service Connection](#create-an-azure-devops-azure-ml-workspace-service-connection) name | | ACI_DEPLOYMENT_NAME | mlops-aci | [Azure Container Instances](https://azure.microsoft.com/en-us/services/container-instances/) name | | - -Make sure you select the **Allow access to all pipelines** checkbox in the variable group configuration. +Make sure you select the **Allow access to all pipelines** checkbox in the variable group configuration. To do this, first **Save** the variable group, then click **Pipeline Permissions**, then the button with 3 vertical dots, and then **Open access** button. More variables are available for further tweaking, but the above variables are all you need to get started with this example. For more information, see the [Additional Variables and Configuration](#additional-variables-and-configuration) section. @@ -75,11 +74,11 @@ More variables are available for further tweaking, but the above variables are a **BASE_NAME** is used as a prefix for naming Azure resources and should be unique. When sharing an Azure subscription, the prefix allows you to avoid naming collisions for resources that require unique names, for example, Azure Blob Storage and Registry DNS. Make sure to set BASE_NAME to a unique name so that created resources will have unique names, for example, MyUniqueMLamlcr, MyUniqueML-AML-KV, and so on. The length of the BASE_NAME value shouldn't exceed 10 characters and must contain letters and numbers only. -**LOCATION** is the name of the [Azure location](https://azure.microsoft.com/en-us/global-infrastructure/locations/) for your resources. There should be no spaces in the name. For example, central, westus, westus2. +**LOCATION** is the name of the [Azure location](https://azure.microsoft.com/en-us/global-infrastructure/locations/) for your resources. There should be no spaces in the name. For example, central, westus, northeurope. You can list all the region codes by running `az account list-locations -o table` in the Azure CLI. **RESOURCE_GROUP** is used as the name for the resource group that will hold the Azure resources for the solution. If providing an existing Azure ML Workspace, set this value to the corresponding resource group name. -**WORKSPACE_NAME** is used for creating the Azure Machine Learning Workspace. You can provide an existing Azure ML Workspace here if you've got one. +**WORKSPACE_NAME** is used for creating the Azure Machine Learning Workspace. *While you should be able to provide an existing Azure ML Workspace if you have one, you will run into problems if this has been provisioned manually and the naming of the associated storage account doesn't follow the convention followed in this repo -- as the environment provisioning will try to associate it with a new Storage Account and this is not supported. To avoid these problems, specify a new workspace/unique name.* **AZURE_RM_SVC_CONNECTION** is used by the [Azure Pipeline](../environment_setup/iac-create-environment-pipeline.yml) in Azure DevOps that creates the Azure ML workspace and associated resources through Azure Resource Manager. You'll create the connection in a [step below](#create-an-azure-devops-service-connection-for-the-azure-resource-manager). @@ -96,11 +95,16 @@ The easiest way to create all required Azure resources (Resource Group, Azure ML ### Create an Azure DevOps Service Connection for the Azure Resource Manager -The [IaC provisioning pipeline](../environment_setup/iac-create-environment-pipeline.yml) requires an **Azure Resource Manager** [service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#create-a-service-connection). +The [IaC provisioning pipeline](../environment_setup/iac-create-environment-pipeline.yml) requires an **Azure Resource Manager** [service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#create-a-service-connection). To create one, in Azure DevOps select **Project Settings**, then **Service Connections**, and create a new one, where: -![Create service connection](./images/create-rm-service-connection.png) +- Type is **Azure Resource Manager** +- Authentication method is **Service principal (automatic)** +- Scope level is **Subscription** +- Leave **`Resource Group`** empty after selecting your subscription in the dropdown +- Use the same **`Service Connection Name`** that you used in the variable group you created +- Select **Grant access permission to all pipelines** -Leave the **`Resource Group`** field empty. +![Create service connection](./images/create-rm-service-connection.png) **Note:** Creating the Azure Resource Manager service connection scope requires 'Owner' or 'User Access Administrator' permissions on the subscription. You'll also need sufficient permissions to register an application with your Azure AD tenant, or you can get the ID and secret of a service principal from your Azure AD Administrator. That principal must have 'Contributor' permissions on the subscription. @@ -111,7 +115,9 @@ In your Azure DevOps project, create a build pipeline from your forked repositor ![Build connect step](./images/build-connect.png) -Select the **Existing Azure Pipelines YAML file** option and set the path to [/environment_setup/iac-create-environment-pipeline-arm.yml](../environment_setup/iac-create-environment-pipeline-arm.yml) or to [/environment_setup/iac-create-environment-pipeline-tf.yml](../environment_setup/iac-create-environment-pipeline-tf.yml), depending on if you want to deploy your infrastructure using ARM templates or Terraform: +If you are using GitHub, after picking the option above, you'll be asked to authorize to GitHub and select the repo you forked. Then you'll have to select your forked repository on GitHub under the **Repository Access** section, and click **Approve and Install**. + +After the above, and when you're redirected back to Azure DevOps, select the **Existing Azure Pipelines YAML file** option and set the path to [/environment_setup/iac-create-environment-pipeline-arm.yml](../environment_setup/iac-create-environment-pipeline-arm.yml) or to [/environment_setup/iac-create-environment-pipeline-tf.yml](../environment_setup/iac-create-environment-pipeline-tf.yml), depending on if you want to deploy your infrastructure using ARM templates or Terraform: ![Configure step](./images/select-iac-pipeline.png) @@ -125,11 +131,13 @@ Check that the newly created resources appear in the [Azure Portal](https://port ![Created resources](./images/created-resources.png) +**Note**: If you have other errors, one good thing to check is what you used in the variable names. If you end up running the pipeline multiple times, you may also run into errors and need to delete the Azure services and re-run the pipeline -- this should include a resource group, a KeyVault, a Storage Account, a Container Registry, an Application Insights and a Machine Learning workspace. + ## Create an Azure DevOps Service Connection for the Azure ML Workspace At this point, you should have an Azure ML Workspace created. Similar to the Azure Resource Manager service connection, you need to create an additional one for the Azure ML Workspace. -Create a new service connection to your Azure ML Workspace using the [Machine Learning Extension](https://marketplace.visualstudio.com/items?itemName=ms-air-aiagility.vss-services-azureml) instructions to enable executing the Azure ML training pipeline. The connection name needs to match `WORKSPACE_SVC_CONNECTION` that you set in the variable group above (eg. 'aml-workspace-connection'). +Create a new service connection to your Azure ML Workspace using the [Machine Learning Extension](https://marketplace.visualstudio.com/items?itemName=ms-air-aiagility.vss-services-azureml) instructions to enable executing the Azure ML training pipeline. The connection name needs to match `WORKSPACE_SVC_CONNECTION` that you set in the variable group above (e.g., 'aml-workspace-connection'). ![Created resources](./images/ml-ws-svc-connection.png) @@ -138,23 +146,25 @@ You'll need sufficient permissions to register an application with your Azure AD ## Set up Build, Release Trigger, and Release Multi-Stage Pipelines -Now that you've provisioned all the required Azure resources and service connections, you can set up the pipelines for training (CI) and deploying (CD) your machine learning model to production. Additionally, you can set up a pipeline for batch scoring. +Now that you've provisioned all the required Azure resources and service connections, you can set up the pipelines for training (Continuous Integration - **CI**) and deploying (Continuous Deployment - **CD**) your machine learning model to production. Additionally, you can set up a pipeline for batch scoring. 1. **Model CI, training, evaluation, and registration** - triggered on code changes to master branch on GitHub. Runs linting, unit tests, code coverage, and publishes and runs the training pipeline. If a new model is registered after evaluation, it creates a build artifact containing the JSON metadata of the model. Definition: [diabetes_regression-ci.yml](../.pipelines/diabetes_regression-ci.yml). 1. **Release deployment** - consumes the artifact of the previous pipeline and deploys a model to either [Azure Container Instances (ACI)](https://azure.microsoft.com/en-us/services/container-instances/), [Azure Kubernetes Service (AKS)](https://azure.microsoft.com/en-us/services/kubernetes-service), or [Azure App Service](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-deploy-app-service) environments. See [Further Exploration](#further-exploration) for other deployment types. Definition: [diabetes_regression-cd.yml](../.pipelines/diabetes_regression-cd.yml). - 1. **Note:** Edit the pipeline definition to remove unused stages. For example, if you're deploying to Azure Container Instances and Azure Kubernetes Service only, delete the unused `Deploy_Webapp` stage. + 1. **Note:** Edit the pipeline definition to remove unused stages. For example, if you're deploying to Azure Container Instances and Azure Kubernetes Service only, you'll need to delete the unused `Deploy_Webapp` stage. 1. **Batch Scoring Code Continuous Integration** - consumes the artifact of the model training pipeline. Runs linting, unit tests, code coverage, publishes a batch scoring pipeline, and invokes the published batch scoring pipeline to score a model. -These pipelines use a Docker container on the Azure Pipelines agents to accomplish the pipeline steps. The container image ***mcr.microsoft.com/mlops/python:latest*** is built with [this Dockerfile](../environment_setup/Dockerfile) and has all the necessary dependencies installed for MLOpsPython and ***diabetes_regression***. This image is an example of a custom Docker image with a pre-baked environment. The environment is guaranteed to be the same on any building agent, VM, or local machine. In your project, you'll want to build your own Docker image that only contains the dependencies and tools required for your use case. Your image will probably be smaller and faster, and it will be maintained by your team. +These pipelines use a Docker container on the Azure Pipelines agents to accomplish the pipeline steps. The container image ***mcr.microsoft.com/mlops/python:latest*** is built with [this Dockerfile](../environment_setup/Dockerfile) and has all the necessary dependencies installed for MLOpsPython and ***diabetes_regression***. This image is an example of a custom Docker image with a pre-baked environment. The environment is guaranteed to be the same on any building agent, VM, or local machine. **In your project, you'll want to build your own Docker image that only contains the dependencies and tools required for your use case. Your image will probably be smaller and faster, and it will be maintained by your team.** ### Set up the Model CI, training, evaluation, and registration pipeline -In your Azure DevOps project, create and run a new build pipeline based on the [diabetes_regression-ci.yml](../.pipelines/diabetes_regression-ci.yml) +In your Azure DevOps project, create and run a new build pipeline based on the [./pipelines/diabetes_regression-ci.yml](../.pipelines/diabetes_regression-ci.yml) pipeline definition in your forked repository. If you plan to use the release deployment pipeline (in the next section), you will need to rename this pipeline to `Model-Train-Register-CI`. -Once the pipeline is finished, check the execution result: +**Note**: *To rename your pipeline, after you saved it, click **Pipelines** on the left menu on Azure DevOps, then **All** to see all the pipelines, then click the menu with the 3 vertical dots that appears when you hover the name of the new pipeline, and click it to pick **"Rename/move pipeline"**.* + +Start a run of the pipeline if you haven't already, and once the pipeline is finished, check the execution result. Note that the run can take 20 minutes, with time mostly spent in **Trigger ML Training Pipeline > Invoke ML Pipeline** step. You can track the execution of the AML pipeline by opening the AML Workspace user interface. Screenshots are below: ![Build](./images/model-train-register.png) @@ -162,13 +172,13 @@ And the pipeline artifacts: ![Build](./images/model-train-register-artifacts.png) -Also check the published training pipeline in the **mlops-AML-WS** workspace in [Azure Machine Learning Studio](https://ml.azure.com/): +Also check the published training pipeline in your newly created AML workspace in [Azure Machine Learning Studio](https://ml.azure.com/): ![Training pipeline](./images/training-pipeline.png) Great, you now have the build pipeline for training set up which automatically triggers every time there's a change in the master branch! -After the pipeline is finished, you'll see a new model in the **ML Workspace**: +After the pipeline is finished, you'll also see a new model in the **AML Workspace** model registry section: ![Trained model](./images/trained-model.png) @@ -188,19 +198,26 @@ The pipeline stages are summarized below: - Trigger the _ML Training Pipeline_ and waits for it to complete. - This is an **agentless** job. The CI pipeline can wait for ML pipeline completion for hours or even days without using agent resources. - Determine if a new model was registered by the _ML Training Pipeline_. - - If the model evaluation determines that the new model doesn't perform any better than the previous one, the new model won't register and the _ML Training Pipeline_ will be **canceled**. In this case, you'll see a message in the 'Train Model' job under the 'Determine if evaluation succeeded and new model is registered' step saying '**Model was not registered for this run.**' - - See [evaluate_model.py](../diabetes_regression/evaluate/evaluate_model.py#L118) for the evaluation logic. + - If the model evaluation step of the AML Pipeline determines that the new model doesn't perform any better than the previous one, the new model won't register and the _ML Training Pipeline_ will be **canceled**. In this case, you'll see a message in the 'Train Model' job under the 'Determine if evaluation succeeded and new model is registered' step saying '**Model was not registered for this run.**' + - See [evaluate_model.py](../diabetes_regression/evaluate/evaluate_model.py#L118) for the evaluation logic. This is a simplified test that just looks at MSE to decide whether or not to register a new model. A more realistic verification would also do some error analysis and verify the inferences/error distribution against a test dataset, for example. + - **Note**: *while it's possible to do an Evaluation Step as part of the ADO pipeline, this evaluation is logically part of the work done by Data Scientists, and as such the recommendation is that this step is done as part of the AML Pipeline and not ADO pipelines.* - [Additional Variables and Configuration](#additional-variables-and-configuration) for configuring this and other behavior. #### Create pipeline artifact - Get the info about the registered model -- Create a pipeline artifact called `model` that contains a `model.json` file containing the model information. +- Create an Azure DevOps pipeline artifact called `model` that contains a `model.json` file containing the model information, for example: + +```json +{ "createdTime": "2021-12-14T13:03:24.494748+00:00", "framework": "Custom", "frameworkVersion": null, "id": "diabetes_regression_model.pkl:1", "name": "diabetes_regression_model.pkl", "version": 1 } +``` + +- Here's [more information on Azure DevOps Artifacts](https://docs.microsoft.com/en-us/azure/devops/pipelines/artifacts/build-artifacts?view=azure-devops&tabs=yaml#explore-download-and-deploy-your-artifacts) and where to find them on the ADO user interface. ### Set up the Release Deployment and/or Batch Scoring pipelines --- -**PREREQUISITE** +**PRE-REQUISITES** In order to use these pipelines: @@ -229,14 +246,17 @@ resources: The release deployment and batch scoring pipelines have the following behaviors: -- The pipeline will **automatically trigger** on completion of the Model-Train-Register-CI pipeline for the master branch. -- The pipeline will default to using the latest successful build of the Model-Train-Register-CI pipeline. It will deploy the model produced by that build. +- The pipeline will **automatically trigger** on completion of the `Model-Train-Register-CI` pipeline for the master branch. +- The pipeline will default to using the latest successful build of the `Model-Train-Register-CI` pipeline. It will deploy the model produced by that build. - You can specify a `Model-Train-Register-CI` build ID when running the pipeline manually. You can find this in the url of the build, and the model registered from that build will also be tagged with the build ID. This is useful to skip model training and registration, and deploy/score a model successfully registered by a `Model-Train-Register-CI` build. + - For example, if you navigate to a specific run of your CI pipeline, the URL should be something like `https://dev.azure.com/yourOrgName/yourProjectName/_build/results?buildId=653&view=results`. **653** is the build ID in this case. See the second screenshot below to verify where this number would be used. ### Set up the Release Deployment pipeline -In your Azure DevOps project, create and run a new build pipeline based on the [diabetes_regression-cd.yml](../.pipelines/diabetes_regression-cd.yml) -pipeline definition in your forked repository. +In your Azure DevOps project, create and run a new **build** pipeline based on the [./pipelines/diabetes_regression-cd.yml](../.pipelines/diabetes_regression-cd.yml) +pipeline definition in your forked repository. It is recommended you rename this pipeline to something like `Model-Deploy-CD` for clarity. + +**Note**: *While Azure DevOps supports both Build and Release pipelines, when using YAML you don't usually need to use Release pipelines. This repository assumes the usage only of Build pipelines.* Your first run will use the latest model created by the `Model-Train-Register-CI` pipeline. @@ -244,11 +264,11 @@ Once the pipeline is finished, check the execution result: ![Build](./images/model-deploy-result.png) -To specify a particular build's model, set the `Model Train CI Build Id` parameter to the build Id you would like to use. +To specify a particular build's model, set the `Model Train CI Build Id` parameter to the build ID you would like to use: ![Build](./images/model-deploy-configure.png) -Once your pipeline run begins, you can see the model name and version downloaded from the `Model-Train-Register-CI` pipeline. +Once your pipeline run begins, you can see the model name and version downloaded from the `Model-Train-Register-CI` pipeline. The run time will typically be 5-10 minutes. ![Build](./images/model-deploy-get-artifact-logs.png) @@ -260,6 +280,10 @@ The pipeline has the following stage: - Smoke test - The test sends a sample query to the scoring web service and verifies that it returns the expected response. Have a look at the [smoke test code](../ml_service/util/smoke_test_scoring_service.py) for an example. +- You can verify that an ACI instance was created in the same resource group you specified: + +![Created Resouces ](./images/aci-in-azure-portal.png) + ### Set up the Batch Scoring pipeline In your Azure DevOps project, create and run a new build pipeline based on the [diabetes_regression-batchscoring-ci.yml](../.pipelines/diabetes_regression-batchscoring-ci.yml) diff --git a/docs/images/aci-in-azure-portal.png b/docs/images/aci-in-azure-portal.png new file mode 100644 index 0000000000000000000000000000000000000000..e7bfa8cd5340b9db52a189ced82aad6db54ee0c5 GIT binary patch literal 52224 zcmdSBbx@qavo=Zu2%eDOmf#ZHJwSlq!JQ4ZIE&jNNeJ#N?oROF4uK8s?yk$??tDw~ zJ9W-?>;7@;R(*BrzE#xD?#}kx)6+9OPe1(<^jT3F3!NAp2?+^H=F>+NBqZeLNJvjH zUOYv7!j6R!k9c|Fs3I+nR60lsL%exzCZ-^Ugj61ZachW-c#mfHN!t+#2^;$O_oNH- z(*y~rFIMKGn3|ix?gH9}*P5%MhbwGvi90CCGnng~pWp~J)ahvH_+CCc&40uE=1;}h z&yp;g2@Og8vx6D^4xJA2@634RU*xhyku|92ax*k^l-0s5vDAIvO0~TZr(SU+5WW%N&gQDl(MW&0$4Zq_Vxw} zgH==%V`5?sB~yRT&d!cG9%l`Ic64-XdGSw=46<2re;M2~UROn1Tb99uTbJ#$6WjCe zi!Xe`$z1=M|GSAYpdBFu`HHDJ8XBEtf0c7W^L**YGIow#Wk4+Ge|vkThhIB8GwZ{5 zHPQi@%6II0TDC`5ZAiuCpMdv*{eI^;n(KGJZmyx%00lx#=IrFwwK?>x+Qr>rb@Z48y-VPp4`YPWLepAtx^h}*5$ zvv;$-e|a@nYZjjauKgpUzMJ)A>;mPF#ns%)dTuJJwd*tZt-#~hK0pL^e9b*P8gg?t zYHa2%oxkF<^#ezNEbo|nZKgXy=3L05%H^0<83%-j%RLO2-7-EZURbi=Z&li~`5UtkZ~n z#(?HE>q!nTp4}6zcfzl0*@qo_?9cZKL}KMD80;MEw8oDrZ!?+Jugs!dS`IE+4edu= zPM_B}Z1m;9IzdEK^D*Pk5WiNLoGZa|Q^b~pQV|E=*i!-As z0}0TBbz1vorb3M|Xz_3|^mp@CwF9?EU^{28o!~HT9n0*lsZ=~;w1!XIVsu(nOs=ar zu)2z%Sp?J($13t*vY^K*eIJ&~=9)0fOY+Z@DsIpKn$71d&|o^#pP zK{8hO{BGfPYv{zacC<-a-PC%a%Nv|8#(5Ho^ZM94af9zcT~}nO5xnOKLdEdNrZm+Z zF2zr6RV*(rA82`E-#s^6Zrqh8pVgQs7Sd^b_f zs?oH+$FVqYvA|q@RJ}z(?i0&WC5G4ybdOQ~*gLLKynmcCAs2B?5UXv0c=XIq!1VXZ zRZ^F>&M6zh#?MMXhv?($XTc$0h1dm5fULntF3A^Ht-CUSlgWmn`VzrtvvP76P-K zj~f53l)vO%;+@V558eGF<*VddZIhEK(L^@whhh!%X`uPtvWO~|**7|H;6U-dr)BiY zBn&6(P@Z#_P5!<(pH;uji$wk9)q_#e4W})kue&(Bc6;TA47YDZg62= zumtQd3CyOY-y*P@)dMJqw(Ex#!*!cm?ykxXO=eVV|As_N|KFxNLM+jfG+Koy?^MR zS%hF>?^P>IGIgky8PhASEw!csa_~tye zewJkLT+R9f@6jS`^w9Y{M?7Y~)dnulHEY}{soAlvY|+Ny;qEp8Tn^(2%@c;$0E7{& zIWO7d`I@e9yr)-oBU}Sb6`WrBP{Gz-UZ9o9W9ig=Ih*YM=_`}SU%epfioYSzOvU^- z&aZ8br|9-T0+Rx5Pppz%c_W_2Wzm6bvcc(d z`-)P7UxDIB(Z7FPhMba#4KK}DO*Y|79^88#WM)S%+|Q$==G54n|C&j@@II<efQP-q_0;m0Mjm>7b-uLl!0u4iulzrunlN|GZ**lscf}t64J6nKY30h)vyLF+k>q%0^t8=Qj>VD53Qem;W1WE=_knE%k8f;uIpw&D6WN~=A2 z)!>kQX+2&_Eo{>$7^270>I55&WzxKKzWanrtGl_83H5rM%0WuIqYbI;{=t_NGRC6C z;VFB$6AzB5p*AEfv6D)*6+eDW^yn=_BtBHN9>u>AjiUG%K_M#Qyff~HL2wMej-r;Y z!9+)2oq9qnPFR|6J8fuFb3Qmv@`P&c!sWBp<>C%!VZ%^<_ z=J8O?Wr?X=hIQW>3<*I#oEZm>UmRNR2pBZ-KTPj3sNv^#Ri<0eAbZA^ z4Lh3dCi#B0c_(ZPA8{X#>)QgpbP8o9Sx3h%8&>wGY2s0 zCS+LGz+f>#G7c4o+}hSwakV@s2rc^Y`2RsOsv|)DF5#TYX0w&oMp9pOOUL18Rfc3n zYP=M#S>}zasaDA&eHS9^iS=>4lkg?Pb^Q^w59%8$SAn~a3a0j*H_1_ z^`T*L#V=i^+CAYt6`DlRTTF?raeu>(ts;w(@; zu)ozKcX4)>#P!z@BlO2sZT0>Oiv)CYDFv_IivD>e(>|C90Hq&~yM|2g=#fhyI$D4U zYiep}MG=stZbGqx%ukRKp*j-M%jO?R-=b&-7d`1Wiul7(M(puFpjzAv%L>+jBEp3E zjQ#}17HRPFW39;vK9OlhU=`CF`Sfb*D~Fahe+3|Alpv4~6BE(2AZ$;q@d2!WqJCE9;5Qh>SUe}O(X z;!IwPfAatT02Kcl%7%_xa<~4pzZkUfM*FA#vi6T8!~YtJfaQOnE>fD!Pon>02-i4A zqv%Rc9{z(&NV4#zgmA%YT{92+Mno&)wm8pUUXCirjrQ8)VV!_!`f>WYfc z%TdM1`?sGBS*;O}V?DNPFTGaSjD;8M-R^pgR*FxNL=J} z(yo?n^a6DM{pj!w`!*YC>qA^(Q2AH+&eQypANy;~%z8}?7ceVXjH{lHU#v$5{^Xb` zL6pl9AXq6~t_0_AX>L&hS{phJupfmiC%TH<=9U{95R;@0l&SXKK065PY$C=@ELoQ$ z_gx(i`?f~s7C=LAg6yB2o%XP+zaC3=GFp0FwpZD22eq3VGR4_1?Ef@rnx825o(|=l zP;kbxDt{*C;{7$&4zP0)$x(GEEicDz-svQ2fAh7kM`YNHATcEQjf47qF6yn~Uy})> ziQ{(W#gxx+kQ>jAv$FCFRmLdmg?<`Xk!wi1H7n<9mDNs(xvWfK_2{o~9gVl{A3VAB z*mB2Bd&rMk@n7M5VAum1SzF5I6OcZC_F&@iPV&k|>&ows^g@5w{jQ~~?H;gSk+xI9 zfCFY|5p&gie1nBY*YfjM1^d+rz2kn%x<*eq-TDF6qeaprb73+)^C~G?S<7*0^x;{O zgK?pD>y#0Oo-$LF*F_PUKAd7cp!8OqBT1As8Mpg%DFiqSDP?L*Oh~8*ni}u^q0FhN zRP;%vZOF31+OVhVi!BG-wpfFcrj^~~%JzvwTnVbhXho3BNEqEQMuQo^eU%_SOd|>w zoKv8g@t!ATZufZl!1ly+pf^6;^uXxNz4R-bC$ zozIDJ2yNxjyO5Kkv1nVTnk)2}`wG}|!EX%{_5*e!m$;o`vjBbBgq@&B7@>cV024^d zrwEF4+-IOVk(J^OR~%RHF7K;4UqPt(HIx1m0l*}|uu?L+%e~dMlWk@dlfmaTQkCxt z4}=G3?)QsbmswB0}A2N5WrJek=L+hExx1c&~Lz2M3`_QkL*O48bUkAz6fekpBSf3!_QB1c%=&U`KR zjJs8`H(I@k30M5qVLFG86u@Pv1z{zh?loX*D+l#`c zQz{OB`%^Nd3N~nH$~vfx&2QByI-(kpmmJ;?Aqbf%_gmlU5RAWS?sYL2Y7;a$Gn2WR z^#WC|un542K?QM?bQTA*$YP6YGd7(~JGEcV%vNtz?lr=*-U@rekKc-3G}vSf^}@X@ zAr$)kiwH1X+S#9(Tn^dHz@m^d?wDB}YF|W4;riAX-+ho;{i=t*f!yG(D@$daF2JWd zJ@%&Gbft&cOX@T#gkbEomP5?lFHKJ#piQ?4ua0>7p+8Q2xOmbleb?jHP0yAPfhzmg z;)aic(1WZtaKWof30sdmmX_*bJ$jw|*3*_e+}R>t`@0 zW@D&{ORW5@#9Cy&m*!nO_aRh23~=4m{Q-LTR?rdGb=&zjW-4!v_vvSS{f;;Y);3RQ zq0ZgUX#LEKW!o&#X)HoV4C4w37qSL_M!2=#55tdCe&?Y*Ka@A05lS8R-4zqx0~g+d zulS=9J#L0-lg-BsVhm~$cg;TIM485WtJciqcSY%HD*(hQsQZ>;*@$K~yJQ`Bat+%>dGa1tNO zh(1VrFc;Z<5hvdL2V((nrh9v|GJgA1#B$Weg*kzE{@pVIrrdz|)*Z46#3}{0q$~jn zxK7;GcfMRC!c|SLMNZY%B?m4r(FNbG49UK`8~Qf!Eln=GcY4C5FecYJ0oMFw>&V(2 zSwCOtvJ@Etz!?H+S}{0ME*+oiRut{8$9A|Uc0t#?Ekxp&$~wKaxzAD?ZZvvMR4-aY zxy?0Q#t8H)QL*&MQW-bCGkG<%FC~_1%_jj+&Y$>Ohn7B6Y|3Q8TTderS85K)?&H%i zC3FQG3*wg7n2mFa)ikZ{ck0B|xwAdHS(K})nJ;`7o}AFpVYnbS$cE`TBsOtKiBZjX zpQL*Hhz6|p21GL{bZM%N0GK-?>yp8u$^fN)?pmDtk1j@4UeN;|T7I5XA3g3Dzd4oM z-zmt8#0e<58pHh zqdBG6VZ(T=b9V9gpL_loext~zC&Z9mniWp(6G)9dgf-eW?+|PB=9;~Ein@e%6LF^n zK2>_!Eupa~y_k8_%pXa%Niz zykdO1Ey|08eysa{fA(-NCuB<^>+Z5O_llZ)uC+XQDO&LAta2~8Ij0Pf#$Bq7QpMxz zFZFuUn#O-V0Va3NTDm{9x|_mYl$*bDF^QdO(AtMlvUV-H4^khg<$&gObRoO9Pa2`G z;S1YI@P^m(_d}*$0%3RZU@F@I^zs;X$Gh#}h{Rik(_EjSx$7VcRjWEfw`n=ydZzDa z_oH8IB}+;fHJO-TI(sD%F*}9mD&j@{^$y;JnW6xXT!L2BZQNLbeNb6WSg_Y32;Mhd*&25C$+q#~L(Jo$wdyVPeNAu<_Q4VLgC$>` zLhZY9YQ8?*ladxUQ!N4Dl?qO%%WV5f`RhYaUs`SYk#7iR(~%9(2? znjjN6j+e;NK-wR0xL?T$9B$N_pb-8p%s`2?&sLr^9rhqOuEHuvtc><8Yz{TO*oj6C zPi8gr++ZZmUF*G`&o#A=QMI2;Om(;wvUH}g4dP<8*+ou@bJCS0oXsWj(zlW)jmY|$JL4UNl>SFV6LKeb z1w9E-3l^~;@2dh}OO2WKXQwKT6NabmPm9$XIkomYc}SU)+1%i5E`{)mHFRj>yUcm_VF;T_PiPxf0a z-~*x;lUH{|yXtvZEncJm@JHOki?1esUvJP+WJL8lZ!}n(e&eFFOKcs6^yfzP2(Z-P zH4CR}1WZmXL8n3nIZJCAbE^iLBvpx1qkqqYXxwn(xleI@Q&si|09Z2LC%>8VOOE65 zev{)g+l!MVn?rsgP8^|S%^d~ahK9bsR|miU z*)8s_Jpe^zRJja{`K3AmO$t_-P3LM24DgRE1H_s=6;{Rp(ri)kp#?>HHoa4 zxkf+xqKVBx*Qq9poAn!qAjBJD^v=OB{UdWtwt>Z|m3l;DfSe7U2Xp4$%X2v_*craC z(~Y6+DKDex5?zmyEhEo1o6w&gqnx5&Nq?4!xzQT9;G-K!!8bd-4*o0F3?vEuhr=U@cjaMd!c1A1+S^Fts~ueBM^9TzBb zk>W!@_)jjNJZsdT54wOf`0pWAsK_=&C|V4NHTTJ}E^bu*I*Kcm9XgQKcrH8l`(0Hm zD^)CasjW3sKj~d#TV7u)-%QQnDrc;UJr(PTyvnWFE5Rw27wG5U2~hH&NTsfr`xf@z zVUJ7qiN~}0Smrstvcnb);h^TQQly1iU!9i?(>uLcf60r^Z(p(DotaqLtbO=TO$3%+L*-*&* z(bAV&gs%xj3zdacqJlJ#nbf&)_9xWn)$~qtMb!%f@Tv}#xflHLN5jOEaRD##zNz5ZK)9kMNN<Xk|n)wiqXoXYnpoUuM+b%T&|=fw>2mL-U6YE`Cat ztUIuVwQz5k?gZeBTeIb5#{XIC4XBE=?xZzrph4ymdP1k1Yforow1oEW$7 zo&ml* zv}Zcr_T;FH&wU!tZY%qFN@~+1fA5uWQJ^uX=M3h*=~5Kz$~vJ<;3hXN*mP;v#)A^t zcvJ(rJVEBxMoNkX;GLMiXVRgR+fyp89k*iP==1E4TTX#{NHX||SqmqS#a+7i%@HK7 z{>41|SSynKQOvom8S3PyDS5zf@Hpr^Qo-7{mG5ezQ?g}$@chnCyj1qmP+eCrV)UiX z+$a;V=~O9v?}wanJGn`Mjc%FszJR!3?Q+Pt+6z}4&KzCj=Gw2>#ZSe6|W(m z%$O2^{ijuC4of&9{O+s6vNev!&oIupgk@c(#0c|f-Lzc}jOC4fsi;e=?L(6aWx2J1 zbSghp7%eDt)Ymm!NJ2z9*I8~;Gaaq3ZtBKkp(^=PR#ozpaMtEYX+B>3GNSd;;8g8jhU=Yx2cokEKXUNPD@WJYP`RDy0;bd%Ds+DaW1&2 z--Y+7t5c$s=mPA(T{p^}BdFh6vc&sqgn~~kUJ-E2FQkHYR#taLaFmXVj{O8hb2#$2 zcpVi1ZeNf+)p$=PjUU5{J~t-ehAK{&*Pu(-x-*wwp>y`Dp&?p6qOhl3;uxa4Ot-J0 z1HKIgQ8*CX4-T7_#!%$M=H{WsPcLAM@t@5}kR1N3eVE<#t7*QYO|t0J@_0|h=)C)H z9RwaXt<&!f%5aU81ntP`UQA81gVv~ZEcn1vJbOGFp9lBD{ag&HTFl8D3S^KVKU!@}ZVr zRp$XI-3}_WIbtwb;ky5_k65#qzEX5f>gIa?!$sl#iJa1FYJo+70nA2>DZq+Rol#(9 zbC~NQ8nYkei$J+ej`{*~SWoBeLIzw-#=A5F%O6$y5_8v(VVqz4CN$@615e-kt;PP{ zh^HE>OK5IDwCb_1zG#+;^j{3@2Bg1d0{R9AbY0RW@1I{BW*V<^97(H^Ojgd!Ui8fq zHoUuUlJCiVn=ACN?O(eXK}A8oyTy7^B5KKAhKR0lpWpQIhIld6EvEIfgya(4;7jY9 zXx?)LlQnBJousdNDyZ5>ij5A|gUo~u@~qU}>Vj*)@JrCzv93z<_Ti?e_$SiRUM-5t z!4{_iy_3vB@i;FxiQ(_hI6C{C?_`(mJ&zz#X{0SD+u9cP3ua=bD>KdM9T4O;5?cit zx7X4S7fr|}H&s}b?bs#+$KcPsCx4Q~@kwj2(j=KI=7x6M!8PdDA8vh!)zr)=FhgVp zAq+c%dQnu16cTel!Nx`*L^0JPamcyiWC`;qwGVIX!eL*dq@TcwLSFr;3|1i{dP+gy!M;*^e zf$Fxvi0`1P3MaANkrl=55UVfIQI}($qMac)om`Z`yje@uHlfCAYHqMWn8EJ}LK-k+W+ zo%iYXmB|m(=5`y3Qm+{&;ip@}+e5JJkHP&z_(P|+Os;|P^LZ`Ru|gj*%_lPV#!e&Z zZl)P>+2I8Jr;{l>?&ot-1W=q17M(oY3%rQX-!Yx<(6pY!W}XODa&c>QO%iwDplgZ+SLiS7c|ti! zr`i&7WtgF)dx7|rnoNMKrG}Z%W5UJ_|4lyasDI-?Rn+DS{pK0ViSfD^f19sgQ0bX5rb)`b*9h6aIt@U@8o7XKIuNs=b+aKHa z@yft_b`jy^rYTdm&H$R+JGgU=(TR-dhJX*EdWQg&>7E(}7f)|~eY43w+|rgrYnSla zYjrqoIVfCXzBBGTE7BR@eJ~e(~_$a1TB>|L*x6nGG|Hh3syc zF2yHtJ5xWe-|7LR*2SSxq&w^v!9qO`NpDSqXQkOTZrV`{3`NBYt!j2xLhp+oo@;Ak z+}B57_l=Ps9UVP+@&v)-ydogjIye|AW5dCvyic%)7^0jt)SBCk0ixsbwl4S;aSLD< zOk}s_;PZ;4A6`!iLwQ0F0CIRN4B_*at2nmtp8 zeGQ>iDyS24dbNrkkLRZ^)4?5bO}r*-wpI|A+NuQ*$^O%!AG`X-i^0Hz$Zfzv%d@vGDmVM*Q|`BO_A z3V-c82h^~iSHiGJBmj1d~h69h(FA^6jH*{G1DvI+NRZi$$yF}OK&Q4zchYX+=YOJi}D9F%o zWui#4va)h0RgezSQ&>3c`;T}M+F-13Ev1x6Fx1<+BDTY&MX;A8B_%yW^YeMqBm0E& z%5_vOHMh677z8Ylr$rh?CB?<0q@4B~$I1|Q zO-xJ_&#d#l^Drw0sz`(|oU?K#fYBKJP;CQ{8$JW=^_39mRv$DSb`I7nB z+|W>rlKo014i%G-&2IUFccaU1ZYc9NtG|4t4(b%9gcrKSrVnmM5A4d_UBxy=Q4WkM zEzMiT`!tVFSD1Z`L|DoO2l~7RQA%{4laoTvz_1b`@U6PKI)7_jGBZ;@@&{V3%2gk0 zYh=FP^Dixd6V2d@Oo@P`1gGC1tadxO|CIKA*s{*mn*|TpXl_P^c3&LpmK*|)JYI;ZWy5K>4{dx*%pX#r=>nMqiDSFN zVr@iCK+l6n8-a89sO*GKnYzhRVgKb?COdSV9{e#aZHu{a;^#!E3;y7<0RWMap@nTb zXyFw)2FBRf*cBcWq4EDDx%2)n%yjJ1S2kVH|6;nh2dbIK@4CA?c!o2h)7|}sV|f@Bzewq~PFvA7BO1dLu|)LhUa+33 z72}^V+zJVu2w6(h6sC25tzM=EsCRk|AahEcE>f^9k2f|ONi~fPaZPo{jL7eJ)AF;2 zAEbHIUU)w|G2x63W{T}x4>!0egQOld{>R1he;{bblUhdUy(SGerHQV7r_L8ENA5Kb z*MOpU)Oq~-&3x~KfrN23p8Ni7ii9WtC(jeh*VPTHMnj~n@u=!i1Grl=9uM_fj=((r zTJosWNc|(h$43hgP%{79H~*C~ALg^my7E5&IQZ_J(nVJE!Ic4rpLJdUaeWC|gnxu}|>iy>L;JnunyCB75 zwi%nYX6bq1zj8BPBS@uC1mis2v1-(Ulu?bzg4uclWp)lzUfQQkZ<(x(K)yL-V&Q02 zFdokhEl*-EHKcP{xp*+OeQ#9CYIJOnNkjc^%%K`V(Rtpn)w{N)nP^UGEGSGcW@kw7 z!m$5ZWR?a@r{H1ilD0KPg2==lM9|f?tk;UEbjLsWU`3=scz-=L`n0~#jCbtN;toZ( z!ns|2$|ChwL#W7W&uX@k`&?10Qclxyr&Y^PZW2O!)d8=~TdRI3i3x>?=E_(x_=S9~ z10D?hT9wGWy4Wdn6|(b@y3`^h8#I0|wPkt=!*0FR?ht zplr%!8(46VGvNn=iljpPj!j+HEOIkn*H|?N=m8~Kl~nR~6jVelq;`;0ImXpSe$wDc z{RY^wyZ;a9)KTxzHH0{0wxCP8>7^$9dQ%7YU#h;c`dy=YYc+E#s@SCikcT7IpGmy( zS@Cv#z1HKi-^LqBt7cl&TYImjEdjO5hJ{C`PJfbbXKT!DI5%@9s;%6*Nw^u!(2>b(b@Tc!C17`m`Y>;bs6uHoFxn`*L04^e^!^wbaUT+qQgLX zym4+(V)BmcXsltu?zQka5Ye<0}6bh7)~Q&nTCZ$VL)72jFED`xfz=)N-~ z9Ql+fY|LR8!;j`24cIpu*=)PkK0kJ(G%$|uGMaEHrUELKn>5)sOKUYnUI%_$7^!EHzSz2tcVBXhP zb!BtalhC-vKv@DwF^~RPWKPK{x}ErWT4ZJnrNOd``ge2=#Ctg&mTzwM>alN8<~UCE z;$mAtj0=u-DQS5w6_+nPwrjEbE}x1s^ojg^j>Uvm*sP;>v1748ZY#IN;mF7X9-XbA z=6yKV#I=Rg=&{Uv{UK?Z?rYnRl@Lkw1^LkQ+BZ%Io%$@rBVN>;>x9Pb=L#Q%K%Jp` znPxjZp*uZ%XBiXSCLDVmp+dc<#YOnE9!Vn-k>g(3Z=PYh<`LJgb2R7hnCIZ>JMX-b z^^RhqkS*mR11L!PR1-?9+V0`o>Betd5tF}b$Dj&z8_VWbSM%%X_XAx@Q=*6H=rh`mCqs)5zZth z8A~e_sw%4QGr38XND$86*}Tm`;N+}ic~2+CL+4TQm$>{#akxiWoZ|Rd{C~awo&%$R}II zJ82rI&TIVrpp;nP8Q?)1wEY&sPSrTZxC8|20Mn9JXbFF_1)23ZRTp?ix&`mM@Ar8% zx_qWoxEOJIOVfy^Gms63F%U!V=W>d2oi;j-$xMLOo17IZ)d<5N9gce(0!nYTH&Ij? zUe)?9y}y3O3VwG{(h@XmINP9aHQ(Kpzn<{1bfuzXi-eMc=}o>>b&N(DB#?=)vvVqJ zk{oUf7l;M+-6p7&kgdxY9TRr;KXBu`1GKZX+b2ilJW{SLt+W7Lf$K6Im@K05pPDSm z|A|iD=k`PA(glatM6v5DZCzK_hNfy z9-&PwF{#EqGsa=c-*VwlVQSiOK~Q*^rSl&WuHp3ioGwA#el{E@CntX6Qw<3l8FOwQ zP3y3aA`0KBoA!Zg{o{CJdgP;qiM23v!Kuuo+=zl_6G#eig?H4Xn_P|Rv)i8CEIwbW zX=~W#iVSd;IX5=(A&82CHU;t2%q8xE^#UMUSwm29D~E!CI)8_WSl1O>iVjFxL&?ApW@9|7DIZ&OZPrR&7^_?ggv&5V$!*qrapZ(kbQsP7WS}h+Ic-9k)cz^ z8EZfv@wQTAGwYh*jQHYpSdJ3-=f)4;Eaz2p>{+Ak2v|;Yy%p4%9~Zo;>8iBu_dUWqN-tw` zx0*%x;qhohH({ssDpP|;BL1}C@OmO0)5@e@*3{E(UYtSDFjAj$VR+h+csH;RUU%9MVL*27Hqe7**T#l7OQ9jvIefOp{B!$TvOMtQq z)(O-@db`6OkX=s)4(upI65aODc%~mS5+C%%D z)uL2H7|7r}bGZL9B%q0Ltn}~=+0ZC)a8W$gb^lGYwt7FQ!b1;MUI7UwOk-5nGeG@& zIVGS1>aX4y%vsQ5vbnK0>p4XwJ|(kgBE>4SiT*;}#fVF$CcG^z+*8I2&jEH-NhFQ4 zduKft(DqGiabM2-5XG$@lLj+_@Jp!*r?#ICvvGBlkhNH{_n~FzrN!_f5W*HkQ$&gC zP&u|#|75%TsFG8vh=n)J$l{>e*>*wjQ>87GXEY^K*h0=i%K4CBA=P*Sw!j(~UY?_K zVyqvaJF2>&Y{K)iD-;_h*{~i#6h{7nm)nDnG~`Lq)&(Y|Psm2BH{kxAjmKB|V-kU; zF1_o3BKf+*uS|vssL>dwg5TMp;86CM*2mIiU&FOVuOVyjG011u|Ki*PUe|lRm^!Kc z3%&Eof10OwR^22;J}dr4@kV!HqvaY6aC8QGlh2wKTJ|L&s-@IA7DRS6^YemE!7iE0 zaqv409GZRJA>(j-9D*!vp@^uBF zO=4UU%Ic~=-ExWxi27@K{WZ$pj0bvqnm86W z%F9G;oxs|xC00}o*1g2aEqdJWcFR+1)8{N1(8ZRB)mHf2ni_frx{ z-W5=a#A0LgcaP!HtiK#v&=IQ3`E6Pu+I*N`#ZV%|$}cNo3rUA!8w_U04{RPOA` zYfKBgxt>+VqeIfq^3%?el;FqE4hM%of)a(El}pO)51PF=qXMd9zH+LN&@F77N9D%$g`HZ*JAFXhp?mDCq?Dhnw;s+6qr1fuRmtV**> zNvt0K>pxAm>LyI@at*+~@3i_1 znM?II5`s~f3iuQiCqdalVj1tOO| z5LC?lxkU{7tFl6bCZ>%l%~k#P8Xt0#70zD)xu3U8_Bf+krttqVW9d&%l$)p7lrBA_EN^9tvOs*8K>ibq8GsrL*0vVv! zfCLybhc0X|3#HfL)}vRn7P|b! zK?S4>+AYaZtGWz%!ls2l#1Hq;x#~D;vg!lzX8XKjGnKu8@7xouRTc2JE0T)wz50ae z{(v(4b`SeFO=Bk*nJ}mv-nP6Eh&(R+v6pGM|5HRksP)_jwl59jRu3Xbw!#^TD9>3; zJAJ!iANn&V**-m^w#0mEG}J?3H*DGnPqVn=;~3awqfIq-{Weid*wY*eLYTlUPupA| z-j;7V`pfODbJb|VkM+o~fJ&=mIi`)#(CuFtzkgNEKD*ZOxNK28rwY2^Nd2fV@+P7| zE?Bp-&9R14rbg6#3dx zny^#!m(8_sDK*`P(GaneLZ>I<2>E_2xa!TX2>G=rsUW?7h7OI}r0tteXY$FBt;^w4 zA2uP7tV*n6pOa5&25Om_eDu~}NMN$M#HzZzyn!CD3cnQuoCo9azXYI@<)6l*CR_0K z{Xb!*X7n0;fr(a#v3vZYK`p)5l~&$@y-L3S`y50XS<+xG33iRiM)d~VFMA*%Sm&#xK+{u=f0{ z6B}gbOKm32oFdTXC!Bkvx-{!-bDc(ZVz6FY&^74;l{HH`f!na6&%3GA9j41ko~qQv z!46&@3hW`%8cQ!1%`3W8w-^v%`abb9Ts>gbZaGZp@s*ySAY-z~DzDP%_(?I6WOxiw zoRD%d@?dARU9B|_+_hAQZqn1Xxky1P^b_oQCWC*2we=Q58C3J3EG**1T03A;QSd<(d;tYxk4@;IY7}?Y5BbxA2MgkG`9H zoq{W9IE-tk{mM7NIK2hFGMv7bJ>K5y-a9%3!4)Aa+4|!01mN}1fvVa$<4Ez7c~8Il zd`WtUY_c%qC2}bR*~6`VL__K+$ZHT4`X$FUuj7{iYgld=myht&JUtItPBymdy~f6A z87PT35%a)|d}C>-A-_~--myg<#*mj0Qx2CP`B2nT`G*k8JK27|8}wPJ=h?*k%x>uOFnPWT_Jn-v`lj;t!eM6vd;n#Ue?6U7p}jwP?mJ}C@h_WI_s(9bm6B>TU} zd&{_}+Wzg=LN29LS_G8tE(uYQE&=Hh=^VO4q@=q`S{S-(KpDEbhY*JD7#JAfS)kW* zU-#bk{r~Le#s2IUd)_HCYpt1ep66QYcO2j2S6t(Fi+xy%^aJQHcdua@Ptg6?y6HO^ zPz5@M$81peC$bGoJ>mMSa+lW@@?)vO_JO+f+Tq?V4?hnuQE!=iVPG|LU6^}2N6Gx5$7E2e>u6$AFJwfFS8B|M*;L=ulY`@F zT`=+N^IDx|RnjDDfxPSuow2OZnsjoZ(lM8NdbKJ_zZDezq=VjSRf2YU@nXTJq~E}w z2p5Qr)%vnuQkCzV&U;cd>9FKK^!p@31!7 zVzIhgzCTP!ogs|Zg9n_vrkPm}8Lnp#>5tztE5Ema9EHmKlf)?<=TS8U_aTzf6* zVv+SU%8*%HJZ_<``#oWEFG$FH+^lN~rcGbhY~lESll#3_ilArv?aGf8+k20*rtH7b z-0~SknEz*Dl9XJa`LfFX+zam1ZO$Ykd#}A6D3jbj*+DdmnA609Jodd4^hQ@s{tlHai+W`iy_i2xOmu#~1yi)$y825%aH@9*@ zlB5W8BdM$7hXxbhMUCIGT`H4+6kd5v%;O^`goo2u3w_vIDm}K3+Yg}wX@I5^KG9tP zQZ~f9|K{mkOShDvzxkALfb z6}he;0}(3_AJEVw0GK6FzW-OaxATv-GUy2mrvHEnzp=9F2c{LPc0C>*m-*BI z+b$!1Hz`?74dWbkn?*fj39UaHf?8!=m;4?wtNZ+W)uwYIe(&(9ATf1iiclK-B4RB45Fu#u7}E*Z~w<(3n~^mne( zp3G|^tA&Fiagryy=XVxjt4P}W&JFXHKo4?eMtA62~*o^c0umn>d`P1h&o{@L# z>PCY{F_oZ$ENx~)VyhfeE94zLP5esz|IUAwyi{>)R?I2LR~Y-wt)$c=Ue1^)C$zBp zXM6G_zIrqVqdw@$+-6N@@s}1|2Sd_019%_y8*X;^IZknfTx)Mb?W6`_%!qt4NE2`I zFlB+q)S*0u^>difn%?QB#Vibt9I3wXv+{WK8h00Rh8rOC8wQ;rvde~?6$A5t>}q(D zeU|H|02`e%vLm1ZA0;Lq+1N^Ij2}YbHqa&5bgvTcib}3iNu@J7{K}VWRuWSPlN^8MxjD0Ej0+b%QKmg)M3Vj-S-0OZ%u1e+wCEvDyh)hRKJkHm-bPVx_30ruKGH9q(17wx#1uc_s8~Z zBA8H|g3-JjWdW)a<9D!Etl(efu2X@U#rZX}_Ac$*zik0)&+vy&OX=TiwjuFDzBOy^ zox#e)hdpDgN4jZr*_C0_tQ&&Ob2N8WUk(YUNuQgAReWZ>ZK9>f%0Z(S9-i(sENKiLpXn>eX_)#pB@R@gp%Z>6ou%gMJ_NXidJnCG-LE$Q$__|4WmJn_?H zoD(%#OIxg4{DKUBHJmV-PqO=Rb3>Tbb~HV-jbBdTH=AFdeyy_dH8?Uf8szCI^j z4US1!gTBqTZ}A7+_FG)5z(His=W*9-jTMi}r^1b7a)Kqt50&Hn=Pvs&>d&Fsu#qCLV_W?#b-{IYTrAh=c-b?{-O|0709O+W}T~JiV(0X-xn~)F(5eP9eYVG z2ZHyj$dRvu?Hz1JmI%g#sa;mQ$6oB%A75nOcT$w#%!JPE>w`X}Z>M&-1i8v<-_1F) zLE9&37|^O^zNCO97Sm=gf|YPioViO%&U+W&0^ut*^74nQN@dOuw6&RNk=DdHX}5A-xoK)julj*{Kt6Q7{$mmy6@$2LSpL63ZmI;vw7VK?$m2Z= z$0|d-#D+V%iBJE4a{^K2eDY673Aj95${qQ~xYacFO#7M2nH4+A6OpTp= z;OKH~?y}=)MbU->Kd{Zp{+?6g1yAna>idA8t<=MHO8Bt8A-8~e*n#`$7qrDL!YP6L zjpKD$`7uyeadz(=E3c!eum>0^g2EiSoB7ekzUZf>og}0g-4^k(e;3wqf48DB2Hz;sPf&%m4UD zk8k^=yKyG`gIQHUuKz{;_}Nr#V?%w5+;=YhNTKgj7YWJ9CqNnn6DE~!@4~rpJD#{e zENZX@Rj#2dyA$iAS@+WKs>rwc{hRbVV{MjSp?37x!QY*@C|#NM#kG9wlELXgI2x+pVjmbP}-gf z71j${NM9agw@~y6QylvnD#q$W_*?0#3PZg0l7Ol6O*`n2r8FP(t5XGA@SXnLuLKl> zx6f|SMTeZR(S<9^=0tD0Jk?g3z;wuL{mHf={{a>9FGu8$B>a5Dt|++>6h_VL7#k2V z``&ut%A-e|5^UPAF1wm}gX3+**=ua{#znw7i&yBGJD5>ai|_coHpAH&wV5Se%fcRc zROjFaojh@BB2_=0j@zZ$lH>4tb=GBpULGBE6n>Iz`$AC!u#WBri01%>CG@KBlisp{ zRRIj5eCM`L+Oa9!qaJ$u10^AFM^CF7kk_Su?uV2RHC)i)PzHIO$QA-Fz|4rp>LozY z(9)|Hw_>p_qO)o)-eT7}&&w@1gX%;J#)MAd#7+pG?-f_C?d1 z)sm*c8!O?eDZKoL=~LHGDnUWFb{M^j7~yYxMzy7 zLs;hlQ^u~ho-O{E!0?WACE~qH-H+9opudV6lLT%qoYnQrJ=vVR=c`j=af(JeE`WNg zw`Jt@xT@c)vgk8dnFql6>X>IwE;LkAU81@LnFxx7k4hdHYqG2*{6(rWe_;BTWd<`W zU85OpHR5_<*fSKvx>7%*X=BMqEXK$55hiYB@CI2#RpDoPYNEBWsp|>potcZ$=6*a` z&kF0?rczH)DCsIakU9Ngl7u)@;7o$z2}$yzY;ZS2{8 z&9yym%mLP#=ArK-oa(3oEMoi+4+%&GK;=1Yr#UQlsnB>u+xxI)lYjyvqAKikoveE- zj7elOD=SYl2rfkbqNHZkIy6Y6T?fC=sA%&dx+wqhNq7EZxx6RDZ&)9j6EKn37YX4; z*$d4-id4_w)83T62Y(ru@a`FjE-wHFY_WZ1L-m9*XjlD|y(m<$i~ZT?Z|-pK&d(p- zce}pi47PL?TFb0QHpe{UvtRbrnpSzx$<$^A`7^V{44GYXYg)m8k2;3LyxYd>)~T zFoVxh)+CXWBpGadbxZCN#>=Idxv(U&GLSa{L!s0vk=VH_D6yyA2c+}L(+$TB9hTKc zXtp=EoR)b7W~S6;9y$5@^TYKG3tqnb=H(`&KvmL-F!Hm+fkA;|uwk-Ga4uIppZdZU zTsoNHpt!2iuOgPNbq0vftQpf4)LPS0u?^P-rFAq$v@hXk${rnO9v;osJLgBB9*L*X zO@xo1Pw$C7y%SGdLE7mv7W+dKG%&G`;u7hgXMZWlPS~ZydrrP<1~XU!%1()f!JJ&7 z@rP5P#NiH`9#v@+-iHm07n=4`59`u$=Pf^ylz-npH2R11mmyze_m$U0C7t^IWB)@d zM$=f<&{_*DYmB(6mXb8C!XdyK`o`yeVsf(P)A(?RG(L1)->N${ISJ<(+sQ8L*?#DI zt^>w!oT{LtPnKPvYdhl}5gmtDR77HENqEy-UO?z!W+08lc4R0p#)3!~eD|YtE9;s^IWyYyC#;vVf#%|Cs zP61cs%2mO&X2r}>6)%a|A^zibQ7U*e_Z+d8I+=7;T8gAA6iq`M97gsB|MHFw7HFQ# ze$l^k1T9+b*;}q>MVmJHWwI@H`gX|t_VQ3?7?%Zo(JB2tTcXQ7XP&e6<$U8udXsNI zW-EfhV-{^CNjhY+TeeF{dWbx!F%*6ttloh#zZNv#gyOH9{Jf9wyjyiA$DxFH>pZzq zM%-^_Bk=AP3F@Kd*Y?u&cs=*o!gpAk*4qKeBmU&v;n}jPGxPPhZ<6zgEOAPc2Wtt` zXh+~m_bW|AfZyzM+}d1l-!nxQ^Rvu4N@K=S&$7w+%gZt6{-;h@HZu@oZpzm;+H=KU`FWPm;gcxm$|E=aF?nBpc9mOcRf0 z;nZ!MQ?p)3c3v&W(9jfWCeoSJs3fyg#KE7vpD|@Rom*iK{_=LmXO)z=$Q&P&By%(4 zaZ3|(oqgit+IuCqk$l?Eb%Z=n%O}vbRm1La`#`1etfFxdihKH^O$4EUdfT5(6P*}e z1Ma7~;^O?uCg>QEnA;tH4>r9qpBrahvFC%1(6;FkOFQllb|^`733O-9Rh}R%M!~M2B#7k*enBV{P5; zG9b@uBl@?yd&=eI7|5d91386W9?NYI?96oln{psT>z9z{mzsG^3_{Q>KCuHIw=k_d z%Ol5b`q=KH(A%R_-tXnxT$GqxO<|G>nP5f^p=Az4)JK>ue`QeUb)cKZd3{AhepDXm zB%krdy#X|V=$s;u>b&Y;s;=2MxExIN{S_eC(s0>{P~PNYHWiFn5S19lOZa| z@lyQeDq-_Vsf}1LQI%r8`_Y;Lkoz=Fep2Dd@3I)Tv2quedPZotNVBF`yFrXV;sXsy zDY`edqKh!^iAv_Xddka+41z} zH%82mBOs0sQEzlTfUqg-pS9&KhJ;j1r)sb3(-`=u*hu#54wvE4>nRbwGr;3i*}2VF z?f%$}YFqrlj8d7wfW2olcH2Zcx_kHW=OW{?k6aBl9k@rIvNE9J4?@ExKNf5|CFK;3 zlRPWX;Az37wm;IwzgSAt*DXrlo}Le(7b3x3A*^I2E50KxCq%L>)JhLmRnH3u+$Cn>5$hxQ?M{S+sioGIzBWN zHa0NOs*iMmaO@U$r50Gh;c(Srtrr1wjEoI-t8&uQ3O+g~R@yo z3Jbn-RK4q(bXu>f#i?~&ia*~#70VEMvrkI^R)p69<;U1Z-wtEGb{;bK@p+tA^nIqPR?RfEY;h*T5AOjQ-{>S6ufwg@%H<6u)PT#gk)YqxFsxi%#7x30Iy2%HxEDME-R-G$+tAa ziI*+Fzpj#HJ?|Cc|nO@=A?^wD$D2(Gkwk>FOM3+aHUbCxgnbVlhQ132u(&ESHw`wKW znB{C>W~QMeg1E;V8|y0kMNn^CNWRE!az{vlt1>*N6g^oQTv_kD_5rdrwK5cyjH5ZC zf9=YC9q@#Yb($+#<(S}^Gp&KoT(J+n%P-(zt>xFxR~3+63D>Cx8o`Dyj0NJy(cgW%^2O^NhiQ?i>xzg#rZw zic4HDS3s`qKuuxC&$Ft0w56iHGfQ2qT#^%Pi9`60|Xjq#>$i&L90UP^a@T2s| zZfX37j!|%`HjuX9lz{aTYt_0E-9znlsbA_pz0vG=TPF~lg^Jx6!zcaeW$Zq{{Wd6R zzTZ_((@m8>_=<<6*>h)mmoG@ga8=YIu#;)|ZbCVm01eZge!Cw;yrUwu$)cE4GGU#> zhwxpFBz_VGK`d{1TLS?KB;i}A#HxoEl3Wu6%0!*W+Ds*h_tC+JEAN7Rj0=n1Oo`fu z-E`o>) zce(215|kh9d87)KMb)y7NJ?^iKkX8mTD28aH7yiNRoj&vq--tkMk(sUf8=&oX9D#6KgOT0k4%-cyM^u ziT5}iGia1go%&*b4pF?F(d+(U7k)$%z9Bl~C7}R>*WS7n9=;v3edT151LTnb{t5Z8 z+1f!+dFO?)V-mqL#Qvhai{E=Ln-AKeC84`{0c(Q--R^%d$^fJda?_x>O<@&$54%Db z1_Z}obI+2gyaEkom?VbmeN(N~^KF^ZknNbVP_903Ulvfh>c(Q_0t&MIj7_>0iSs7! zv#}|yb!v?pSi7J(#wS3aIKzo^mrY)PMaU~~>5=uuvXxv>?$#;)sQPQ2R9{PwDcP~~ z5^bNmu;9yd8*4JfX6(5a1}DwAk;a(}S05*36t?qOCX;!s4%2eTnAg@~=_IjrNd=Dq zEDNS|n7D!-AHBn}p3ABd+nSe5HEcnNNwzm3$8EIZ5w(0})Ryn`zvPAJCjn3A0YX-*e z2&jyuc#L7-o3AVeBiR}l2m7qG}}9UdvPnQ~ZlqD}g<3t{zj;h`ZNPK$?Lh1tGU zX`!rR<0l+d5kK0T$OJxBEjXkbmywvJMbW^aF;dcoZDqN!@$7^$51bdA8pc}_da>ZCX-#_b%j{p;7KVxH6KD)Wo; zlii7ud7xQB9hy}mwJoFi_rk*2azhN5Ax3LQ2l~BxdyM|HEG+$AN#`ru2i$P?K>wlK zj{-wz>pg_TM%l6a5}eaR@cGbB48-lI(Q)C((dpOM>M=PL8x49q|9x(`p&Ry+tv7dJ z5gc$Rny1goJNGh&;X;|8GD9je%c$0Bz;)-^aeix%;U?FXlW5|YRyY-5z$K{y7nQ}C z)ih~fTbb0FR+y^jDmC*r$ExN^y;AMD;;jR{iXp{rh@*ZEbBe)HqQs7_77P4x5)3=iiB~UTAP`0T}D`ce_ROL{w^W8DCPW{ap77 zc3_~D4wleaY~uH`(Q()UPgJ#cyZRHE^jVv8@5g#eiMr*x?JfN5OXdYKXMq@ANsA_@ zT{5TNlF(-zzh7e{5>IwY6-xMDdukaxgsR=^O+H|v)yo8kv+1O2g1){n-U27_Utukz zW#$P0fSHu^%Jt~0F6gU)Hp>nHAz}2IG=ao@8e!6B&uCq-jg^aw3k?m8 zmJplqkI%R7qGMNZg9)h9LL5=3eqp`Rf{O zYAT1CnBGUES2= z#f{#TKA?e)!E{r|JbOMf#*e&;>AT?{NoMMWj}T*v>~NswWuaraD5 z^CRbJPbq%C?Q^x68@k4$eT9|-sdfSgtUb4B&{r};wxduh0Be{NUsX>9+h7573qw0)n^ z_3s7zM^JF^;mOJBzuwLjz0*x4V}FyL2W;b;vh{D?99X9Q@12I^=>T*FH4cB{xx*St znw2`F2C3m6b*5_)#z~JM^k0>81=n*TcZNKi1lg0)}llVI&XV>e8ndh5}HE_hr za+g#r>glaY{NI7ZKXApUaRbmiQ{Hlv4#Hu~y3{Esg{XG9j!!2$Is)@EDnCFHu~N}E zpz}dij$~c*?>RAsK_|sn>Ko}8XOkam5JY5v=S@myW)$;zc32eh&2{D`4uC6ld% zjqA2vExrG+Op?K6Qc_Yes*C)_1M$rv=A_Qtm)Vs&-S_slBFr44({SO-g?OU&(b3WF zf~97{$R7CE*cj>|v~jNm0~JN~op{{;CPDjiebS{E7RN9i~fw1nyl2@Q6q4}OSov^vD6twgl8_6Msf~NFPwdn@u`%LZvKc*Pm zRwpBf{2$DvAOAG*?9n(l`~|{prM~FhNuO;yuBywZ&^|k?;gQdYFrsQFe{5`vZs?0x zg1OIY5eVwY-+SN95&rC}uq_X#xKujn*;g9mSs%i|(%O_Ki(=;DkzK80jgbnr6`5z# z&k@*Di;2^#vmAQdKiUo3!PpmpIge9P8o*tw(dID54d}2x^KTB_B)ap?)Ovrf-p1}R&%-U@ov{5G}>A+ z;afP}&Ju_dsT?NgVO)A%>38g;d82ARjduyxG#u&^Bq2+kvG~cQAn`#i;P`fNZ=idR zfVBfxvTvYbJ{3PxXR=_~7DHtSfD0)LrN(^dvO?Z1JykRb6~~d5m-fWL8X&GtBHcGR zHVpp?2Ch!XWOMDy)VcbNlM#cudKug0?idIAbMMWAx8bu41x0D9F;uN9&5d!evFQ|- zGG1GJW>4*9(XuimS@Pt0alN1cZrwS#x*;mGaRa?B@kZ?vDXd4?p2mJ{C%L|+RVEGE zP4UZXbkJTn?vE~D{X*TZlYw?SIgOXBmV3g4leJSX@YPpx>&^5G5ot(iY(S%X=An8m z>Vd5JsTW6({daO1+y>_3$;a?H80EwY18b1id6Zk)j_0G~lsD~Ln4GqoGXNkgb z%YzS{I1heGmz(MuK?~!_`^b_W2hD*6j=~>kenB_3!i435rIJ&&f8m5n5le^TAjk#! zNY4O0Zym@mW+V`YAI@-#dNNmx_WlIHRmF$RPWIMG5L7WMUezM*KJ!5$cdvfk1$-9P zsLX+I6EcOB0r8WPqnZ~uQdtyktEe`1q;d$n*3XTbRxGWZa)?o2drn5cHA9ZK1wtsrIUe30$cNE>^lO$><<>WVhk zUQgOQi=KUX<%&Jl;(&RSq}Nm_5(yjMX^uWXaDrgG$iEf3)e`^6*}|l} zl`b=~;hf#?5UDj1`9XYkPQ7!&+1xUQZ+;e9));FQzUnhvGIJPhJBMDnFqa_<3!>8M z4-OoWg~4M_WHIC8q8o%I8oNpqd9M=67Q^)(zy+)dkRoio>rV_#P_p5>K}YUm4}dc~ z_!jVwc%ItFWqrO^?Y`gs`b3q`pLR2>RaPPGf$k&b9Ejp>pw4-{oh7HXcPwPHVQ9X{ zhVn7{Qu^a3P1qG_P;?H#=GjaCtWpI^%f;G0@!?nLyGHZ66|BOq=k{^T$ae%T z%NB>qV47--ETvBq_xNxK#&>=mUmXABF`(B>QP?fb$tQV>GiXdpb=ErP!s5f7XF^d* zN?Q6UHdmdm4+-kPV+(<8m4tb)J2kGfc7~w~M0f188A}_kka zHtm|y6_KmVEd5hYq^8|!vOf{GXCg5y=ez4eSCw#k4AYLgX4zO-c`Q}#wWH=09m+d{ z+mm;D=R`3*1@5H?B9+UFy`VrJKKSkxFJa$!C^UVTgL1w~d#M*@zZA-vV#i7Pnqt2Q zJ>vnIz^86%q293OD+-9|VQ>wq&!dyot;%*MQnQb0Oq2KRmAp%m-j~@k*i2_yyGlm@ zITqimxzX~=bZK>mx7?IZrboChRN_3=zTbK;<=k?>>|at@xkPI~!PBvYsrPs(p& zl3H@NDDy+g%ToS9yMuG|a?QvbyeRTtoyK%C5y!#yp#ug29z$=RJZ6k)HJwIw>Fa#0 zKzJ0=^dp}3vQDh;QrKqLB{uFXA1GlD+Jp;DSf=H2w+HzhW`^IXbnH(r+7#dPFtsj{ zU*OQMzANcE&+~PrJ`^)SKyh=!(7R&h9sdApv4DtrH*Xg{RRhcPvx23_@w!Oo=XF8v zR{S*M`8D?p`D1e1y`*_6ur9^FhxN%=c+6|ROZ)4Iic&DL+;MP!L+y_k=wCHIcn>J$ zQl*YlZkG<)L+M&Ln1IF?scbn{Z2HY--_a$HH$K!If5(8bx8PnIX zgHvjicpeRTivM^ne}cr*2rq@PXX zQa$z`){4{kel-0oDfKhK^l}IYPmcY*a|gTIv-YLOK#>cT<-tY9hoN#-juaiWl~Etv zV(LmXKBqFl5Tm<#>dt<6VAC=B7sZ%v@VdpX&W zZh0oQ%g!1)mZU|{I;c4y1sYL5jCrWuKh?Wj3rY7Y(IFyQUVU|#?t+qi7Aun^X>>O5 zKI^?}910JUWcbL@X}M2$ipSphnb>|QT8Z|^mZd{#NL8#*^F3w*HE!svA;e3tKv2Ua zJe|ypU>f3np+H^ZwW9{C0T8D9rMoc?L~#J4kxa*{0r7c$9+-(Nhd!0-{!2$l+U+}t z3-Pfdx9`!A&)DVmN@X+`EULp#Y3`m40=cLP%8wha$Ic+Tj4wh*T0eXXk>^l4oN#p_ z>sb-Yn!`~I&2RE>2zME!@(_8}Gav>LHvKY_ooGkEq)tv&#VQvS9k0vuh^OFtqf)1s z2w}44mYMXP@r)I|B7l6Tb~MOi=Xtao1!)1i!0Fk`2fmBd;KuZ1H%0{t*kdn;x$0-(4yr;& zO{_`SqvvR;`>(fiDeo-3LMbZsimQuaQrJ1P?rYwifP>=j@ad1Ql(GsVbn2)jS)Ut| zJWX3FTR(_6l3P!^eqj1sZhQW@VY>Qtif1h2aETi$&ktDL$c|B62OHfM-ETwt0k~cKb~r$$Pjc+PS4*N>Qmj zBB^<2m8tMBX&UmnVpZiNbt-?LhImA&r52b8U83n#I~3 ze`U^I1t&R0xVbO)^*vWf^Gj}PXsF)&Y-*HZnIM>@pKow{nP&7V zS0=LDRW`_dp}vtAxWOWU=&N??H}{j2%kdjrX@@Vtmn|rzMxzc`0GJj?{Aau4CSF`o zaNd{0ms`T@Mh&X|aAp0QR|^$}T6S;q zWAc{jNugxcu@S1;+EN=2bgybcgKZ*ri5EyC&}|pK3SnUa2vS>jVhEuU_x6Qmhevbf z;#j*%b=c9vxke1tB)Jk59t(6MdI@^-X?AIB@W@_Q7>rzN)`0^vyB z8LOm~GR^eZtQj_r9_j8;?j4&R3A?DZUb`sirOOn2 zqWID$4Adk5g$@(86jqoO^1?;C>Rr+0c! zG4j9$qSJZbdV4ZSZQtZ^w3_={5=Fnq?P<&PsLjucLobl6y|C-Jz7A1rd77m{9e3Yk zW_U393H6#Xn=79;5SdWwSKNl`s$Dec@1V%uEaT)f4zs3_$K@Z%tRL9=v-1j6P1}TD zVZ4%4x$7&$GzA#qUbRYJs}kEWq!oaqB9KX6f#W5jpY2y7z0@BXs{#2q<%;eWy+D1= zBx4+)Sx))bO;WHTk#tXrk;NX8WitCw%~6n|+{WX3ED)fSNPkTNdaS+O?`SZ=>BE`= z%w{$mK!Rvl@w9SVTjyyo%9e8n2C|ocBRwK3!5e4ySVmTdx@Tjsz~Maj&1fu5hbE~G z8~(?i)N%23d_0WYBJG)Ht~ELZp_ds^fx^Mh1In;mphY`f)#Pnq{xR-cRr_feo|Dq( zW*cmMJcm3o{?2`Dk(A9INNizvr!>Do-R0$D|Cx4|&!+!U!S>nN%btCzSyz5)AG||X z$9m5WW0H3=Lx`4Zt29ZE%m9Dk=9${0B+5sRQ`g4J+|rkiSze{!eQ7ikExp-dc1w0G5*tXPWA5e1Sxs} zT%_Edf6IjNc{$qgl$q9nDdu3Ubu5N9|L%{XM0mfzD%()NKP1esstmGpzqGzP^^iF$KK-_|-` z|NYYK+d(`fQ6+i_vOl4!IIF>O9Xg)KPtep&3fV3aiodZli;j77iS&R&%Z-l3?w;o0 zPYmn3aYoYDmyYqCxlMv6Iob`KxIrqSAWvA6POhSng@dkbt&Zp?iKCL;Aq^F&>*AD^ z%#iYs#Uc@EKMhBV#9lH9V&1Dcgmv;BOyOp1ErB`HU@L_9dER_{x~uCj3ubfBU$eDY z+Ln}?v$YPkY?PM3gx=c)P144tHN1}_OUAK0*%&BU7bDv$CngzrI+mYQSA6A(R$)1P zX5a1r3xqW};HXg#oKo2%e+rTwuVB^082hs7*gV(m2>w;gV_E~+R);@meA9nj#%hcPK88g}*}|+et+<|6oBzLy5pc-G@asa54Bwp%xq*;TP? zHb-Xe{klzO^^QZzVd-pm)o#XEj_T#)KBvbUj}%|6CWmtUtK&MAdM!mojXnZp;ZE6R ze(LIO&*uV_Pp;SKnU}06(6p1=gvVZefR7J8xhr$wGk=%UlIe?7s%>4YD7F{Y7i*`h zyRMUVS*Ac0>ZoL5YpQkpWT_JOe4;OvBW(UNdxh;sr&#;%3Qw&3{SMLDJ6?HAKr@7k zEU5C91r66%B_EK7SUK}oSqP4k-6X2AIL;yy)I$-M3(>w;Grugszce^z5F7n-iouW` z)lERTn>q~l7Tmkh|4!L+fXT-1NmJ$-SPRO;=JcR)q)5 z-Ior9A7`cdKFHV1vM|}`TxZyMFuL)#$F|4`BVF{xED?Vcs z^Xhadd^z$d@leP4%!Qjk`TFD(sri%yA-XhpI!h*nFS7X!&M4+4n+rE5PlRr62oKs! zAd3Wv>YSrdADeEbVN1ju`YQG>O`D0?V3-o$CP9Oid_-O!O|G+qls4&2`FZUx!33&pG5K+jJd2v99h+EOtlYI@`1wtB%$3 zIZS!luHgC*ZplM_WsmKGg5~?8W5*rFWY8t2A9L7w@Y9#$OqEOM4gi@YnLr8N$zq<2 z#Lc?(>tjaMB3s+1?eUIxqI_%qJ>5}r7f|=Y$%KBX@+$!$uCB)$Z6#{~M#IyCGEaeB zEfD{7ip+^l^qF7rDmp0TUYxj177TEBFV8u39&_9$ikudTyj_m`Ri7Dn)o!rfC7yn`LFH?sYSI=zqnztyO)FkmNiKzR7g zNds~UC`K`_@AQC$`{$4X=bsPkS%~}(a>aiE7ysuSWB+NC_@DHT|FXfGNYqc8$|@>i z|FPj`QgZU4=%4SgJ&0hFiP~$dvjdbHQM>_t?7b9lPrWDrgXUkoW8m~y3GkdbMIg!-Ax9@BQD4_p? z5|!CMWKYDy@hK+V-kpf$Z!R?O-?>mfU>f~Xf~sG!4yVTK_}c_P1(*Ql=hJW6Fas@c z->%7C4|d>%`G5M`y*3BR{Qo;r^4m+>uG}9FtfEzDdyro`#8`A>`rUh@vcfN9G-MOK z7D$H@R7EH%V%;B9;p~(`%CR^L?N>VWngq zPBgx|x*c9uPhCS}C1WzH>)U@oeMd)zgS28gw$4WMS+zuwNznM$0xy1#GQm2kfG38i zPthM-_T3bba9Y``W~8(F1E#{pTvT5W>lYO}JZ9T!17Au)7tAH;ZvX;$BjKjW7~R3} zuLCMnXx!f2GByS;no-MT2h+9(_GevvI-M<+v_vm=fd-c0Lx(A7zrV@#Pxm7lh!pFO z)H8_e=;v{Y{r>hqBqgfs_`r4vK=a{@~tO~9=dYK>%#6~bcKmYx?{&9gppWgDU=mTY~z_{VjyF*lW-I&~A zLm{lgnK6dsp=uB|>vEDASmjLiH=xfO^6Wfcr5n&g&eMyEKF%$d8rw=&M zRR$8dysch(+AbVib7t*%1$M8q{QcUD^~e3K^LT;Qbk_`AQwKQ8m>lbhEkfW|g6ahU zSCtKzE}M8rZ}+rrB%tu5?0t8O#lErLu1UY%7pH}QhA4Z%)K%z|w%^Dc1b%e8d|>sH zm2du*_ED4j#C%ZWvUZPg(BzXy-9tC9a`XF~PI>IB2w|_2MA@d+pp@*?o;&8@r~Tu# z7OpDpyek3&cA7#8cdvNhGP?fm9~@r4&-t(=Yr%$r-Lz3_=cmVFNeq zBYzao#~Tqag?GBRFbnYuc{3w>qOI`htL7;;g;RoNkH8hBJ9O9s{Drq;!Vr8g#{TW%A*YQ6-&?pLH&Cy4dbeiunm%38Sl^ z%OT%S{AfPFtQV3Lk9tObwU}TZfifr zhVm7Q+1>MFcKY1PYL&ykQ?Qyf8NZ*r{lM$;A-zYSQ2etoGp27h65%sI;!AIwrZgg( zLyX(own{m|-ap4YRJmL=dV4A4DcV!sjQtm}`osyWoSbD(dJx+sC;+8+ms_z*InU!{ zV)lbwd}B(JI=B8jc`~ai8}|wIJc@^{iQ*R-)UbQjt$Xk^nM_=2l3S>d*;p+)k$=CY zefK!FSt#V2-F9+8|ApB66iQ4Jd*R_%H$U@gd`w9?0G;yCIJ}Gnf1itVbXxFd6nNSs zJ}yGmk2SPkpoT~-|Gr3~D}tZb4B_4+P-mOQ<8m%0MT&>Nmz39bacOWLZ!-&|QIP5x zQy?U#np;*Sp=)B_O|QuXI4-lxix&fPf(LkQgxs$Bj1cQFeUZf;`q;`A`ytlOI|^m(eX9mMfdRDodJI60QfgH1+fYlz-Sf^#y9Pj)`9q zyU3cC(nS)`@Up%Z36eK`+`oM=B*y1Cv?|UuV|?jue)y^ zA{!%aU+y-jU;6lul`H459Dr0pK?QYAN#pn=$j0xc81L09U;dgiLgRc*_Z73+r8ZH> zyN`9wt!99Ru^eKC;O3@fN7^?CT-@`Nxz4&DvB^fC0?);m_G4b@*9KJQT4Lb zSovP2A8bpM#jUazUsMGah( zy=g6qQI$3$YK}i!W-5Wz>Mp-_RT^!Urox(QTdxZoF37 zsMT^MI-h}qM?=W)mRI#P%|@R9L}4aU_sDY>hyX5u&vCYU$*dF=F`#HGYcz+`$FXAl z2(wRH>i>`>k3%xV$@$P(I#dBt1ee~}y0=eoh5 z&Tj2;zN2n^@{DFVpNM!LMDEwA%oS^9oa@w3$>YCn8XQ`)+9>)!X1~r<2Q;=A6(3)Z z#;r!-uV)+ykxxJ1So{U;T~()EG(P4w`6#&Mx}Cz6psSaV&rTaw zrfF(w#m>WJZKbIXygZMISp1R_PHLu(jBv=y)v{9Jb%e$(UuV};<T z4+=MhGd)RXy~Qg@TYjzH9T<{Vv5O$>NVh4Wh{+ znp|;3mYo#3Pdzpt^PQg#*(vBi#)tn`Yi}9WX4Ag=(o$&g0;M<@3?ob?p zwT0q^;!bb~?h*)4DDDy*g1ftCrO)%eYpqZF*!$T3e8`tf?t6}zx#yml>vx?enKh5| zc!F#Z$CswZDxwwt$sY2-nQ$r<@XXU||( zR}#xmz!bmuv&wkA2En|mpeRJ*Wy>kruZec=FKX7|6+MNyjoTvjakXz_e5iiL*z=7e zj2S20>f4^i#S~n zG+T#;q1x4%ScJf%^sZ=*ymh4gPl?ME8GPW`MIs=vv0dV!Ed*ES;wrDQihCchq#~os zLWjxonUORnyk?=gj{NI*;S8!uap@~4=Mg?{_jd<9t&?}^Es%!h?lax{lRsK#s3zTSP|X^2&oT2)#d5SkFKD=PA*6u1b8S9AT`Ijm#9{+TTzWq z9^Fj}lvP&MUJ+85Aw+sE66#`+gh(y#^}nA$UAt6m%5td~xifj^g+q8qV*b|;(f}3V z2N_VXvbCD)|0?G&(qXOq-b@N0DP{6jcg96nO`db{M}s6M5yJQo?qBY3iUy$^EeEFu ztUgL{@I0xVPn6f_nZSqGW&r2uwf#cE&&lhQ>$UccEWc(7-HU6OQ2HK`_HGaN@&|-+ z*EU++U$d9k?OAd*08)pCVB0Xu8BkdyPAw4)P~w{O-Wog!5J&`?ORpO;h~Fm~S0KFh z&X2^qNR1fsR>%BNAC)vR!MRY)Dd12q@1vZ~U$Jw=IDQ%PvfR3fLPQKzr*&DYOrcW5 z4N?Y}d3i*atE=@PAcZWNzw%dAveM#1KAGdiO@ zeR!5L_3)_av~C3n!dKf39rsA^irCdGiNdyRRr5@1*kxTR$*vmDO6;CE;MrZ<34<6U zzeIVd`j#zv+j-myHl=CiT)My8&@geQyh!UyYUI0$4`9;{XAK>?X#Z30f12eQLK+gt z&I(Nb)KR%36?Xyzu1n0qm@26iZk&EZTM<4p$$Ve*9+gm~M9j%xx!p9!r0>+;SZ@g6cjAkDki( zm1u?*TffJ1x#zuAi_djGZ+ltIHxwMJ)BE_+rdR%4T?aR)&yd{ZVY$y7{X&L!T^Bl%EF|mKZjK>%HtY-eS`2cb7nJabp~=g{9sV6@U75Z;o7@CS1J;V2YOK&Uf+{FT#gFK zfbF5o3CwY!fh|jGS<5RfoN%{m84Deq#BTp0-LrSqQI4-vo}QQa-d)fOXlHGefC8;K zChPZ&4KR$fdY-a&BG!g$4l1B;nzJ;mDaVvziSwAu0lUQ?>7wp$Aoj`Ywp-={Zwl&Jg}Df_4q+#fkmRL;Uy*}g2CF7+<_@PI&;{L-?0|MSn6&^Vf#CF z-~8L8zsDHDksmMU59t+odg81;6wHZ()39ckRE)&@e=qY2O~Qr)p-H3W(T|J@<|2Wr z_wK^&Xrl?GXia$zXLq%|-O7DB@7mk$c^B@hvuv3bKD21RKi<&eGUQD9#3y)A^E!WvX$~ z@Bp&g8@{{~5$mnw9J-o%n1VT-vw-^)DNzq3G|%3b&SPbfNh#r$o0W@ezc(q&jl@yt zndA*_Ai1}uIV%c05{4)s1p8P+PD%BzwL#573wQ?V32NHHbsWlp^orJca~+)5zOpfk?``Ury}kEz~g?uwAsBe40BKQ zJ)RQczwSJssCks2beQ5u<`Sdsni6#Y^Ps!oH+*YQ-)M1Qm;e+W%MgJOWOO$9i;*WP z+Wl&C*80yM=u`tbRp%L!_QA)|hsLMi#z9VloPuA64|*UQUf%q~a+Oe<1Cf*fj9(my_1Wj;tv5yr7CbY2FyM_vLHmqg=x?bUx_flS4W@s6)C|Hgu&e2{gnig`nrl2L6@70*?%9uve<->+NzHneb zkEwb3tb2)@hMj**w`X{mG>aIrV=fM2u+P1p6o=&%90ESjDI!Aso+%r*jXmV=N-H+` zC^V+WElD2T6a2Uz(UiM%x|YcLSQ)r#yUAG_f92J3WRk13k2z$pjkLU9(E6K7TjADL za_KG}?vS~-tDKSJOJt+hgX6J;LW=a)T?Y+rSPUJE?U^EEYiuo9M}JD%SOo^O1^P*_nQ+6<+&o@)rHxc3H@=JEWH5MS_aG8N*DH$b2S`GVODP;GjrW2G)9lE8bNbU)86}Xnmcf1h24HL? z0-_40u+rE1CGJ&XinU)1E7Q`*| zQ{iKdazWoRrLObGHJ#T@VGnFJ+ZL38N#*NZ35rQ%ne$cGWg<8v{s!4`;qCdEsbZ&P zhLD}a?E&XT8JphKzvRID)f@40OcqQIahFHf%mudmeeyT4wv!4{c~C5(>GB9Mw!5D? zD?8%1ZYjjKXeV_t?~l8JFl%irfMENtC=eOXi^4^Bz+_6S*sWxuAo4wXE;3GWGUYUZ zj{Q+LMP*;^HC}_|N90bwj=fv4=DX!&gPEu1oU{@?7y)0KSH0-kD>2A3Rme`k_+>(p zz}}J4Kk1x3*MUwUq=)0e`!gcx90GQV+Q7NNJexy+`VoX&SfIs&W=&bsT=H*n`XIt7 zlZge9Tb~=3I7yV2d@ft%4ClZ~Tm$C6T19!@ykaY;5CiU=XX7ct09)d8C zmzYyDmy-6RS0Y%feBpyV`k@WKHRt4V&Vj8thclbAm0#s;a%tnjwxrZ=LSqhh9;-@h zXda!9;NA9joNmG!0`Hnnk+Ku^LFR$w%x22Q6!SqGg@7n>J1M6KVWm);wAGJZ-GZe> zF&vaSkDZktjM{p=NQ@B4myj=R-m7JJHOS|$B*>Q}8@1qsxyyAYtJtTTlo~3E!@^mV zvBuPFm5#oKMu5#2a*0WSayV=`vxCnc21kaw5khorG5Rd2p*fKS2~O(!v5cf|U_^ZN|nLHJj%XE9hsTPDH!5 zlTE%>s#jCjhVCFgC7mA`w*8;fV^6!-VtBvX1}kLV{*i2%{XYETcYi@#AxZ6K%sIF7 zJzb5XzKannP3qhlx^#TH9Ykk#OblD}Z9Tr?-{I2B<;WVFW7?~);8dE=!4PKD#3YSdw&Bbugkze_X2(d z(9MT&BqbXnvi0k<>OfRTz6r?EO32o?$qWEMg4N7Lm0q*Xx8+T_HRCr*#g#}~fRr2frCNvE<0^%{qlR?#wo zj;p(ebmwhQr<0zadbP3Qr}LC^oD#OV#Iv&|1z^qO& zlM&~zb)K`SD&DLQtvJ^zpSf_j?FnSLY}4YPzM$trS^xWzVe@noH|(Iwc(fKTm7xm> zKOacZycfUvQk9FLoO+T8(e+QwXmT|qjjekY=-MY>jgQC<$m(F1B043fA2K9)f&v<3 zH@Ewo05@L!JS_c4j4oG&Wr^1WHwOLfG2=%T8rh6RIsW;O(T;u%{Lwu zTkas{AXQRN9-a)|B*Kkl*G(+GI2Ihw$A=HJ+MDANf&xk-i!P!PUP|`hc}2vaK-y^; zh!a1JYI@(~EWa#K^hn=gj1I=bgt7XH-Yz`%%%*$Hu)q5uF!v4HUV>u8V4bTo#I|93 z&YhnqIwl4v5hyN#v@|x-^*3yZ5Qfn(oIPrw&U*LOKec&4`z0Q?J6-yPeCe}759q;H zlE;Nxqp)BF`iYl?ICaV60yKLf6lnj(#Sy`;i%A77udPpVOMMeFm!D@R;s%z}l9LB9 zub8b^hTgUefdcmjsxKmrcD7-kUkX>2dwUP95ElRWdt<~$-QCsblOnm~?CogaekwGf zoeWHYsRk4e@`JTG8aVfMZQS*j{}Mx7=pb`OKLHo&d(*KpZ|Rv`TG!K6n*joc{Cl0d zq?(PY(FTV64MJ`HhIcZrBYfu+TApt?Dl^ETJ#Rp**KVFyD%!UU*>t^)>*z+L{vLec za6{+^;CUnr9s+*U)kgsCg9KiQG5de2)VKO|il4KSI=^a=K&b;lq#HD4f>>TzhUBDw zwE8D#E*xA=t;9m}637;4vTs_%1I_XjvrLIn>ff1~nW;=~P6mb>N;D@r$#&A>N3Xs| zF>+{n0tHMLWJLrk?Co~xgbb+4bpBiOcHB~-RW1YF_U+Y`4rgUJ-S5yZ>?^nL(fbTNL~I ze@c!Cz4{+72w+Z(I7IQTK%}@L>`~{QjjbFVIoaC}{ym>TypXTmS%{JbH)CI7=ND$N zad246T|QS*8r+?5MDXS=T<2sH#83egmDByX1$p}$wSP%**@>6iCb zSXpar74>XNMp(jkfI{?mB`|XjYrdinIk(d2+ zg@-Ficl05Ok5BVLqvJgc{WvEo0CmF>I-;1oU2Bt}RraaC7CNYHrtyk}vfqZ1R{n=tD+b;$a*0s2{@ z?p=Z?&kM<{)VIgDD^Ah0KypwB6I{-CWQlT9qF64b;JnqzLCuHWZSz6ka$&VSvC zziwmv<~+?t9MMF#e#_SIC*7%oMPCrg@3*&UbyfT`7a}1UGrkH5cMb%BoNcmAN_w)e zFX4TCq3Pm3`~SM zGX;8bQRj!3o~^9MJb6GG+9V4c{Ob3%N${kHFL-+eE=^iu{vD{R0$+`R(tk3po35*Ba_mz@fPi^fTVAT{evG{^va|X`xGlFENB~D7)OJg67`` z1CX98pMlp-W06z2{b#1Pg_A>I^heuMj!3li;B2b#yIX5dqa@s#17wMRQb;N{eKn(ejL2!y_ec(XlNL3SwFu-TCv3a z=g|;Zg1psM7&R;tj4yhvpzSlHz4%ehW_)zG-}}g0iI9Z`@Foay6zdn<08?wsma~KQ z5O8ld7)NCsI_Ijrx+McPqTV&1Aoz2arNXjS>eTd4lsBn)m2@KLNZCL16{J3NL~Akc zCYx`H!`D%a+07k&hIqTXI$QT=CS}&DR}I8&hnisDmE&CLsrUlMa{qL=95C+SMyw55 ztI}pRT~Cj|E|8(N!U^21#}Dfz42r|9?d5mPA;DI(%6INH;|pC_eB0K52xg#@LiubU zMK}c#l9(djC+JxK>eO5{~+EaV)+Nlacl(v0@?*HsQiAA z?(;N#&h$rlWd0<2a?d2&M&>DdNU|4%cdT23oN4I}b}_ivVm0d}det}1hkopjLj5yg zeWaLU7%V2R)+Uz}C(4fWm7lQ&UTrq%!_AI&L;2|0 zF6WzHWf9`BX$~nN&!X>I2n!~ra(01{+0Bjs%qj;e7JJvpkiI&xw5&HCyJ(4Ndv4yV zkxhtG9PQgNt)!++a{QJjr5werUZqjoax1SlYVbc0`nzlRRDY|;CQ{;d!AjC7<2*7w zGs86lRH}%#`52r9=op$(fa>KM1gx5+zwj{=Gj%MHON2i|8rxfLzam3u1NZM=SCY*< z#Y-UVP#d9zoKrFuKMXMoJM%nN%$exTR7tG&7ZsJH61LDDex1Kzkx^SPQS%<4@#I3R z{?bCuj6zG-N&4hde7W57V^l~t-7kNZ8HKzQu9jwtB5Sg*qnKOrC=tvvKx-au$_sTU zY)nkloGc(=Eg8+9?q=#+n#M>K7jy?HGL!>bL+^m5=V=!|9BFOx1^qlnT zI~yYf{N4I|F)EByF$U`=o3831ZcmL``xX#rXju_Nlc|VRA)kW-VVl3I#b+o_Rd%HU zciU2VojqURRJo!6Y()Lsof`WQMtt%4oSrBm_aHvjbdVHh(m&0)@952NyhhRmTKz!; z?H#5(2RVkpkc0bLRQJ9ad%b^MMDEb~0{q}u8u-@gaRHy*#?L+l`7~APnCcQbA0Njt z3Fxz&Q}C7cpA#}Fy!!bI@lUo`2%(Av?#ENPQ+yh8t^gIWaEqRs~GMB;+ z_-ckA7=aQjpC?y0l&cfw;&NF{-atmKm&V=<@6NynF#NMt(Wx!$ZuP z_BKWLwL&jJD08@qcfd(_+(e;CS(zx8s;Dh8pMtaWQCyR+&phQ|T}~Ubw7ZD6*HJMi z0L$Ku{;;i?1=6Z<9`P~2nw?KNfJ~b}#ou-$lP$KnYe%NoNYTc;1+jHgRye=wauxN2 zMYU>-OE=~topi<}eV`byuE6A9ov$#$!Z8ah>}hHhADby5{jt&1un!TMei0jc$#`aE z%^u^(BVhg{kXWESP?Ouk?Gj>;quCUYQw2Ajwuw zS7bdxgyb%x_v9hG=j*G?KzfnW*-F5*CLGW)hF9Bn+f+}XRSx~p`*g>)(8(Tn6<{|3wFv}_!5PIEF7-E+ zW2kgB-C0ab#Z7Ow!>uRhK-*^1%KTKwQ*~iGgX9p23c=d#E*g)(*}KdzV}-3iwScz1 zZ-xqvchShUY+Z6PSKkN%#hIvv#B}({j|((S*gdvh$K^v=a{nCo*}bt1ouO;3p^w(P zg+98K&7x&OW8-Dj4VTYNX^AN?mSLkK3T+c={`D@gUx(pGt#K4So_QjCyh0DGJfqMu z|Ju2M@i#cMYx-IHz4z@(PaG|%;{v5cM6st|ZO&Jf*{pS_qK&V&&%4spFofXlcE_~i zq%rlPY!UV?$>oBwvbC+@k|tR;h7vyWF%_xKrPIdJE*gv;?_{w9eo|sx-iC};Gt9ic zEgH6?9ERLnmd4Wtk8ia5xkb<2LCd11{`dm{`d8Jt$B4aq44H*I;lAU}jYtcv3dqh` zb@-aBo_+-^V+^*u_m5GE;JMIk)yj!#z3AIqAy+`A;$Xd4c=~+w^A3~Vvs_nZ$y;>K z8O2&y=Nn=3Od{nTBky*(MOqWX_aEjeBb^($&YlnLq zWYmHYN;l142?Rx6O2piM+Iod_!^S7izMN5auKpz_u9-W5dB=Wcx$~`1s`JbrjUYgP z`AZ%e@Q~lciacpeDf;88xX@+Jf3=+ZFEV6y^&9Z5FUnjQ!@(dJ}q$`O0^>CA7Drw3fG2g<=*n zu)x~s2qy%fwCGRt5Y)MK`HgOfFSxZ#rKqHt+HONKd{`(|=@>dr=PDE*GTF6cwpE+m zjx)zrX$(UyhiRJ(3Yq-W-mvNDkW7j z_Gla(G1;>s1UXrQz6URc1yaE;=oX+=(rFp=@D();X#%F|$vkXBSt^t!NDEV574JoT5=P^J0|Ap6d&LcF%P z#Xa~N{3UUTR$%pruX<2|Ypin-vcqhF z-K1HY7-3(V6?=Ee$edP^)H7cgIB(qrG2R`G=HEba7KGyTu zWv#{y=i*Spv!3^NsWAe0p=aKs8{@-pYs-goQL3!+LJLXPR_KR@q<&q~J59CwNh4T~lD0?$XL&!<6B` zk{y3KK}UD-IT?%O!wli<>ZS7O7ig>d*+Iy7PUeP_IT=ICgB$al3t=0G&EKQ=YcxLH zX1Vk@T6`vGv2$tWcYKc#EDwWSzqzwW!pKBG zPPWUp+#yQPhsFNDJL&s^$?oL1J{iHuBHPFmp(||szUZbfEXo&oi=bu=oWQJ!nLIPZ zqFBy=SjBfZILa1e2EkHZUADf6M|WkWn8fiQnNN|kZ|58RQtU%Y0QA(DU6^J&Mo|f7 zcW~I<93gCI)y9q5Lho1Y(hN_cMdw>9kr&=xuJ}=D*F3&49}M^?0(9AQ36cHicxZvk zt-r322L1JsaL@QkIV;MMr&cz+bqpyRWro(BnY@CJ$JMQXhg9NxzMFYTDmljSbl$&c z3Dp6|>u6k=ylLS4 z$UjiKqT(&)M3SLXT#{HgiQbR;;dl~e?qX!F6y7*d6Qq*|BAje|GP;S8MRl=+iCx!` z?>hb8><%1u>6a|j^ox?f^Yx0W`KPQetNkHnHw{|=(5&p3ogub8dC15wJ&StSGnxnyaaNn5dHoDmVC;5PP z%BXcX%I2-0XH`Spb7VpWfW#oMM&`_$p51-=cc#SR@ZK)NXQY$08qbNO+i5V2w&Xm@ zUbk__0Cl(^?G-zLu2b7`P1_)UKLa1if@ve`mBOPFV}A>KqbV{wyYAqsS^qVul&4m; zz?9o0W3AIiU46pjXPsAP86IB~$BU=!IbY9Cs2w-UuJ5u=?m3j*t7o18?oeaRjyek5 z!dWW#3y}Mniw8sxfA+@kUQ$=??!`Mv+-9{#8GKm+$Jdi0fn!vRSi$T^W9CvJWl7ZzW6f9N>UddE-{0Z1H9hGb++l{uDMuct7UY{?ROj zs<9O@sthOG?b+L0wpn~nO*S|0SwG^gv*jt9ZX8EIP!|StO*Wdkrzr!?+h(>?Th5^{ z=QmLGeglb4E~4(mZ?+^3@}WDIKhZ>xA6uKH{ByPRMFQ;GuBrM{TiE6utr8;6((yf~ zw^+mWybm#vPNdR!`%(@ydwCoPCS97f&giMiXOe(UANz#wBYJe%GvUoNO{)CvwOu7_ z5(&lHU&hd!q@&CpLk$guwZ^FT)dF)cJaiL1^1wnx?$WoZtY?a)guFQ|1YdE3?&=ZC zy`oB7jQAqeb?2p;_GIEY^{@a-47|ZWe>{cP-|~is!@tGLHl)xp_P~0B)Eo)84@?+3&SG!8)*gg#jrgi}I$91` z#UfNBOsXnD5mt_siR(M5K!K+}boX%_nM#Z86AP(wQ{kGPffp4GNx5|;${4<0bHC3u zItTop=F3fbGK3j~)n;)0155-)%c$Z{u0EayA2o#Yd=1E2JxTtfQ`FdP56{?|?f$yW z={u|of{Xd^E(W3lrTe(^W`@s?wUmt7Q4n3OU|#k!;AZkQY1XsEV5Q_^axE=!(e^JZ zU5e}aw;j*4ScU#fTn(#Ns(H8sx=oP|X{dx0-$9Ca7s~34uia0McZ-{B#fI)hDpM=` zTZ-a-_=dM8u#?ks9xqv&Esl3h>R>I}%KSn|OQ73B+MCR4waI)QxkOTb^Sw^n<%5Kf zy3<`#BenXyP(HKjniL%dW4w}PADwDFwaj5_atcCQIQa5YbSEL{(vDMj>5WHa5%cp_ zBHm6fUILv4vnN?Nr)&M5P{Fw!_Q%~)`pyw;7Z zeqJuwPZ(z1L-}c~&*_M}d?C}l@IEB>Z<*UtI3?*MJ{$USlq*;@xs(=`O7qcG1F0EP zo)63NpkcMybmgP{jY}Bt(Qo)66`w5R%zFr{Xw4;=LP~t_od~$&;4)X0PG4MC)Yl8H zGQJ&mJ<8w(;rE}Mzllx&5ilI(>@}s4JuQJAOJV2kN0(-{Bz1hQU@(fU%>E! zA2XY7f!#YTWMyxQoqZt}5@O_do>{&5IQ8mkbzkjJOu9QrB{yvO^LbIow;_P?&x>|F z8^U9I>z+d!?rt|T8&$#=@mg0q*C(crG9~7p%LDTI8qsbY-1|xPC-H@hLq34Y#BUaS z-QC`??kmttPDj}R93Szu&nS>O{Afc*9CF`;*IO{}f(xxec?#4Ko(6{#wKfr8l?Arz zL1M=I5`KpPCL;zgH3wZ-hl27Cu2JBiO^KGL?Y9i@OD9IcOODx1joXY6_k8@psG8SkdQSPHTEk#;ClqAn9)Kxe^caa zL+sLzcs|96<$?B&t4qeVJdwn7E(xL9%9)9&w8Z4bT#g~@ux{`)@Vs5)mtB`-F*q#|%t(6n&eNIAdZ7025Fv47EU%S&5 z)t^>uy>$1LL~;I8VciCkHatSFy{E7XnsN$RCifBc>$7EGwxwpTXDvx5zV>d4alI{Lg*S|Glei&VD zF|rRZpZiIJCNTR%s32oB*qTrlS~Hy&sVYaY>r^7xd$~k6HN(nBePH1W#Ou`-gayB| zTleydE#C~%eOtimk7L3JzK^kX9(NvLT%IyrD&cf1!xHdIqid$%?y!=(^iiexS&V1% zgD;mqjR@3gjIZF?)U$42eWDre1ZwOU&%ZOHNbFhT?#v&ycrDGt5g5r?qSn7LiHc-n z5C|lu*Sha=_385(KE%0xacLC_RDWNBpW&TjC|p$ciK$&3Qt++HbC2Ud1V{doZLzrK z_duk8J#D*qzg;Zu{LrvuOs3X*sN_%CM5H_HktS_l0iTaEah>^9UJQ2?k;-7&Se*fj zi&91bA=8Bj;w%h-$B8%oXTiUJrm7>;;8hSXr6_YM{;1=8T$yN2XaD8Z5y{VSXa%my z{LBqixZyW0qqx==iY|z&Ij3oqwR&5-8izPGw_55X4`zXU7_Idcp#9bFe9+Fi*P0KS z*Xf2F1D$E96Q%Om5vA^u(lml&9N{`EfgnW&cEhH&?xHrg0W&F&xS|s3J@v z4HY~T?XFiN=>ENKlG#OW|5j1#SusvTTOOV12jd!^@~nfqR=!^q>EItcyPGbS){Zvip}Rm{F0wa!v5ZIn}bQhiG}YxG;c+rk7AsL$*n7^y3AnEvG_BLf#1~Jrmb8>Cruz(^aBk-gOx!eL=gQOCZ;kDwk1}8_~jFe=@Fg!7Og>Fv7CBUaeT`XHF&WoernpPakSs#a{tK|QK z>}9BAG(86O#N1qlPj2})h{z}l3gRGoQ(Squ)@k(sCO~t+ zN=w+9OHy$Mi@LhvYbW-t0F~tQGhc(Xqp&_cfu(k4<_|D!nOLzqS@-8CNbm2X47VVF z*a*(>sPDa9oo>mri$ZR-xf(js?G)RaOEGuCff{y%S%@@G5i-5L=sj^l0ep_{eayuX zEz+B1xX}j&b3cZ9KUxNb-}9+&24Bj9O}FX0c9o|#^6|b`ZA2Z5pv@z_Ul(GXb=`aD z&s+Vrj8Aqz8kkG(7aE@RjTFcK8h9RfZL6p3xRcRd^mGI5q2pmdpKb0>g&9}fo&Txj zDy6^vP(o&XmZll{J@{bcs=sY*-iyg2>Z6)uX?pOi1Cv# z#CXMi(b``ZO9s0Y67cN#tf?7;O+iJa$iT;xH1X&zB{Ka?V&vZs9C!k)+l$`M$q!RV z`jj(tdot#)t3#15KLzUt)n1oy#N+Em+n@G(W&X?efzm1%s?y&@-C*Nev17sQAo?id zFM3T~+z01gWr>L`fX-d)gs{o{uPOsZ^KSR;*uPvF*R1Zuz#K1Zj1F#nCN)9DA)er` zn5X^MEV}6naXZ?Oy5@BgV}(eT&m|+&bS8JF!|KkMWbf;RFLqou1Kk3B?APifANT`h zgHTW@lmM%{fg_F(A|W3M50`~6%z4@1GZ8m0T?RbV>QXX)gGOXMuw}Wsn54rMB{_c@ zu_i&|eVd8UDvy8PwbbQ`j-C(Xi_>?*{PMnx&tbc}?|m6AyX@w7xZM<@-2Jgcz~Knu za<+r&d!Zc5!2`(!mgm!H;;-p;UmYo_&hID~v(J#c^$_Cnqd>SS=?rzqR(+`ggx08r zRxjt$th;K9DCBN7IEuUjyU}OKUNXze;6}{{bqR)Wh^pu~^m%jH7)R$$@Xc7$LDkoe z7o`#e*%ylazlcJC3N7Y=Mg?uWq(>zk$UHzlU1T)tt;XyFZo1KII+1?n&b|SKyLskc zrqEf74xtDMe$Lbd?(gtBIH}{~FDfEJIV+xyVGIF?UoC!uQ=v$*(U^J^PMg|?co z`21pS{8RX8#39NDz^l_zI$UhK*e`N?{344%sDSq0qDTBEzIn*8C(b{aNR`)K$(71= z((>c;eM7vm5LECx%O+HSm7 zyXr!L5h1Q)^n?2^Z#E|2@qeLojcy$A`wWc}q_Ldq3QO@UYx~^^K{jRez>Q!3GgG5yfRl`R7(DVlfzHxUQF0=>2SAGZ0J^M-++S1Ze zUd|@+w-Lvz-w}nh3<`AVY?^leb3WMYP67Bvqm Date: Tue, 14 Dec 2021 20:56:50 +0100 Subject: [PATCH 09/13] JP aks deployment fix (#388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change AKS deployment configuration Deployment config changed from 1CPU/4GB to 0.5CPU/2GB so it fits a AKS created with default parameters * Update custom_model.md Co-authored-by: João Pedro Martins --- diabetes_regression/scoring/deployment_config_aks.yml | 4 ++-- docs/custom_model.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/diabetes_regression/scoring/deployment_config_aks.yml b/diabetes_regression/scoring/deployment_config_aks.yml index 1299dc9d..cd81009d 100644 --- a/diabetes_regression/scoring/deployment_config_aks.yml +++ b/diabetes_regression/scoring/deployment_config_aks.yml @@ -7,8 +7,8 @@ autoScaler: targetUtilization: 70 authEnabled: True containerResourceRequirements: - cpu: 1 - memoryInGB: 4 + cpu: 0.5 + memoryInGB: 2 appInsightsEnabled: True scoringTimeoutMs: 5000 maxConcurrentRequestsPerContainer: 2 diff --git a/docs/custom_model.md b/docs/custom_model.md index 5c7f8f4a..28a15d78 100644 --- a/docs/custom_model.md +++ b/docs/custom_model.md @@ -97,6 +97,7 @@ If you want to keep scoring: 1. Update or replace `[project name]/scoring/score.py` 1. Add any dependencies required by scoring to `[project name]/conda_dependencies.yml` 1. Modify the test cases in the `ml_service/util/smoke_test_scoring_service.py` script to match the schema of the training features in your data +1. Check and modify [project name]/scoring/deployment_config_aks.yml if AKS deployment is planned. The deployment configuration shall suit custom model as well as AKS cluster size. # Configure Custom Batch Scoring From d081fc2fe66f2541b33f6f118f6cbc41640a1a75 Mon Sep 17 00:00:00 2001 From: Katzmann1983 Date: Tue, 14 Dec 2021 21:00:54 +0100 Subject: [PATCH 10/13] Fix evaluate issue (#381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update documentation * fix evaluate issue * linting Co-authored-by: Jens Humrich Co-authored-by: João Pedro Martins --- diabetes_regression/evaluate/evaluate_model.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/diabetes_regression/evaluate/evaluate_model.py b/diabetes_regression/evaluate/evaluate_model.py index 5a69addb..d1ff3c6a 100644 --- a/diabetes_regression/evaluate/evaluate_model.py +++ b/diabetes_regression/evaluate/evaluate_model.py @@ -118,17 +118,21 @@ production_model_mse = 10000 if (metric_eval in model.tags): production_model_mse = float(model.tags[metric_eval]) - new_model_mse = float(run.parent.get_metrics().get(metric_eval)) + try: + new_model_mse = float(run.parent.get_metrics().get(metric_eval)) + except TypeError: + new_model_mse = None if (production_model_mse is None or new_model_mse is None): - print("Unable to find", metric_eval, "metrics, " + print("Unable to find ", metric_eval, " metrics, " "exiting evaluation") if((allow_run_cancel).lower() == 'true'): run.parent.cancel() else: print( - "Current Production model mse: {}, " - "New trained model mse: {}".format( - production_model_mse, new_model_mse + "Current Production model {}: {}, ".format( + metric_eval, production_model_mse) + + "New trained model {}: {}".format( + metric_eval, new_model_mse ) ) From 4b2667e34be793fa74abe801223279454b502d97 Mon Sep 17 00:00:00 2001 From: Tim Johnson <43890311+Inevitable-Marzipan@users.noreply.github.com> Date: Tue, 14 Dec 2021 20:27:26 +0000 Subject: [PATCH 11/13] Improve readability of exceptions in build pipeline script (#357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update manage_environment.py * Update attach_compute.py * Update attach_compute.py * Update manage_environment.py Co-authored-by: João Pedro Martins --- ml_service/util/attach_compute.py | 5 +++-- ml_service/util/manage_environment.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ml_service/util/attach_compute.py b/ml_service/util/attach_compute.py index ad9668db..cf8c07a6 100644 --- a/ml_service/util/attach_compute.py +++ b/ml_service/util/attach_compute.py @@ -1,4 +1,5 @@ +import traceback from azureml.core import Workspace from azureml.core.compute import AmlCompute from azureml.core.compute import ComputeTarget @@ -32,7 +33,7 @@ def get_compute(workspace: Workspace, compute_name: str, vm_size: str, for_batch show_output=True, min_node_count=None, timeout_in_minutes=10 ) return compute_target - except ComputeTargetException as ex: - print(ex) + except ComputeTargetException: + traceback.print_exc() print("An error occurred trying to provision compute.") exit(1) diff --git a/ml_service/util/manage_environment.py b/ml_service/util/manage_environment.py index 54c5a72f..b61c97fe 100644 --- a/ml_service/util/manage_environment.py +++ b/ml_service/util/manage_environment.py @@ -1,5 +1,6 @@ import os +import traceback from azureml.core import Workspace, Environment from ml_service.util.env_variables import Env from azureml.core.runconfig import DEFAULT_CPU_IMAGE, DEFAULT_GPU_IMAGE @@ -35,6 +36,6 @@ def get_environment( if restored_environment is not None: print(restored_environment) return restored_environment - except Exception as e: - print(e) + except Exception: + traceback.print_exc() exit(1) From 4a9a1ff7bbf41aa34ada00494db07c52b5faa647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Martins?= Date: Tue, 14 Dec 2021 22:04:31 +0100 Subject: [PATCH 12/13] Add details on the Batch scoring session of Getting Started (#389) * Revision of getting started guide up to Batch scoring. Also new diagam and fix to ARM template to remove region restrictions. * Detail on Batch scoring for Getting Started and additional debug message in the copy to ease of diagnosing issues * Tweaked text and added a NOQA for message Co-authored-by: Joao Pedro Martins --- .../scoring/parallel_batchscore_copyoutput.py | 2 +- docs/getting_started.md | 32 ++++++++++++------ docs/images/batch-child-run-scoringstep.png | Bin 0 -> 9057 bytes 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 docs/images/batch-child-run-scoringstep.png diff --git a/diabetes_regression/scoring/parallel_batchscore_copyoutput.py b/diabetes_regression/scoring/parallel_batchscore_copyoutput.py index cc4af42c..1bcde4b6 100644 --- a/diabetes_regression/scoring/parallel_batchscore_copyoutput.py +++ b/diabetes_regression/scoring/parallel_batchscore_copyoutput.py @@ -86,6 +86,6 @@ def copy_output(args): or args.output_path is None or args.output_path.strip() == "" ): - print("Missing parameters") + print("Missing parameters in parallel_batchscore_copyoutput.py -- Not going to copy inferences to an output datastore") # NOQA E501 else: copy_output(args) diff --git a/docs/getting_started.md b/docs/getting_started.md index 3cd1f263..977fe626 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -286,39 +286,49 @@ The pipeline has the following stage: ### Set up the Batch Scoring pipeline -In your Azure DevOps project, create and run a new build pipeline based on the [diabetes_regression-batchscoring-ci.yml](../.pipelines/diabetes_regression-batchscoring-ci.yml) -pipeline definition in your forked repository. +In your Azure DevOps project, create and run a new build pipeline based on the [.pipelines/diabetes_regression-batchscoring-ci.yml](../.pipelines/diabetes_regression-batchscoring-ci.yml) +pipeline definition in your forked repository. Rename this pipeline to `Batch-Scoring`. Once the pipeline is finished, check the execution result: ![Build](./images/batchscoring-ci-result.png) -Also check the published batch scoring pipeline in the **mlops-AML-WS** workspace in [Azure Portal](https://portal.azure.com/): +Also check the published batch scoring pipeline in your AML workspace in the [Azure Portal](https://portal.azure.com/): ![Batch scoring pipeline](./images/batchscoring-pipeline.png) Great, you now have the build pipeline set up for batch scoring which automatically triggers every time there's a change in the master branch! -The pipeline stages are summarized below: +The pipeline stages are described below in detail -- and you must do further configurations to actually see the batch inferences: #### Batch Scoring CI - Linting (code quality analysis) - Unit tests and code coverage analysis -- Build and publish *ML Batch Scoring Pipeline* in an *ML Workspace* +- Build and publish *ML Batch Scoring Pipeline* in an *AML Workspace* #### Batch Score model - Determine the model to be used based on the model name (required), model version, model tag name and model tag value bound pipeline parameters. - If run via Azure DevOps pipeline, the batch scoring pipeline will take the model name and version from the `Model-Train-Register-CI` build used as input. - If run locally without the model version, the batch scoring pipeline will use the model's latest version. -- Trigger the *ML Batch Scoring Pipeline* and waits for it to complete. +- Trigger the *ML Batch Scoring Pipeline* and wait for it to complete. - This is an **agentless** job. The CI pipeline can wait for ML pipeline completion for hours or even days without using agent resources. -- Use the scoring input data supplied via the SCORING_DATASTORE_INPUT_* configuration variables, or uses the default datastore and sample data. -- Once scoring is completed, the scores are made available in the same blob storage at the locations specified via the SCORING_DATASTORE_OUTPUT_* configuration variables. - -To configure your own custom scoring data, see [Configure Custom Batch Scoring](custom_model.md#Configure-Custom-Batch-Scoring). - +- Create an Azure ML pipeline with two steps. The pipeline is created by the code in `ml_service\pipelines\diabetes_regression_build_parallel_batchscore_pipeline.py` and has two steps: + - `scoringstep` - this step is a **`ParallelRunStep`** that executes the code in `diabetes_regression\scoring\parallel_batchscore.py` with several different batches of the data to be scored. + - `scorecopystep` - this is a **`PythonScriptStep`** step that copies the output inferences from Azure ML's internal storage into a target location in a another storage account. + - If you run the instructions as defined above with no changes to variables, this step will be **not** executed. You'll see a message in the logs for the corresponding step saying `Missing Parameters`. In this case, you'll be able to find the file with the inferences in the same Storage Account associated with Azure ML, in a location similar to `azureml-blobstore-SomeGuid\azureml\SomeOtherGuid\defaultoutput\parallel_run_step.txt`. One way to find the right path is this: + - Open your experiment in Azure ML (by default called `mlopspython`). + - Open the run that you want to look at (named something like `neat_morning_qc10dzjy` or similar). + - In the graphical pipeline view with 2 steps, click the button to open the details tab: `Show run overview`. + - You'll see two steps (corresponding to `scoringstep`and `scorecopystep` as described above). + - Click the step with the with older "Submitted time". + - Click "Output + logs" at the top, and you'll see something like the following: + ![Outputs of `scoringstep`](./images/batch-child-run-scoringstep.png) + - The `defaultoutput` file will have JSON content with the path to a file called `parallel_run_step.txt` containing the scoring. + +To properly configure this step for your own custom scoring data, you must follow the instructions in [Configure Custom Batch Scoring](custom_model.md#Configure-Custom-Batch-Scoring), which let you specify both the location of the files to score (via the `SCORING_DATASTORE_INPUT_*` configuration variables) and where to store the inferences (via the `SCORING_DATASTORE_OUTPUT_*` configuration variables). + ## Further Exploration You should now have a working set of pipelines that can get you started with MLOpsPython. Below are some additional features offered that might suit your scenario. diff --git a/docs/images/batch-child-run-scoringstep.png b/docs/images/batch-child-run-scoringstep.png new file mode 100644 index 0000000000000000000000000000000000000000..6b87f52dbe502d9a9e262aecfa169c24f97301a9 GIT binary patch literal 9057 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 Y>K~DKA#y$FZ83nnw95N3Ns}-C4bb Date: Wed, 15 Dec 2021 18:09:36 +0100 Subject: [PATCH 13/13] Clarified/Fixed getting started instructions for WebApp/AppService deployment (#390) * Revision of getting started guide up to Batch scoring. Also new diagam and fix to ARM template to remove region restrictions. * Detail on Batch scoring for Getting Started and additional debug message in the copy to ease of diagnosing issues * Tweaked text and added a NOQA for message * Clarified/Fixed getting started instructions for WebApp/AppService deployment Co-authored-by: Joao Pedro Martins --- docs/getting_started.md | 47 +++++++++++++----- docs/images/ADO-CD-pipeline-to-webapp.png | Bin 0 -> 8019 bytes .../appservice-webapp-deploymentcenter.png | Bin 0 -> 27306 bytes .../container-registry-webapp-image.png | Bin 0 -> 16757 bytes 4 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 docs/images/ADO-CD-pipeline-to-webapp.png create mode 100644 docs/images/appservice-webapp-deploymentcenter.png create mode 100644 docs/images/container-registry-webapp-image.png diff --git a/docs/getting_started.md b/docs/getting_started.md index 977fe626..4ba694d7 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -366,23 +366,46 @@ When deploying to Azure Kubernetes Service, key-based authentication is enabled ### Deploy the model to Azure App Service (Azure Web App for containers) -If you want to deploy your scoring service as an [Azure App Service](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-deploy-app-service) instead of Azure Container Instances and Azure Kubernetes Service, follow these additional steps. +If you want to deploy your scoring service as an [Azure App Service](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-deploy-app-service) instead of Azure Container Instances or Azure Kubernetes Service, follow these additional steps. -In the Variables tab, edit your variable group (`devopsforai-aml-vg`) and add a variable: +- First, you'll need to create an App Service Plan using Linux. The simplest way is to run this from your Azure CLI: `az appservice plan create --name nameOfAppServicePlan --resource-group nameOfYourResourceGroup --sku B1 --is-linux`. -| Variable Name | Suggested Value | -| ---------------------- | ---------------------- | -| WEBAPP_DEPLOYMENT_NAME | _name of your web app_ | +- Second, you'll need to create a webapp in this App Service Plan, and configure it to run a certain container. As currently there is no UI in the Azure Portal to do this, this has to be done from the command line. We'll come back to this. -Set **WEBAPP_DEPLOYMENT_NAME** to the name of your Azure Web App. This app must exist before you can deploy the model to it. +- In the Variables tab, edit your variable group (`devopsforai-aml-vg`) and add a variable: -Delete the **ACI_DEPLOYMENT_NAME** variable. + | Variable Name | Suggested Value | + | ---------------------- | ---------------------- | + | WEBAPP_DEPLOYMENT_NAME | _name of your web app_ | -The pipeline uses the [Azure ML CLI](../.pipelines/diabetes_regression-package-model-template.yml) to create a scoring image. The image will be registered under an Azure Container Registry instance that belongs to the Azure Machine Learning Service. Any dependencies that the scoring file depends on can also be packaged with the container with an image config. Learn more about how to create a container using the Azure ML SDK with the [Image class](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.image.image.image?view=azure-ml-py#create-workspace--name--models--image-config-) API documentation. + Set **WEBAPP_DEPLOYMENT_NAME** to the name of your Azure Web App. You have not yet created this webapp, so just use the name you're planning on giving it. -Make sure your webapp has the credentials to pull the image from the Azure Container Registry created by the Infrastructure as Code pipeline. Instructions can be found on the [Configure registry credentials in web app](https://docs.microsoft.com/en-us/azure/devops/pipelines/targets/webapp-on-container-linux?view=azure-devops&tabs=dotnet-core%2Cyaml#configure-registry-credentials-in-web-app) page. You'll need to run the pipeline once (including the Deploy to Webapp stage up to the `Create scoring image` step) so an image is present in the registry. After that, you can connect the Webapp to the Azure Container Registry in the Azure Portal. +- Delete the **ACI_DEPLOYMENT_NAME** or any AKS-related variable. -![build](./images/multi-stage-webapp.png) +- Next, you'll need to run your `Model-Deploy-CD` pipeline + + - The pipeline uses the [Azure ML CLI](../.pipelines/diabetes_regression-package-model-template.yml) to create a scoring image. The image will be registered under an Azure Container Registry instance that belongs to the Azure Machine Learning Service. Any dependencies that the scoring file depends on can also be packaged with the container with an image config. Learn more about how to create a container using the Azure ML SDK with the [Image class](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.image.image.image?view=azure-ml-py#create-workspace--name--models--image-config-) API documentation. + + - This pipeline will **fail** on the `Azure Web App on Container Deploy` step, with an error saying the webapp doesn't exist yet. This is expected. Go to the next step. + +- If you want to confirm that the scoring image has been created, open the Azure Container Registry mentioned above, which will be in the Resource Group of the Azure ML workspace, and look for the repositories. You'll have one that was created by the pipeline, called `package`, which was created by the CD pipeline: + + ![Azure Container Registry repository list](./images/container-registry-webapp-image.png) + +- Notedown the name of the Login Server of your Azure Container Registry. It'll be something like `YourAcrName.azurecr.io`. + +- Going back to the Step Two, now you can create a Web App in you App Service Plan using this scoring image but with the `latest` tag. The easiest way to do this is to run this in the Azure CLI: `az webapp create --resource-group yourResourceGroup --plan nameOfAppServicePlan --name nameOfWebApp --deployment-container-image-name YourAcrName.azurecr.io/package:latest` + - Here, `nameOfWebApp` is the same you put in your Azure DevOps `WEBAPP_DEPLOYMENT_NAME` variable. + +From now on, whenever you run the CD pipeline, it will update the image in the container registry and it'll automatically update the one used in the WebApp. CD pipeline runs will now succeed. + +![build](./images/ADO-CD-pipeline-to-webapp.png) + +To confirm, you can open the App Service Plan, open your new WebApp, and open the **Deployment Center**, where you'll see something like: + +![WebApp Deployment Center page](./images/appservice-webapp-deploymentcenter.png) + +If you run into problems, you may have to make sure your webapp has the credentials to pull the image from the Azure Container Registry created by the Infrastructure as Code pipeline. Instructions can be found on the [Configure registry credentials in web app](https://docs.microsoft.com/en-us/azure/devops/pipelines/targets/webapp-on-container-linux?view=azure-devops&tabs=dotnet-core%2Cyaml#configure-registry-credentials-in-web-app) page. ### Example pipelines using R @@ -424,7 +447,7 @@ To remove the resources created for this project, use the [/environment_setup/ia - The [custom model](custom_model.md) guide includes information on bringing your own code to this repository template. - We recommend using a [custom container](custom_model.md#customize-the-build-agent-environment) to manage your pipeline environment and dependencies. The container provided with the getting started guide may not be suitable or up to date with your project needs. -- Consider using [Azure Pipelines self-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&tabs=browser#install) to speed up your Azure ML pipeline execution. The Docker container image for the Azure ML pipeline is sizable, and having it cached on the agent between runs can trim several minutes from your runs. +- Consider using [Azure Pipelines self-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&tabs=browser#install) to speed up your Azure ML pipeline execution. The Docker container image for the Azure ML pipeline is sizable, and having it cached on the agent between runs can trim several minutes from your runs. Additionally, for secure deployments of Azure Machine Learning, you'll probably need to have a self-hosted agent in a Virtual Network. ### Additional Variables and Configuration @@ -434,7 +457,7 @@ There are more variables used in the project. They're defined in two places: one For using Azure Pipelines, all other variables are stored in the file `.pipelines/diabetes_regression-variables-template.yml`. Using the default values as a starting point, adjust the variables to suit your requirements. -In that folder, you'll also find the `parameters.json` file that we recommend using to provide parameters for training, evaluation, and scoring scripts. The sample parameter that `diabetes_regression` uses is the ridge regression [_alpha_ hyperparameter](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html). We don't provide any serializers for this config file. +In the `diabetes_regression` folder, you'll also find the `parameters.json` file that we recommend using to provide parameters for training, evaluation, and scoring scripts. The sample parameter that `diabetes_regression` uses is the ridge regression [_alpha_ hyperparameter](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html). We don't provide any serializers for this config file. #### Local configuration diff --git a/docs/images/ADO-CD-pipeline-to-webapp.png b/docs/images/ADO-CD-pipeline-to-webapp.png new file mode 100644 index 0000000000000000000000000000000000000000..aac8c9ee5c6da089c683b522edcc94eeadbb3d42 GIT binary patch literal 8019 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 literal 0 HcmV?d00001 diff --git a/docs/images/appservice-webapp-deploymentcenter.png b/docs/images/appservice-webapp-deploymentcenter.png new file mode 100644 index 0000000000000000000000000000000000000000..b79ff615a59944eace04f79e36bc6261499ecc7a GIT binary patch literal 27306 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

iNuf;M@bp`Gf0UCg_x$BNkQ52O;N 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

#rx%Wjrlpl&uq$u+^9bPQW@ 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 literal 0 HcmV?d00001 diff --git a/docs/images/container-registry-webapp-image.png b/docs/images/container-registry-webapp-image.png new file mode 100644 index 0000000000000000000000000000000000000000..4ec09f8fa931dae8f9cfca53b8bb526624ad91a7 GIT binary patch literal 16757 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 literal 0 HcmV?d00001