From 61fc76f5a493021ad7ca07444822207868dbdf0e Mon Sep 17 00:00:00 2001
From: "Ehsan M. Kermani" <6980212+ehsanmok@users.noreply.github.com>
Date: Wed, 26 May 2021 17:31:03 -0700
Subject: [PATCH] Add SageMaker Studio support (#32)
* Init studio support
Fix build
Fix assert syntax error
Update README
Init IAM prune
Reformat
Tighten permissions and bind with unique prefix
Update build
Update README
Add pyproject.toml
Cleanup
Pin black version in build and cleanup
Improve onboarding and various cleanups
Update README for SageMaker Studio
Update project prefix description
Find the custom resource stack deletion
Remove unnecessary return None
Fix datacapture uri and remove canary
Remove canary.js
Update canary deployment descriptions
* Address comments
Update docs
Remove synthetics windonw from dashboard
Tag pipeline with sagemaker project id and format
Fix retrain rule
Include necessary permissions to run worflow.ipynb
Update README
Setup pre-commit to lint and add default kernel
Fix trailing newline
Update README with one-click button
Cleanup
Update project tree structure in README
Remove dev artifacts from build
Update README
---
.gitignore | 4 +
.pre-commit-config.yaml | 16 +
LICENSE | 1 -
README.md | 94 +-
api/app.py | 4 +-
api/pre_traffic_hook.py | 13 +-
assets/deploy-model-dev.yml | 8 +-
assets/deploy-model-prd.yml | 32 +-
assets/suggest-baseline.yml | 12 +-
assets/training-job.yml | 10 +-
.../sagemaker_add_transform_header.py | 1 -
.../sagemaker_create_experiment.py | 5 +-
custom_resource/sagemaker_query_drift.py | 4 +-
custom_resource/sagemaker_suggest_baseline.py | 49 +-
custom_resource/sagemaker_training_job.py | 6 +-
docs/cloudwatch-dashboard.png | Bin 32368 -> 31516 bytes
docs/sagemaker-notebook.png | Bin 38548 -> 0 bytes
docs/studio-cft.png | Bin 0 -> 74648 bytes
docs/studio-execution-role.png | Bin 0 -> 156976 bytes
docs/studio-sagemaker-project-template.png | Bin 0 -> 239466 bytes
model/buildspec.yml | 24 +-
model/dashboard.json | 2 +-
model/requirements.txt | 2 +-
model/{run.py => run_pipeline.py} | 66 +-
notebook/canary.js | 37 -
notebook/dashboard.json | 17 -
notebook/mlops.ipynb | 1906 +----------------
notebook/workflow.ipynb | 1187 +---------
pipeline.yml | 384 ++--
pyproject.toml | 3 +
scripts/build.sh | 52 +
scripts/lint.sh | 11 +
scripts/set_kernelspec.py | 24 +
studio.yml | 208 ++
34 files changed, 665 insertions(+), 3517 deletions(-)
create mode 100644 .gitignore
create mode 100644 .pre-commit-config.yaml
delete mode 100644 docs/sagemaker-notebook.png
create mode 100644 docs/studio-cft.png
create mode 100644 docs/studio-execution-role.png
create mode 100644 docs/studio-sagemaker-project-template.png
rename model/{run.py => run_pipeline.py} (89%)
delete mode 100644 notebook/canary.js
create mode 100644 pyproject.toml
create mode 100755 scripts/build.sh
create mode 100755 scripts/lint.sh
create mode 100644 scripts/set_kernelspec.py
create mode 100644 studio.yml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f4da28d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.vscode
+build
+__pycache__
+*.ipynb_checkpoints
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..3c2c231
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,16 @@
+default_language_version:
+ python: python3.7
+
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.3.0
+ hooks:
+ - id: trailing-whitespace
+ - repo: local
+ hooks:
+ - id: lint
+ name: lint
+ always_run: true
+ entry: scripts/lint.sh
+ language: system
+ types: [python]
diff --git a/LICENSE b/LICENSE
index 1bb4f21..6aa0c45 100644
--- a/LICENSE
+++ b/LICENSE
@@ -12,4 +12,3 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
diff --git a/README.md b/README.md
index 6be3441..d6b3a35 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
# Amazon SageMaker Safe Deployment Pipeline
-
## Introduction
This is a sample solution to build a safe deployment pipeline for Amazon SageMaker. This example could be useful for any organization looking to operationalize machine learning with native AWS development tools such as AWS CodePipeline, AWS CodeBuild and AWS CodeDeploy.
@@ -32,48 +31,52 @@ In the following diagram, you can view the continuous delivery stages of AWS Cod
The following is the list of steps required to get up and running with this sample.
-### Prepare an AWS Account
+### Requirements
+
+* Create your AWS account at [http://aws.amazon.com](http://aws.amazon.com) by following the instructions on the site.
+* A Studio user account, see [onboard to Amazon SageMaker Studio](https://docs.aws.amazon.com/sagemaker/latest/dg/gs-studio-onboard.html)
+### Enable Amazon SageMaker Studio Project
-Create your AWS account at [http://aws.amazon.com](http://aws.amazon.com) by following the instructions on the site.
+1. From AWS console navigate to Amazon SageMaker Studio and click on your studio user name (do **not** Open Studio now) and copy the name of execution role as shown below (similar to `AmazonSageMaker-ExecutionRole-20210112T085906`)
-### *Optionally* fork this GitHub Repository and create an Access Token
-
-1. [Fork](https://github.com/aws-samples/sagemaker-safe-deployment-pipeline/fork) a copy of this repository into your own GitHub account by clicking the **Fork** in the upper right-hand corner.
-2. Follow the steps in the [GitHub documentation](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) to create a new (OAuth 2) token with the following scopes (permissions): `admin:repo_hook` and `repo`. If you already have a token with these permissions, you can use that. You can find a list of all your personal access tokens in [https://github.com/settings/tokens](https://github.com/settings/tokens).
-3. Copy the access token to your clipboard. For security reasons, after you navigate off the page, you will not be able to see the token again. If you have lost your token, you can [regenerate](https://docs.aws.amazon.com/codepipeline/latest/userguide/GitHub-authentication.html#GitHub-rotate-personal-token-CLI) your token.
+
+
+
-### Launch the AWS CloudFormation Stack
+2. Click on the launch button below to setup the stack
-Click on the **Launch Stack** button below to launch the CloudFormation Stack to set up the SageMaker safe deployment pipeline.
+
+
+
-[![Launch CFN stack](https://s3.amazonaws.com/cloudformation-examples/cloudformation-launch-stack.png)](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Famazon-sagemaker-safe-deployment-pipeline.s3.amazonaws.com%2Fsfn%2Fpipeline.yml&stackName=nyctaxi¶m_GitHubBranch=master¶m_GitHubRepo=amazon-sagemaker-safe-deployment-pipeline¶m_GitHubUser=aws-samples¶m_ModelName=nyctaxi¶m_NotebookInstanceType=ml.t3.medium)
+and paste the role name copied in step 1 as the value of the parameter `SageMakerStudioRoleName` as shown below and click **Create Stack**
-Provide a stack name eg **sagemaker-safe-deployment-pipeline** and specify the parameters.
+
+
+
+
+*Alternatively*, one can use the provided `scripts/build.sh` (which required [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed with appropriate IAM permissions) as follows
+```
+# bash scripts/build.sh S3_BUCKET_NAME STACK_NAME REGION STUDIO_ROLE_NAME
+# REGION should match your default AWS CLI region
+# STUDIO_ROLE_NAME is copied from step 1. Example:
+bash scripts/build.sh example-studio example-pipeline us-east-1 AmazonSageMaker-ExecutionRole-20210112T085906
+```
-Parameters | Description
------------ | -----------
-Model Name | A unique name for this model (must be less than 15 characters long).
-S3 Bucket for Dataset | The bucket containing the dataset (defaults to [nyc-tlc](https://registry.opendata.aws/nyc-tlc-trip-records-pds/))
-Notebook Instance Type | The [Amazon SageMaker instance type](https://aws.amazon.com/sagemaker/pricing/instance-types/). Default is ml.t3.medium.
-GitHub Repository | The name (not URL) of the GitHub repository to pull from.
-GitHub Branch | The name (not URL) of the GitHub repository’s branch to use.
-GitHub Username | GitHub Username for this repository. Update this if you have forked the repository.
-GitHub Access Token | The optional Secret OAuthToken with access to your GitHub repository.
-Email Address | The optional Email address to notify on successful or failed deployments.
+3. From the AWS console navigate to `cloudformation` and once the stack `STACK_NAME` is ready
+4. Go to your SageMaker Studio and **Open Studio** (and possibly refresh your browser if you're already in Studio) and from the your left hand side panel, click on the inverted triangle. As with the screenshot below, under `Projects -> Create project -> Organization templates`, you should be able to see the added **SageMaker Safe Deployment Pipeline**. Click on the template name and **Select project template**
-![code-pipeline](docs/stack-parameters.png)
+
+
+
-You can launch the same stack using the AWS CLI. Here's an example:
+5. Choose a name for the project and can leave the rest of the fields with their default values (can use your own email for SNS notifications) and click on **Create project**
+6. Once the project is created, it gives you the option to clone it locally from AWS CodeCommit by a single click. Click clone and it goes directly to the project
+7. Navigate to the code base and go to `notebook/mlops.ipynb`
+8. Choose a kernel from the prompt such as `Python 3 (Data Science)`
+9. Assign your project name to the placeholder `PROJECT_NAME` in the first code cell of the `mlops.ipynb` notebook
+10. Now you are ready to go through the rest of the cells in `notebook/mlops.ipynb`
-`
- aws cloudformation create-stack --stack-name sagemaker-safe-deployment \
- --template-body file://pipeline.yml \
- --capabilities CAPABILITY_IAM \
- --parameters \
- ParameterKey=ModelName,ParameterValue=mymodelname \
- ParameterKey=GitHubUser,ParameterValue=youremailaddress@example.com \
- ParameterKey=GitHubToken,ParameterValue=YOURGITHUBTOKEN12345ab1234234
-`
### Start, Test and Approve the Deployment
@@ -81,15 +84,11 @@ Once the deployment is complete, there will be a new AWS CodePipeline created, w
![code-pipeline](docs/data-source-before.png)
-Launch the newly created SageMaker Notebook in your [AWS console](https://aws.amazon.com/getting-started/hands-on/build-train-deploy-machine-learning-model-sagemaker/), navigate to the `notebook` directory and opening the notebook by clicking on the `mlops.ipynb` link.
-
-![code-pipeline](docs/sagemaker-notebook.png)
-
Once the notebook is running, you will be guided through a series of steps starting with downloading the [New York City Taxi](https://registry.opendata.aws/nyc-tlc-trip-records-pds/) dataset, uploading this to an Amazon SageMaker S3 bucket along with the data source meta data to trigger a new build in the AWS CodePipeline.
![code-pipeline](docs/datasource-after.png)
-Once your pipeline is kicked off it will run model training and deploy a development SageMaker Endpoint.
+Once your pipeline is kicked off it will run model training and deploy a development SageMaker Endpoint.
There is a manual approval step which you can action directly within the SageMaker Notebook to promote this to production, send some traffic to the live endpoint and create a REST API.
@@ -138,15 +137,20 @@ This project is written in Python, and design to be customized for your own mode
│ ├── buildspec.yml
│ ├── dashboard.json
│ ├── requirements.txt
-│ └── run.py
+│ └── run_pipeline.py
├── notebook
-│ ├── canary.js
│ ├── dashboard.json
+| ├── workflow.ipynb
│ └── mlops.ipynb
-└── pipeline.yml
+├── scripts
+| ├── build.sh
+| ├── lint.sh
+| └── set_kernelspec.py
+├── pipeline.yml
+└── studio.yml
```
-Edit the `get_training_params` method in the `model/run.py` script that is run as part of the AWS CodeBuild step to add your own estimator or model definition.
+Edit the `get_training_params` method in the `model/run_pipeline.py` script that is run as part of the AWS CodeBuild step to add your own estimator or model definition.
Extend the AWS Lambda hooks in `api/pre_traffic_hook.py` and `api/post_traffic_hook.py` to add your own validation or inference against the deployed Amazon SageMaker endpoints. You can also edit the `api/app.py` lambda to add any enrichment or transformation to the request/response payload.
@@ -158,8 +162,7 @@ This section outlines cost considerations for running the SageMaker Safe Deploym
- **CodeCommit** – $1/month if you didn't opt to use your own GitHub repository.
- **CodeDeploy** – No cost with AWS Lambda.
- **CodePipeline** – CodePipeline costs $1 per active pipeline* per month. Pipelines are free for the first 30 days after creation. More can be found at [AWS CodePipeline Pricing](https://aws.amazon.com/codepipeline/pricing/).
-- **CloudWatch** - This template includes a [Canary](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html), 1 dashboard and 4 alarms (2 for deployment, 1 for model drift and 1 for canary) which costs less than $10 per month.
- - Canaries cost $0.0012 per run, or $5/month if they run every 10 minutes.
+- **CloudWatch** - This template includes 1 dashboard and 3 alarms (2 for deployment and 1 for model drift) which costs less than $10 per month.
- Dashboards cost $3/month.
- Alarm metrics cost $0.10 per alarm.
- **CloudTrail** - Low cost, $0.10 per 100,000 data events to enable [S3 CloudWatch Event](https://docs.aws.amazon.com/codepipeline/latest/userguide/create-cloudtrail-S3-source-console.html). For more information, see [AWS CloudTrail Pricing](https://aws.amazon.com/cloudtrail/pricing/)
@@ -170,7 +173,7 @@ This section outlines cost considerations for running the SageMaker Safe Deploym
- The `ml.t3.medium` instance *notebook* costs $0.0582 an hour.
- The `ml.m4.xlarge` instance for the *training* job costs $0.28 an hour.
- The `ml.m5.xlarge` instance for the *monitoring* baseline costs $0.269 an hour.
- - The `ml.t2.medium` instance for the dev *hosting* endpoint costs $0.065 an hour.
+ - The `ml.t2.medium` instance for the dev *hosting* endpoint costs $0.065 an hour.
- The two `ml.m5.large` instances for production *hosting* endpoint costs 2 x $0.134 per hour.
- The `ml.m5.xlarge` instance for the hourly scheduled *monitoring* job costs $0.269 an hour.
- **S3** – Prices will vary depending on the size of the model/artifacts stored. The first 50 TB each month will cost only $0.023 per GB stored. For more information, see [Amazon S3 Pricing](https://aws.amazon.com/s3/pricing/).
@@ -193,4 +196,3 @@ See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more inform
## License
This library is licensed under the MIT-0 License. See the LICENSE file.
-
diff --git a/api/app.py b/api/app.py
index 86b6c34..c499b17 100644
--- a/api/app.py
+++ b/api/app.py
@@ -50,8 +50,6 @@ def lambda_handler(event, context):
"body": predictions,
}
except ClientError as e:
- logger.error(
- "Unexpected sagemaker error: {}".format(e.response["Error"]["Message"])
- )
+ logger.error("Unexpected sagemaker error: {}".format(e.response["Error"]["Message"]))
logger.error(e)
return {"statusCode": 500, "message": "Unexpected sagemaker error"}
diff --git a/api/pre_traffic_hook.py b/api/pre_traffic_hook.py
index e6fded5..03780fa 100644
--- a/api/pre_traffic_hook.py
+++ b/api/pre_traffic_hook.py
@@ -29,16 +29,9 @@ def lambda_handler(event, context):
else:
# Validate that endpoint config has data capture enabled
endpoint_config_name = response["EndpointConfigName"]
- response = sm.describe_endpoint_config(
- EndpointConfigName=endpoint_config_name
- )
- if (
- "DataCaptureConfig" in response
- and response["DataCaptureConfig"]["EnableCapture"]
- ):
- logger.info(
- "data capture enabled for endpoint config %s", endpoint_config_name
- )
+ response = sm.describe_endpoint_config(EndpointConfigName=endpoint_config_name)
+ if "DataCaptureConfig" in response and response["DataCaptureConfig"]["EnableCapture"]:
+ logger.info("data capture enabled for endpoint config %s", endpoint_config_name)
else:
error_message = "SageMaker data capture not enabled for endpoint config"
# TODO: Invoke endpoint if don't have canary / live traffic
diff --git a/assets/deploy-model-dev.yml b/assets/deploy-model-dev.yml
index e73eafd..3b86b03 100644
--- a/assets/deploy-model-dev.yml
+++ b/assets/deploy-model-dev.yml
@@ -23,10 +23,10 @@ Resources:
Model:
Type: "AWS::SageMaker::Model"
Properties:
- ModelName: !Sub mlops-${ModelName}-dev-${TrainJobId}
+ ModelName: !Sub ${ModelName}-dev-${TrainJobId}
PrimaryContainer:
Image: !Ref ImageRepoUri
- ModelDataUrl: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/mlops-${ModelName}-${TrainJobId}/output/model.tar.gz
+ ModelDataUrl: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/${ModelName}-${TrainJobId}/output/model.tar.gz
ExecutionRoleArn: !Ref DeployRoleArn
EndpointConfig:
@@ -38,11 +38,11 @@ Resources:
InstanceType: ml.t2.medium
ModelName: !GetAtt Model.ModelName
VariantName: !Sub ${ModelVariant}-${ModelName}
- EndpointConfigName: !Sub mlops-${ModelName}-dec-${TrainJobId}
+ EndpointConfigName: !Sub ${ModelName}-dec-${TrainJobId}
KmsKeyId: !Ref KmsKeyId
Endpoint:
Type: "AWS::SageMaker::Endpoint"
Properties:
- EndpointName: !Sub mlops-${ModelName}-dev-${TrainJobId}
+ EndpointName: !Sub ${ModelName}-dev-${TrainJobId}
EndpointConfigName: !GetAtt EndpointConfig.EndpointConfigName
diff --git a/assets/deploy-model-prd.yml b/assets/deploy-model-prd.yml
index 73f7303..42aee98 100644
--- a/assets/deploy-model-prd.yml
+++ b/assets/deploy-model-prd.yml
@@ -81,10 +81,10 @@ Resources:
Model:
Type: "AWS::SageMaker::Model"
Properties:
- ModelName: !Sub mlops-${ModelName}-prd-${TrainJobId}
+ ModelName: !Sub ${ModelName}-prd-${TrainJobId}
PrimaryContainer:
Image: !Ref ImageRepoUri
- ModelDataUrl: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/mlops-${ModelName}-${TrainJobId}/output/model.tar.gz
+ ModelDataUrl: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/${ModelName}-${TrainJobId}/output/model.tar.gz
ExecutionRoleArn: !Ref DeployRoleArn
EndpointConfig:
@@ -109,19 +109,19 @@ Resources:
EnableCapture: True
InitialSamplingPercentage: 100
KmsKeyId: !Ref KmsKeyId
- EndpointConfigName: !Sub mlops-${ModelName}-pec-${TrainJobId}
+ EndpointConfigName: !Sub ${ModelName}-pec-${TrainJobId}
KmsKeyId: !Ref KmsKeyId
Endpoint:
Type: "AWS::SageMaker::Endpoint"
Properties:
- EndpointName: !Sub mlops-${ModelName}-prd-${TrainJobId}
+ EndpointName: !Sub ${ModelName}-prd-${TrainJobId}
EndpointConfigName: !GetAtt EndpointConfig.EndpointConfigName
ApiFunction:
Type: AWS::Serverless::Function
Properties:
- FunctionName: !Sub mlops-${ModelName}-api
+ FunctionName: !Sub ${ModelName}-api
CodeUri: ../api
Handler: app.lambda_handler
Runtime: python3.7
@@ -177,7 +177,7 @@ Resources:
Effect: Allow
Action:
- sagemaker:InvokeEndpoint
- Resource: "arn:aws:sagemaker:*:*:endpoint/mlops-*"
+ Resource: "arn:aws:sagemaker:*:*:endpoint/*"
- Sid: AlowSNS
Effect: Allow
Action:
@@ -205,7 +205,7 @@ Resources:
- sagemaker:DescribeEndpointConfig
- sagemaker:InvokeEndpoint
Resource:
- - "arn:aws:sagemaker:*:*:*/mlops-*"
+ - "arn:aws:sagemaker:*:*:*/*"
- Sid: AllowCodeDeploy
Effect: Allow
Action:
@@ -261,9 +261,9 @@ Resources:
MonitoringJobDefinition:
BaselineConfig:
ConstraintsResource:
- S3Uri: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/monitoring/baseline/mlops-${ModelName}-pbl-${TrainJobId}/constraints.json
+ S3Uri: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/monitoring/baseline/${ModelName}-pbl-${TrainJobId}/constraints.json
StatisticsResource:
- S3Uri: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/monitoring/baseline/mlops-${ModelName}-pbl-${TrainJobId}/statistics.json
+ S3Uri: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/monitoring/baseline/${ModelName}-pbl-${TrainJobId}/statistics.json
MonitoringAppSpecification:
ImageUri:
!FindInMap [ModelAnalyzerMap, !Ref "AWS::Region", "ImageUri"]
@@ -287,19 +287,19 @@ Resources:
MaxRuntimeInSeconds: 1800
ScheduleConfig:
ScheduleExpression: "cron(0 * ? * * *)"
- MonitoringScheduleName: !Sub mlops-${ModelName}-pms
+ MonitoringScheduleName: !Sub ${ModelName}-pms
SagemakerScheduleAlarm:
Type: "AWS::CloudWatch::Alarm"
Properties:
- AlarmName: !Sub mlops-${ModelName}-metric-gt-threshold
+ AlarmName: !Sub ${ModelName}-metric-gt-threshold
AlarmDescription: Schedule Metric > Threshold
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: Endpoint
Value: !GetAtt Endpoint.EndpointName
- Name: MonitoringSchedule
- Value: !Sub mlops-${ModelName}-pms
+ Value: !Sub ${ModelName}-pms
EvaluationPeriods: 1
DatapointsToAlarm: 1
MetricName: !Ref ScheduleMetricName
@@ -311,7 +311,7 @@ Resources:
AliasErrorMetricGreaterThanZeroAlarm:
Type: "AWS::CloudWatch::Alarm"
Properties:
- AlarmName: !Sub mlops-${ModelName}-alias-gt-zero
+ AlarmName: !Sub ${ModelName}-alias-gt-zero
AlarmDescription: Lambda Function Error > 0
ComparisonOperator: GreaterThanThreshold
Dimensions:
@@ -329,7 +329,7 @@ Resources:
LatestVersionErrorMetricGreaterThanZeroAlarm:
Type: "AWS::CloudWatch::Alarm"
Properties:
- AlarmName: !Sub mlops-${ModelName}-version-gt-zero
+ AlarmName: !Sub ${ModelName}-version-gt-zero
AlarmDescription: Lambda Function Error > 0
ComparisonOperator: GreaterThanThreshold
Dimensions:
@@ -351,7 +351,7 @@ Resources:
Properties:
MaxCapacity: 10
MinCapacity: 2
- ResourceId: !Sub endpoint/mlops-${ModelName}-prd-${TrainJobId}/variant/${ModelVariant}-${ModelName}
+ ResourceId: !Sub endpoint/${ModelName}-prd-${TrainJobId}/variant/${ModelVariant}-${ModelName}
RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/MLOps
ScalableDimension: sagemaker:variant:DesiredInstanceCount
ServiceNamespace: sagemaker
@@ -362,7 +362,7 @@ Resources:
Properties:
PolicyName: SageMakerVariantInvocationsPerInstance
PolicyType: TargetTrackingScaling
- ResourceId: !Sub endpoint/mlops-${ModelName}-prd-${TrainJobId}/variant/${ModelVariant}-${ModelName}
+ ResourceId: !Sub endpoint/${ModelName}-prd-${TrainJobId}/variant/${ModelVariant}-${ModelName}
ScalableDimension: sagemaker:variant:DesiredInstanceCount
ServiceNamespace: sagemaker
TargetTrackingScalingPolicyConfiguration:
diff --git a/assets/suggest-baseline.yml b/assets/suggest-baseline.yml
index 20fc986..e70787c 100644
--- a/assets/suggest-baseline.yml
+++ b/assets/suggest-baseline.yml
@@ -1,5 +1,11 @@
Description: Suggest baseline for training job
Parameters:
+ ProjectPrefix:
+ Type: String
+ Description: |
+ Makes resource privileges for services using this template scoped-limited.
+ Changing the default must be done with care
+ Default: PROJECT_PREFIX
ModelName:
Type: String
Description: Name of the model
@@ -20,10 +26,10 @@ Resources:
SagemakerSuggestBaseline:
Type: Custom::SuggestBaseline
Properties:
- ServiceToken: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:sagemaker-cfn-suggest-baseline
- ProcessingJobName: !Sub mlops-${ModelName}-pbl-${TrainJobId}
+ ServiceToken: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ProjectPrefix}-sagemaker-cfn-suggest-baseline
+ ProcessingJobName: !Sub ${ProjectPrefix}-${ModelName}-pbl-${TrainJobId}
BaselineInputUri: !Ref BaselineInputUri
- BaselineResultsUri: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/monitoring/baseline/mlops-${ModelName}-pbl-${TrainJobId}
+ BaselineResultsUri: !Sub s3://sagemaker-${AWS::Region}-${AWS::AccountId}/${ModelName}/monitoring/baseline/${ProjectPrefix}-${ModelName}-pbl-${TrainJobId}
KmsKeyId: !Ref KmsKeyId
PassRoleArn: !Ref MLOpsRoleArn
ExperimentName: !Ref ModelName
diff --git a/assets/training-job.yml b/assets/training-job.yml
index b782a1f..e207d0f 100644
--- a/assets/training-job.yml
+++ b/assets/training-job.yml
@@ -1,5 +1,11 @@
Description: Wait for a training job
Parameters:
+ ProjectPrefix:
+ Type: String
+ Description: |
+ Makes resource privileges for services using this template scoped-limited.
+ Changing the default must be done with care
+ Default: PROJECT_PREFIX
ModelName:
Type: String
Description: Name of the model
@@ -17,8 +23,8 @@ Resources:
SagemakerTrainingJob:
Type: Custom::TrainingJob
Properties:
- ServiceToken: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:sagemaker-cfn-training-job"
- TrainingJobName: !Sub mlops-${ModelName}-${TrainJobId}
+ ServiceToken: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ProjectPrefix}-sagemaker-cfn-training-job"
+ TrainingJobName: !Sub ${ProjectPrefix}-${ModelName}-${TrainJobId}
TrainingJobRequest: !Ref TrainJobRequest
ExperimentName: !Ref ModelName
TrialName: !Ref TrainJobId
diff --git a/custom_resource/sagemaker_add_transform_header.py b/custom_resource/sagemaker_add_transform_header.py
index 8cd58df..d1e22d9 100644
--- a/custom_resource/sagemaker_add_transform_header.py
+++ b/custom_resource/sagemaker_add_transform_header.py
@@ -37,4 +37,3 @@ def lambda_handler(event, context):
return new_obj.put(
Body=body.encode("utf-8"), ContentType="text/csv", Metadata={"header": "true"}
)
-
diff --git a/custom_resource/sagemaker_create_experiment.py b/custom_resource/sagemaker_create_experiment.py
index af6c2ab..3d9e3d9 100644
--- a/custom_resource/sagemaker_create_experiment.py
+++ b/custom_resource/sagemaker_create_experiment.py
@@ -46,5 +46,8 @@ def lambda_handler(event, context):
raise error
return {
"statusCode": 200,
- "results": {"ExperimentCreated": experiment_created, "TrialCreated": trial_created,},
+ "results": {
+ "ExperimentCreated": experiment_created,
+ "TrialCreated": trial_created,
+ },
}
diff --git a/custom_resource/sagemaker_query_drift.py b/custom_resource/sagemaker_query_drift.py
index 6e33af4..ec6884d 100644
--- a/custom_resource/sagemaker_query_drift.py
+++ b/custom_resource/sagemaker_query_drift.py
@@ -23,7 +23,8 @@ def get_processing_job(processing_job_name):
def get_s3_results_json(result_bucket, result_path, filename):
s3_object = s3_client.get_object(
- Bucket=result_bucket, Key=os.path.join(result_path.lstrip("/"), filename),
+ Bucket=result_bucket,
+ Key=os.path.join(result_path.lstrip("/"), filename),
)
return json.loads(s3_object["Body"].read())
@@ -81,4 +82,3 @@ def lambda_handler(event, context):
message = "Failed to read processing status!"
print(e)
return {"statusCode": 500, "error": message}
-
diff --git a/custom_resource/sagemaker_suggest_baseline.py b/custom_resource/sagemaker_suggest_baseline.py
index e2d0efe..40fc9a3 100644
--- a/custom_resource/sagemaker_suggest_baseline.py
+++ b/custom_resource/sagemaker_suggest_baseline.py
@@ -53,7 +53,7 @@ def poll_create(event, context):
@helper.poll_delete
def poll_delete(event, context):
"""
- Return true if the resource has been stopped.
+ Return true if the resource has been stopped.
"""
processing_job_name = get_processing_job_name(event)
logger.info("Polling for stopped processing job: %s", processing_job_name)
@@ -64,9 +64,7 @@ def poll_delete(event, context):
def get_model_monitor_container_uri(region):
- container_uri_format = (
- "{0}.dkr.ecr.{1}.amazonaws.com/sagemaker-model-monitor-analyzer"
- )
+ container_uri_format = "{0}.dkr.ecr.{1}.amazonaws.com/sagemaker-model-monitor-analyzer"
regions_to_accounts = {
"eu-north-1": "895015795356",
@@ -113,9 +111,7 @@ def is_processing_job_ready(processing_job_name):
)
else:
raise Exception(
- "Processing Job ({}) has unexpected status: {}".format(
- processing_job_name, status
- )
+ "Processing Job ({}) has unexpected status: {}".format(processing_job_name, status)
)
return is_ready
@@ -140,9 +136,7 @@ def create_processing_job(event):
def stop_processing_job(processing_job_name):
try:
- processing_job = sm.describe_processing_job(
- ProcessingJobName=processing_job_name
- )
+ processing_job = sm.describe_processing_job(ProcessingJobName=processing_job_name)
status = processing_job["ProcessingJobStatus"]
if status == "InProgress":
logger.info("Stopping InProgress processing job: %s", processing_job_name)
@@ -165,8 +159,7 @@ def stop_processing_job(processing_job_name):
class DatasetFormat(object):
- """Represents a Dataset Format that is used when calling a DefaultModelMonitor.
- """
+ """Represents a Dataset Format that is used when calling a DefaultModelMonitor."""
@staticmethod
def csv(header=True, output_columns_position="START"):
@@ -203,9 +196,7 @@ def sagemaker_capture_json():
dict: JSON string containing DatasetFormat to be used by DefaultModelMonitor.
"""
return {
- "sagemaker_capture_json": {
- "captureIndexNames": ["endpointInput", "endpointOutput"]
- }
+ "sagemaker_capture_json": {"captureIndexNames": ["endpointInput", "endpointOutput"]}
}
@@ -256,22 +247,16 @@ def get_processing_request(event, dataset_format=DatasetFormat.csv()):
}
},
"StoppingCondition": {
- "MaxRuntimeInSeconds": int(
- props.get("MaxRuntimeInSeconds", 1800)
- ) # 30 minutes
+ "MaxRuntimeInSeconds": int(props.get("MaxRuntimeInSeconds", 1800)) # 30 minutes
},
"AppSpecification": {
- "ImageUri": props.get(
- "ImageURI", get_model_monitor_container_uri(helper._region)
- ),
+ "ImageUri": props.get("ImageURI", get_model_monitor_container_uri(helper._region)),
},
"Environment": {
"dataset_format": json.dumps(dataset_format),
"dataset_source": "/opt/ml/processing/input/baseline_dataset_input",
"output_path": "/opt/ml/processing/output",
- "publish_cloudwatch_metrics": props.get(
- "PublishCloudwatchMetrics", "Disabled"
- ),
+ "publish_cloudwatch_metrics": props.get("PublishCloudwatchMetrics", "Disabled"),
},
"RoleArn": props["PassRoleArn"],
}
@@ -279,9 +264,7 @@ def get_processing_request(event, dataset_format=DatasetFormat.csv()):
# Add the KmsKeyId to monitoring outputs and cluster volume if provided
if props.get("KmsKeyId") is not None:
request["ProcessingOutputConfig"]["KmsKeyId"] = props["KmsKeyId"]
- request["ProcessingResources"]["ClusterConfig"]["VolumeKmsKeyId"] = props[
- "KmsKeyId"
- ]
+ request["ProcessingResources"]["ClusterConfig"]["VolumeKmsKeyId"] = props["KmsKeyId"]
# Add experiment tracking
request["ExperimentConfig"] = {
@@ -295,9 +278,7 @@ def get_processing_request(event, dataset_format=DatasetFormat.csv()):
if props.get("RecordPreprocessorSourceUri"):
env = request["Environment"]
fn = get_file_name(props["RecordPreprocessorSourceUri"])
- env["record_preprocessor_script"] = (
- "/opt/ml/processing/code/postprocessing/" + fn
- )
+ env["record_preprocessor_script"] = "/opt/ml/processing/code/postprocessing/" + fn
request["ProcessingInputs"].append(
{
"InputName": "pre_processor_script",
@@ -315,9 +296,7 @@ def get_processing_request(event, dataset_format=DatasetFormat.csv()):
if props.get("PostAnalyticsProcessorSourceUri"):
env = request["Environment"]
fn = get_file_name(props["PostAnalyticsProcessorSourceUri"])
- env["post_analytics_processor_script"] = (
- "/opt/ml/processing/code/postprocessing/" + fn
- )
+ env["post_analytics_processor_script"] = "/opt/ml/processing/code/postprocessing/" + fn
request["ProcessingInputs"].append(
{
"InputName": "post_processor_script",
@@ -339,9 +318,7 @@ def get_processing_request(event, dataset_format=DatasetFormat.csv()):
# Add baseline constraints
logger.debug("Update with constraints: %s", data["BaselineConstraintsUri"])
env = request["Environment"]
- env[
- "baseline_constraints"
- ] = "/opt/ml/processing/baseline/constraints/constraints.json"
+ env["baseline_constraints"] = "/opt/ml/processing/baseline/constraints/constraints.json"
request["ProcessingInputs"].append(
{
"InputName": "constraints",
diff --git a/custom_resource/sagemaker_training_job.py b/custom_resource/sagemaker_training_job.py
index 9fd8c04..bd4b0ed 100644
--- a/custom_resource/sagemaker_training_job.py
+++ b/custom_resource/sagemaker_training_job.py
@@ -53,7 +53,7 @@ def poll_create(event, context):
@helper.poll_delete
def poll_delete(event, context):
"""
- Return true if the resource has been stopped.
+ Return true if the resource has been stopped.
"""
training_job_name = get_training_job_name(event)
logger.info("Polling for stopped training job: %s", training_job_name)
@@ -87,9 +87,7 @@ def is_training_job_ready(training_job_name):
)
else:
raise Exception(
- "Training job ({}) has unexpected status: {}".format(
- training_job_name, status
- )
+ "Training job ({}) has unexpected status: {}".format(training_job_name, status)
)
return is_ready
diff --git a/docs/cloudwatch-dashboard.png b/docs/cloudwatch-dashboard.png
index 84ebe7ae8f22d4417881bc0ae5a99d013aea96b6..ef17cb3e2b6dfa93de7557c327f1a13fbd571e3f 100644
GIT binary patch
literal 31516
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
literal 32368
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 |