diff --git a/.coveragerc b/.coveragerc index 4dcac743ff..8ed7382211 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,4 @@ [run] -omit = sagemaker/tests/*, sagemaker/tensorflow/tensorflow_serving/* +concurrency = threading +omit = sagemaker/tests/* +timid = True diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d8f7b561..5cabaf143c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,63 @@ * add KFP Processing component +## v2.0.0.rc1 (2020-07-08) + +### Breaking Changes + + * Move StreamDeserializer to sagemaker.deserializers + * Move StringDeserializer to sagemaker.deserializers + * rename record_deserializer to RecordDeserializer + * remove "train_" where redundant in parameter/variable names + * Add BytesDeserializer + * rename image to image_uri + * rename image_name to image_uri + * create new inference resources during model.deploy() and model.transformer() + * rename session parameter to sagemaker_session in S3 utility classes + * rename distributions to distribution in TF/MXNet estimators + * deprecate update_endpoint arg in deploy() + * create new inference resources during estimator.deploy() or estimator.transformer() + * deprecate delete_endpoint() for estimators and HyperparameterTuner + * refactor Predictor attribute endpoint to endpoint_name + * make instance_type optional for Airflow model configs + * refactor name of RealTimePredictor to Predictor + * remove check for Python 2 string in sagemaker.predictor._is_sequence_like() + * deprecate sagemaker.utils.to_str() + * drop Python 2 support + +### Features + + * add BaseSerializer and BaseDeserializer + * add Predictor.update_endpoint() + +### Bug Fixes and Other Changes + + * handle "train_*" renames in v2 migration tool + * handle image_uri rename for Session methods in v2 migration tool + * Update BytesDeserializer accept header + * handle image_uri rename for estimators and models in v2 migration tool + * handle image_uri rename in Airflow model config functions in v2 migration tool + * update migration tool for S3 utility functions + * set _current_job_name and base_tuning_job_name in HyperparameterTuner.attach() + * infer base name from job name in estimator.attach() + * ensure generated names are < 63 characters when deploying compiled models + * add TF migration documentation to error message + +### Documentation Changes + + * update documentation with v2.0.0.rc1 changes + * remove 'train_*' prefix from estimator parameters + * update documentation for image_name/image --> image_uri + +### Testing and Release Infrastructure + + * refactor matching logic in v2 migration tool + * add cli modifier for RealTimePredictor and derived classes + * change coverage settings to reduce intermittent errors + * clean up pickle.load logic in integ tests + * use fixture for Python version in framework integ tests + * remove assumption of Python 2 unit test runs + ## v1.68.0 (2020-07-07) ### Features @@ -165,6 +222,31 @@ * set logs to False if wait is False in AutoML * workflow passing spot training param to training job +## v2.0.0.rc0 (2020-06-17) + +### Breaking Changes + + * remove estimator parameters for TF legacy mode + * remove legacy `TensorFlowModel` and `TensorFlowPredictor` classes + * force image URI to be passed for legacy TF images + * rename `sagemaker.tensorflow.serving` to `sagemaker.tensorflow.model` + * require `framework_version` and `py_version` for framework estimator and model classes + * change `Model` parameter order to make `model_data` optional + +### Bug Fixes and Other Changes + + * add v2 migration tool + +### Documentation Changes + + * update TF documentation to reflect breaking changes and how to upgrade + * start v2 usage and migration documentation + +### Testing and Release Infrastructure + + * remove scipy from dependencies + * remove TF from optional dependencies + ## v1.64.1 (2020-06-16) ### Bug Fixes and Other Changes diff --git a/MANIFEST.in b/MANIFEST.in index fcc507dec2..3cf0e93c89 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,6 @@ -recursive-include src/sagemaker * +recursive-include src/sagemaker *.py + +include src/sagemaker/image_uri_config/*.json include VERSION include LICENSE.txt diff --git a/README.rst b/README.rst index ba78d700a9..e5ee66ace6 100644 --- a/README.rst +++ b/README.rst @@ -93,7 +93,6 @@ Supported Python Versions SageMaker Python SDK is tested on: -- Python 2.7 - Python 3.6 - Python 3.7 - Python 3.8 @@ -122,10 +121,9 @@ You can install the libraries needed to run the tests by running :code:`pip inst **Unit tests** - We run unit tests with tox, which is a program that lets you run unit tests for multiple Python versions, and also make sure the -code fits our style guidelines. We run tox with Python 2.7, 3.6, 3.7, and 3.8, so to run unit tests -with the same configuration we do, you'll need to have interpreters for Python 2.7, Python 3.6, Python 3.7, and Python 3.8 installed. +code fits our style guidelines. We run tox with `all of our supported Python versions <#supported-python-versions>`_, so to run unit tests +with the same configuration we do, you need to have interpreters for those Python versions installed. To run the unit tests with tox, run: diff --git a/VERSION b/VERSION index e8b9bc5395..36b9b0cf16 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.72.1.dev0 +2.0.0.rc1 diff --git a/bin/sagemaker-submit b/bin/sagemaker-submit deleted file mode 100644 index 84a6f00d7a..0000000000 --- a/bin/sagemaker-submit +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -import argparse -import json - -import sagemaker - -if __name__ == '__main__': - # TODO: integrate with custom/user-defined frameworks? - frameworks = {'mxnet': sagemaker.MxNet, - 'tensorflow': sagemaker.TensorFlow} - - parser = argparse.ArgumentParser(description='Submit a job for learning in AWS') - parser.add_argument('-f', '--framework', dest='framework', help='The framework to use for training', - required=True, choices=frameworks.keys()) - parser.add_argument('--role', help='The role for accessing user data', default=None) - # TODO: will also need a way to specify the hyperparameters directly, rather than through a file. - # Should this use the same argument name or a separate one? - parser.add_argument('-hp', '--hyperparameters', dest='hyperparameters_file', help='hyperparameters json file', - default=None) - parser.add_argument('-it', '--instance-type', dest='instance_type', - help='The EC2 instance type to use for training', - default='c4.4xlarge') - parser.add_argument('-ic', '--instance-count', dest='instance_count', - help='The number of EC2 instances to use for training', - default=1) - parser.add_argument('-s', '--script', dest='script', help='Local path of the script file to run on SageMaker training.', - required=True) - parser.add_argument('-d', '--directory', dest='directory', - help='Local path of the directory containing the files to use ' + - 'when running on SageMaker training. Script name must also be specified via -s.', - default=None) - parser.add_argument('data', help='S3 location of the data to be used for training') - args = parser.parse_args() - - if args.hyperparameters_file: - with open(args.hyperparameters_file, 'r') as hf: - hyperparameters = json.load(hf) - else: - hyperparameters = {} - - estimator = frameworks[args.framework](entry_point=args.script, - source_dir=args.directory, - role=args.role, - hyperparameters=hyperparameters, - instance_count=args.instance_count, - instance_type=args.instance_type) - estimator.fit(sagemaker.s3_input(args.data)) diff --git a/buildspec-localmodetests.yml b/buildspec-localmodetests.yml index a0ceed78b5..2a2aeb30ac 100644 --- a/buildspec-localmodetests.yml +++ b/buildspec-localmodetests.yml @@ -11,5 +11,5 @@ phases: # local mode tests - start_time=`date +%s` - - execute-command-if-has-matching-changes "tox -e py27,py38 -- tests/integ -m local_mode --durations 50" "tests/integ" "tests/data" "tests/conftest.py" "tests/__init__.py" "src/*.py" "setup.py" "setup.cfg" "buildspec-localmodetests.yml" - - ./ci-scripts/displaytime.sh 'py27,py38 local mode' $start_time + - execute-command-if-has-matching-changes "tox -e py38 -- tests/integ -m local_mode --durations 50" "tests/integ" "tests/data" "tests/conftest.py" "tests/__init__.py" "src/*.py" "setup.py" "setup.cfg" "buildspec-localmodetests.yml" + - ./ci-scripts/displaytime.sh 'py38 local mode' $start_time diff --git a/buildspec-release.yml b/buildspec-release.yml index b32397c25d..ad1193bc9f 100644 --- a/buildspec-release.yml +++ b/buildspec-release.yml @@ -18,7 +18,7 @@ phases: # run unit tests - AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_SESSION_TOKEN= AWS_CONTAINER_CREDENTIALS_RELATIVE_URI= AWS_DEFAULT_REGION= - tox -e py27,py36,py37,py38 -- tests/unit + tox -e py36,py37,py38 -- tests/unit # run a subset of the integration tests - IGNORE_COVERAGE=- tox -e py36 -- tests/integ -m canary_quick -n 64 --boxed --reruns 2 diff --git a/buildspec-unittests.yml b/buildspec-unittests.yml index 8732090421..3e81a5a0c4 100644 --- a/buildspec-unittests.yml +++ b/buildspec-unittests.yml @@ -18,5 +18,5 @@ phases: - start_time=`date +%s` - AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_SESSION_TOKEN= AWS_CONTAINER_CREDENTIALS_RELATIVE_URI= AWS_DEFAULT_REGION= - tox -e py27,py36,py37,py38 --parallel all -- tests/unit - - ./ci-scripts/displaytime.sh 'py27,py36,py37,py38 unit' $start_time + tox -e py36,py37,py38 --parallel all -- tests/unit + - ./ci-scripts/displaytime.sh 'py36,py37,py38 unit' $start_time diff --git a/doc/algorithms/factorization_machines.rst b/doc/algorithms/factorization_machines.rst index 4427f0cd35..e6a509d167 100644 --- a/doc/algorithms/factorization_machines.rst +++ b/doc/algorithms/factorization_machines.rst @@ -8,7 +8,7 @@ The Amazon SageMaker Factorization Machines algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, num_factors, predictor_type, epochs, clip_gradient, mini_batch_size, feature_dim, eps, rescale_grad, bias_lr, linear_lr, factors_lr, bias_wd, linear_wd, factors_wd, bias_init_method, bias_init_scale, bias_init_sigma, bias_init_value, linear_init_method, linear_init_scale, linear_init_sigma, linear_init_value, factors_init_method, factors_init_scale, factors_init_sigma, factors_init_value + :exclude-members: image_uri, num_factors, predictor_type, epochs, clip_gradient, mini_batch_size, feature_dim, eps, rescale_grad, bias_lr, linear_lr, factors_lr, bias_wd, linear_wd, factors_wd, bias_init_method, bias_init_scale, bias_init_sigma, bias_init_value, linear_init_method, linear_init_scale, linear_init_sigma, linear_init_value, factors_init_method, factors_init_scale, factors_init_sigma, factors_init_value .. autoclass:: sagemaker.FactorizationMachinesModel diff --git a/doc/algorithms/ipinsights.rst b/doc/algorithms/ipinsights.rst index aea09bd478..f3aa8ac437 100644 --- a/doc/algorithms/ipinsights.rst +++ b/doc/algorithms/ipinsights.rst @@ -8,7 +8,7 @@ The Amazon SageMaker IP Insights algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, num_entity_vectors, vector_dim, batch_metrics_publish_interval, epochs, learning_rate, + :exclude-members: image_uri, num_entity_vectors, vector_dim, batch_metrics_publish_interval, epochs, learning_rate, num_ip_encoder_layers, random_negative_sampling_rate, shuffled_negative_sampling_rate, weight_decay .. autoclass:: sagemaker.IPInsightsModel diff --git a/doc/algorithms/kmeans.rst b/doc/algorithms/kmeans.rst index ca468a30fc..bcf2b221bf 100644 --- a/doc/algorithms/kmeans.rst +++ b/doc/algorithms/kmeans.rst @@ -8,7 +8,7 @@ The Amazon SageMaker K-means algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, k, init_method, max_iterations, tol, num_trials, local_init_method, half_life_time_size, epochs, center_factor, mini_batch_size, feature_dim, MAX_DEFAULT_BATCH_SIZE + :exclude-members: image_uri, k, init_method, max_iterations, tol, num_trials, local_init_method, half_life_time_size, epochs, center_factor, mini_batch_size, feature_dim, MAX_DEFAULT_BATCH_SIZE .. autoclass:: sagemaker.KMeansModel :members: diff --git a/doc/algorithms/knn.rst b/doc/algorithms/knn.rst index 022d4704a8..89180ce3d8 100644 --- a/doc/algorithms/knn.rst +++ b/doc/algorithms/knn.rst @@ -8,7 +8,7 @@ The Amazon SageMaker K-Nearest Neighbors (k-NN) algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, k, sample_size, predictor_type, dimension_reduction_target, dimension_reduction_type, + :exclude-members: image_uri, k, sample_size, predictor_type, dimension_reduction_target, dimension_reduction_type, index_metric, index_type, faiss_index_ivf_nlists, faiss_index_pq_m .. autoclass:: sagemaker.KNNModel diff --git a/doc/algorithms/lda.rst b/doc/algorithms/lda.rst index f6b965e577..394503b90b 100644 --- a/doc/algorithms/lda.rst +++ b/doc/algorithms/lda.rst @@ -8,7 +8,7 @@ The Amazon SageMaker LDA algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, num_topics, alpha0, max_restarts, max_iterations, mini_batch_size, feature_dim, tol + :exclude-members: image_uri, num_topics, alpha0, max_restarts, max_iterations, mini_batch_size, feature_dim, tol .. autoclass:: sagemaker.LDAModel diff --git a/doc/algorithms/linear_learner.rst b/doc/algorithms/linear_learner.rst index 7d5529e963..db9685d24f 100644 --- a/doc/algorithms/linear_learner.rst +++ b/doc/algorithms/linear_learner.rst @@ -8,7 +8,7 @@ The Amazon SageMaker LinearLearner algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, train_instance_count, train_instance_type, predictor_type, binary_classifier_model_selection_criteria, target_recall, target_precision, positive_example_weight_mult, epochs, use_bias, num_models, parameter, num_calibration_samples, calibration, init_method, init_scale, init_sigma, init_bias, optimizer, loss, wd, l1, momentum, learning_rate, beta_1, beta_2, bias_lr_mult, use_lr_scheduler, lr_scheduler_step, lr_scheduler_factor, lr_scheduler_minimum_lr, lr_scheduler_minimum_lr, mini_batch_size, feature_dim, bias_wd_mult, MAX_DEFAULT_BATCH_SIZE + :exclude-members: image_uri, instance_count, instance_type, predictor_type, binary_classifier_model_selection_criteria, target_recall, target_precision, positive_example_weight_mult, epochs, use_bias, num_models, parameter, num_calibration_samples, calibration, init_method, init_scale, init_sigma, init_bias, optimizer, loss, wd, l1, momentum, learning_rate, beta_1, beta_2, bias_lr_mult, use_lr_scheduler, lr_scheduler_step, lr_scheduler_factor, lr_scheduler_minimum_lr, lr_scheduler_minimum_lr, mini_batch_size, feature_dim, bias_wd_mult, MAX_DEFAULT_BATCH_SIZE .. autoclass:: sagemaker.LinearLearnerModel :members: diff --git a/doc/algorithms/ntm.rst b/doc/algorithms/ntm.rst index 47063f56aa..2d72cda2b2 100644 --- a/doc/algorithms/ntm.rst +++ b/doc/algorithms/ntm.rst @@ -8,8 +8,8 @@ The Amazon SageMaker NTM algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, num_topics, encoder_layers, epochs, encoder_layers_activation, optimizer, tolerance, - num_patience_epochs, batch_norm, rescale_gradient, clip_gradient, weight_decay, learning_rate + :exclude-members: image_uri, num_topics, encoder_layers, epochs, encoder_layers_activation, optimizer, tolerance, + num_patience_epochs, batch_norm, rescale_gradient, clip_gradient, weight_decay, learning_rate .. autoclass:: sagemaker.NTMModel diff --git a/doc/algorithms/object2vec.rst b/doc/algorithms/object2vec.rst index 05b37f0124..102db5ee5d 100644 --- a/doc/algorithms/object2vec.rst +++ b/doc/algorithms/object2vec.rst @@ -8,7 +8,7 @@ The Amazon SageMaker Object2Vec algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, enc_dim, mini_batch_size, epochs, early_stopping_patience, early_stopping_tolerance, + :exclude-members: image_uri, enc_dim, mini_batch_size, epochs, early_stopping_patience, early_stopping_tolerance, dropout, weight_decay, bucket_width, num_classes, mlp_layers, mlp_dim, mlp_activation, output_layer, optimizer, learning_rate, enc0_network, enc1_network, enc0_cnn_filter_width, enc1_cnn_filter_width, enc0_max_seq_len, enc1_max_seq_len, enc0_token_embedding_dim, diff --git a/doc/algorithms/pca.rst b/doc/algorithms/pca.rst index 7924a0f771..68a5db29f3 100644 --- a/doc/algorithms/pca.rst +++ b/doc/algorithms/pca.rst @@ -8,7 +8,7 @@ The Amazon SageMaker PCA algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, num_components, algorithm_mode, subtract_mean, extra_components, mini_batch_size, feature_dim, MAX_DEFAULT_BATCH_SIZE + :exclude-members: image_uri, num_components, algorithm_mode, subtract_mean, extra_components, mini_batch_size, feature_dim, MAX_DEFAULT_BATCH_SIZE .. autoclass:: sagemaker.PCAModel diff --git a/doc/algorithms/randomcutforest.rst b/doc/algorithms/randomcutforest.rst index 48c290203b..b723319a52 100644 --- a/doc/algorithms/randomcutforest.rst +++ b/doc/algorithms/randomcutforest.rst @@ -8,7 +8,7 @@ The Amazon SageMaker Random Cut Forest algorithm. :undoc-members: :show-inheritance: :inherited-members: - :exclude-members: image, num_trees, num_samples_per_tree, eval_metrics, feature_dim, MINI_BATCH_SIZE + :exclude-members: image_uri, num_trees, num_samples_per_tree, eval_metrics, feature_dim, MINI_BATCH_SIZE .. autoclass:: sagemaker.RandomCutForestModel diff --git a/doc/amazon_sagemaker_debugger.rst b/doc/amazon_sagemaker_debugger.rst index 6a85251421..23081138b1 100644 --- a/doc/amazon_sagemaker_debugger.rst +++ b/doc/amazon_sagemaker_debugger.rst @@ -54,8 +54,8 @@ The ``DebuggerHookConfig`` accepts one or more objects of type ``CollectionConfi estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, debugger_hook_config=debugger_hook_config ) @@ -215,8 +215,8 @@ Sample Usages estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, rules=[Rule.sagemaker(vanishing_gradient())] ) @@ -232,8 +232,8 @@ In the example above, Amazon SageMaker pulls the collection configuration best s estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, rules=[Rule.sagemaker(vanishing_gradient()), Rule.sagemaker(weight_update_ratio())] ) @@ -269,8 +269,8 @@ Here we modify the ``weight_update_ratio`` rule to store a custom collection rat estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, rules=[ Rule.sagemaker(vanishing_gradient()), wur_with_customization @@ -317,8 +317,8 @@ To evaluate the custom rule against the training: estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, rules=[ custom_gradient_rule ] @@ -344,8 +344,8 @@ To enable the debugging hook to emit TensorBoard data, you need to specify the n estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, tensorboard_output_config=tensorboard_output_config ) @@ -392,8 +392,8 @@ To disable the hook initialization, you can do so by specifying ``False`` for va estimator = TensorFlow( role=role, - train_instance_count=1, - train_instance_type=train_instance_type, + instance_count=1, + instance_type=instance_type, debugger_hook_config=False ) diff --git a/doc/amazon_sagemaker_model_monitoring.rst b/doc/amazon_sagemaker_model_monitoring.rst index e82bae582a..e493c24e2f 100644 --- a/doc/amazon_sagemaker_model_monitoring.rst +++ b/doc/amazon_sagemaker_model_monitoring.rst @@ -125,7 +125,7 @@ Using ``DefaultMonitor.create_monitoring_schedule()``, you can create a model mo my_monitor.create_monitoring_schedule( monitor_schedule_name='my-monitoring-schedule', - endpoint_input=predictor.endpoint, + endpoint_input=predictor.endpoint_name, statistics=my_monitor.baseline_statistics(), constraints=my_monitor.suggested_constraints(), schedule_cron_expression=CronExpressionGenerator.hourly(), diff --git a/doc/api/inference/predictors.rst b/doc/api/inference/predictors.rst index 632d7e6872..6a9243f329 100644 --- a/doc/api/inference/predictors.rst +++ b/doc/api/inference/predictors.rst @@ -3,7 +3,7 @@ Predictors Make real-time predictions against SageMaker endpoints with Python objects -.. autoclass:: sagemaker.predictor.RealTimePredictor +.. autoclass:: sagemaker.predictor.Predictor :members: :undoc-members: :show-inheritance: diff --git a/doc/api/utility/image_uris.rst b/doc/api/utility/image_uris.rst new file mode 100644 index 0000000000..e6be7e8424 --- /dev/null +++ b/doc/api/utility/image_uris.rst @@ -0,0 +1,7 @@ +Image URIs +---------- + +.. automodule:: sagemaker.image_uris + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/conf.py b/doc/conf.py index e8e9d90fdf..61b42bef1e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -14,36 +14,7 @@ from __future__ import absolute_import import pkg_resources -import sys from datetime import datetime -from unittest.mock import MagicMock - - -class Mock(MagicMock): - @classmethod - def __getattr__(cls, name): - """ - Args: - name: - """ - if name == "__version__": - return "1.4.0" - else: - return MagicMock() - - -MOCK_MODULES = [ - "tensorflow", - "tensorflow.core", - "tensorflow.core.framework", - "tensorflow.python", - "tensorflow.python.framework", - "tensorflow_serving", - "tensorflow_serving.apis", - "scipy", - "scipy.sparse", -] -sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) project = u"sagemaker" version = pkg_resources.require(project)[0].version diff --git a/doc/frameworks/chainer/using_chainer.rst b/doc/frameworks/chainer/using_chainer.rst index a501e210f7..85c197350d 100644 --- a/doc/frameworks/chainer/using_chainer.rst +++ b/doc/frameworks/chainer/using_chainer.rst @@ -138,9 +138,10 @@ directories ('train' and 'test'). .. code:: python chainer_estimator = Chainer('chainer-train.py', - train_instance_type='ml.p3.2xlarge', - train_instance_count=1, + instance_type='ml.p3.2xlarge', + instance_count=1, framework_version='5.0.0', + py_version='py3', hyperparameters = {'epochs': 20, 'batch-size': 64, 'learning-rate': 0.1}) chainer_estimator.fit({'train': 's3://my-data-bucket/path/to/my/training/data', 'test': 's3://my-data-bucket/path/to/my/test/data'}) @@ -190,7 +191,7 @@ Chainer allows you to train a model on multiple nodes using ChainerMN_, which di In order to run distributed Chainer training on SageMaker, your training script should use a ``chainermn`` Communicator object to coordinate training between multiple hosts. -SageMaker runs your script with ``mpirun`` if ``train_instance_count`` is greater than two. +SageMaker runs your script with ``mpirun`` if ``instance_count`` is greater than two. The following are optional arguments modify how MPI runs your distributed training script. - ``use_mpi`` Boolean that overrides whether to run your training script with MPI. @@ -220,9 +221,10 @@ operation. # Train my estimator chainer_estimator = Chainer(entry_point='train_and_deploy.py', - train_instance_type='ml.p3.2xlarge', - train_instance_count=1, - framework_version='5.0.0') + instance_type='ml.p3.2xlarge', + instance_count=1, + framework_version='5.0.0', + py_version='py3') chainer_estimator.fit('s3://my_bucket/my_training_data/') # Deploy my estimator to a SageMaker Endpoint and get a Predictor @@ -323,7 +325,7 @@ You can provide your own implementations for these functions in your hosting scr If you omit any definition then the SageMaker Chainer model server will use its default implementation for that function. -The ``RealTimePredictor`` used by Chainer in the SageMaker Python SDK serializes NumPy arrays to the `NPY `_ format +The ``Predictor`` used by Chainer in the SageMaker Python SDK serializes NumPy arrays to the `NPY `_ format by default, with Content-Type ``application/x-npy``. The SageMaker Chainer model server can deserialize NPY-formatted data (along with JSON and CSV data). @@ -490,41 +492,15 @@ The following code sample shows how to do this, using the ``ChainerModel`` class .. code:: python - chainer_model = ChainerModel(model_data="s3://bucket/model.tar.gz", role="SageMakerRole", - entry_point="transform_script.py") + chainer_model = ChainerModel( + model_data="s3://bucket/model.tar.gz", + role="SageMakerRole", + entry_point="transform_script.py", + ) predictor = chainer_model.deploy(instance_type="ml.c4.xlarge", initial_instance_count=1) -The ChainerModel constructor takes the following arguments: - -- ``model_data (str):`` An S3 location of a SageMaker model data - .tar.gz file -- ``image (str):`` A Docker image URI -- ``role (str):`` An IAM role name or Arn for SageMaker to access AWS - resources on your behalf. -- ``predictor_cls (callable[string,sagemaker.Session]):`` A function to - call to create a predictor. If not None, ``deploy`` will return the - result of invoking this function on the created endpoint name -- ``env (dict[string,string]):`` Environment variables to run with - ``image`` when hosted in SageMaker. -- ``name (str):`` The model name. If None, a default model name will be - selected on each ``deploy.`` -- ``entry_point (str):`` Path (absolute or relative) to the Python file - which should be executed as the entry point to model hosting. -- ``source_dir (str):`` Optional. Path (absolute or relative) to a - directory with any other training source code dependencies including - the entry point file. Structure within this directory will be - preserved when training on SageMaker. -- ``enable_cloudwatch_metrics (boolean):`` Optional. If true, training - and hosting containers will generate Cloudwatch metrics under the - AWS/SageMakerContainer namespace. -- ``container_log_level (int):`` Log level to use within the container. - Valid values are defined in the Python logging module. -- ``code_location (str):`` Optional. Name of the S3 bucket where your - custom code will be uploaded to. If not specified, will use the - SageMaker default bucket created by sagemaker.Session. -- ``sagemaker_session (sagemaker.Session):`` The SageMaker Session - object, used for SageMaker interaction""" +To see what arguments are accepted by the ``ChainerModel`` constructor, see :class:`sagemaker.chainer.model.ChainerModel`. Your model data must be a .tar.gz file in S3. SageMaker Training Job model data is saved to .tar.gz files in S3, however if you have local data you want to deploy, you can prepare the data yourself. @@ -554,89 +530,11 @@ https://github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-pytho These are also available in SageMaker Notebook Instance hosted Jupyter notebooks under the "sample notebooks" folder. -******************************* -sagemaker.chainer.Chainer Class -******************************* - -The `Chainer` constructor takes both required and optional arguments. - -Required arguments -================== - -The following are required arguments to the ``Chainer`` constructor. When you create a Chainer object, you must include -these in the constructor, either positionally or as keyword arguments. - -- ``entry_point`` Path (absolute or relative) to the Python file which - should be executed as the entry point to training. -- ``role`` An AWS IAM role (either name or full ARN). The Amazon - SageMaker training jobs and APIs that create Amazon SageMaker - endpoints use this role to access training data and model artifacts. - After the endpoint is created, the inference code might use the IAM - role, if accessing AWS resource. -- ``train_instance_count`` Number of Amazon EC2 instances to use for - training. -- ``train_instance_type`` Type of EC2 instance to use for training, for - example, 'ml.m4.xlarge'. - -Optional arguments -================== - -The following are optional arguments. When you create a ``Chainer`` object, you can specify these as keyword arguments. - -- ``source_dir`` Path (absolute or relative) to a directory with any - other training source code dependencies including the entry point - file. Structure within this directory will be preserved when training - on SageMaker. -- ``dependencies (list[str])`` A list of paths to directories (absolute or relative) with - any additional libraries that will be exported to the container (default: []). - The library folders will be copied to SageMaker in the same folder where the entrypoint is copied. - If the ```source_dir``` points to S3, code will be uploaded and the S3 location will be used - instead. Example: - - The following call - >>> Chainer(entry_point='train.py', dependencies=['my/libs/common', 'virtual-env']) - results in the following inside the container: - - >>> $ ls - - >>> opt/ml/code - >>> ├── train.py - >>> ├── common - >>> └── virtual-env - -- ``hyperparameters`` Hyperparameters that will be used for training. - Will be made accessible as a dict[str, str] to the training code on - SageMaker. For convenience, accepts other types besides str, but - str() will be called on keys and values to convert them before - training. -- ``py_version`` Python version you want to use for executing your - model training code. -- ``train_volume_size`` Size in GB of the EBS volume to use for storing - input data during training. Must be large enough to store training - data if input_mode='File' is used (which is the default). -- ``train_max_run`` Timeout in seconds for training, after which Amazon - SageMaker terminates the job regardless of its current status. -- ``input_mode`` The input mode that the algorithm supports. Valid - modes: 'File' - Amazon SageMaker copies the training dataset from the - s3 location to a directory in the Docker container. 'Pipe' - Amazon - SageMaker streams data directly from s3 to the container via a Unix - named pipe. -- ``output_path`` s3 location where you want the training result (model - artifacts and optional output files) saved. If not specified, results - are stored to a default bucket. If the bucket with the specific name - does not exist, the estimator creates the bucket during the fit() - method execution. -- ``output_kms_key`` Optional KMS key ID to optionally encrypt training - output with. -- ``job_name`` Name to assign for the training job that the fit() - method launches. If not specified, the estimator generates a default - job name, based on the training image name and current timestamp -- ``image_name`` An alternative docker image to use for training and - serving. If specified, the estimator will use this image for training and - hosting, instead of selecting the appropriate SageMaker official image based on - framework_version and py_version. Refer to: `SageMaker Chainer Docker Containers - <#sagemaker-chainer-docker-containers>`__ for details on what the Official images support - and where to find the source code to build your custom image. +************************* +SageMaker Chainer Classes +************************* + +For information about the different Chainer-related classes in the SageMaker Python SDK, see https://sagemaker.readthedocs.io/en/stable/frameworks/chainer/sagemaker.chainer.html. *********************************** SageMaker Chainer Docker containers @@ -687,6 +585,6 @@ specify major and minor version, which will cause your training script to be run version of that minor version. Alternatively, you can build your own image by following the instructions in the SageMaker Chainer containers -repository, and passing ``image_name`` to the Chainer Estimator constructor. +repository, and passing ``image_uri`` to the Chainer Estimator constructor. You can visit the SageMaker Chainer containers repository at https://github.com/aws/sagemaker-chainer-container diff --git a/doc/frameworks/mxnet/using_mxnet.rst b/doc/frameworks/mxnet/using_mxnet.rst index 6476112da1..ebcb455188 100644 --- a/doc/frameworks/mxnet/using_mxnet.rst +++ b/doc/frameworks/mxnet/using_mxnet.rst @@ -30,15 +30,6 @@ To train an MXNet model by using the SageMaker Python SDK: Prepare an MXNet Training Script ================================ -.. warning:: - The structure for training scripts changed starting at MXNet version 1.3. - Make sure you refer to the correct section of this README when you prepare your script. - For information on how to upgrade an old script to the new format, see `"Updating your MXNet training script" <#updating-your-mxnet-training-script>`__. - -For versions 1.3 and higher ---------------------------- -Your MXNet training script must be compatible with Python 2.7 or 3.6. - The training script is very similar to a training script you might run outside of Amazon SageMaker, but you can access useful properties about the training environment through various environment variables, including the following: * ``SM_MODEL_DIR``: A string that represents the path where the training job writes the model artifacts to. @@ -89,119 +80,8 @@ If you want to use, for example, boolean hyperparameters, you need to specify `` For more on training environment variables, please visit `SageMaker Containers `_. -For versions 1.2 and lower --------------------------- - -Your MXNet training script must be compatible with Python 2.7 or 3.5. -The script must contain a function named ``train``, which Amazon SageMaker invokes to run training. -You can include other functions as well, but it must contain a ``train`` function. - -When you run your script on Amazon SageMaker via the ``MXNet`` estimator, Amazon SageMaker injects information about the training environment into your training function via Python keyword arguments. -You can choose to take advantage of these by including them as keyword arguments in your train function. The full list of arguments is: - -- ``hyperparameters (dict[string,string])``: The hyperparameters passed - to an Amazon SageMaker TrainingJob that runs your MXNet training script. You - can use this to pass hyperparameters to your training script. -- ``input_data_config (dict[string,dict])``: The Amazon SageMaker TrainingJob - InputDataConfig object, that's set when the Amazon SageMaker TrainingJob is - created. This is discussed in more detail below. -- ``channel_input_dirs (dict[string,string])``: A collection of - directories containing training data. When you run training, you can - partition your training data into different logical "channels". - Depending on your problem, some common channel ideas are: "train", - "test", "evaluation" or "images',"labels". -- ``output_data_dir (str)``: A directory where your training script can - write data that is moved to Amazon S3 after training is complete. -- ``num_gpus (int)``: The number of GPU devices available on your - training instance. -- ``num_cpus (int)``: The number of CPU devices available on your training instance. -- ``hosts (list[str])``: The list of host names running in the - Amazon SageMaker Training Job cluster. -- ``current_host (str)``: The name of the host executing the script. - When you use Amazon SageMaker for MXNet training, the script is run on each - host in the cluster. - -A training script that takes advantage of all arguments would have the following definition: - -.. code:: python - - def train(hyperparameters, input_data_config, channel_input_dirs, output_data_dir, - num_gpus, num_cpus, hosts, current_host) - -You don't have to use all the arguments. -Arguments you don't care about can be ignored by including ``**kwargs``. - -.. code:: python - - # Only work with hyperparameters and num_gpus, and ignore all other hyperparameters - def train(hyperparameters, num_gpus, **kwargs) - -.. note:: - **Writing a training script that imports correctly:** - When Amazon SageMaker runs your training script, it imports it as a Python module and then invokes ``train`` on the imported module. - Consequently, you should not include any statements that won't execute successfully in Amazon SageMaker when your module is imported. - For example, don't attempt to open any local files in top-level statements in your training script. - -If you want to run your training script locally by using the Python interpreter, use a ``___name__ == '__main__'`` guard. -For more information, see https://stackoverflow.com/questions/419163/what-does-if-name-main-do. - -Save the Model -^^^^^^^^^^^^^^ - -Just as you enable training by defining a ``train`` function in your training script, you enable model saving by defining a ``save`` function in your script. -If your script includes a ``save`` function, Amazon SageMaker invokes it with the return value of ``train``. -Model saving is a two-step process. -First, return the model you want to save from ``train``. -Then, define your model-serialization logic in ``save``. - -Amazon SageMaker provides a default implementation of ``save`` that works with MXNet Module API ``Module`` objects. -If your training script does not define a ``save`` function, then the default ``save`` function is invoked on the return value of your ``train`` function. - -The default serialization system generates three files: - -- ``model-shapes.json``: A JSON list, containing a serialization of the - ``Module`` ``data_shapes`` property. Each object in the list contains - the serialization of one ``DataShape`` in the returned ``Module``. - Each object has a ``name`` property, containing the ``DataShape`` - name and a ``shape`` property, which is a list of that dimensions for - the shape of that ``DataShape``. For example: - -.. code:: javascript - - [ - {"name":"images", "shape":[100, 1, 28, 28]}, - {"name":"labels", "shape":[100, 1]} - ] - -- ``model-symbol.json``: The MXNet ``Module`` ``Symbol`` serialization, - produced by invoking ``save`` on the ``symbol`` property of the - ``Module`` being saved. -- ``model.params``: The MXNet ``Module`` parameters, produced by - invoking ``save_params`` on the ``Module`` being saved. - -You can provide your own save function. This is useful if you are not working with the ``Module`` API or you need special processing. - -To provide your own save function, define a ``save`` function in your training script: - -.. code:: python - - def save(model, model_dir) - -The function should take two arguments: - -- ``model``: This is the object that is returned from your ``train`` function. - You may return an object of any type from ``train``; - you do not have to return ``Module`` or ``Gluon`` API specific objects. - If your ``train`` function does not return an object, ``model`` is set to ``None``. -- ``model_dir``: This is the string path on the Amazon SageMaker training host where you save your model. - Files created in this directory are accessible in Amazon S3 after your Amazon SageMaker Training Job completes. - -After your ``train`` function completes, Amazon SageMaker invokes ``save`` with the object returned from ``train``. - .. note:: - **How to save Gluon models with Amazon SageMaker:** - If your train function returns a Gluon API ``net`` object as its model, you need to write your own ``save`` function and serialize the ``net`` parameters. - Saving ``net`` parameters is covered in the `Serialization section `__ of the collaborative Gluon deep-learning book `"The Straight Dope" `__. + If you want to use MXNet 1.2 or lower, see `an older version of this page `_. Save a Checkpoint ----------------- @@ -233,86 +113,44 @@ To save MXNet model checkpoints, do the following in your training script: For a complete example of an MXNet training script that impelements checkpointing, see https://github.com/awslabs/amazon-sagemaker-examples/blob/master/sagemaker-python-sdk/mxnet_gluon_cifar10/cifar10.py. +Save the Model +-------------- -Update your MXNet training script ---------------------------------- - -The structure for training scripts changed with MXNet version 1.3. -The ``train`` function is no longer be required; instead the training script must be able to be run as a standalone script. -In this way, the training script is similar to a training script you might run outside of Amazon SageMaker. - -There are a few steps needed to make a training script with the old format compatible with the new format. - -First, add a `main guard `__ (``if __name__ == '__main__':``). -The code executed from your main guard needs to: - -1. Set hyperparameters and directory locations -2. Initiate training -3. Save the model - -Hyperparameters are passed as command-line arguments to your training script. -In addition, the container defines the locations of input data and where to save the model artifacts and output data as environment variables rather than passing that information as arguments to the ``train`` function. -You can find the full list of available environment variables in the `SageMaker Containers README `__. - -We recommend using `an argument parser `__ for this part. -Using the ``argparse`` library as an example, the code looks something like this: - -.. code:: python - - import argparse - import os - - if __name__ == '__main__': - parser = argparse.ArgumentParser() - - # hyperparameters sent by the client are passed as command-line arguments to the script. - parser.add_argument('--epochs', type=int, default=10) - parser.add_argument('--batch-size', type=int, default=100) - parser.add_argument('--learning-rate', type=float, default=0.1) - - # input data and model directories - parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) - parser.add_argument('--train', type=str, default=os.environ['SM_CHANNEL_TRAIN']) - parser.add_argument('--test', type=str, default=os.environ['SM_CHANNEL_TEST']) - - args, _ = parser.parse_known_args() - -The code in the main guard should also take care of training and saving the model. -This can be as simple as just calling the ``train`` and ``save`` methods used in the previous training script format: - -.. code:: python - - if __name__ == '__main__': - # arg parsing (shown above) goes here - - model = train(args.batch_size, args.epochs, args.learning_rate, args.train, args.test) - save(args.model_dir, model) - -Note that saving the model is no longer be done by default; this must be done by the training script. -If you were previously relying on the default save method, you can import one from the container: +There is a default save method that can be imported when training on SageMaker: .. code:: python - from sagemaker_mxnet_container.training_utils import save + from sagemaker_mxnet_training.training_utils import save if __name__ == '__main__': # arg parsing and training (shown above) goes here save(args.model_dir, model) -Lastly, if you were relying on the container launching a parameter server for use with distributed training, you must set ``distributions`` to the following dictionary when creating an MXNet estimator: +The default serialization system generates three files: -.. code:: python +- ``model-shapes.json``: A JSON list, containing a serialization of the + ``Module`` ``data_shapes`` property. Each object in the list contains + the serialization of one ``DataShape`` in the returned ``Module``. + Each object has a ``name`` property, containing the ``DataShape`` + name and a ``shape`` property, which is a list of that dimensions for + the shape of that ``DataShape``. For example: - from sagemaker.mxnet import MXNet +.. code:: javascript - estimator = MXNet('path-to-distributed-training-script.py', - ..., - distributions={'parameter_server': {'enabled': True}}) + [ + {"name":"images", "shape":[100, 1, 28, 28]}, + {"name":"labels", "shape":[100, 1]} + ] +- ``model-symbol.json``: The MXNet ``Module`` ``Symbol`` serialization, + produced by invoking ``save`` on the ``symbol`` property of the + ``Module`` being saved. +- ``modle.params``: The MXNet ``Module`` parameters, produced by + invoking ``save_params`` on the ``Module`` being saved. Use third-party libraries -------------------------- +========================= When running your training script on Amazon SageMaker, it has access to some pre-installed third-party libraries, including ``mxnet``, ``numpy``, ``onnx``, and ``keras-mxnet``. For more information on the runtime environment, including specific package versions, see `SageMaker MXNet Containers <#sagemaker-mxnet-containers>`__. @@ -344,9 +182,10 @@ The following code sample shows how you train a custom MXNet script "train.py". .. code:: python mxnet_estimator = MXNet('train.py', - train_instance_type='ml.p2.xlarge', - train_instance_count=1, - framework_version='1.3.0', + instance_type='ml.p2.xlarge', + instance_count=1, + framework_version='1.6.0', + py_version='py3', hyperparameters={'batch-size': 100, 'epochs': 10, 'learning-rate': 0.1}) @@ -362,7 +201,7 @@ If you want to use parameter servers for distributed training, set the following .. code:: python - distributions={'parameter_server': {'enabled': True}} + distribution={'parameter_server': {'enabled': True}} Then, when writing a distributed training script, use an MXNet kvstore to store and share model parameters. During training, Amazon SageMaker automatically starts an MXNet kvstore server and scheduler processes on hosts in your training job cluster. @@ -393,10 +232,10 @@ If you use the ``MXNet`` estimator to train the model, you can call ``deploy`` t # Train my estimator mxnet_estimator = MXNet('train.py', - train_instance_type='ml.p2.xlarge', - train_instance_count=1, + framework_version='1.6.0', py_version='py3', - framework_version='1.6.0') + instance_type='ml.p2.xlarge', + instance_count=1) mxnet_estimator.fit('s3://my_bucket/my_training_data/') # Deploy my estimator to an Amazon SageMaker Endpoint and get a Predictor @@ -410,8 +249,8 @@ If using a pretrained model, create an ``MXNetModel`` object, and then call ``de mxnet_model = MXNetModel(model_data='s3://my_bucket/pretrained_model/model.tar.gz', role=role, entry_point='inference.py', - py_version='py3', - framework_version='1.6.0') + framework_version='1.6.0', + py_version='py3') predictor = mxnet_model.deploy(instance_type='ml.m4.xlarge', initial_instance_count=1) diff --git a/doc/frameworks/pytorch/using_pytorch.rst b/doc/frameworks/pytorch/using_pytorch.rst index 4c8cde6e98..ef023f2513 100644 --- a/doc/frameworks/pytorch/using_pytorch.rst +++ b/doc/frameworks/pytorch/using_pytorch.rst @@ -152,9 +152,10 @@ directories ('train' and 'test'). .. code:: python pytorch_estimator = PyTorch('pytorch-train.py', - train_instance_type='ml.p3.2xlarge', - train_instance_count=1, - framework_version='1.0.0', + instance_type='ml.p3.2xlarge', + instance_count=1, + framework_version='1.5.0', + py_version='py3', hyperparameters = {'epochs': 20, 'batch-size': 64, 'learning-rate': 0.1}) pytorch_estimator.fit({'train': 's3://my-data-bucket/path/to/my/training/data', 'test': 's3://my-data-bucket/path/to/my/test/data'}) @@ -200,7 +201,7 @@ Distributed PyTorch Training ============================ You can run a multi-machine, distributed PyTorch training using the PyTorch Estimator. By default, PyTorch objects will -submit single-machine training jobs to SageMaker. If you set ``train_instance_count`` to be greater than one, multi-machine +submit single-machine training jobs to SageMaker. If you set ``instance_count`` to be greater than one, multi-machine training jobs will be launched when ``fit`` is called. When you run multi-machine training, SageMaker will import your training script and run it on each host in the cluster. @@ -245,9 +246,10 @@ operation. # Train my estimator pytorch_estimator = PyTorch(entry_point='train_and_deploy.py', - train_instance_type='ml.p3.2xlarge', - train_instance_count=1, - framework_version='1.0.0') + instance_type='ml.p3.2xlarge', + instance_count=1, + framework_version='1.5.0', + py_version='py3') pytorch_estimator.fit('s3://my_bucket/my_training_data/') # Deploy my estimator to a SageMaker Endpoint and get a Predictor @@ -415,7 +417,7 @@ You can provide your own implementations for these functions in your hosting scr If you omit any definition then the SageMaker PyTorch model server will use its default implementation for that function. -The ``RealTimePredictor`` used by PyTorch in the SageMaker Python SDK serializes NumPy arrays to the `NPY `_ format +The ``Predictor`` used by PyTorch in the SageMaker Python SDK serializes NumPy arrays to the `NPY `_ format by default, with Content-Type ``application/x-npy``. The SageMaker PyTorch model server can deserialize NPY-formatted data (along with JSON and CSV data). @@ -665,92 +667,11 @@ https://github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-pytho These are also available in SageMaker Notebook Instance hosted Jupyter notebooks under the sample notebooks folder. -****************** -PyTorch Estimators -****************** - -The `PyTorch` constructor takes both required and optional arguments. - -Required arguments -================== - -The following are required arguments to the ``PyTorch`` constructor. When you create a PyTorch object, you must include -these in the constructor, either positionally or as keyword arguments. - -- ``entry_point`` Path (absolute or relative) to the Python file which - should be executed as the entry point to training. -- ``role`` An AWS IAM role (either name or full ARN). The Amazon - SageMaker training jobs and APIs that create Amazon SageMaker - endpoints use this role to access training data and model artifacts. - After the endpoint is created, the inference code might use the IAM - role, if accessing AWS resource. -- ``train_instance_count`` Number of Amazon EC2 instances to use for - training. -- ``train_instance_type`` Type of EC2 instance to use for training, for - example, 'ml.m4.xlarge'. - -Optional arguments -================== - -The following are optional arguments. When you create a ``PyTorch`` object, you can specify these as keyword arguments. - -- ``source_dir`` Path (absolute or relative) to a directory with any - other training source code dependencies including the entry point - file. Structure within this directory will be preserved when training - on SageMaker. -- ``dependencies (list[str])`` A list of paths to directories (absolute or relative) with - any additional libraries that will be exported to the container (default: []). - The library folders will be copied to SageMaker in the same folder where the entrypoint is copied. - If the ```source_dir``` points to S3, code will be uploaded and the S3 location will be used - instead. Example: - - The following call - >>> PyTorch(entry_point='train.py', dependencies=['my/libs/common', 'virtual-env']) - results in the following inside the container: - - >>> $ ls - - >>> opt/ml/code - >>> ├── train.py - >>> ├── common - >>> └── virtual-env - -- ``hyperparameters`` Hyperparameters that will be used for training. - Will be made accessible as a dict[str, str] to the training code on - SageMaker. For convenience, accepts other types besides strings, but - ``str`` will be called on keys and values to convert them before - training. -- ``py_version`` Python version you want to use for executing your - model training code. -- ``framework_version`` PyTorch version you want to use for executing - your model training code. You can find the list of supported versions - in `SageMaker PyTorch Docker Containers `_. -- ``train_volume_size`` Size in GB of the EBS volume to use for storing - input data during training. Must be large enough to store training - data if input_mode='File' is used (which is the default). -- ``train_max_run`` Timeout in seconds for training, after which Amazon - SageMaker terminates the job regardless of its current status. -- ``input_mode`` The input mode that the algorithm supports. Valid - modes: 'File' - Amazon SageMaker copies the training dataset from the - S3 location to a directory in the Docker container. 'Pipe' - Amazon - SageMaker streams data directly from S3 to the container via a Unix - named pipe. -- ``output_path`` S3 location where you want the training result (model - artifacts and optional output files) saved. If not specified, results - are stored to a default bucket. If the bucket with the specific name - does not exist, the estimator creates the bucket during the ``fit`` - method execution. -- ``output_kms_key`` Optional KMS key ID to optionally encrypt training - output with. -- ``job_name`` Name to assign for the training job that the ``fit``` - method launches. If not specified, the estimator generates a default - job name, based on the training image name and current timestamp -- ``image_name`` An alternative docker image to use for training and - serving. If specified, the estimator will use this image for training and - hosting, instead of selecting the appropriate SageMaker official image based on - framework_version and py_version. Refer to: `SageMaker PyTorch Docker Containers - `_ for details on what the Official images support - and where to find the source code to build your custom image. +************************* +SageMaker PyTorch Classes +************************* + +For information about the different PyTorch-related classes in the SageMaker Python SDK, see https://sagemaker.readthedocs.io/en/stable/frameworks/pytorch/sagemaker.pytorch.html. *********************************** SageMaker PyTorch Docker Containers diff --git a/doc/frameworks/rl/using_rl.rst b/doc/frameworks/rl/using_rl.rst index 5bba4f3dc9..8937f18cb3 100644 --- a/doc/frameworks/rl/using_rl.rst +++ b/doc/frameworks/rl/using_rl.rst @@ -14,10 +14,10 @@ RL Training Training RL models using ``RLEstimator`` is a two-step process: 1. Prepare a training script to run on SageMaker -2. Run this script on SageMaker via an ``RlEstimator``. +2. Run this script on SageMaker via an ``RLEstimator``. You should prepare your script in a separate source file than the notebook, terminal session, or source file you're -using to submit the script to SageMaker via an ``RlEstimator``. This will be discussed in further detail below. +using to submit the script to SageMaker via an ``RLEstimator``. This will be discussed in further detail below. Suppose that you already have a training script called ``coach-train.py``. You can then create an ``RLEstimator`` with keyword arguments to point to this script and define how SageMaker runs it: @@ -31,8 +31,8 @@ You can then create an ``RLEstimator`` with keyword arguments to point to this s toolkit_version='0.11.1', framework=RLFramework.TENSORFLOW, role='SageMakerRole', - train_instance_type='ml.p3.2xlarge', - train_instance_count=1) + instance_type='ml.p3.2xlarge', + instance_count=1) After that, you simply tell the estimator to start a training job: @@ -71,7 +71,7 @@ The ``RLEstimator`` constructor takes both required and optional arguments. Required arguments ~~~~~~~~~~~~~~~~~~ -The following are required arguments to the ``RLEstimator`` constructor. When you create an instance of RLEstimator, you must include +The following are required arguments to the ``RLEstimator`` constructor. When you create an instance of ``RLEstimator``, you must include these in the constructor, either positionally or as keyword arguments. - ``entry_point`` Path (absolute or relative) to the Python file which @@ -81,9 +81,9 @@ these in the constructor, either positionally or as keyword arguments. endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. -- ``train_instance_count`` Number of Amazon EC2 instances to use for +- ``instance_count`` Number of Amazon EC2 instances to use for training. -- ``train_instance_type`` Type of EC2 instance to use for training, for +- ``instance_type`` Type of EC2 instance to use for training, for example, 'ml.m4.xlarge'. You must as well include either: @@ -97,7 +97,7 @@ You must as well include either: or provide: -- ``image_name`` An alternative docker image to use for training and +- ``image_uri`` An alternative Docker image to use for training and serving. If specified, the estimator will use this image for training and hosting, instead of selecting the appropriate SageMaker official image based on framework_version and py_version. Refer to: `SageMaker RL Docker Containers @@ -108,85 +108,14 @@ or provide: Optional arguments ~~~~~~~~~~~~~~~~~~ -The following are optional arguments. When you create an ``RlEstimator`` object, you can specify these as keyword arguments. - -- ``source_dir`` Path (absolute or relative) to a directory with any - other training source code dependencies including the entry point - file. Structure within this directory will be preserved when training - on SageMaker. -- ``dependencies (list[str])`` A list of paths to directories (absolute or relative) with - any additional libraries that will be exported to the container (default: ``[]``). - The library folders will be copied to SageMaker in the same folder where the entrypoint is copied. - If the ``source_dir`` points to S3, code will be uploaded and the S3 location will be used - instead. - - For example, the following call: - - .. code:: python - - >>> RLEstimator(entry_point='train.py', - toolkit=RLToolkit.COACH, - toolkit_version='0.11.0', - framework=RLFramework.TENSORFLOW, - dependencies=['my/libs/common', 'virtual-env']) - - results in the following inside the container: - - .. code:: bash - - >>> $ ls - - >>> opt/ml/code - >>> ├── train.py - >>> ├── common - >>> └── virtual-env - -- ``hyperparameters`` Hyperparameters that will be used for training. - Will be made accessible as a ``dict[str, str]`` to the training code on - SageMaker. For convenience, accepts other types besides strings, but - ``str`` will be called on keys and values to convert them before - training. -- ``train_volume_size`` Size in GB of the EBS volume to use for storing - input data during training. Must be large enough to store training - data if ``input_mode='File'`` is used (which is the default). -- ``train_max_run`` Timeout in seconds for training, after which Amazon - SageMaker terminates the job regardless of its current status. -- ``input_mode`` The input mode that the algorithm supports. Valid - modes: 'File' - Amazon SageMaker copies the training dataset from the - S3 location to a directory in the Docker container. 'Pipe' - Amazon - SageMaker streams data directly from S3 to the container via a Unix - named pipe. -- ``output_path`` S3 location where you want the training result (model - artifacts and optional output files) saved. If not specified, results - are stored to a default bucket. If the bucket with the specific name - does not exist, the estimator creates the bucket during the ``fit`` - method execution. -- ``output_kms_key`` Optional KMS key ID to optionally encrypt training - output with. -- ``job_name`` Name to assign for the training job that the ``fit``` - method launches. If not specified, the estimator generates a default - job name, based on the training image name and current timestamp +When you create an ``RLEstimator`` object, you can specify a number of optional arguments. +For more information, see :class:`sagemaker.rl.estimator.RLEstimator`. Calling fit ~~~~~~~~~~~ -You start your training script by calling ``fit`` on an ``RLEstimator``. ``fit`` takes a few optional -arguments. - -fit Optional arguments -'''''''''''''''''''''' - -- ``inputs``: This can take one of the following forms: A string - S3 URI, for example ``s3://my-bucket/my-training-data``. In this - case, the S3 objects rooted at the ``my-training-data`` prefix will - be available in the default ``train`` channel. A dict from - string channel names to S3 URIs. In this case, the objects rooted at - each S3 prefix will available as files in each channel directory. -- ``wait``: Defaults to True, whether to block and wait for the - training script to complete before returning. -- ``logs``: Defaults to True, whether to show logs produced by training - job in the Python session. Only meaningful when wait is True. - +You start your training script by calling ``fit`` on an ``RLEstimator``. +For more information about what arguments can be passed to ``fit``, see :func:`sagemaker.estimator.EstimatorBase.fit`. Distributed RL Training ----------------------- @@ -210,14 +139,14 @@ Deploying RL Models After an RL Estimator has been fit, you can host the newly created model in SageMaker. -After calling ``fit``, you can call ``deploy`` on an ``RlEstimator`` Estimator to create a SageMaker Endpoint. +After calling ``fit``, you can call ``deploy`` on an ``RLEstimator`` Estimator to create a SageMaker Endpoint. The Endpoint runs one of the SageMaker-provided model server based on the ``framework`` parameter specified in the ``RLEstimator`` constructor and hosts the model produced by your training script, which was run when you called ``fit``. This was the model you saved to ``model_dir``. -In case if ``image_name`` was specified it would use provided image for the deployment. +In case if ``image_uri`` was specified it would use provided image for the deployment. ``deploy`` returns a ``sagemaker.mxnet.MXNetPredictor`` for MXNet or -``sagemaker.tensorflow.serving.Predictor`` for TensorFlow. +``sagemaker.tensorflow.TensorFlowPredictor`` for TensorFlow. ``predict`` returns the result of inference against your model. @@ -229,8 +158,8 @@ In case if ``image_name`` was specified it would use provided image for the depl toolkit_version='0.11.0', framework=RLFramework.MXNET, role='SageMakerRole', - train_instance_type='ml.c4.2xlarge', - train_instance_count=1) + instance_type='ml.c4.2xlarge', + instance_count=1) rl_estimator.fit() @@ -241,7 +170,7 @@ In case if ``image_name`` was specified it would use provided image for the depl response = predictor.predict(data) For more information please see `The SageMaker MXNet Model Server `_ -and `Deploying to TensorFlow Serving Endpoints `_ documentation. +and `Deploying to TensorFlow Serving Endpoints `_ documentation. Working with Existing Training Jobs diff --git a/doc/frameworks/sklearn/using_sklearn.rst b/doc/frameworks/sklearn/using_sklearn.rst index c49ffb0947..53e28bb86a 100644 --- a/doc/frameworks/sklearn/using_sklearn.rst +++ b/doc/frameworks/sklearn/using_sklearn.rst @@ -31,7 +31,7 @@ To train a Scikit-learn model by using the SageMaker Python SDK: Prepare a Scikit-learn Training Script ====================================== -Your Scikit-learn training script must be a Python 2.7 or 3.6 compatible source file. +Your Scikit-learn training script must be a Python 3.6 compatible source file. The training script is similar to a training script you might run outside of SageMaker, but you can access useful properties about the training environment through various environment variables. @@ -134,7 +134,7 @@ directories ('train' and 'test'). .. code:: python sklearn_estimator = SKLearn('sklearn-train.py', - train_instance_type='ml.m4.xlarge', + instance_type='ml.m4.xlarge', framework_version='0.20.0', hyperparameters = {'epochs': 20, 'batch-size': 64, 'learning-rate': 0.1}) sklearn_estimator.fit({'train': 's3://my-data-bucket/path/to/my/training/data', @@ -198,7 +198,7 @@ operation. # Train my estimator sklearn_estimator = SKLearn(entry_point='train_and_deploy.py', - train_instance_type='ml.m4.xlarge', + instance_type='ml.m4.xlarge', framework_version='0.20.0') sklearn_estimator.fit('s3://my_bucket/my_training_data/') @@ -299,7 +299,7 @@ You can provide your own implementations for these functions in your hosting scr If you omit any definition then the SageMaker Scikit-learn model server will use its default implementation for that function. -The ``RealTimePredictor`` used by Scikit-learn in the SageMaker Python SDK serializes NumPy arrays to the `NPY `_ format +The ``Predictor`` used by Scikit-learn in the SageMaker Python SDK serializes NumPy arrays to the `NPY `_ format by default, with Content-Type ``application/x-npy``. The SageMaker Scikit-learn model server can deserialize NPY-formatted data (along with JSON and CSV data). @@ -465,41 +465,14 @@ The following code sample shows how to do this, using the ``SKLearnModel`` class .. code:: python - sklearn_model = SKLearnModel(model_data="s3://bucket/model.tar.gz", role="SageMakerRole", - entry_point="transform_script.py") + sklearn_model = SKLearnModel(model_data="s3://bucket/model.tar.gz", + role="SageMakerRole", + entry_point="transform_script.py", + framework_version="0.20.0") predictor = sklearn_model.deploy(instance_type="ml.c4.xlarge", initial_instance_count=1) -The sklearn_model constructor takes the following arguments: - -- ``model_data (str):`` An S3 location of a SageMaker model data - .tar.gz file -- ``image (str):`` A Docker image URI -- ``role (str):`` An IAM role name or Arn for SageMaker to access AWS - resources on your behalf. -- ``predictor_cls (callable[string,sagemaker.Session]):`` A function to - call to create a predictor. If not None, ``deploy`` will return the - result of invoking this function on the created endpoint name -- ``env (dict[string,string]):`` Environment variables to run with - ``image`` when hosted in SageMaker. -- ``name (str):`` The model name. If None, a default model name will be - selected on each ``deploy.`` -- ``entry_point (str):`` Path (absolute or relative) to the Python file - which should be executed as the entry point to model hosting. -- ``source_dir (str):`` Optional. Path (absolute or relative) to a - directory with any other training source code dependencies including - the entry point file. Structure within this directory will be - preserved when training on SageMaker. -- ``enable_cloudwatch_metrics (boolean):`` Optional. If true, training - and hosting containers will generate Cloudwatch metrics under the - AWS/SageMakerContainer namespace. -- ``container_log_level (int):`` Log level to use within the container. - Valid values are defined in the Python logging module. -- ``code_location (str):`` Optional. Name of the S3 bucket where your - custom code will be uploaded to. If not specified, will use the - SageMaker default bucket created by sagemaker.Session. -- ``sagemaker_session (sagemaker.Session):`` The SageMaker Session - object, used for SageMaker interaction""" +To see what arguments are accepted by the ``SKLearnModel`` constructor, see :class:`sagemaker.sklearn.model.SKLearnModel`. Your model data must be a .tar.gz file in S3. SageMaker Training Job model data is saved to .tar.gz files in S3, however if you have local data you want to deploy, you can prepare the data yourself. @@ -528,69 +501,11 @@ https://github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-pytho These are also available in SageMaker Notebook Instance hosted Jupyter notebooks under the "sample notebooks" folder. -******************************* -sagemaker.sklearn.SKLearn Class -******************************* +****************************** +SageMaker scikit-learn Classes +****************************** -The `SKLearn` constructor takes both required and optional arguments. - -Required arguments -================== - -The following are required arguments to the ``SKLearn`` constructor. When you create a Scikit-learn object, you must -include these in the constructor, either positionally or as keyword arguments. - -- ``entry_point`` Path (absolute or relative) to the Python file which - should be executed as the entry point to training. -- ``role`` An AWS IAM role (either name or full ARN). The Amazon - SageMaker training jobs and APIs that create Amazon SageMaker - endpoints use this role to access training data and model artifacts. - After the endpoint is created, the inference code might use the IAM - role, if accessing AWS resource. -- ``train_instance_type`` Type of EC2 instance to use for training, for - example, 'ml.m4.xlarge'. Please note that Scikit-learn does not have GPU support. - -Optional arguments -================== - -The following are optional arguments. When you create a ``SKLearn`` object, you can specify these as keyword arguments. - -- ``source_dir`` Path (absolute or relative) to a directory with any - other training source code dependencies including the entry point - file. Structure within this directory will be preserved when training - on SageMaker. -- ``hyperparameters`` Hyperparameters that will be used for training. - Will be made accessible as a dict[str, str] to the training code on - SageMaker. For convenience, accepts other types besides str, but - str() will be called on keys and values to convert them before - training. -- ``py_version`` Python version you want to use for executing your - model training code. -- ``train_volume_size`` Size in GB of the EBS volume to use for storing - input data during training. Must be large enough to store training - data if input_mode='File' is used (which is the default). -- ``train_max_run`` Timeout in seconds for training, after which Amazon - SageMaker terminates the job regardless of its current status. -- ``input_mode`` The input mode that the algorithm supports. Valid - modes: 'File' - Amazon SageMaker copies the training dataset from the - s3 location to a directory in the Docker container. 'Pipe' - Amazon - SageMaker streams data directly from s3 to the container via a Unix - named pipe. -- ``output_path`` s3 location where you want the training result (model - artifacts and optional output files) saved. If not specified, results - are stored to a default bucket. If the bucket with the specific name - does not exist, the estimator creates the bucket during the fit() - method execution. -- ``output_kms_key`` Optional KMS key ID to optionally encrypt training - output with. -- ``base_job_name`` Name to assign for the training job that the fit() - method launches. If not specified, the estimator generates a default - job name, based on the training image name and current timestamp -- ``image_name`` An alternative docker image to use for training and - serving. If specified, the estimator will use this image for training and - hosting, instead of selecting the appropriate SageMaker official image based on - framework_version and py_version. Refer to: `SageMaker Scikit-learn Docker Containers `_ for details on what the official images support - and where to find the source code to build your custom image. +For information about the different scikit-learn classes in the SageMaker Python SDK, see https://sagemaker.readthedocs.io/en/stable/frameworks/sklearn/sagemaker.sklearn.html. **************************************** SageMaker Scikit-learn Docker Containers diff --git a/src/sagemaker/tensorflow/deploying_tensorflow_serving.rst b/doc/frameworks/tensorflow/deploying_tensorflow_serving.rst similarity index 95% rename from src/sagemaker/tensorflow/deploying_tensorflow_serving.rst rename to doc/frameworks/tensorflow/deploying_tensorflow_serving.rst index 45b607ae94..8926d36486 100644 --- a/src/sagemaker/tensorflow/deploying_tensorflow_serving.rst +++ b/doc/frameworks/tensorflow/deploying_tensorflow_serving.rst @@ -22,17 +22,21 @@ estimator object to create a SageMaker Endpoint: from sagemaker.tensorflow import TensorFlow - estimator = TensorFlow(entry_point='tf-train.py', ..., train_instance_count=1, - train_instance_type='ml.c4.xlarge', framework_version='1.11') + estimator = TensorFlow( + entry_point="tf-train.py", + ..., + instance_count=1, + instance_type="ml.c4.xlarge", + framework_version="2.2", + py_version="py37", + ) estimator.fit(inputs) - predictor = estimator.deploy(initial_instance_count=1, - instance_type='ml.c5.xlarge', - endpoint_type='tensorflow-serving') + predictor = estimator.deploy(initial_instance_count=1, instance_type="ml.c5.xlarge") -The code block above deploys a SageMaker Endpoint with one instance of the type 'ml.c5.xlarge'. +The code block above deploys a SageMaker Endpoint with one instance of the type "ml.c5.xlarge". What happens when deploy is called ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -58,9 +62,9 @@ If you already have existing model artifacts in S3, you can skip training and de .. code:: python - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') + model = TensorFlowModel(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') predictor = model.deploy(initial_instance_count=1, instance_type='ml.c5.xlarge') @@ -68,9 +72,9 @@ Python-based TensorFlow serving on SageMaker has support for `Elastic Inference .. code:: python - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') + model = TensorFlowModel(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') predictor = model.deploy(initial_instance_count=1, instance_type='ml.c5.xlarge', accelerator_type='ml.eia1.medium') @@ -236,7 +240,7 @@ your input data to CSV format: # create a Predictor with JSON serialization - predictor = Predictor('endpoint-name', serializer=sagemaker.predictor.csv_serializer) + predictor = Predictor('endpoint-name', serializer=sagemaker.serializers.CSVSerializer()) # CSV-formatted string input input = '1.0,2.0,5.0\n1.0,2.0,5.0\n1.0,2.0,5.0' @@ -252,7 +256,7 @@ your input data to CSV format: ] } -You can also use python arrays or numpy arrays as input and let the `csv_serializer` object +You can also use python arrays or numpy arrays as input and let the ``CSVSerializer`` object convert them to CSV, but the client-size CSV conversion is more sophisticated than the CSV parsing on the Endpoint, so if you encounter conversion problems, try using one of the JSON options instead. @@ -276,7 +280,7 @@ This customized Python code must be named ``inference.py`` and specified through .. code:: - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel model = Model(entry_point='inference.py', model_data='s3://mybucket/model.tar.gz', @@ -429,7 +433,7 @@ processing. There are 2 ways to do this: .. code:: - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel model = Model(entry_point='inference.py', source_dir='source/directory', @@ -447,7 +451,7 @@ processing. There are 2 ways to do this: .. code:: - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel model = Model(entry_point='inference.py', dependencies=['/path/to/folder/named/lib'], @@ -546,7 +550,7 @@ For the remaining steps, let's return to python code using the SageMaker Python .. code:: python - from sagemaker.tensorflow.serving import Model, Predictor + from sagemaker.tensorflow import TensorFlowModel, TensorFlowPredictor # change this to the name or ARN of your SageMaker execution role role = 'SageMakerRole' @@ -578,7 +582,7 @@ additional ``Predictor`` instances. Here's how: # ... continuing from the previous example # get the endpoint name from the default predictor - endpoint = predictor.endpoint + endpoint = predictor.endpoint_name # get a predictor for 'model2' model2_predictor = Predictor(endpoint, model_name='model2') diff --git a/doc/frameworks/tensorflow/index.rst b/doc/frameworks/tensorflow/index.rst index 9a6639abbc..46f535ba1d 100644 --- a/doc/frameworks/tensorflow/index.rst +++ b/doc/frameworks/tensorflow/index.rst @@ -1,6 +1,6 @@ -############################# +########## TensorFlow -############################# +########## A managed environment for TensorFlow training and hosting on Amazon SageMaker @@ -8,6 +8,8 @@ A managed environment for TensorFlow training and hosting on Amazon SageMaker :maxdepth: 1 using_tf + deploying_tensorflow_serving + upgrade_from_legacy .. toctree:: :maxdepth: 2 diff --git a/doc/frameworks/tensorflow/sagemaker.tensorflow.rst b/doc/frameworks/tensorflow/sagemaker.tensorflow.rst index 29da217d53..c9187ffa04 100644 --- a/doc/frameworks/tensorflow/sagemaker.tensorflow.rst +++ b/doc/frameworks/tensorflow/sagemaker.tensorflow.rst @@ -10,26 +10,10 @@ TensorFlow Estimator :undoc-members: :show-inheritance: -TensorFlow Model ----------------- - -.. autoclass:: sagemaker.tensorflow.model.TensorFlowModel - :members: - :undoc-members: - :show-inheritance: - -TensorFlow Predictor --------------------- - -.. autoclass:: sagemaker.tensorflow.model.TensorFlowPredictor - :members: - :undoc-members: - :show-inheritance: - TensorFlow Serving Model ------------------------ -.. autoclass:: sagemaker.tensorflow.serving.Model +.. autoclass:: sagemaker.tensorflow.model.TensorFlowModel :members: :undoc-members: :show-inheritance: @@ -37,7 +21,7 @@ TensorFlow Serving Model TensorFlow Serving Predictor ---------------------------- -.. autoclass:: sagemaker.tensorflow.serving.Predictor +.. autoclass:: sagemaker.tensorflow.model.TensorFlowPredictor :members: :undoc-members: :show-inheritance: diff --git a/doc/frameworks/tensorflow/upgrade_from_legacy.rst b/doc/frameworks/tensorflow/upgrade_from_legacy.rst new file mode 100644 index 0000000000..143a1180d0 --- /dev/null +++ b/doc/frameworks/tensorflow/upgrade_from_legacy.rst @@ -0,0 +1,253 @@ +###################################### +Upgrade from Legacy TensorFlow Support +###################################### + +With version 2.0 and later of the SageMaker Python SDK, support for legacy SageMaker TensorFlow images has been deprecated. +This guide explains how to upgrade your SageMaker Python SDK usage. + +For more information about using TensorFlow with the SageMaker Python SDK, see `Use TensorFlow with the SageMaker Python SDK `_. + +.. contents:: + +******************************************** +What Constitutes "Legacy TensorFlow Support" +******************************************** + +This guide is relevant if one of the following applies: + +#. You are using TensorFlow versions 1.4-1.10 +#. You are using TensorFlow versions 1.11-1.12 with Python 2, and + + - you do *not* have ``script_mode=True`` when creating your estimator + - you are using ``sagemaker.tensorflow.model.TensorFlowModel`` and/or ``sagemaker.tensorflow.model.TensorFlowPredictor`` + +#. You are using a pre-built SageMaker image whose URI looks like ``520713654638.dkr.ecr..amazonaws.com/sagemaker-tensorflow:`` + +If one of the above applies, then keep reading. + +************** +How to Upgrade +************** + +We recommend that you use the latest supported version of TensorFlow because that's where we focus our development efforts. +For information about supported versions of TensorFlow, see the `AWS documentation `_. + +For general information about using TensorFlow with the SageMaker Python SDK, see `Use TensorFlow with the SageMaker Python SDK `_. + +Training Script +=============== + +Newer versions of TensorFlow require your training script to be runnable as a command-line script, similar to what you might run outside of SageMaker. For more information, including how to adapt a locally-runnable script, see `Prepare a Training Script `_. + +In addition, your training script needs to save your model. If you have your own ``serving_input_fn`` implementation, then that can be passed to an exporter: + +.. code:: python + + import tensorflow as tf + + exporter = tf.estimator.LatestExporter("Servo", serving_input_receiver_fn=serving_input_fn) + +For an example of how to repackage your legacy TensorFlow training script for use with a newer version of TensorFlow, +see `this example notebook `_. + +Inference Script +================ + +Newer versions of TensorFlow Serving require a different format for the inference script. Some key differences: + +- The script must be named ``inference.py``. +- ``input_fn`` has been replaced by ``input_handler``. +- ``output_fn`` has been replaced by ``output_handler``. + +Like with the legacy versions, the pre-built SageMaker TensorFlow Serving images do have default implementations for pre- and post-processing. + +For more information about implementing your own handlers, see `How to implement the pre- and/or post-processing handler(s) `_. + +***************************** +Continue with Legacy Versions +***************************** + +While not recommended, you can still use a legacy TensorFlow version with version 2.0 and later of the SageMaker Python SDK. +In order to do so, you need to change how a few parameters are defined. + +Training +======== + +When creating an estimator, the Python SDK version 2.0 and later requires the following changes: + +#. Explicitly specify the ECR image URI via ``image_uri``. + To determine the URI, you can use :func:`sagemaker.fw_utils.create_image_uri`. +#. Specify ``model_dir=False``. +#. Use hyperparameters for ``training_steps``, ``evaluation_steps``, ``checkpoint_path``, and ``requirements_file``. + +For example, if using TF 1.10.0 with an ml.m4.xlarge instance in us-west-2, +the difference in code would be as follows: + +.. code:: python + + from sagemaker.tensorflow import TensorFlow + + # v1.x + estimator = TensorFlow( + ... + source_dir="code", + framework_version="1.10.0", + train_instance_type="ml.m4.xlarge", + training_steps=100, + evaluation_steps=10, + checkpoint_path="s3://bucket/path", + requirements_file="requirements.txt", + ) + + # v2.0 and later + estimator = TensorFlow( + ... + source_dir="code", + framework_version="1.10.0", + py_version="py2", + instance_type="ml.m4.xlarge", + image_uri="520713654638.dkr.ecr.us-west-2.amazonaws.com/sagemaker-tensorflow:1.10.0-cpu-py2", + hyperparameters={ + "training_steps": 100, + "evaluation_steps": 10, + "checkpoint_path": "s3://bucket/path", + "sagemaker_requirements": "requirements.txt", + }, + model_dir=False, + ) + +Requirements File with Training +------------------------------- + +To provide a requirements file, define a hyperparameter named "sagemaker_requirements" that contains the relative path to the requirements file from ``source_dir``. + +Inference +========= + +Using a legacy TensorFlow version for endpoints and batch transform can be achieved with version 2.0 and later of the SageMaker Python SDK with some minor changes to your code. + +From an Estimator +----------------- + +If you are starting with a training job, you can call :func:`sagemaker.estimator.EstimatorBase.deploy` or :func:`sagemaker.tensorflow.estimator.Estimator.transformer` from your estimator for inference. + +To specify the number of model server workers, you need to set it through an environment variable named ``MODEL_SERVER_WORKERS``: + +.. code:: python + + # v1.x + estimator.deploy(..., model_server_workers=4) + + # v2.0 and later + estimator.deploy(..., env={"MODEL_SERVER_WORKERS": 4}) + +From a Trained Model +-------------------- + +If you are starting with a trained model, the Python SDK version 2.0 and later requires the following changes: + +#. Use the the :class:`sagemaker.model.FrameworkModel` class. +#. Explicitly specify the ECR image URI via ``image_uri``. + To determine the URI, you can use :func:`sagemaker.fw_utils.create_image_uri`. +#. Use an environment variable for ``model_server_workers``. + +For example, if using TF 1.10.0 with a CPU instance in us-west-2, +the difference in code would be as follows: + +.. code:: python + + # v1.x + from sagemaker.tensorflow import TensorFlowModel + + model = TensorFlowModel( + ... + py_version="py2", + framework_version="1.10.0", + model_server_workers=4, + ) + + # v2.0 and later + from sagemaker.model import FrameworkModel + + model = FrameworkModel( + ... + image_uri="520713654638.dkr.ecr.us-west-2.amazonaws.com/sagemaker-tensorflow:1.10.0-cpu-py2", + env={"MODEL_SERVER_WORKERS": 4}, + ) + +Requirements File with Inference +-------------------------------- + +To provide a requirements file, define an environment variable named ``SAGEMAKER_REQUIREMENTS`` that contains the relative path to the requirements file from ``source_dir``. + +From an estimator: + +.. code:: python + + # for an endpoint + estimator.deploy(..., env={"SAGEMAKER_REQUIREMENTS": "requirements.txt"}) + + # for batch transform + estimator.transformer(..., env={"SAGEMAKER_REQUIREMENTS": "requirements.txt"}) + +From a model: + +.. code:: python + + from sagemaker.model import FrameworkModel + + model = FrameworkModel( + ... + source_dir="code", + env={"SAGEMAKER_REQUIREMENTS": "requirements.txt"}, + ) + + +Predictors +---------- + +If you want to use your model for endpoints, then you can use the :class:`sagemaker.predictor.Predictor` class instead of the legacy ``sagemaker.tensorflow.TensorFlowPredictor`` class: + +.. code:: python + + from sagemaker.model import FrameworkModel + from sagemaker.predictor import Predictor + + model = FrameworkModel( + ... + predictor_cls=Predictor, + ) + + predictor = model.deploy(...) + +If you are using protobuf prediction data, then you need to serialize and deserialize the data yourself. + +For example: + +.. code:: python + + from google.protobuf import json_format + from protobuf_to_dict import protobuf_to_dict + from tensorflow.core.framework import tensor_pb2 + + # Serialize the prediction data + json_format.MessageToJson(data) + + # Get the prediction result + result = predictor.predict(data) + + # Deserialize the prediction result + protobuf_to_dict(json_format.Parse(result, tensor_pb2.TensorProto())) + +Otherwise, you can use the serializers and deserialzers available in the SageMaker Python SDK or write your own. + +For example, if you want to use JSON serialization and deserialization: + +.. code:: python + + from sagemaker.deserializers import JSONDeserializer + from sagemaker.serializers import JSONSerializer + + predictor = model.deploy(..., serializer=JSONSerializer(), deserializer=JSONDeserializer()) + + predictor.predict(data) diff --git a/doc/frameworks/tensorflow/using_tf.rst b/doc/frameworks/tensorflow/using_tf.rst index 49cd97e68b..bd6cd36dcf 100644 --- a/doc/frameworks/tensorflow/using_tf.rst +++ b/doc/frameworks/tensorflow/using_tf.rst @@ -1,23 +1,17 @@ -############################################## -Using TensorFlow with the SageMaker Python SDK -############################################## +############################################ +Use TensorFlow with the SageMaker Python SDK +############################################ With the SageMaker Python SDK, you can train and host TensorFlow models on Amazon SageMaker. -For information about supported versions of TensorFlow, see the `AWS documentation `__. +For information about supported versions of TensorFlow, see the `AWS documentation `_. We recommend that you use the latest supported version because that's where we focus our development efforts. For general information about using the SageMaker Python SDK, see :ref:`overview:Using the SageMaker Python SDK`. .. warning:: - We have added a new format of your TensorFlow training script with TensorFlow version 1.11. - This new way gives the user script more flexibility. - This new format is called Script Mode, as opposed to Legacy Mode, which is what we support with TensorFlow 1.11 and older versions. - In addition we are adding Python 3 support with Script Mode. - The last supported version of Legacy Mode will be TensorFlow 1.12. - Script Mode is available with TensorFlow version 1.11 and newer. - Make sure you refer to the correct version of this README when you prepare your script. - You can find the Legacy Mode README `here `_. + Support for TensorFlow versions 1.4-1.10 has been deprecated. + For information on how to upgrade, see `Upgrade from Legacy TensorFlow Support `_. .. contents:: @@ -33,14 +27,12 @@ To train a TensorFlow model by using the SageMaker Python SDK: .. |call fit| replace:: Call the estimator's ``fit`` method .. _call fit: #call-the-fit-method -1. `Prepare a training script <#prepare-a-script-mode-training-script>`_ +1. `Prepare a training script <#prepare-a-training-script>`_ 2. |create tf estimator|_ 3. |call fit|_ -Prepare a Script Mode Training Script -===================================== - -Your TensorFlow training script must be a Python 2.7-, 3.6- or 3.7-compatible source file. +Prepare a Training Script +========================= The training script is very similar to a training script you might run outside of SageMaker, but you can access useful properties about the training environment through various environment variables, including the following: @@ -141,18 +133,18 @@ For training, support for installing packages using ``requirements.txt`` varies - For TensorFlow 1.15.2 with Python 3.7 or newer, and TensorFlow 2.2 or newer: - Include a ``requirements.txt`` file in the same directory as your training script. - You must specify this directory using the ``source_dir`` argument when creating a TensorFlow estimator. -- For older versions of TensorFlow using Script Mode (TensorFlow 1.11-1.15.2, 2.0-2.1 with Python 2.7 or 3.6): +- For TensorFlow versions 1.11-1.15.2, 2.0-2.1 with Python 2.7 or 3.6: - Write a shell script for your entry point that first calls ``pip install -r requirements.txt``, then runs your training script. - For an example of using shell scripts, see `this example notebook `__. -- For older versions of TensorFlow using Legacy Mode: - - Specify the path to your ``requirements.txt`` file using the ``requirements_file`` argument. +- For legacy versions of TensorFlow: + - See `Upgrade from Legacy TensorFlow Support `_. For serving, support for installing packages using ``requirements.txt`` varies by TensorFlow version as follows: - For TensorFlow 1.11 or newer: - Include a ``requirements.txt`` file in the ``code`` directory. -- For older versions of TensorFlow: - - Specify the path to your ``requirements.txt`` file using the ``SAGEMAKER_REQUIREMENTS`` environment variable. +- For legacy versions of TensorFlow: + - See `Upgrade from Legacy TensorFlow Support `_. A ``requirements.txt`` file is a text file that contains a list of items that are installed by using ``pip install``. You can also specify the version of an item to install. @@ -164,35 +156,24 @@ Create an Estimator After you create your training script, create an instance of the :class:`sagemaker.tensorflow.TensorFlow` estimator. -To use Script Mode, set at least one of these args - -- ``py_version='py3'`` -- ``script_mode=True`` - To use Python 3.7, please specify both of the args: - ``py_version='py37'`` - ``framework_version='1.15.2'`` -When using Script Mode, your training script needs to accept the following args: - -- ``model_dir`` - -The following args are not permitted when using Script Mode: - -- ``checkpoint_path`` -- ``training_steps`` -- ``evaluation_steps`` -- ``requirements_file`` - .. code:: python from sagemaker.tensorflow import TensorFlow - tf_estimator = TensorFlow(entry_point='tf-train.py', role='SageMakerRole', - train_instance_count=1, train_instance_type='ml.p2.xlarge', - framework_version='1.12', py_version='py3') - tf_estimator.fit('s3://bucket/path/to/training/data') + tf_estimator = TensorFlow( + entry_point="tf-train.py", + role="SageMakerRole", + instance_count=1, + instance_type="ml.p2.xlarge", + framework_version="2.2", + py_version="py37", + ) + tf_estimator.fit("s3://bucket/path/to/training/data") Where the S3 url is a path to your training data within Amazon S3. The constructor keyword arguments define how SageMaker runs your training script. @@ -224,36 +205,11 @@ For more information about the sagemaker.tensorflow.TensorFlow estimator, see `S Call the fit Method =================== -You start your training script by calling the ``fit`` method on a ``TensorFlow`` estimator. ``fit`` takes -both required and optional arguments. - -Required arguments ------------------- - -- ``inputs``: The S3 location(s) of datasets to be used for training. This can take one of two forms: - - - ``str``: An S3 URI, for example ``s3://my-bucket/my-training-data``, which indicates the dataset's location. - - ``dict[str, str]``: A dictionary mapping channel names to S3 locations, for example ``{'train': 's3://my-bucket/my-training-data/train', 'test': 's3://my-bucket/my-training-data/test'}`` - - ``sagemaker.session.s3_input``: channel configuration for S3 data sources that can provide additional information as well as the path to the training dataset. See `the API docs `_ for full details. - -Optional arguments ------------------- - -- ``wait (bool)``: Defaults to True, whether to block and wait for the - training script to complete before returning. - If set to False, it will return immediately, and can later be attached to. -- ``logs (bool)``: Defaults to True, whether to show logs produced by training - job in the Python session. Only meaningful when wait is True. -- ``run_tensorboard_locally (bool)``: Defaults to False. If set to True a Tensorboard command will be printed out. -- ``job_name (str)``: Training job name. If not specified, the estimator generates a default job name, - based on the training image name and current timestamp. - -What happens when fit is called -------------------------------- +You start your training script by calling the ``fit`` method on a ``TensorFlow`` estimator. Calling ``fit`` starts a SageMaker training job. The training job will execute the following. -- Starts ``train_instance_count`` EC2 instances of the type ``train_instance_type``. +- Starts ``instance_count`` EC2 instances of the type ``instance_type``. - On each instance, it will do the following steps: - starts a Docker container optimized for TensorFlow. @@ -276,17 +232,19 @@ After attaching, the estimator can be deployed as usual. tf_estimator = TensorFlow.attach(training_job_name=training_job_name) +For more information about the options available for ``fit``, see the `API documentation `_. + Distributed Training ==================== -To run your training job with multiple instances in a distributed fashion, set ``train_instance_count`` +To run your training job with multiple instances in a distributed fashion, set ``instance_count`` to a number larger than 1. We support two different types of distributed training, parameter server and Horovod. -The ``distributions`` parameter is used to configure which distributed training strategy to use. +The ``distribution`` parameter is used to configure which distributed training strategy to use. Training with parameter servers ------------------------------- -If you specify parameter_server as the value of the distributions parameter, the container launches a parameter server +If you specify parameter_server as the value of the distribution parameter, the container launches a parameter server thread on each instance in the training cluster, and then executes your training code. You can find more information on TensorFlow distributed training at `TensorFlow docs `__. To enable parameter server training: @@ -295,11 +253,16 @@ To enable parameter server training: from sagemaker.tensorflow import TensorFlow - tf_estimator = TensorFlow(entry_point='tf-train.py', role='SageMakerRole', - train_instance_count=2, train_instance_type='ml.p2.xlarge', - framework_version='1.11', py_version='py3', - distributions={'parameter_server': {'enabled': True}}) - tf_estimator.fit('s3://bucket/path/to/training/data') + tf_estimator = TensorFlow( + entry_point="tf-train.py", + role="SageMakerRole", + instance_count=2, + instance_type="ml.p2.xlarge", + framework_version="2.2", + py_version="py37", + distribution={"parameter_server": {"enabled": True}}, + ) + tf_estimator.fit("s3://bucket/path/to/training/data") Training with Horovod --------------------- @@ -307,16 +270,16 @@ Training with Horovod Horovod is a distributed training framework based on MPI. Horovod is only available with TensorFlow version ``1.12`` or newer. You can find more details at `Horovod README `__. -The container sets up the MPI environment and executes the ``mpirun`` command enabling you to run any Horovod -training script with Script Mode. +The container sets up the MPI environment and executes the ``mpirun`` command, enabling you to run any Horovod +training script. -Training with ``MPI`` is configured by specifying following fields in ``distributions``: +Training with ``MPI`` is configured by specifying following fields in ``distribution``: - ``enabled (bool)``: If set to ``True``, the MPI setup is performed and ``mpirun`` command is executed. - ``processes_per_host (int)``: Number of processes MPI should launch on each host. Note, this should not be greater than the available slots on the selected instance type. This flag should be set for the multi-cpu/gpu training. -- ``custom_mpi_options (str)``: Any `mpirun` flag(s) can be passed in this field that will be added to the `mpirun` +- ``custom_mpi_options (str)``: Any ``mpirun`` flag(s) can be passed in this field that will be added to the ``mpirun`` command executed by SageMaker to launch distributed horovod training. @@ -326,17 +289,22 @@ In the below example we create an estimator to launch Horovod distributed traini from sagemaker.tensorflow import TensorFlow - tf_estimator = TensorFlow(entry_point='tf-train.py', role='SageMakerRole', - train_instance_count=1, train_instance_type='ml.p3.8xlarge', - framework_version='2.1.0', py_version='py3', - distributions={ - 'mpi': { - 'enabled': True, - 'processes_per_host': 4, - 'custom_mpi_options': '--NCCL_DEBUG INFO' - } - }) - tf_estimator.fit('s3://bucket/path/to/training/data') + tf_estimator = TensorFlow( + entry_point="tf-train.py", + role="SageMakerRole", + instance_count=1, + instance_type="ml.p3.8xlarge", + framework_version="2.1.0", + py_version="py3", + distribution={ + "mpi": { + "enabled": True, + "processes_per_host": 4, + "custom_mpi_options": "--NCCL_DEBUG INFO" + }, + }, + ) + tf_estimator.fit("s3://bucket/path/to/training/data") Training with Pipe Mode using PipeModeDataset @@ -385,12 +353,19 @@ To run training job with Pipe input mode, pass in ``input_mode='Pipe'`` to your from sagemaker.tensorflow import TensorFlow - tf_estimator = TensorFlow(entry_point='tf-train-with-pipemodedataset.py', role='SageMakerRole', - training_steps=10000, evaluation_steps=100, - train_instance_count=1, train_instance_type='ml.p2.xlarge', - framework_version='1.10.0', input_mode='Pipe') + tf_estimator = TensorFlow( + entry_point="tf-train-with-pipemodedataset.py", + role="SageMakerRole", + training_steps=10000, + evaluation_steps=100, + instance_count=1, + instance_type="ml.p2.xlarge", + framework_version="1.10.0", + py_version="py3", + input_mode="Pipe", + ) - tf_estimator.fit('s3://bucket/path/to/training/data') + tf_estimator.fit("s3://bucket/path/to/training/data") If your TFRecords are compressed, you can train on Gzipped TF Records by passing in ``compression='Gzip'`` to the call to @@ -398,9 +373,9 @@ If your TFRecords are compressed, you can train on Gzipped TF Records by passing .. code:: python - from sagemaker.session import s3_input + from sagemaker.inputs import TrainingInput - train_s3_input = s3_input('s3://bucket/path/to/training/data', compression='Gzip') + train_s3_input = TrainingInput('s3://bucket/path/to/training/data', compression='Gzip') tf_estimator.fit(train_s3_input) @@ -451,14 +426,18 @@ estimator object to create a SageMaker Endpoint: from sagemaker.tensorflow import TensorFlow - estimator = TensorFlow(entry_point='tf-train.py', ..., train_instance_count=1, - train_instance_type='ml.c4.xlarge', framework_version='1.11') + estimator = TensorFlow( + entry_point="tf-train.py", + ..., + instance_count=1, + instance_type="ml.c4.xlarge", + framework_version="2.2", + py_version="py37", + ) estimator.fit(inputs) - predictor = estimator.deploy(initial_instance_count=1, - instance_type='ml.c5.xlarge', - endpoint_type='tensorflow-serving') + predictor = estimator.deploy(initial_instance_count=1, instance_type="ml.c5.xlarge") The code block above deploys a SageMaker Endpoint with one instance of the type 'ml.c5.xlarge'. @@ -487,9 +466,9 @@ If you already have existing model artifacts in S3, you can skip training and de .. code:: python - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') + model = TensorFlowModel(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') predictor = model.deploy(initial_instance_count=1, instance_type='ml.c5.xlarge') @@ -497,9 +476,9 @@ Python-based TensorFlow serving on SageMaker has support for `Elastic Inference .. code:: python - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') + model = TensorFlowModel(model_data='s3://mybucket/model.tar.gz', role='MySageMakerRole') predictor = model.deploy(initial_instance_count=1, instance_type='ml.c5.xlarge', accelerator_type='ml.eia1.medium') @@ -571,7 +550,7 @@ classify/regress requests) to get multiple prediction results in one request to If your application allows request grouping like this, it is **much** more efficient than making separate requests. -See `Deploying to TensorFlow Serving Endpoints ` to learn how to deploy your model and make inference requests. +See `Deploying to TensorFlow Serving Endpoints `_ to learn how to deploy your model and make inference requests. Run a Batch Transform Job ========================= @@ -753,7 +732,7 @@ your input data to CSV format: # create a Predictor with JSON serialization - predictor = Predictor('endpoint-name', serializer=sagemaker.predictor.csv_serializer) + predictor = Predictor('endpoint-name', serializer=sagemaker.serializers.CSVSerializer()) # CSV-formatted string input input = '1.0,2.0,5.0\n1.0,2.0,5.0\n1.0,2.0,5.0' @@ -769,7 +748,7 @@ your input data to CSV format: ] } -You can also use python arrays or numpy arrays as input and let the `csv_serializer` object +You can also use python arrays or numpy arrays as input and let the ``CSVSerializer`` object convert them to CSV, but the client-size CSV conversion is more sophisticated than the CSV parsing on the Endpoint, so if you encounter conversion problems, try using one of the JSON options instead. @@ -784,20 +763,20 @@ This customized Python code must be named ``inference.py`` and specified through .. code:: - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(entry_point='inference.py', - model_data='s3://mybucket/model.tar.gz', - role='MySageMakerRole') + model = TensorFlowModel(entry_point='inference.py', + model_data='s3://mybucket/model.tar.gz', + role='MySageMakerRole') How to implement the pre- and/or post-processing handler(s) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Your entry point file must be named ``inference.py`` and should implement - either a pair of ``input_handler`` and ``output_handler`` functions or - a single ``handler`` function. - Note that if ``handler`` function is implemented, ``input_handler`` - and ``output_handler`` are ignored. +either a pair of ``input_handler`` and ``output_handler`` functions or +a single ``handler`` function. +Note that if ``handler`` function is implemented, ``input_handler`` +and ``output_handler`` are ignored. To implement pre- and/or post-processing handler(s), use the Context object that the Python service creates. The Context object is a namedtuple with the following attributes: @@ -937,12 +916,12 @@ processing. There are 2 ways to do this: .. code:: - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(entry_point='inference.py', - dependencies=['requirements.txt'], - model_data='s3://mybucket/model.tar.gz', - role='MySageMakerRole') + model = TensorFlowModel(entry_point='inference.py', + dependencies=['requirements.txt'], + model_data='s3://mybucket/model.tar.gz', + role='MySageMakerRole') 2. If you are working in a network-isolation situation or if you don't @@ -955,12 +934,12 @@ processing. There are 2 ways to do this: .. code:: - from sagemaker.tensorflow.serving import Model + from sagemaker.tensorflow import TensorFlowModel - model = Model(entry_point='inference.py', - dependencies=['/path/to/folder/named/lib'], - model_data='s3://mybucket/model.tar.gz', - role='MySageMakerRole') + model = TensorFlowModel(entry_point='inference.py', + dependencies=['/path/to/folder/named/lib'], + model_data='s3://mybucket/model.tar.gz', + role='MySageMakerRole') For more information, see: https://github.com/aws/sagemaker-tensorflow-serving-container#prepost-processing diff --git a/doc/frameworks/xgboost/using_xgboost.rst b/doc/frameworks/xgboost/using_xgboost.rst index dde1a34e02..130fab1b9a 100644 --- a/doc/frameworks/xgboost/using_xgboost.rst +++ b/doc/frameworks/xgboost/using_xgboost.rst @@ -159,8 +159,8 @@ and a dictionary of the hyperparameters to pass to the training script. entry_point="abalone.py", hyperparameters=hyperparameters, role=role, - train_instance_count=1, - train_instance_type="ml.m5.2xlarge", + instance_count=1, + instance_type="ml.m5.2xlarge", framework_version="1.0-1", ) @@ -192,12 +192,14 @@ inference against your model. .. code:: + serializer = StringSerializer() + serializer.CONTENT_TYPE = "text/libsvm" + predictor = estimator.deploy( initial_instance_count=1, - instance_type="ml.m5.xlarge" + instance_type="ml.m5.xlarge", + serializer=serializer ) - predictor.serializer = str - predictor.content_type = "text/libsvm" with open("abalone") as f: payload = f.read() diff --git a/doc/index.rst b/doc/index.rst index db8e8e166b..cdcb9f8608 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,7 +1,3 @@ -.. important:: - We are working on v2.0.0. See https://github.com/aws/sagemaker-python-sdk/issues/1459 - for more info on our plans and to leave feedback! - ########################### Amazon SageMaker Python SDK ########################### @@ -20,6 +16,7 @@ Overview :maxdepth: 2 overview + v2 The SageMaker Python SDK APIs: diff --git a/doc/overview.rst b/doc/overview.rst index f8f95e2638..d942893884 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -97,8 +97,8 @@ Here is an end to end example of how to use a SageMaker Estimator: # Configure an MXNet Estimator (no training happens yet) mxnet_estimator = MXNet('train.py', role='SageMakerRole', - train_instance_type='ml.p2.xlarge', - train_instance_count=1, + instance_type='ml.p2.xlarge', + instance_count=1, framework_version='1.2.1') # Starts a SageMaker training job and waits until completion. @@ -116,7 +116,7 @@ Here is an end to end example of how to use a SageMaker Estimator: # Deletes the SageMaker model mxnet_predictor.delete_model() -The example above will eventually delete both the SageMaker endpoint and endpoint configuration through `delete_endpoint()`. If you want to keep your SageMaker endpoint configuration, use the value False for the `delete_endpoint_config` parameter, as shown below. +The example above will eventually delete both the SageMaker endpoint and endpoint configuration through ``delete_endpoint()``. If you want to keep your SageMaker endpoint configuration, use the value ``False`` for the ``delete_endpoint_config`` parameter, as shown below. .. code:: python @@ -134,8 +134,8 @@ For more `information =(\S+)'}, {'Name': 'test:ssd', 'Regex': '#quality_metric: host=\S+, test ssd =(\S+)'}]) @@ -479,10 +479,10 @@ Here's an example of how to use incremental training: # Configure an estimator estimator = sagemaker.estimator.Estimator(training_image, role, - train_instance_count=1, - train_instance_type='ml.p2.xlarge', - train_volume_size=50, - train_max_run=360000, + instance_count=1, + instance_type='ml.p2.xlarge', + volume_size=50, + max_run=360000, input_mode='File', output_path=s3_output_location) @@ -492,10 +492,10 @@ Here's an example of how to use incremental training: # Create a new estimator using the previous' model artifacts incr_estimator = sagemaker.estimator.Estimator(training_image, role, - train_instance_count=1, - train_instance_type='ml.p2.xlarge', - train_volume_size=50, - train_max_run=360000, + instance_count=1, + instance_type='ml.p2.xlarge', + volume_size=50, + max_run=360000, input_mode='File', output_path=s3_output_location, model_uri=estimator.model_data) @@ -609,7 +609,7 @@ Here is a basic example of how to use it: response = my_predictor.predict(my_prediction_data) # Tear down the SageMaker endpoint - my_tuner.delete_endpoint() + my_predictor.delete_endpoint() This example shows a hyperparameter tuning job that creates up to 100 training jobs, running up to 10 training jobs at a time. Each training job's learning rate is a value between 0.05 and 0.06, but this value will differ between training jobs. @@ -764,8 +764,8 @@ We can take the example in `Using Estimators <#using-estimators>`__ , and use e # Configure an MXNet Estimator (no training happens yet) mxnet_estimator = MXNet('train.py', role='SageMakerRole', - train_instance_type='local', - train_instance_count=1, + instance_type='local', + instance_count=1, framework_version='1.2.1') # In Local Mode, fit will pull the MXNet container Docker image and run it locally @@ -826,8 +826,8 @@ Here is an end-to-end example: mxnet_estimator = MXNet('train.py', role='SageMakerRole', - train_instance_type='local', - train_instance_count=1, + instance_type='local', + instance_count=1, framework_version='1.2.1') mxnet_estimator.fit('file:///tmp/my_training_data') @@ -889,8 +889,8 @@ To train a model using your own VPC, set the optional parameters ``subnets`` and # Configure an MXNet Estimator with subnets and security groups from your VPC mxnet_vpc_estimator = MXNet('train.py', - train_instance_type='ml.p2.xlarge', - train_instance_count=1, + instance_type='ml.p2.xlarge', + instance_count=1, framework_version='1.2.1', subnets=['subnet-1', 'subnet-2'], security_group_ids=['sg-1']) @@ -908,8 +908,8 @@ job runs in a VPC): # Configure an MXNet Estimator with subnets and security groups from your VPC mxnet_vpc_estimator = MXNet('train.py', - train_instance_type='ml.p2.xlarge', - train_instance_count=1, + instance_type='ml.p2.xlarge', + instance_count=1, framework_version='1.2.1', subnets=['subnet-1', 'subnet-2'], security_group_ids=['sg-1'], @@ -964,7 +964,7 @@ To train a model in network isolation mode, set the optional parameter ``enable_ # set the enable_network_isolation parameter to True sklearn_estimator = SKLearn('sklearn-train.py', - train_instance_type='ml.m4.xlarge', + instance_type='ml.m4.xlarge', framework_version='0.20.0', hyperparameters = {'epochs': 20, 'batch-size': 64, 'learning-rate': 0.1}, enable_network_isolation=True) @@ -993,12 +993,17 @@ the ML Pipeline. .. code:: python - xgb_image = get_image_uri(sess.boto_region_name, 'xgboost', repo_version="latest") - xgb_model = Model(model_data='s3://path/to/model.tar.gz', image=xgb_image) - sparkml_model = SparkMLModel(model_data='s3://path/to/model.tar.gz', env={'SAGEMAKER_SPARKML_SCHEMA': schema}) + from sagemaker import image_uris, session + from sagemaker.model import Model + from sagemaker.pipeline import PipelineModel + from sagemaker.sparkml import SparkMLModel - model_name = 'inference-pipeline-model' - endpoint_name = 'inference-pipeline-endpoint' + xgb_image = image_uris.retrieve("xgboost", session.Session().boto_region_name, repo_version="latest") + xgb_model = Model(model_data="s3://path/to/model.tar.gz", image_uri=xgb_image) + sparkml_model = SparkMLModel(model_data="s3://path/to/model.tar.gz", env={"SAGEMAKER_SPARKML_SCHEMA": schema}) + + model_name = "inference-pipeline-model" + endpoint_name = "inference-pipeline-endpoint" sm_model = PipelineModel(name=model_name, role=sagemaker_role, models=[sparkml_model, xgb_model]) This defines a ``PipelineModel`` consisting of SparkML model and an XGBoost model stacked sequentially. @@ -1127,7 +1132,7 @@ How do I make predictions against an existing endpoint? Create a ``Predictor`` object and provide it with your endpoint name, then call its ``predict()`` method with your input. -You can use either the generic ``RealTimePredictor`` class, which by default does not perform any serialization/deserialization transformations on your input, +You can use either the generic ``Predictor`` class, which by default does not perform any serialization/deserialization transformations on your input, but can be configured to do so through constructor arguments: http://sagemaker.readthedocs.io/en/stable/predictors.html diff --git a/doc/v2.rst b/doc/v2.rst new file mode 100644 index 0000000000..cecf8b8672 --- /dev/null +++ b/doc/v2.rst @@ -0,0 +1,447 @@ +########################################### +Use Version 2.x of the SageMaker Python SDK +########################################### + +.. contents:: + :local: + :depth: 2 + +************ +Installation +************ + +To install the latest version: + +.. code:: bash + + pip install --upgrade sagemaker + +If you are executing this pip install command in a notebook, make sure to restart your kernel. + +**************** +Breaking Changes +**************** + +This section is for major changes that may require updates to your SageMaker Python SDK code. +For the full list of changes, see the `CHANGELOG `_. + +Deprecations +============ + +Python 2 Support +---------------- + +This library is no longer supported for Python 2. +Please upgrade to Python 3 if you haven't already. + +Deprecate Legacy TensorFlow +--------------------------- + +TensorFlow versions 1.4-1.10 and some variations of versions 1.11-1.12 +(see `What Constitutes "Legacy TensorFlow Support" `_) +are no longer natively supported by the SageMaker Python SDK. + +To use those versions of TensorFlow, you must specify the Docker image URI explicitly, +and configure settings via hyperparameters or environment variables rather than using SDK parameters. +For more information, see `Upgrade from Legacy TensorFlow Support `_. + +``delete_endpoint()`` for Estimators and ``HyperparameterTuner`` +---------------------------------------------------------------- + +The ``delete_endpoint()`` method for estimators and ``HyperparameterTuner`` has been deprecated. +Please use :func:`sagemaker.predictor.Predictor.delete_endpoint` instead. + +Pre-instantiated Serializer and Deserializer Objects +---------------------------------------------------- + +The ``csv_serializer``, ``json_serializer``, ``npy_serializer``, ``csv_deserializer``, +``json_deserializer``, and ``numpy_deserializer`` objects have been deprecated. + +Please instantiate the objects instead. + ++--------------------------------------------+------------------------------------------------+ +| v1.x | v2.0 and later | ++============================================+================================================+ +| ``sagemaker.predictor.csv_serializer`` | ``sagemaker.deserializers.CSVSerializer()`` | ++--------------------------------------------+------------------------------------------------+ +| ``sagemaker.predictor.json_serializer`` | ``sagemaker.serializers.JSONSerializer()`` | ++--------------------------------------------+------------------------------------------------+ +| ``sagemaker.predictor.npy_serializer`` | ``sagemaker.deserializers.NumpySerializer()`` | ++--------------------------------------------+------------------------------------------------+ +| ``sagemaker.predictor.csv_deserializer`` | ``sagemaker.deserializers.CSVDeserializer()`` | ++--------------------------------------------+------------------------------------------------+ +| ``sagemaker.predictor.json_deserializer`` | ``sagemaker.deserializers.JSONDeserializer()`` | ++--------------------------------------------+------------------------------------------------+ +| ``sagemaker.predictor.numpy_deserializer`` | ``sagemaker.serializers.NumpyDeserializer()`` | ++--------------------------------------------+------------------------------------------------+ + +``update_endpoint`` in ``deploy()`` +----------------------------------- + +The ``update_endpoint`` argument in ``deploy()`` methods for estimators and models has been deprecated. +Please use :func:`sagemaker.predictor.Predictor.update_endpoint` instead. + +``serializer`` and ``deserializer`` in ``create_model()`` +--------------------------------------------------------- + +The ``serializer`` and ``deserializer`` arguments in +:func:`sagemaker.estimator.Estimator.create_model` have been deprecated. Please +specify serializers and deserializers in ``deploy()`` methods instead. + +``content_type`` and ``accept`` in the Predictor Constructor +------------------------------------------------------------ + +The ``content_type`` and ``accept`` parameters have been removed from the +following classes and methods: +- ``sagemaker.predictor.Predictor`` +- ``sagemaker.estimator.Estimator.create_model`` +- ``sagemaker.algorithms.AlgorithmEstimator.create_model`` +- ``sagemaker.tensorflow.model.TensorFlowPredictor`` + +Please specify content types in a serializer or deserializer class instead. + +``sagemaker.content_types`` +--------------------------- + +The ``sagemaker.content_types`` module is removed in v2.0 and later of the +SageMaker Python SDK. + +Instead of importing constants from ``sagemaker.content_types``, explicitly +write MIME types as a string, + ++-------------------------------+--------------------------------+ +| v1.x | v2.0 and later | ++===============================+================================+ +| ``CONTENT_TYPE_JSON`` | ``"application/json"`` | ++-------------------------------+--------------------------------+ +| ``CONTENT_TYPE_CSV`` | ``"text/csv"`` | ++-------------------------------+--------------------------------+ +| ``CONTENT_TYPE_OCTET_STREAM`` | ``"application/octet-stream"`` | ++-------------------------------+--------------------------------+ +| ``CONTENT_TYPE_NPY`` | ``"application/x-npy"`` | ++-------------------------------+--------------------------------+ + +Image URI Functions (e.g. ``get_image_uri``) +-------------------------------------------- + +The following functions have been deprecated in favor of :func:`sagemaker.image_uris.retrieve`: + +- ``sagemaker.amazon_estimator.get_image_uri()`` +- ``sagemaker.fw_utils.create_image_uri()`` +- ``sagemaker.fw_registry.registry()`` +- ``sagemaker.utils.get_ecr_image_uri_prefix()`` + +For more information about usage, see :func:`sagemaker.image_uris.retrieve`. + +SageMaker Python SDK CLI +------------------------ + +The SageMaker Python SDK CLI has been deprecated. +(This is different from the AWS CLI.) + +``enable_cloudwatch_metrics`` for Estimators and Models +------------------------------------------------------- + +The parameter ``enable_cloudwatch_metrics`` has been deprecated. +CloudWatch metrics are already emitted for all Training Jobs, etc. + +``sagemaker.fw_utils.parse_s3_url`` +----------------------------------- + +The ``sagemaker.fw_utils.parse_s3_url`` function has been deprecated. +Please use :func:`sagemaker.s3.parse_s3_url` instead. + +``sagemaker.session.ModelContainer`` +------------------------------------ + +The class ``sagemaker.session.ModelContainer`` has been deprecated, as it is not needed for creating inference pipelines. + +Changes in Default Behavior +=========================== + +Require ``framework_version`` and ``py_version`` for Frameworks +--------------------------------------------------------------- + +Framework estimator and model classes now require ``framework_version`` and ``py_version`` instead of supplying defaults, +unless an image URI is explicitly supplied. + +For example: + +.. code:: python + + from sagemaker.tensorflow import TensorFlow + + TensorFlow( + entry_point="script.py", + framework_version="2.2.0", # now required + py_version="py37", # now required + role="my-role", + instance_type="ml.m5.xlarge", + instance_count=1, + ) + + from sagemaker.mxnet import MXNetModel + + MXNetModel( + model_data="s3://bucket/model.tar.gz", + role="my-role", + entry_point="inference.py", + framework_version="1.6.0", # now required + py_version="py3", # now required + ) + +Log Display Behavior with ``attach()`` +-------------------------------------- + +Logs are no longer printed when using ``attach()`` with an estimator. +To view logs after attaching a training job to an estimator, use :func:`sagemaker.estimator.EstimatorBase.logs`. + +``HyperparameterTuner.fit()`` and ``Transformer.transform()`` +------------------------------------------------------------- + +:func:`sagemaker.tuner.HyperparameterTuner.fit` and :func:`sagemaker.transformer.Transformer.transform` now wait +until the completion of the Hyperparameter Tuning Job or Batch Transform Job, respectively. +To make the function non-blocking, use ``wait=False``. + +XGBoost Predictor +----------------- + +The default serializer of ``sagemaker.xgboost.model.XGBoostPredictor`` has been changed from ``NumpySerializer`` to ``LibSVMSerializer``. + + +Parameter and Class Name Changes +================================ + +Estimators +---------- + +Renamed Estimator Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following estimator parameters have been renamed: + ++------------------------------+------------------------+ +| v1.x | v2.0 and later | ++==============================+========================+ +| ``train_instance_count`` | ``instance_count`` | ++------------------------------+------------------------+ +| ``train_instance_type`` | ``instance_type`` | ++------------------------------+------------------------+ +| ``train_max_run`` | ``max_run`` | ++------------------------------+------------------------+ +| ``train_use_spot_instances`` | ``use_spot_instances`` | ++------------------------------+------------------------+ +| ``train_max_run_wait`` | ``max_run_wait`` | ++------------------------------+------------------------+ +| ``train_volume_size`` | ``volume_size`` | ++------------------------------+------------------------+ +| ``train_volume_kms_key`` | ``volume_kms_key`` | ++------------------------------+------------------------+ + +Serializer and Deserializer Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The follow serializer/deserializer classes have been renamed and/or moved: + ++--------------------------------------------------------+-------------------------------------------------------+ +| v1.x | v2.0 and later | ++========================================================+=======================================================+ +| ``sagemaker.predictor._CsvDeserializer`` | ``sagemaker.deserializers.CSVDeserializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor._CsvSerializer`` | ``sagemaker.serializers.CSVSerializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor.BytesDeserializer`` | ``sagemaker.deserializers.BytesDeserializers`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor.StringDeserializer`` | ``sagemaker.deserializers.StringDeserializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor.StreamDeserializer`` | ``sagemaker.deserializers.StreamDeserializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor._JsonSerializer`` | ``sagemaker.serializers.JSONSerializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor._NumpyDeserializer`` | ``sagemaker.deserializers.NumpyDeserializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor._NPYSerializer`` | ``sagemaker.serializers.NumpySerializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.amazon.common.numpy_to_record_serializer`` | ``sagemaker.amazon.serializers.RecordSerializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.amazon.common.record_deserializer`` | ``sagemaker.amazon.deserializers.RecordDeserializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ +| ``sagemaker.predictor._JsonDeserializer`` | ``sagemaker.deserializers.JSONDeserializer`` | ++--------------------------------------------------------+-------------------------------------------------------+ + +``sagemaker.serializers.LibSVMSerializer`` has been added in v2.0. + +``distributions`` +~~~~~~~~~~~~~~~~~ + +For TensorFlow and MXNet estimators, ``distributions`` has been renamed to ``distribution``. + +Specify Custom Training Images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``image_name`` parameter has been renamed to ``image_uri`` for specifying a custom Docker image URI to use with training. + + +Models +------ + +``sagemaker.model.Model`` Parameter Order +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The parameter order for :class:`sagemaker.model.Model` changed: instead of ``model_data`` being first, ``image_uri`` (formerly ``image``) is first. +As a result, ``model_data`` has been made into an optional parameter. + +If you are using the :class:`sagemaker.model.Model` class, your code should be changed as follows: + +.. code:: python + + # v1.x + Model("s3://bucket/path/model.tar.gz", "my-image:latest") + + # v2.0 and later + Model("my-image:latest", model_data="s3://bucket/path/model.tar.gz") + +Specify Custom Serving Image +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``image`` parameter has been renamed to ``image_uri`` for specifying a custom Docker image URI to use with inference. + +TensorFlow Serving Model +~~~~~~~~~~~~~~~~~~~~~~~~ + +``sagemaker.tensorflow.serving.Model`` has been renamed to :class:`sagemaker.tensorflow.model.TensorFlowModel`. +(For the previous implementation of that class, see `Deprecate Legacy TensorFlow <#deprecate-legacy-tensorflow>`_). + +Predictors +---------- + +Generic Predictor Class Name +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``sagemaker.predictor.RealTimePredictor`` has been renamed to :class:`sagemaker.predictor.Predictor`. + +Endpoint Argument Name +~~~~~~~~~~~~~~~~~~~~~~ + +For :class:`sagemaker.predictor.Predictor`, :class:`sagemaker.sparkml.model.SparkMLPredictor`, +and predictors for Amazon algorithm (e.g. Factorization Machines, Linear Learner, etc.), +the ``endpoint`` attribute has been renamed to ``endpoint_name``. + +TensorFlow Serving Predictor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``sagemaker.tensorflow.serving.Predictor`` has been renamed to :class:`sagemaker.tensorflow.model.TensorFlowPredictor`. +(For the previous implementation of that class, see `Deprecate Legacy TensorFlow <#deprecate-legacy-tensorflow>`_). + + +Inputs +------ + +``s3_input`` +~~~~~~~~~~~~ + +``sagemaker.session.s3_input`` has been renamed to :class:`sagemaker.inputs.TrainingInput`. + +``ShuffleConfig`` +~~~~~~~~~~~~~~~~~ + +``sagemaker.session.ShuffleConfig`` has been renamed to :class:`sagemaker.inputs.ShuffleConfig`. + +Airflow +------- + +For :func:`sagemaker.workflow.airflow.model_config` and :func:`sagemaker.workflow.airflow.model_config_from_estimator`, +``instance_type`` is no longer the first positional argument and is now an optional keyword argument. + +For :func:`sagemaker.workflow.airflow.model_config`, :func:`sagemaker.workflow.airflow.model_config_from_estimator`, and +:func:`sagemaker.workflow.airflow.transform_config_from_estimator`, the ``image`` argument has been renamed to ``image_uri``. + +Dependency Changes +================== + +SciPy +----- + +SciPy is no longer a required dependency of the SageMaker Python SDK. + +If you use :func:`sagemaker.amazon.common.write_spmatrix_to_sparse_tensor` and +don't already install SciPy in your environment, you can use our ``scipy`` installation target: + +.. code:: bash + + pip install sagemaker[scipy] + +TensorFlow +---------- + +The ``tensorflow`` installation target has been removed, as it is no longer needed for any SageMaker Python SDK functionality. + +If you want to install TensorFlow, see `the TensorFlow documentation `_. + +******************************* +Automatically Upgrade Your Code +******************************* + +To help make your transition as seamless as possible, v2 of the SageMaker Python SDK comes with a command-line tool to automate updating your code. +It automates as much as possible, but there are still syntactical and stylistic changes that cannot be performed by the script. + +.. warning:: + While the tool is intended to be easy to use, we recommend using it as part of a process that includes testing before and after you run the tool. + +Usage +===== + +Currently, the tool supports only converting one file at a time: + +.. code:: + + $ sagemaker-upgrade-v2 --in-file input.py --out-file output.py + $ sagemaker-upgrade-v2 --in-file input.ipynb --out-file output.ipynb + +You can apply it to a set of files using a loop: + +.. code:: bash + + $ for file in $(find input-dir); do sagemaker-upgrade-v2 --in-file $file --out-file output-dir/$file; done + +Limitations +=========== + +Jupyter Notebook Cells with Shell Commands +------------------------------------------ + +If your Jupyter notebook has a code cell with lines that start with either ``%%`` or ``!``, the tool ignores that cell. +The other cells in the notebook are still updated. + +Aliased Imports +--------------- + +The tool checks for a limited number of patterns when looking for constructors. +For example, if you are using a TensorFlow estimator, only the following invocation styles are handled: + +.. code:: python + + TensorFlow() + sagemaker.tensorflow.TensorFlow() + sagemaker.tensorflow.estimator.TensorFlow() + +If you have aliased an import, e.g. ``from sagemaker.tensorflow import TensorFlow as TF``, the tool does not take care of updating its parameters. + +TensorFlow Serving +------------------ + +If you are using the ``sagemaker.tensorflow.serving.Model`` class, the tool does not take care of adding a framework version or changing it to ``sagemaker.tensorflow.TensorFlowModel``. + +``sagemaker.model.Model`` +------------------------- + +If you are using the :class:`sagemaker.model.Model` class, the tool does not take care of switching the order between ``model_data`` and ``image_uri`` (formerly ``image``). + +``update_endpoint`` and ``delete_endpoint`` +------------------------------------------- + +The tool does not take care of removing the ``update_endpoint`` argument from a ``deploy`` call. +If you are using that argument, please modify your code to use :func:`sagemaker.predictor.Predictor.update_endpoint` instead. + +The tool also does not handle ``delete_endpoint`` calls on estimators or ``HyperparameterTuner``. +If you are using that method, please modify your code to use :func:`sagemaker.predictor.Predictor.delete_endpoint` instead. diff --git a/doc/workflows/airflow/using_workflow.rst b/doc/workflows/airflow/using_workflow.rst index e1e7133cb4..ed8e7ed226 100644 --- a/doc/workflows/airflow/using_workflow.rst +++ b/doc/workflows/airflow/using_workflow.rst @@ -49,8 +49,8 @@ dictionary that can be generated by the SageMaker Python SDK. For example: framework_version='1.11.0', training_steps=1000, evaluation_steps=100, - train_instance_count=2, - train_instance_type='ml.p2.xlarge') + instance_count=2, + instance_type='ml.p2.xlarge') # train_config specifies SageMaker training configuration train_config = training_config(estimator=estimator, @@ -117,8 +117,8 @@ flexible way, write your python callables for SageMaker operations by using the framework_version='1.11.0', training_steps=1000, evaluation_steps=100, - train_instance_count=2, - train_instance_type='ml.p2.xlarge') + instance_count=2, + instance_type='ml.p2.xlarge') estimator.fit(data) return estimator.latest_training_job.job_name diff --git a/doc/workflows/kubernetes/using_amazon_sagemaker_components.rst b/doc/workflows/kubernetes/using_amazon_sagemaker_components.rst index 06bd29d3db..958ba2b3ce 100644 --- a/doc/workflows/kubernetes/using_amazon_sagemaker_components.rst +++ b/doc/workflows/kubernetes/using_amazon_sagemaker_components.rst @@ -463,21 +463,24 @@ you can create your classification pipeline. To create your pipeline, you need to define and compile it. You then deploy it and use it to run workflows. You can define your pipeline in Python and use the KFP dashboard, KFP CLI, or Python SDK to compile, deploy, and run your -workflows. +workflows. The full code for the MNIST classification pipeline example is available in the +`Kubeflow Github +repository `__. +To use it, clone the example Python files to your gateway node. Prepare datasets ~~~~~~~~~~~~~~~~ -To run the pipelines, you need to have the datasets in an S3 bucket in -your account. This bucket must be located in the region where you want -to run Amazon SageMaker jobs. If you don’t have a bucket, create one +To run the pipelines, you need to upload the data extraction pre-processing script to an S3 bucket. This bucket and all resources for this example must be located in the ``us-east-1`` Amazon Region. If you don’t have a bucket, create one using the steps in `Creating a bucket `__. -From your gateway node, run the `sample dataset -creation `__ -script to copy the datasets into your bucket. Change the bucket name in -the script to the one you created. +From the ``mnist-kmeans-sagemaker`` folder of the Kubeflow repository you cloned on your gateway node, run the following command to upload the ``kmeans_preprocessing.py`` file to your S3 bucket. Change ```` to the name of the S3 bucket you created. + +:: + + aws s3 cp mnist-kmeans-sagemaker/kmeans_preprocessing.py s3:///mnist_kmeans_example/processing_code/kmeans_preprocessing.py + Create a Kubeflow Pipeline using Amazon SageMaker Components ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -496,54 +499,14 @@ parameters for each component of your pipeline. These parameters can also be updated when using other pipelines. We have provided default values for all parameters in the sample classification pipeline file. -The following are the only parameters you may need to modify to run the -sample pipelines. To modify these parameters, update their entries in -the sample classification pipeline file. +The following are the only parameters you need to pass to run the +sample pipelines. To pass these parameters, update their entries when creating a new run. - **Role-ARN:** This must be the ARN of an IAM role that has full Amazon SageMaker access in your AWS account. Use the ARN of  ``kfp-example-pod-role``. -- **The Dataset Buckets**: You must change the S3 bucket with the input - data for each of the components. Replace the following with the link - to your S3 bucket: - - - **Train channel:** ``"S3Uri": "s3:///data"`` - - - **HPO channels for test/HPO channel for - train:** ``"S3Uri": "s3:///data"`` - - - **Batch - transform:** ``"batch-input": "s3:///data"`` - -- **Output buckets:** Replace the output buckets with S3 buckets you - have write permission to. Replace the following with the link to your - S3 bucket: - - - **Training/HPO**: - ``output_location='s3:///output'`` - - - **Batch Transform**: - ``batch_transform_ouput='s3:///output'`` - -- **Region:**\ The default pipelines work in us-east-1. If your - cluster is in a different region, update the following: - - - The ``region='us-east-1'`` Parameter in the input list. - - - The algorithm images for Amazon SageMaker. If you use one of - the Amazon SageMaker built-in algorithm images, select the image - for your region. Construct the image name using the information - in `Common parameters for built-in - algorithms `__. - For Example: - - :: - - 382416733822.dkr.ecr.us-east-1.amazonaws.com/kmeans:1 - - - The S3 buckets with the dataset. Use the steps in Prepare datasets - to copy the data to a bucket in the same region as the cluster. +- **Bucket**: This is the name of the S3 bucket that you uploaded the ``kmeans_preprocessing.py`` file to. You can adjust any of the input parameters using the KFP UI and trigger your run again. @@ -632,18 +595,18 @@ currently does not support specifying input parameters while creating the run. You need to update your parameters in the Python pipeline file before compiling. Replace ```` and ```` with any names. Replace ```` with the ID of your submitted -pipeline. +pipeline. Replace ```` with the ARN of ``kfp-example-pod-role``. Replace ```` with the name of the S3 bucket you created. :: - kfp run submit --experiment-name --run-name --pipeline-id + kfp run submit --experiment-name --run-name --pipeline-id role_arn="" bucket_name="" You can also directly submit a run using the compiled pipeline package created as the output of the ``dsl-compile`` command. :: - kfp run submit --experiment-name --run-name --package-file + kfp run submit --experiment-name --run-name --package-file role_arn="" bucket_name="" Your output should look like the following: diff --git a/examples/cli/host/data/model.json b/examples/cli/host/data/model.json deleted file mode 100644 index 8d0d57a51e..0000000000 --- a/examples/cli/host/data/model.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "nodes": [ - { - "op": "null", - "name": "data", - "inputs": [] - }, - { - "op": "null", - "name": "sequential0_dense0_weight", - "attr": { - "__dtype__": "0", - "__lr_mult__": "1.0", - "__shape__": "(128, 0)", - "__wd_mult__": "1.0" - }, - "inputs": [] - }, - { - "op": "null", - "name": "sequential0_dense0_bias", - "attr": { - "__dtype__": "0", - "__init__": "zeros", - "__lr_mult__": "1.0", - "__shape__": "(128,)", - "__wd_mult__": "1.0" - }, - "inputs": [] - }, - { - "op": "FullyConnected", - "name": "sequential0_dense0_fwd", - "attr": {"num_hidden": "128"}, - "inputs": [[0, 0, 0], [1, 0, 0], [2, 0, 0]] - }, - { - "op": "Activation", - "name": "sequential0_dense0_relu_fwd", - "attr": {"act_type": "relu"}, - "inputs": [[3, 0, 0]] - }, - { - "op": "null", - "name": "sequential0_dense1_weight", - "attr": { - "__dtype__": "0", - "__lr_mult__": "1.0", - "__shape__": "(64, 0)", - "__wd_mult__": "1.0" - }, - "inputs": [] - }, - { - "op": "null", - "name": "sequential0_dense1_bias", - "attr": { - "__dtype__": "0", - "__init__": "zeros", - "__lr_mult__": "1.0", - "__shape__": "(64,)", - "__wd_mult__": "1.0" - }, - "inputs": [] - }, - { - "op": "FullyConnected", - "name": "sequential0_dense1_fwd", - "attr": {"num_hidden": "64"}, - "inputs": [[4, 0, 0], [5, 0, 0], [6, 0, 0]] - }, - { - "op": "Activation", - "name": "sequential0_dense1_relu_fwd", - "attr": {"act_type": "relu"}, - "inputs": [[7, 0, 0]] - }, - { - "op": "null", - "name": "sequential0_dense2_weight", - "attr": { - "__dtype__": "0", - "__lr_mult__": "1.0", - "__shape__": "(10, 0)", - "__wd_mult__": "1.0" - }, - "inputs": [] - }, - { - "op": "null", - "name": "sequential0_dense2_bias", - "attr": { - "__dtype__": "0", - "__init__": "zeros", - "__lr_mult__": "1.0", - "__shape__": "(10,)", - "__wd_mult__": "1.0" - }, - "inputs": [] - }, - { - "op": "FullyConnected", - "name": "sequential0_dense2_fwd", - "attr": {"num_hidden": "10"}, - "inputs": [[8, 0, 0], [9, 0, 0], [10, 0, 0]] - } - ], - "arg_nodes": [0, 1, 2, 5, 6, 9, 10], - "node_row_ptr": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12 - ], - "heads": [[11, 0, 0]], - "attrs": {"mxnet_version": ["int", 1100]} -} \ No newline at end of file diff --git a/examples/cli/host/data/model.params b/examples/cli/host/data/model.params deleted file mode 100644 index 3757d543c8..0000000000 Binary files a/examples/cli/host/data/model.params and /dev/null differ diff --git a/examples/cli/host/run_hosting_example.sh b/examples/cli/host/run_hosting_example.sh deleted file mode 100644 index b6d7e92d4d..0000000000 --- a/examples/cli/host/run_hosting_example.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -sagemaker mxnet host --role-name diff --git a/examples/cli/host/script.py b/examples/cli/host/script.py deleted file mode 100644 index a5a549a04b..0000000000 --- a/examples/cli/host/script.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import print_function - -import json -import mxnet as mx -from mxnet import gluon - - -def model_fn(model_dir): - """Load the gluon model. Called once when hosting service starts. - - Args: - model_dir: The directory where model files are stored. - - Returns: - a model (in this case a Gluon network) - """ - symbol = mx.sym.load("%s/model.json" % model_dir) - outputs = mx.symbol.softmax(data=symbol, name="softmax_label") - inputs = mx.sym.var("data") - param_dict = gluon.ParameterDict("model_") - net = gluon.SymbolBlock(outputs, inputs, param_dict) - net.load_params("%s/model.params" % model_dir, ctx=mx.cpu()) - return net - - -def transform_fn(net, data, input_content_type, output_content_type): - """Transform a request using the Gluon model. Called once per request. - - Args: - net: The Gluon model. - data: The request payload. - input_content_type: The request content type. - output_content_type: The (desired) response content type. - - Returns: - response payload and content type. - """ - # we can use content types to vary input/output handling, but - # here we just assume json for both - parsed = json.loads(data) - nda = mx.nd.array(parsed) - output = net(nda) - prediction = mx.nd.argmax(output, axis=1) - response_body = json.dumps(prediction.asnumpy().tolist()) - return response_body, output_content_type diff --git a/examples/cli/train/data/training/t10k-images-idx3-ubyte.gz b/examples/cli/train/data/training/t10k-images-idx3-ubyte.gz deleted file mode 100644 index 5ace8ea93f..0000000000 Binary files a/examples/cli/train/data/training/t10k-images-idx3-ubyte.gz and /dev/null differ diff --git a/examples/cli/train/data/training/t10k-labels-idx1-ubyte.gz b/examples/cli/train/data/training/t10k-labels-idx1-ubyte.gz deleted file mode 100644 index a7e141541c..0000000000 Binary files a/examples/cli/train/data/training/t10k-labels-idx1-ubyte.gz and /dev/null differ diff --git a/examples/cli/train/data/training/train-images-idx3-ubyte.gz b/examples/cli/train/data/training/train-images-idx3-ubyte.gz deleted file mode 100644 index b50e4b6bcc..0000000000 Binary files a/examples/cli/train/data/training/train-images-idx3-ubyte.gz and /dev/null differ diff --git a/examples/cli/train/data/training/train-labels-idx1-ubyte.gz b/examples/cli/train/data/training/train-labels-idx1-ubyte.gz deleted file mode 100644 index 707a576bb5..0000000000 Binary files a/examples/cli/train/data/training/train-labels-idx1-ubyte.gz and /dev/null differ diff --git a/examples/cli/train/download_training_data.py b/examples/cli/train/download_training_data.py deleted file mode 100644 index 2bc97d9588..0000000000 --- a/examples/cli/train/download_training_data.py +++ /dev/null @@ -1,10 +0,0 @@ -from mxnet import gluon - - -def download_training_data(): - gluon.data.vision.MNIST("./data/training", train=True) - gluon.data.vision.MNIST("./data/training", train=False) - - -if __name__ == "__main__": - download_training_data() diff --git a/examples/cli/train/hyperparameters.json b/examples/cli/train/hyperparameters.json deleted file mode 100644 index 01c3269250..0000000000 --- a/examples/cli/train/hyperparameters.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "batch_size": 100, - "epochs": 10, - "learning_rate": 0.1, - "momentum": 0.9, - "log_interval": 100 -} diff --git a/examples/cli/train/run_training_example.sh b/examples/cli/train/run_training_example.sh deleted file mode 100755 index 10176920d4..0000000000 --- a/examples/cli/train/run_training_example.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -python ./download_training_data.py -sagemaker mxnet train --role-name diff --git a/examples/cli/train/script.py b/examples/cli/train/script.py deleted file mode 100644 index d97a364f85..0000000000 --- a/examples/cli/train/script.py +++ /dev/null @@ -1,158 +0,0 @@ -import logging -import time - -import mxnet as mx -import numpy as np -from mxnet import gluon, autograd -from mxnet.gluon import nn - -logger = logging.getLogger(__name__) - - -def train(channel_input_dirs, hyperparameters, **kwargs): - # SageMaker passes num_cpus, num_gpus and other args we can use to tailor training to - # the current container environment, but here we just use simple cpu context. - """ - Args: - channel_input_dirs: - hyperparameters: - **kwargs: - """ - ctx = mx.cpu() - - # retrieve the hyperparameters we set in notebook (with some defaults) - batch_size = hyperparameters.get("batch_size", 100) - epochs = hyperparameters.get("epochs", 10) - learning_rate = hyperparameters.get("learning_rate", 0.1) - momentum = hyperparameters.get("momentum", 0.9) - log_interval = hyperparameters.get("log_interval", 100) - - training_data = channel_input_dirs["training"] - - # load training and validation data - # we use the gluon.data.vision.MNIST class because of its built in mnist pre-processing logic, - # but point it at the location where SageMaker placed the data files, so it doesn't download them again. - train_data = get_train_data(training_data, batch_size) - val_data = get_val_data(training_data, batch_size) - - # define the network - net = define_network() - - # Collect all parameters from net and its children, then initialize them. - net.initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx) - # Trainer is for updating parameters with gradient. - trainer = gluon.Trainer( - net.collect_params(), "sgd", {"learning_rate": learning_rate, "momentum": momentum} - ) - metric = mx.metric.Accuracy() - loss = gluon.loss.SoftmaxCrossEntropyLoss() - - for epoch in range(epochs): - # reset data iterator and metric at begining of epoch. - metric.reset() - btic = time.time() - for i, (data, label) in enumerate(train_data): - # Copy data to ctx if necessary - data = data.as_in_context(ctx) - label = label.as_in_context(ctx) - # Start recording computation graph with record() section. - # Recorded graphs can then be differentiated with backward. - with autograd.record(): - output = net(data) - L = loss(output, label) - L.backward() - # take a gradient step with batch_size equal to data.shape[0] - trainer.step(data.shape[0]) - # update metric at last. - metric.update([label], [output]) - - if i % log_interval == 0 and i > 0: - name, acc = metric.get() - logger.info( - "[Epoch %d Batch %d] Training: %s=%f, %f samples/s" - % (epoch, i, name, acc, batch_size / (time.time() - btic)) - ) - - btic = time.time() - - name, acc = metric.get() - logger.info("[Epoch %d] Training: %s=%f" % (epoch, name, acc)) - - name, val_acc = test(ctx, net, val_data) - logger.info("[Epoch %d] Validation: %s=%f" % (epoch, name, val_acc)) - - return net - - -def save(net, model_dir): - # save the model - """ - Args: - net: - model_dir: - """ - y = net(mx.sym.var("data")) - y.save("%s/model.json" % model_dir) - net.collect_params().save("%s/model.params" % model_dir) - - -def define_network(): - net = nn.Sequential() - with net.name_scope(): - net.add(nn.Dense(128, activation="relu")) - net.add(nn.Dense(64, activation="relu")) - net.add(nn.Dense(10)) - return net - - -def input_transformer(data, label): - """ - Args: - data: - label: - """ - data = data.reshape((-1,)).astype(np.float32) / 255 - return data, label - - -def get_train_data(data_dir, batch_size): - """ - Args: - data_dir: - batch_size: - """ - return gluon.data.DataLoader( - gluon.data.vision.MNIST(data_dir, train=True, transform=input_transformer), - batch_size=batch_size, - shuffle=True, - last_batch="discard", - ) - - -def get_val_data(data_dir, batch_size): - """ - Args: - data_dir: - batch_size: - """ - return gluon.data.DataLoader( - gluon.data.vision.MNIST(data_dir, train=False, transform=input_transformer), - batch_size=batch_size, - shuffle=False, - ) - - -def test(ctx, net, val_data): - """ - Args: - ctx: - net: - val_data: - """ - metric = mx.metric.Accuracy() - for data, label in val_data: - data = data.as_in_context(ctx) - label = label.as_in_context(ctx) - output = net(data) - metric.update([label], [output]) - return metric.get() diff --git a/setup.py b/setup.py index ddb37d767d..99c4668417 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,6 @@ import os from glob import glob -import sys from setuptools import setup, find_packages @@ -35,9 +34,9 @@ def read_version(): # Declare minimal set for installation required_packages = [ "boto3>=1.14.12", + "google-pasta", "numpy>=1.9.0", "protobuf>=3.1", - "scipy>=0.19.0", "protobuf3-to-dict>=0.1.5", "smdebug-rulesconfig==0.1.4", "importlib-metadata>=1.4.0", @@ -52,7 +51,7 @@ def read_version(): "docker-compose>=1.25.2", "PyYAML>=5.3, <6", # PyYAML version has to match docker-compose requirements ], - "tensorflow": ["tensorflow>=1.3.0"], + "scipy": ["scipy>=0.19.0"], } # Meta dependency groups extras["all"] = [item for group in extras.values() for item in group] @@ -77,10 +76,6 @@ def read_version(): ], ) -# enum is introduced in Python 3.4. Installing enum back port -if sys.version_info < (3, 4): - required_packages.append("enum34>=1.1.6") - setup( name="sagemaker", version=read_version(), @@ -88,6 +83,7 @@ def read_version(): packages=find_packages("src"), package_dir={"": "src"}, py_modules=[os.path.splitext(os.path.basename(path))[0] for path in glob("src/*.py")], + include_package_data=True, long_description=read("README.rst"), author="Amazon Web Services", url="https://github.com/aws/sagemaker-python-sdk/", @@ -99,12 +95,15 @@ def read_version(): "Natural Language :: English", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], install_requires=required_packages, extras_require=extras, - entry_points={"console_scripts": ["sagemaker=sagemaker.cli.main:main"]}, + entry_points={ + "console_scripts": [ + "sagemaker-upgrade-v2=sagemaker.cli.compatibility.v2.sagemaker_upgrade_v2:main", + ] + }, ) diff --git a/src/sagemaker/__init__.py b/src/sagemaker/__init__.py index 122e10e927..c8558880d6 100644 --- a/src/sagemaker/__init__.py +++ b/src/sagemaker/__init__.py @@ -13,8 +13,6 @@ """Placeholder docstring""" from __future__ import absolute_import -import logging -import sys import importlib_metadata from sagemaker import estimator, parameter, tuner # noqa: F401 @@ -31,6 +29,7 @@ FactorizationMachinesModel, ) from sagemaker.amazon.factorization_machines import FactorizationMachinesPredictor # noqa: F401 +from sagemaker.inputs import TrainingInput # noqa: F401 from sagemaker.amazon.ntm import NTM, NTMModel, NTMPredictor # noqa: F401 from sagemaker.amazon.randomcutforest import ( # noqa: F401 RandomCutForest, @@ -51,22 +50,14 @@ from sagemaker.model import Model, ModelPackage # noqa: F401 from sagemaker.pipeline import PipelineModel # noqa: F401 -from sagemaker.predictor import RealTimePredictor # noqa: F401 +from sagemaker.predictor import Predictor # noqa: F401 from sagemaker.processing import Processor, ScriptProcessor # noqa: F401 from sagemaker.session import Session # noqa: F401 from sagemaker.session import container_def, pipeline_container_def # noqa: F401 from sagemaker.session import production_variant # noqa: F401 -from sagemaker.session import s3_input # noqa: F401 from sagemaker.session import get_execution_role # noqa: F401 from sagemaker.automl.automl import AutoML, AutoMLJob, AutoMLInput # noqa: F401 from sagemaker.automl.candidate_estimator import CandidateEstimator, CandidateStep # noqa: F401 __version__ = importlib_metadata.version("sagemaker") - -if sys.version[0] == "2": - logging.getLogger("sagemaker").warning( - "SageMaker Python SDK v2 will no longer support Python 2. " - "Please see https://github.com/aws/sagemaker-python-sdk/issues/1459 " - "for more information" - ) diff --git a/src/sagemaker/algorithm.py b/src/sagemaker/algorithm.py index 58e7252913..c7114aee2b 100644 --- a/src/sagemaker/algorithm.py +++ b/src/sagemaker/algorithm.py @@ -16,9 +16,11 @@ import sagemaker import sagemaker.parameter from sagemaker import vpc_utils +from sagemaker.deserializers import BytesDeserializer from sagemaker.estimator import EstimatorBase +from sagemaker.serializers import IdentitySerializer from sagemaker.transformer import Transformer -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor class AlgorithmEstimator(EstimatorBase): @@ -35,11 +37,11 @@ def __init__( self, algorithm_arn, role, - train_instance_count, - train_instance_type, - train_volume_size=30, - train_volume_kms_key=None, - train_max_run=24 * 60 * 60, + instance_count, + instance_type, + volume_size=30, + volume_kms_key=None, + max_run=24 * 60 * 60, input_mode="File", output_path=None, output_kms_key=None, @@ -53,8 +55,8 @@ def __init__( model_channel_name="model", metric_definitions=None, encrypt_inter_container_traffic=False, - train_use_spot_instances=False, - train_max_wait=None, + use_spot_instances=False, + max_wait=None, **kwargs # pylint: disable=W0613 ): """Initialize an ``AlgorithmEstimator`` instance. @@ -67,15 +69,15 @@ def __init__( access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if it needs to access an AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to - use for training. train_instance_type (str): Type of EC2 + instance_count (int): Number of Amazon EC2 instances to + use for training. instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. - train_volume_size (int): Size in GB of the EBS volume to use for + volume_size (int): Size in GB of the EBS volume to use for storing input data during training (default: 30). Must be large enough to store training data if File Mode is used (which is the default). - train_volume_kms_key (str): Optional. KMS key ID for encrypting EBS volume attached + volume_kms_key (str): Optional. KMS key ID for encrypting EBS volume attached to the training instance (default: None). - train_max_run (int): Timeout in seconds for training (default: 24 * 60 * 60). + max_run (int): Timeout in seconds for training (default: 24 * 60 * 60). After this amount of time Amazon SageMaker terminates the job regardless of its current status. input_mode (str): The input mode that the algorithm supports @@ -87,7 +89,7 @@ def __init__( the container via a Unix-named pipe. This argument can be overriden on a per-channel basis using - ``sagemaker.session.s3_input.input_mode``. + ``sagemaker.inputs.TrainingInput.input_mode``. output_path (str): S3 location for saving the training result (model artifacts and output files). If not specified, results are stored to a default bucket. If @@ -127,14 +129,14 @@ def __init__( expression used to extract the metric from the logs. encrypt_inter_container_traffic (bool): Specifies whether traffic between training containers is encrypted for the training job (default: ``False``). - train_use_spot_instances (bool): Specifies whether to use SageMaker + use_spot_instances (bool): Specifies whether to use SageMaker Managed Spot instances for training. If enabled then the - `train_max_wait` arg should also be set. + `max_wait` arg should also be set. More information: https://docs.aws.amazon.com/sagemaker/latest/dg/model-managed-spot-training.html (default: ``False``). - train_max_wait (int): Timeout in seconds waiting for spot training + max_wait (int): Timeout in seconds waiting for spot training instances (default: None). After this amount of time Amazon SageMaker will stop waiting for Spot instances to become available (default: ``None``). @@ -144,11 +146,11 @@ def __init__( self.algorithm_arn = algorithm_arn super(AlgorithmEstimator, self).__init__( role, - train_instance_count, - train_instance_type, - train_volume_size, - train_volume_kms_key, - train_max_run, + instance_count, + instance_type, + volume_size, + volume_kms_key, + max_run, input_mode, output_path, output_kms_key, @@ -161,8 +163,8 @@ def __init__( model_channel_name=model_channel_name, metric_definitions=metric_definitions, encrypt_inter_container_traffic=encrypt_inter_container_traffic, - train_use_spot_instances=train_use_spot_instances, - train_max_wait=train_max_wait, + use_spot_instances=use_spot_instances, + max_wait=max_wait, ) self.algorithm_spec = self.sagemaker_session.sagemaker_client.describe_algorithm( @@ -182,30 +184,30 @@ def validate_train_spec(self): # Check that the input mode provided is compatible with the training input modes for the # algorithm. - train_input_modes = self._algorithm_training_input_modes(train_spec["TrainingChannels"]) - if self.input_mode not in train_input_modes: + input_modes = self._algorithm_training_input_modes(train_spec["TrainingChannels"]) + if self.input_mode not in input_modes: raise ValueError( "Invalid input mode: %s. %s only supports: %s" - % (self.input_mode, algorithm_name, train_input_modes) + % (self.input_mode, algorithm_name, input_modes) ) # Check that the training instance type is compatible with the algorithm. supported_instances = train_spec["SupportedTrainingInstanceTypes"] - if self.train_instance_type not in supported_instances: + if self.instance_type not in supported_instances: raise ValueError( - "Invalid train_instance_type: %s. %s supports the following instance types: %s" - % (self.train_instance_type, algorithm_name, supported_instances) + "Invalid instance_type: %s. %s supports the following instance types: %s" + % (self.instance_type, algorithm_name, supported_instances) ) # Verify if distributed training is supported by the algorithm if ( - self.train_instance_count > 1 + self.instance_count > 1 and "SupportsDistributedTraining" in train_spec and not train_spec["SupportsDistributedTraining"] ): raise ValueError( "Distributed training is not supported by %s. " - "Please set train_instance_count=1" % algorithm_name + "Please set instance_count=1" % algorithm_name ) def set_hyperparameters(self, **kwargs): @@ -227,13 +229,13 @@ def hyperparameters(self): """ return self.hyperparam_dict - def train_image(self): + def training_image_uri(self): """Returns the docker image to use for training. The fit() method, that does the model training, calls this method to find the image to use for model training. """ - raise RuntimeError("train_image is never meant to be called on Algorithm Estimators") + raise RuntimeError("training_image_uri is never meant to be called on Algorithm Estimators") def enable_network_isolation(self): """Return True if this Estimator will need network isolation to run. @@ -251,37 +253,29 @@ def create_model( self, role=None, predictor_cls=None, - serializer=None, - deserializer=None, - content_type=None, - accept=None, + serializer=IdentitySerializer(), + deserializer=BytesDeserializer(), vpc_config_override=vpc_utils.VPC_CONFIG_DEFAULT, **kwargs ): """Create a model to deploy. - The serializer, deserializer, content_type, and accept arguments are - only used to define a default RealTimePredictor. They are ignored if an - explicit predictor class is passed in. Other arguments are passed - through to the Model class. + The serializer and deserializer are only used to define a default + Predictor. They are ignored if an explicit predictor class is passed in. + Other arguments are passed through to the Model class. Args: role (str): The ``ExecutionRoleArn`` IAM Role ARN for the ``Model``, which is also used during transform jobs. If not specified, the role from the Estimator will be used. - predictor_cls (RealTimePredictor): The predictor class to use when + predictor_cls (Predictor): The predictor class to use when deploying the model. - serializer (callable): Should accept a single argument, the input - data, and return a sequence of bytes. May provide a content_type - attribute that defines the endpoint request content type - deserializer (callable): Should accept two arguments, the result - data and the response content type, and return a sequence of - bytes. May provide a content_type attribute that defines the - endpoint response Accept content type. - content_type (str): The invocation ContentType, overriding any - content_type from the serializer - accept (str): The invocation Accept, overriding any accept from the - deserializer. + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: :class:`~sagemaker.serializers.IdentitySerializer`). + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: :class:`~sagemaker.deserializers.BytesDeserializer`). vpc_config_override (dict[str, list[str]]): Optional override for VpcConfig set on the model. Default: use subnets and security groups from this Estimator. * 'Subnets' (list[str]): List of subnet ids. @@ -300,9 +294,7 @@ def create_model( if predictor_cls is None: def predict_wrapper(endpoint, session): - return RealTimePredictor( - endpoint, session, serializer, deserializer, content_type, accept - ) + return Predictor(endpoint, session, serializer, deserializer) predictor_cls = predict_wrapper @@ -408,6 +400,11 @@ def _is_marketplace(self): """Placeholder docstring""" return "ProductId" in self.algorithm_spec + def _ensure_base_job_name(self): + """Set ``self.base_job_name`` if it is not set already.""" + if self.base_job_name is None: + self.base_job_name = self.algorithm_arn.split("/")[-1] + def _prepare_for_training(self, job_name=None): # Validate hyperparameters # an explicit call to set_hyperparameters() will also validate the hyperparameters diff --git a/src/sagemaker/amazon/README.rst b/src/sagemaker/amazon/README.rst index 411b93509a..0bae05c540 100644 --- a/src/sagemaker/amazon/README.rst +++ b/src/sagemaker/amazon/README.rst @@ -34,7 +34,7 @@ Please find an example code snippet for illustration: .. code:: python from sagemaker import PCA - pca_estimator = PCA(role='SageMakerRole', train_instance_count=1, train_instance_type='ml.m4.xlarge', num_components=3) + pca_estimator = PCA(role='SageMakerRole', instance_count=1, instance_type='ml.m4.xlarge', num_components=3) import numpy as np records = pca_estimator.record_set(np.arange(10).reshape(2,5)) diff --git a/src/sagemaker/amazon/amazon_estimator.py b/src/sagemaker/amazon/amazon_estimator.py index 0466ae23f0..307ac97f6d 100644 --- a/src/sagemaker/amazon/amazon_estimator.py +++ b/src/sagemaker/amazon/amazon_estimator.py @@ -19,22 +19,13 @@ from six.moves.urllib.parse import urlparse +from sagemaker import image_uris from sagemaker.amazon import validation from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.common import write_numpy_to_dense_tensor from sagemaker.estimator import EstimatorBase, _TrainingJob -from sagemaker.inputs import FileSystemInput -from sagemaker.model import NEO_IMAGE_ACCOUNT -from sagemaker.session import s3_input -from sagemaker.utils import sagemaker_timestamp, get_ecr_image_uri_prefix -from sagemaker.xgboost.defaults import ( - XGBOOST_1P_VERSIONS, - XGBOOST_LATEST_VERSION, - XGBOOST_NAME, - XGBOOST_SUPPORTED_VERSIONS, - XGBOOST_VERSION_EQUIVALENTS, -) -from sagemaker.xgboost.estimator import get_xgboost_image_uri +from sagemaker.inputs import FileSystemInput, TrainingInput +from sagemaker.utils import sagemaker_timestamp logger = logging.getLogger(__name__) @@ -52,8 +43,8 @@ class AmazonAlgorithmEstimatorBase(EstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, data_location=None, enable_network_isolation=False, **kwargs @@ -66,9 +57,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if it needs to access an AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. data_location (str or None): The s3 prefix to upload RecordSet objects to, expressed as an S3 url. For example @@ -89,8 +80,8 @@ def __init__( """ super(AmazonAlgorithmEstimatorBase, self).__init__( role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, enable_network_isolation=enable_network_isolation, **kwargs ) @@ -100,10 +91,10 @@ def __init__( ) self._data_location = data_location - def train_image(self): + def training_image_uri(self): """Placeholder docstring""" - return get_image_uri( - self.sagemaker_session.boto_region_name, type(self).repo_name, type(self).repo_version + return image_uris.retrieve( + self.repo_name, self.sagemaker_session.boto_region_name, version=self.repo_version, ) def hyperparameters(self): @@ -156,7 +147,7 @@ class constructor init_params[attribute] = init_params["hyperparameters"][value.name] del init_params["hyperparameters"] - del init_params["image"] + del init_params["image_uri"] return init_params def prepare_workflow_for_training(self, records=None, mini_batch_size=None, job_name=None): @@ -266,7 +257,7 @@ def record_set(self, train, labels=None, channel="train", encrypt=False): the list of objects created and also stored in S3. The number of S3 objects created is controlled by the - ``train_instance_count`` property on this Estimator. One S3 object is + ``instance_count`` property on this Estimator. One S3 object is created per training instance. Args: @@ -291,7 +282,7 @@ def record_set(self, train, labels=None, channel="train", encrypt=False): key_prefix = key_prefix.lstrip("/") logger.debug("Uploading to bucket %s and key_prefix %s", bucket, key_prefix) manifest_s3_file = upload_numpy_to_s3_shards( - self.train_instance_count, s3, bucket, key_prefix, train, labels, encrypt + self.instance_count, s3, bucket, key_prefix, train, labels, encrypt ) logger.debug("Created manifest file %s", manifest_s3_file) return RecordSet( @@ -341,8 +332,10 @@ def data_channel(self): return {self.channel: self.records_s3_input()} def records_s3_input(self): - """Return a s3_input to represent the training data""" - return s3_input(self.s3_data, distribution="ShardedByS3Key", s3_data_type=self.s3_data_type) + """Return a TrainingInput to represent the training data""" + return TrainingInput( + self.s3_data, distribution="ShardedByS3Key", s3_data_type=self.s3_data_type + ) class FileSystemRecordSet(object): @@ -459,238 +452,3 @@ def upload_numpy_to_s3_shards( s3.Object(bucket, key_prefix + file).delete() finally: raise ex - - -def registry(region_name, algorithm=None): - """Return docker registry for the given AWS region - - Note: Not all the algorithms listed below have an Amazon Estimator - implemented. For full list of pre-implemented Estimators, look at: - - https://github.com/aws/sagemaker-python-sdk/tree/master/src/sagemaker/amazon - - Args: - region_name (str): The region name for the account. - algorithm (str): The algorithm for the account. - - Raises: - ValueError: If invalid algorithm passed in or if mapping does not exist for given algorithm - and region. - """ - region_to_accounts = {} - if algorithm in [ - None, - "pca", - "kmeans", - "linear-learner", - "factorization-machines", - "ntm", - "randomcutforest", - "knn", - "object2vec", - "ipinsights", - ]: - region_to_accounts = { - "us-east-1": "382416733822", - "us-east-2": "404615174143", - "us-west-2": "174872318107", - "eu-west-1": "438346466558", - "eu-central-1": "664544806723", - "ap-northeast-1": "351501993468", - "ap-northeast-2": "835164637446", - "ap-southeast-2": "712309505854", - "us-gov-west-1": "226302683700", - "ap-southeast-1": "475088953585", - "ap-south-1": "991648021394", - "ca-central-1": "469771592824", - "eu-west-2": "644912444149", - "us-west-1": "632365934929", - "us-iso-east-1": "490574956308", - "ap-east-1": "286214385809", - "eu-north-1": "669576153137", - "eu-west-3": "749696950732", - "sa-east-1": "855470959533", - "me-south-1": "249704162688", - "cn-north-1": "390948362332", - "cn-northwest-1": "387376663083", - } - elif algorithm in ["lda"]: - region_to_accounts = { - "us-east-1": "766337827248", - "us-east-2": "999911452149", - "us-west-2": "266724342769", - "eu-west-1": "999678624901", - "eu-central-1": "353608530281", - "ap-northeast-1": "258307448986", - "ap-northeast-2": "293181348795", - "ap-southeast-2": "297031611018", - "us-gov-west-1": "226302683700", - "ap-southeast-1": "475088953585", - "ap-south-1": "991648021394", - "ca-central-1": "469771592824", - "eu-west-2": "644912444149", - "us-west-1": "632365934929", - "us-iso-east-1": "490574956308", - } - elif algorithm in ["forecasting-deepar"]: - region_to_accounts = { - "us-east-1": "522234722520", - "us-east-2": "566113047672", - "us-west-2": "156387875391", - "eu-west-1": "224300973850", - "eu-central-1": "495149712605", - "ap-northeast-1": "633353088612", - "ap-northeast-2": "204372634319", - "ap-southeast-2": "514117268639", - "us-gov-west-1": "226302683700", - "ap-southeast-1": "475088953585", - "ap-south-1": "991648021394", - "ca-central-1": "469771592824", - "eu-west-2": "644912444149", - "us-west-1": "632365934929", - "us-iso-east-1": "490574956308", - "ap-east-1": "286214385809", - "eu-north-1": "669576153137", - "eu-west-3": "749696950732", - "sa-east-1": "855470959533", - "me-south-1": "249704162688", - "cn-north-1": "390948362332", - "cn-northwest-1": "387376663083", - } - elif algorithm in [ - "xgboost", - "seq2seq", - "image-classification", - "blazingtext", - "object-detection", - "semantic-segmentation", - ]: - region_to_accounts = { - "us-east-1": "811284229777", - "us-east-2": "825641698319", - "us-west-2": "433757028032", - "eu-west-1": "685385470294", - "eu-central-1": "813361260812", - "ap-northeast-1": "501404015308", - "ap-northeast-2": "306986355934", - "ap-southeast-2": "544295431143", - "us-gov-west-1": "226302683700", - "ap-southeast-1": "475088953585", - "ap-south-1": "991648021394", - "ca-central-1": "469771592824", - "eu-west-2": "644912444149", - "us-west-1": "632365934929", - "us-iso-east-1": "490574956308", - "ap-east-1": "286214385809", - "eu-north-1": "669576153137", - "eu-west-3": "749696950732", - "sa-east-1": "855470959533", - "me-south-1": "249704162688", - "cn-north-1": "390948362332", - "cn-northwest-1": "387376663083", - } - elif algorithm in ["image-classification-neo", "xgboost-neo"]: - region_to_accounts = NEO_IMAGE_ACCOUNT - else: - raise ValueError( - "Algorithm class:{} does not have mapping to account_id with images".format(algorithm) - ) - - if region_name in region_to_accounts: - account_id = region_to_accounts[region_name] - return get_ecr_image_uri_prefix(account_id, region_name) - - raise ValueError( - "Algorithm ({algorithm}) is unsupported for region ({region_name}).".format( - algorithm=algorithm, region_name=region_name - ) - ) - - -def get_image_uri(region_name, repo_name, repo_version=1): - """Return algorithm image URI for the given AWS region, repository name, and - repository version - - Args: - region_name: - repo_name: - repo_version: - """ - logger.warning( - "'get_image_uri' method will be deprecated in favor of 'ImageURIProvider' class " - "in SageMaker Python SDK v2." - ) - - repo_version = str(repo_version) - - if repo_name == XGBOOST_NAME: - - if repo_version in XGBOOST_1P_VERSIONS: - _warn_newer_xgboost_image() - return "{}/{}:{}".format(registry(region_name, repo_name), repo_name, repo_version) - - if "-" not in repo_version: - xgboost_version_matches = [ - version - for version in XGBOOST_SUPPORTED_VERSIONS - if repo_version == version.split("-")[0] - ] - if xgboost_version_matches: - # Assumes that XGBOOST_SUPPORTED_VERSION is sorted from oldest version to latest. - # When SageMaker version is not specified, we use the oldest one that matches - # XGBoost version for backward compatibility. - repo_version = xgboost_version_matches[0] - - supported_framework_versions = [ - version - for version in XGBOOST_SUPPORTED_VERSIONS - if repo_version in _generate_version_equivalents(version) - ] - - if not supported_framework_versions: - raise ValueError( - "SageMaker XGBoost version {} is not supported. Supported versions: {}".format( - repo_version, ", ".join(XGBOOST_SUPPORTED_VERSIONS) - ) - ) - - if not _is_latest_xgboost_version(repo_version): - _warn_newer_xgboost_image() - - return get_xgboost_image_uri(region_name, supported_framework_versions[-1]) - - repo = "{}:{}".format(repo_name, repo_version) - return "{}/{}".format(registry(region_name, repo_name), repo) - - -def _warn_newer_xgboost_image(): - """Print a warning when there is a newer XGBoost image""" - logger.warning( - "There is a more up to date SageMaker XGBoost image. " - "To use the newer image, please set 'repo_version'=" - "'%s'. For example:\n" - "\tget_image_uri(region, '%s', '%s').", - XGBOOST_LATEST_VERSION, - XGBOOST_NAME, - XGBOOST_LATEST_VERSION, - ) - - -def _is_latest_xgboost_version(repo_version): - """Compare xgboost image version with latest version - - Args: - repo_version: - """ - if repo_version in XGBOOST_1P_VERSIONS: - return False - return repo_version in _generate_version_equivalents(XGBOOST_LATEST_VERSION) - - -def _generate_version_equivalents(version): - """Returns a list of version equivalents for XGBoost - - Args: - version: - """ - return [version + suffix for suffix in XGBOOST_VERSION_EQUIVALENTS] + [version] diff --git a/src/sagemaker/amazon/common.py b/src/sagemaker/amazon/common.py index 2be29e7a33..d276356207 100644 --- a/src/sagemaker/amazon/common.py +++ b/src/sagemaker/amazon/common.py @@ -14,59 +14,65 @@ from __future__ import absolute_import import io +import logging import struct import sys import numpy as np -from scipy.sparse import issparse from sagemaker.amazon.record_pb2 import Record +from sagemaker.deserializers import BaseDeserializer +from sagemaker.serializers import BaseSerializer +from sagemaker.utils import DeferredError -class numpy_to_record_serializer(object): - """Placeholder docstring""" +class RecordSerializer(BaseSerializer): + """Serialize a NumPy array for an inference request.""" - def __init__(self, content_type="application/x-recordio-protobuf"): - """ - Args: - content_type: - """ - self.content_type = content_type + CONTENT_TYPE = "application/x-recordio-protobuf" + + def serialize(self, data): + """Serialize a NumPy array into a buffer containing RecordIO records. - def __call__(self, array): - """ Args: - array: + data (numpy.ndarray): The data to serialize. + + Returns: + io.BytesIO: A buffer containing the data serialized as records. """ - if len(array.shape) == 1: - array = array.reshape(1, array.shape[0]) - assert len(array.shape) == 2, "Expecting a 1 or 2 dimensional array" - buf = io.BytesIO() - write_numpy_to_dense_tensor(buf, array) - buf.seek(0) - return buf + if len(data.shape) == 1: + data = data.reshape(1, data.shape[0]) + if len(data.shape) != 2: + raise ValueError( + "Expected a 1D or 2D array, but got a %dD array instead." % len(data.shape) + ) -class record_deserializer(object): - """Placeholder docstring""" + buffer = io.BytesIO() + write_numpy_to_dense_tensor(buffer, data) + buffer.seek(0) - def __init__(self, accept="application/x-recordio-protobuf"): - """ - Args: - accept: - """ - self.accept = accept + return buffer + + +class RecordDeserializer(BaseDeserializer): + """Deserialize RecordIO Protobuf data from an inference endpoint.""" + + ACCEPT = ("application/x-recordio-protobuf",) + + def deserialize(self, data, content_type): + """Deserialize RecordIO Protobuf data from an inference endpoint. - def __call__(self, stream, content_type): - """ Args: - stream: - content_type: + data (object): The protobuf message to deserialize. + content_type (str): The MIME type of the data. + Returns: + list: A list of records. """ try: - return read_records(stream) + return read_records(data) finally: - stream.close() + data.close() def _write_feature_tensor(resolved_type, record, vector): @@ -171,8 +177,16 @@ def write_spmatrix_to_sparse_tensor(file, array, labels=None): array: labels: """ - - if not issparse(array): + try: + import scipy + except ImportError as e: + logging.warning( + "scipy failed to import. Sparse matrix functions will be impaired or broken." + ) + # Any subsequent attempt to use scipy will raise the ImportError + scipy = DeferredError(e) + + if not scipy.sparse.issparse(array): raise TypeError("Array must be sparse") # Validate shape of array and labels, resolve array and label types diff --git a/src/sagemaker/amazon/factorization_machines.py b/src/sagemaker/amazon/factorization_machines.py index e1adfe4d8f..b739530870 100644 --- a/src/sagemaker/amazon/factorization_machines.py +++ b/src/sagemaker/amazon/factorization_machines.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import gt, isin, ge -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -77,8 +78,8 @@ class FactorizationMachines(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, num_factors, predictor_type, epochs=None, @@ -150,9 +151,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. num_factors (int): Dimensionality of factorization. predictor_type (str): Type of predictor 'binary_classifier' or @@ -212,9 +213,7 @@ def __init__( :class:`~sagemaker.estimator.amazon_estimator.AmazonAlgorithmEstimatorBase` and :class:`~sagemaker.estimator.EstimatorBase`. """ - super(FactorizationMachines, self).__init__( - role, train_instance_count, train_instance_type, **kwargs - ) + super(FactorizationMachines, self).__init__(role, instance_count, instance_type, **kwargs) self.num_factors = num_factors self.predictor_type = predictor_type @@ -261,13 +260,13 @@ def create_model(self, vpc_config_override=VPC_CONFIG_DEFAULT, **kwargs): ) -class FactorizationMachinesPredictor(RealTimePredictor): +class FactorizationMachinesPredictor(Predictor): """Performs binary-classification or regression prediction from input vectors. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -278,17 +277,21 @@ class FactorizationMachinesPredictor(RealTimePredictor): https://docs.aws.amazon.com/sagemaker/latest/dg/fm-in-formats.html """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(FactorizationMachinesPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -307,11 +310,14 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(FactorizationMachines.repo_name, FactorizationMachines.repo_version) - image = "{}/{}".format(registry(sagemaker_session.boto_session.region_name), repo) + image_uri = image_uris.retrieve( + FactorizationMachines.repo_name, + sagemaker_session.boto_region_name, + version=FactorizationMachines.repo_version, + ) super(FactorizationMachinesModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=FactorizationMachinesPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/ipinsights.py b/src/sagemaker/amazon/ipinsights.py index 935be0dae1..8ad70462a6 100644 --- a/src/sagemaker/amazon/ipinsights.py +++ b/src/sagemaker/amazon/ipinsights.py @@ -13,11 +13,14 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import ge, le -from sagemaker.predictor import RealTimePredictor, csv_serializer, json_deserializer +from sagemaker.deserializers import JSONDeserializer +from sagemaker.predictor import Predictor from sagemaker.model import Model +from sagemaker.serializers import CSVSerializer from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -53,8 +56,8 @@ class IPInsights(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, num_entity_vectors, vector_dim, batch_metrics_publish_interval=None, @@ -94,9 +97,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.m5.xlarge'. num_entity_vectors (int): Required. The number of embeddings to train for entities accessing online resources. We recommend 2x @@ -126,7 +129,7 @@ def __init__( :class:`~sagemaker.estimator.amazon_estimator.AmazonAlgorithmEstimatorBase` and :class:`~sagemaker.estimator.EstimatorBase`. """ - super(IPInsights, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + super(IPInsights, self).__init__(role, instance_count, instance_type, **kwargs) self.num_entity_vectors = num_entity_vectors self.vector_dim = vector_dim self.batch_metrics_publish_interval = batch_metrics_publish_interval @@ -173,25 +176,32 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): ) -class IPInsightsPredictor(RealTimePredictor): +class IPInsightsPredictor(Predictor): """Returns dot product of entity and IP address embeddings as a score for compatibility. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain two columns. The first column should contain the entity ID. The second column should contain the IPv4 address in dot notation. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(IPInsightsPredictor, self).__init__( - endpoint, sagemaker_session, serializer=csv_serializer, deserializer=json_deserializer + endpoint_name, + sagemaker_session, + serializer=CSVSerializer(), + deserializer=JSONDeserializer(), ) @@ -210,14 +220,14 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(IPInsights.repo_name, IPInsights.repo_version) - image = "{}/{}".format( - registry(sagemaker_session.boto_session.region_name, IPInsights.repo_name), repo + image_uri = image_uris.retrieve( + IPInsights.repo_name, + sagemaker_session.boto_region_name, + version=IPInsights.repo_version, ) - super(IPInsightsModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=IPInsightsPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/kmeans.py b/src/sagemaker/amazon/kmeans.py index d6b4ddda20..bc771292b1 100644 --- a/src/sagemaker/amazon/kmeans.py +++ b/src/sagemaker/amazon/kmeans.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import gt, isin, ge, le -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -51,8 +52,8 @@ class KMeans(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, k, init_method=None, max_iterations=None, @@ -103,9 +104,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. k (int): The number of clusters to produce. init_method (str): How to initialize cluster locations. One of @@ -142,7 +143,7 @@ def __init__( :class:`~sagemaker.estimator.amazon_estimator.AmazonAlgorithmEstimatorBase` and :class:`~sagemaker.estimator.EstimatorBase`. """ - super(KMeans, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + super(KMeans, self).__init__(role, instance_count, instance_type, **kwargs) self.k = k self.init_method = init_method self.max_iterations = max_iterations @@ -194,12 +195,12 @@ def hyperparameters(self): return hp_dict -class KMeansPredictor(RealTimePredictor): +class KMeansPredictor(Predictor): """Assigns input vectors to their closest cluster in a KMeans model. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -209,17 +210,21 @@ class KMeansPredictor(RealTimePredictor): ``closest_cluster`` key of the ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(KMeansPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -238,11 +243,12 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(KMeans.repo_name, KMeans.repo_version) - image = "{}/{}".format(registry(sagemaker_session.boto_session.region_name), repo) + image_uri = image_uris.retrieve( + KMeans.repo_name, sagemaker_session.boto_region_name, version=KMeans.repo_version, + ) super(KMeansModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=KMeansPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/knn.py b/src/sagemaker/amazon/knn.py index dc1319e191..596091f8c2 100644 --- a/src/sagemaker/amazon/knn.py +++ b/src/sagemaker/amazon/knn.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import ge, isin -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -63,8 +64,8 @@ class KNN(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, k, sample_size, predictor_type, @@ -105,8 +106,8 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count: - train_instance_type (str): Type of EC2 instance to use for training, + instance_count: + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. k (int): Required. Number of nearest neighbors. sample_size (int): Required. Number of data points to be sampled @@ -136,7 +137,7 @@ def __init__( :class:`~sagemaker.estimator.EstimatorBase`. """ - super(KNN, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + super(KNN, self).__init__(role, instance_count, instance_type, **kwargs) self.k = k self.sample_size = sample_size self.predictor_type = predictor_type @@ -182,12 +183,12 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): ) -class KNNPredictor(RealTimePredictor): +class KNNPredictor(Predictor): """Performs classification or regression prediction from input vectors. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -197,17 +198,21 @@ class KNNPredictor(RealTimePredictor): key of the ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(KNNPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -226,13 +231,12 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(KNN.repo_name, KNN.repo_version) - image = "{}/{}".format( - registry(sagemaker_session.boto_session.region_name, KNN.repo_name), repo + image_uri = image_uris.retrieve( + KNN.repo_name, sagemaker_session.boto_region_name, version=KNN.repo_version, ) super(KNNModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=KNNPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/lda.py b/src/sagemaker/amazon/lda.py index 1074573a56..7c615bff46 100644 --- a/src/sagemaker/amazon/lda.py +++ b/src/sagemaker/amazon/lda.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import gt -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -38,7 +39,7 @@ class LDA(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_type, + instance_type, num_topics, alpha0=None, max_restarts=None, @@ -92,7 +93,7 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. num_topics (int): The number of topics for LDA to find within the data. @@ -114,14 +115,14 @@ def __init__( :class:`~sagemaker.estimator.EstimatorBase`. """ # this algorithm only supports single instance training - if kwargs.pop("train_instance_count", 1) != 1: + if kwargs.pop("instance_count", 1) != 1: print( "LDA only supports single instance training. Defaulting to 1 {}.".format( - train_instance_type + instance_type ) ) - super(LDA, self).__init__(role, 1, train_instance_type, **kwargs) + super(LDA, self).__init__(role, 1, instance_type, **kwargs) self.num_topics = num_topics self.alpha0 = alpha0 self.max_restarts = max_restarts @@ -166,12 +167,12 @@ def _prepare_for_training( # pylint: disable=signature-differs ) -class LDAPredictor(RealTimePredictor): +class LDAPredictor(Predictor): """Transforms input vectors to lower-dimesional representations. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -181,17 +182,21 @@ class LDAPredictor(RealTimePredictor): ``projection`` key of the ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(LDAPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -210,13 +215,12 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(LDA.repo_name, LDA.repo_version) - image = "{}/{}".format( - registry(sagemaker_session.boto_session.region_name, LDA.repo_name), repo + image_uri = image_uris.retrieve( + LDA.repo_name, sagemaker_session.boto_region_name, version=LDA.repo_version, ) super(LDAModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=LDAPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/linear_learner.py b/src/sagemaker/amazon/linear_learner.py index 0ed9a60770..e6aa1eb1ea 100644 --- a/src/sagemaker/amazon/linear_learner.py +++ b/src/sagemaker/amazon/linear_learner.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import isin, gt, lt, ge, le -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -120,8 +121,8 @@ class LinearLearner(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, predictor_type, binary_classifier_model_selection_criteria=None, target_recall=None, @@ -214,9 +215,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. predictor_type (str): The type of predictor to learn. Either "binary_classifier" or "multiclass_classifier" or "regressor". @@ -325,9 +326,7 @@ def __init__( :class:`~sagemaker.estimator.amazon_estimator.AmazonAlgorithmEstimatorBase` and :class:`~sagemaker.estimator.EstimatorBase`. """ - super(LinearLearner, self).__init__( - role, train_instance_count, train_instance_type, **kwargs - ) + super(LinearLearner, self).__init__(role, instance_count, instance_type, **kwargs) self.predictor_type = predictor_type self.binary_classifier_model_selection_criteria = binary_classifier_model_selection_criteria self.target_recall = target_recall @@ -418,7 +417,7 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): # mini_batch_size can't be greater than number of records or training job fails default_mini_batch_size = min( - self.DEFAULT_MINI_BATCH_SIZE, max(1, int(num_records / self.train_instance_count)) + self.DEFAULT_MINI_BATCH_SIZE, max(1, int(num_records / self.instance_count)) ) mini_batch_size = mini_batch_size or default_mini_batch_size super(LinearLearner, self)._prepare_for_training( @@ -426,13 +425,13 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): ) -class LinearLearnerPredictor(RealTimePredictor): +class LinearLearnerPredictor(Predictor): """Performs binary-classification or regression prediction from input vectors. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -442,17 +441,21 @@ class LinearLearnerPredictor(RealTimePredictor): key of the ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(LinearLearnerPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -471,11 +474,14 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(LinearLearner.repo_name, LinearLearner.repo_version) - image = "{}/{}".format(registry(sagemaker_session.boto_session.region_name), repo) + image_uri = image_uris.retrieve( + LinearLearner.repo_name, + sagemaker_session.boto_region_name, + version=LinearLearner.repo_version, + ) super(LinearLearnerModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=LinearLearnerPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/ntm.py b/src/sagemaker/amazon/ntm.py index 01aa9c2796..bd17fcf26b 100644 --- a/src/sagemaker/amazon/ntm.py +++ b/src/sagemaker/amazon/ntm.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import ge, le, isin -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -59,8 +60,8 @@ class NTM(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, num_topics, encoder_layers=None, epochs=None, @@ -113,8 +114,8 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count: - train_instance_type (str): Type of EC2 instance to use for training, + instance_count: + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. num_topics (int): Required. The number of topics for NTM to find within the data. @@ -147,7 +148,7 @@ def __init__( :class:`~sagemaker.estimator.EstimatorBase`. """ - super(NTM, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + super(NTM, self).__init__(role, instance_count, instance_type, **kwargs) self.num_topics = num_topics self.encoder_layers = encoder_layers self.epochs = epochs @@ -196,12 +197,12 @@ def _prepare_for_training( # pylint: disable=signature-differs ) -class NTMPredictor(RealTimePredictor): +class NTMPredictor(Predictor): """Transforms input vectors to lower-dimesional representations. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -211,17 +212,21 @@ class NTMPredictor(RealTimePredictor): ``projection`` key of the ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(NTMPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -240,13 +245,12 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(NTM.repo_name, NTM.repo_version) - image = "{}/{}".format( - registry(sagemaker_session.boto_session.region_name, NTM.repo_name), repo + image_uri = image_uris.retrieve( + NTM.repo_name, sagemaker_session.boto_region_name, version=NTM.repo_version, ) super(NTMModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=NTMPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/object2vec.py b/src/sagemaker/amazon/object2vec.py index c9b1585480..6c68a4ef15 100644 --- a/src/sagemaker/amazon/object2vec.py +++ b/src/sagemaker/amazon/object2vec.py @@ -13,10 +13,11 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import ge, le, isin -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -133,8 +134,8 @@ class Object2Vec(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, epochs, enc0_max_seq_len, enc0_vocab_size, @@ -184,7 +185,7 @@ def __init__( may be deployed to an Amazon SageMaker Endpoint by invoking :meth:`~sagemaker.amazon.estimator.EstimatorBase.deploy`. As well as deploying an Endpoint, deploy returns a - :class:`~sagemaker.amazon.RealTimePredictor` object that can be used for + :class:`~sagemaker.amazon.Predictor` object that can be used for inference calls using the trained model hosted in the SageMaker Endpoint. @@ -201,9 +202,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. epochs (int): Total number of epochs for SGD training enc0_max_seq_len (int): Maximum sequence length @@ -263,7 +264,7 @@ def __init__( :class:`~sagemaker.estimator.EstimatorBase`. """ - super(Object2Vec, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + super(Object2Vec, self).__init__(role, instance_count, instance_type, **kwargs) self.enc_dim = enc_dim self.mini_batch_size = mini_batch_size @@ -350,15 +351,16 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(Object2Vec.repo_name, Object2Vec.repo_version) - image = "{}/{}".format( - registry(sagemaker_session.boto_session.region_name, Object2Vec.repo_name), repo + image_uri = image_uris.retrieve( + Object2Vec.repo_name, + sagemaker_session.boto_region_name, + version=Object2Vec.repo_version, ) super(Object2VecModel, self).__init__( + image_uri, model_data, - image, role, - predictor_cls=RealTimePredictor, + predictor_cls=Predictor, sagemaker_session=sagemaker_session, **kwargs ) diff --git a/src/sagemaker/amazon/pca.py b/src/sagemaker/amazon/pca.py index d0f0c7807f..9e53ec6939 100644 --- a/src/sagemaker/amazon/pca.py +++ b/src/sagemaker/amazon/pca.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import gt, isin -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -50,8 +51,8 @@ class PCA(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, num_components, algorithm_mode=None, subtract_mean=None, @@ -97,9 +98,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. num_components (int): The number of principal components. Must be greater than zero. @@ -120,7 +121,7 @@ def __init__( :class:`~sagemaker.estimator.amazon_estimator.AmazonAlgorithmEstimatorBase` and :class:`~sagemaker.estimator.EstimatorBase`. """ - super(PCA, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + super(PCA, self).__init__(role, instance_count, instance_type, **kwargs) self.num_components = num_components self.algorithm_mode = algorithm_mode self.subtract_mean = subtract_mean @@ -169,7 +170,7 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): # mini_batch_size is a required parameter default_mini_batch_size = min( - self.DEFAULT_MINI_BATCH_SIZE, max(1, int(num_records / self.train_instance_count)) + self.DEFAULT_MINI_BATCH_SIZE, max(1, int(num_records / self.instance_count)) ) use_mini_batch_size = mini_batch_size or default_mini_batch_size @@ -178,12 +179,12 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): ) -class PCAPredictor(RealTimePredictor): +class PCAPredictor(Predictor): """Transforms input vectors to lower-dimesional representations. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -193,17 +194,21 @@ class PCAPredictor(RealTimePredictor): ``projection`` key of the ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(PCAPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -222,11 +227,12 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(PCA.repo_name, PCA.repo_version) - image = "{}/{}".format(registry(sagemaker_session.boto_session.region_name), repo) + image_uri = image_uris.retrieve( + PCA.repo_name, sagemaker_session.boto_region_name, version=PCA.repo_version, + ) super(PCAModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=PCAPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/amazon/randomcutforest.py b/src/sagemaker/amazon/randomcutforest.py index 8e188c95ae..6a141150e8 100644 --- a/src/sagemaker/amazon/randomcutforest.py +++ b/src/sagemaker/amazon/randomcutforest.py @@ -13,11 +13,12 @@ """Placeholder docstring""" from __future__ import absolute_import -from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry -from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker import image_uris +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase +from sagemaker.amazon.common import RecordSerializer, RecordDeserializer from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa from sagemaker.amazon.validation import ge, le -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.model import Model from sagemaker.session import Session from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT @@ -45,8 +46,8 @@ class RandomCutForest(AmazonAlgorithmEstimatorBase): def __init__( self, role, - train_instance_count, - train_instance_type, + instance_count, + instance_type, num_samples_per_tree=None, num_trees=None, eval_metrics=None, @@ -90,9 +91,9 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if accessing AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. num_samples_per_tree (int): Optional. The number of samples used to build each tree in the forest. The total number of samples drawn @@ -112,9 +113,7 @@ def __init__( :class:`~sagemaker.estimator.EstimatorBase`. """ - super(RandomCutForest, self).__init__( - role, train_instance_count, train_instance_type, **kwargs - ) + super(RandomCutForest, self).__init__(role, instance_count, instance_type, **kwargs) self.num_samples_per_tree = num_samples_per_tree self.num_trees = num_trees self.eval_metrics = eval_metrics @@ -157,12 +156,12 @@ def _prepare_for_training(self, records, mini_batch_size=None, job_name=None): ) -class RandomCutForestPredictor(RealTimePredictor): +class RandomCutForestPredictor(Predictor): """Assigns an anomaly score to each of the datapoints provided. The implementation of - :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this - `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should + :meth:`~sagemaker.predictor.Predictor.predict` in this + `Predictor` requires a numpy ``ndarray`` as input. The array should contain the same number of columns as the feature-dimension of the data used to fit the model this Predictor performs inference on. @@ -172,17 +171,21 @@ class RandomCutForestPredictor(RealTimePredictor): ``Record.label`` field. """ - def __init__(self, endpoint, sagemaker_session=None): + def __init__(self, endpoint_name, sagemaker_session=None): """ Args: - endpoint: - sagemaker_session: + endpoint_name (str): Name of the Amazon SageMaker endpoint to which + requests are sent. + sagemaker_session (sagemaker.session.Session): A SageMaker Session + object, used for SageMaker interactions (default: None). If not + specified, one is created using the default AWS configuration + chain. """ super(RandomCutForestPredictor, self).__init__( - endpoint, + endpoint_name, sagemaker_session, - serializer=numpy_to_record_serializer(), - deserializer=record_deserializer(), + serializer=RecordSerializer(), + deserializer=RecordDeserializer(), ) @@ -201,13 +204,14 @@ def __init__(self, model_data, role, sagemaker_session=None, **kwargs): **kwargs: """ sagemaker_session = sagemaker_session or Session() - repo = "{}:{}".format(RandomCutForest.repo_name, RandomCutForest.repo_version) - image = "{}/{}".format( - registry(sagemaker_session.boto_session.region_name, RandomCutForest.repo_name), repo + image_uri = image_uris.retrieve( + RandomCutForest.repo_name, + sagemaker_session.boto_region_name, + version=RandomCutForest.repo_version, ) super(RandomCutForestModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=RandomCutForestPredictor, sagemaker_session=sagemaker_session, diff --git a/src/sagemaker/automl/automl.py b/src/sagemaker/automl/automl.py index ed2a1fcc7c..ab381565e7 100644 --- a/src/sagemaker/automl/automl.py +++ b/src/sagemaker/automl/automl.py @@ -307,12 +307,12 @@ def create_model( models = [] for container in inference_containers: - image = container["Image"] + image_uri = container["Image"] model_data = container["ModelDataUrl"] env = container["Environment"] model = Model( - image=image, + image_uri=image_uri, model_data=model_data, role=self.role, env=env, @@ -337,13 +337,14 @@ def deploy( self, initial_instance_count, instance_type, + serializer=None, + deserializer=None, candidate=None, sagemaker_session=None, name=None, endpoint_name=None, tags=None, wait=True, - update_endpoint=False, vpc_config=None, enable_network_isolation=False, model_kms_key=None, @@ -357,6 +358,16 @@ def deploy( in the ``Endpoint`` created from this ``Model``. instance_type (str): The EC2 instance type to deploy this Model to. For example, 'ml.p2.xlarge'. + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: None). If ``serializer`` is not None, then + ``serializer`` will override the default serializer. The + default serializer is set by the ``predictor_cls``. + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: None). If ``deserializer`` is not None, then + ``deserializer`` will override the default deserializer. The + default deserializer is set by the ``predictor_cls``. candidate (CandidateEstimator or dict): a CandidateEstimator used for deploying to a SageMaker Inference Pipeline. If None, the best candidate will be used. If the candidate input is a dict, a CandidateEstimator will be @@ -372,11 +383,6 @@ def deploy( specific endpoint. wait (bool): Whether the call should wait until the deployment of model completes (default: True). - update_endpoint (bool): Flag to update the model in an existing - Amazon SageMaker endpoint. If True, this will deploy a new - EndpointConfig to an already existing endpoint and delete - resources corresponding to the previous EndpointConfig. If - False, a new endpoint will be created. Default: False vpc_config (dict): Specifies a VPC that your training jobs and hosted models have access to. Contents include "SecurityGroupIds" and "Subnets". enable_network_isolation (bool): Isolates the training container. No inbound or @@ -411,10 +417,11 @@ def deploy( return model.deploy( initial_instance_count=initial_instance_count, instance_type=instance_type, + serializer=serializer, + deserializer=deserializer, endpoint_name=endpoint_name, tags=tags, wait=wait, - update_endpoint=update_endpoint, ) def _check_problem_type_and_job_objective(self, problem_type, job_objective): diff --git a/src/sagemaker/automl/candidate_estimator.py b/src/sagemaker/automl/candidate_estimator.py index 864e2cd070..32d1d10180 100644 --- a/src/sagemaker/automl/candidate_estimator.py +++ b/src/sagemaker/automl/candidate_estimator.py @@ -103,7 +103,7 @@ def fit( self.name = candidate_name or self.name running_jobs = {} - # convert inputs to s3_input format + # convert inputs to TrainingInput format if isinstance(inputs, string_types): if not inputs.startswith("s3://"): inputs = self.sagemaker_session.upload_data(inputs, key_prefix="auto-ml-input-data") @@ -211,24 +211,25 @@ def _get_train_args( Returns (dcit): a dictionary that can be used as args of sagemaker_session.train method. """ - train_args = {} - train_args["input_config"] = inputs - train_args["job_name"] = name - train_args["input_mode"] = desc["AlgorithmSpecification"]["TrainingInputMode"] - train_args["role"] = desc["RoleArn"] - train_args["output_config"] = desc["OutputDataConfig"] - train_args["resource_config"] = desc["ResourceConfig"] - train_args["image"] = desc["AlgorithmSpecification"]["TrainingImage"] - train_args["enable_network_isolation"] = desc["EnableNetworkIsolation"] - train_args["encrypt_inter_container_traffic"] = encrypt_inter_container_traffic - train_args["train_use_spot_instances"] = desc["EnableManagedSpotTraining"] - train_args["hyperparameters"] = {} - train_args["stop_condition"] = {} - train_args["metric_definitions"] = None - train_args["checkpoint_s3_uri"] = None - train_args["checkpoint_local_path"] = None - train_args["tags"] = [] - train_args["vpc_config"] = None + train_args = { + "input_config": inputs, + "job_name": name, + "input_mode": desc["AlgorithmSpecification"]["TrainingInputMode"], + "role": desc["RoleArn"], + "output_config": desc["OutputDataConfig"], + "resource_config": desc["ResourceConfig"], + "image_uri": desc["AlgorithmSpecification"]["TrainingImage"], + "enable_network_isolation": desc["EnableNetworkIsolation"], + "encrypt_inter_container_traffic": encrypt_inter_container_traffic, + "use_spot_instances": desc["EnableManagedSpotTraining"], + "hyperparameters": {}, + "stop_condition": {}, + "metric_definitions": None, + "checkpoint_s3_uri": None, + "checkpoint_local_path": None, + "tags": [], + "vpc_config": None, + } if volume_kms_key is not None: train_args["resource_config"]["VolumeKmsKeyId"] = volume_kms_key diff --git a/src/sagemaker/chainer/defaults.py b/src/sagemaker/chainer/defaults.py index 49966e9e34..9f5936208c 100644 --- a/src/sagemaker/chainer/defaults.py +++ b/src/sagemaker/chainer/defaults.py @@ -13,12 +13,4 @@ """Placeholder docstring""" from __future__ import absolute_import -CHAINER_VERSION = "4.1.0" -"""Default Chainer version for when the framework version is not specified. -This is no longer updated so as to not break existing workflows. -""" - -LATEST_VERSION = "5.0.0" -"""The latest version of Chainer included in the SageMaker pre-built Docker images.""" - LATEST_PY2_VERSION = "5.0.0" diff --git a/src/sagemaker/chainer/estimator.py b/src/sagemaker/chainer/estimator.py index dd8f5f378e..16a3a1e833 100644 --- a/src/sagemaker/chainer/estimator.py +++ b/src/sagemaker/chainer/estimator.py @@ -19,8 +19,8 @@ from sagemaker.fw_utils import ( framework_name_from_image, framework_version_from_tag, - empty_framework_version_warning, python_deprecation_warning, + validate_version_or_image_args, ) from sagemaker.chainer import defaults from sagemaker.chainer.model import ChainerModel @@ -32,7 +32,7 @@ class Chainer(Framework): """Handle end-to-end training and deployment of custom Chainer code.""" - __framework_name__ = "chainer" + _framework_name = "chainer" # Hyperparameters _use_mpi = "sagemaker_use_mpi" @@ -40,8 +40,6 @@ class Chainer(Framework): _process_slots_per_host = "sagemaker_process_slots_per_host" _additional_mpi_options = "sagemaker_additional_mpi_options" - LATEST_VERSION = defaults.LATEST_VERSION - def __init__( self, entry_point, @@ -51,9 +49,9 @@ def __init__( additional_mpi_options=None, source_dir=None, hyperparameters=None, - py_version="py3", framework_version=None, - image_name=None, + py_version=None, + image_uri=None, **kwargs ): """This ``Estimator`` executes an Chainer script in a managed Chainer @@ -103,11 +101,13 @@ def __init__( and values, but ``str()`` will be called to convert them before training. py_version (str): Python version you want to use for executing your - model training code (default: 'py2'). One of 'py2' or 'py3'. + model training code. Defaults to ``None``. Required unless ``image_uri`` + is provided. framework_version (str): Chainer version you want to use for - executing your model training code. If not specified, this will - default to 4.1. - image_name (str): If specified, the estimator will use this image + executing your model training code. Defaults to ``None``. Required unless + ``image_uri`` is provided. List of supported versions: + https://github.com/aws/sagemaker-python-sdk#chainer-sagemaker-estimators. + image_uri (str): If specified, the estimator will use this image for training and hosting, instead of selecting the appropriate SageMaker official image based on framework_version and py_version. It can be an ECR url or dockerhub image and tag. @@ -116,6 +116,9 @@ def __init__( * ``123412341234.dkr.ecr.us-west-2.amazonaws.com/my-custom-image:1.0`` * ``custom-image:latest`` + If ``framework_version`` or ``py_version`` are ``None``, then + ``image_uri`` is required. If also ``None``, then a ``ValueError`` + will be raised. **kwargs: Additional kwargs passed to the :class:`~sagemaker.estimator.Framework` constructor. @@ -125,22 +128,18 @@ def __init__( :class:`~sagemaker.estimator.Framework` and :class:`~sagemaker.estimator.EstimatorBase`. """ - if framework_version is None: + validate_version_or_image_args(framework_version, py_version, image_uri) + if py_version == "py2": logger.warning( - empty_framework_version_warning(defaults.CHAINER_VERSION, self.LATEST_VERSION) + python_deprecation_warning(self._framework_name, defaults.LATEST_PY2_VERSION) ) - self.framework_version = framework_version or defaults.CHAINER_VERSION + self.framework_version = framework_version + self.py_version = py_version super(Chainer, self).__init__( - entry_point, source_dir, hyperparameters, image_name=image_name, **kwargs + entry_point, source_dir, hyperparameters, image_uri=image_uri, **kwargs ) - if py_version == "py2": - logger.warning( - python_deprecation_warning(self.__framework_name__, defaults.LATEST_PY2_VERSION) - ) - - self.py_version = py_version self.use_mpi = use_mpi self.num_processes = num_processes self.process_slots_per_host = process_slots_per_host @@ -207,18 +206,16 @@ def create_model( sagemaker.chainer.model.ChainerModel: A SageMaker ``ChainerModel`` object. See :func:`~sagemaker.chainer.model.ChainerModel` for full details. """ - if "image" not in kwargs: - kwargs["image"] = self.image_name + kwargs["name"] = self._get_or_create_name(kwargs.get("name")) - if "name" not in kwargs: - kwargs["name"] = self._current_job_name + if "image_uri" not in kwargs: + kwargs["image_uri"] = self.image_uri return ChainerModel( self.model_data, role or self.role, - entry_point or self.entry_point, + entry_point or self._model_entry_point(), source_dir=(source_dir or self._model_source_dir()), - enable_cloudwatch_metrics=self.enable_cloudwatch_metrics, container_log_level=self.container_log_level, code_location=self.code_location, py_version=self.py_version, @@ -259,24 +256,26 @@ class constructor if value: init_params[argument[len("sagemaker_") :]] = value - image_name = init_params.pop("image") - framework, py_version, tag, _ = framework_name_from_image(image_name) + image_uri = init_params.pop("image_uri") + framework, py_version, tag, _ = framework_name_from_image(image_uri) + + if tag is None: + framework_version = None + else: + framework_version = framework_version_from_tag(tag) + init_params["framework_version"] = framework_version + init_params["py_version"] = py_version if not framework: # If we were unable to parse the framework name from the image it is not one of our # officially supported images, in this case just add the image to the init params. - init_params["image_name"] = image_name + init_params["image_uri"] = image_uri return init_params - init_params["py_version"] = py_version - init_params["framework_version"] = framework_version_from_tag(tag) - - training_job_name = init_params["base_job_name"] - - if framework != cls.__framework_name__: + if framework != cls._framework_name: raise ValueError( "Training job: {} didn't use image for requested framework".format( - training_job_name + job_details["TrainingJobName"] ) ) return init_params diff --git a/src/sagemaker/chainer/model.py b/src/sagemaker/chainer/model.py index fc17be17a8..48ca779257 100644 --- a/src/sagemaker/chainer/model.py +++ b/src/sagemaker/chainer/model.py @@ -16,21 +16,23 @@ import logging import sagemaker +from sagemaker import image_uris from sagemaker.fw_utils import ( - create_image_uri, model_code_key_prefix, python_deprecation_warning, - empty_framework_version_warning, + validate_version_or_image_args, ) from sagemaker.model import FrameworkModel, MODEL_SERVER_WORKERS_PARAM_NAME from sagemaker.chainer import defaults -from sagemaker.predictor import RealTimePredictor, npy_serializer, numpy_deserializer +from sagemaker.deserializers import NumpyDeserializer +from sagemaker.predictor import Predictor +from sagemaker.serializers import NumpySerializer logger = logging.getLogger("sagemaker") -class ChainerPredictor(RealTimePredictor): - """A RealTimePredictor for inference against Chainer Endpoints. +class ChainerPredictor(Predictor): + """A Predictor for inference against Chainer Endpoints. This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for Chainer inference. @@ -48,7 +50,7 @@ def __init__(self, endpoint_name, sagemaker_session=None): using the default AWS configuration chain. """ super(ChainerPredictor, self).__init__( - endpoint_name, sagemaker_session, npy_serializer, numpy_deserializer + endpoint_name, sagemaker_session, NumpySerializer(), NumpyDeserializer() ) @@ -57,16 +59,16 @@ class ChainerModel(FrameworkModel): ``Endpoint``. """ - __framework_name__ = "chainer" + _framework_name = "chainer" def __init__( self, model_data, role, entry_point, - image=None, - py_version="py3", + image_uri=None, framework_version=None, + py_version=None, predictor_cls=ChainerPredictor, model_server_workers=None, **kwargs @@ -85,12 +87,16 @@ def __init__( file which should be executed as the entry point to model hosting. If ``source_dir`` is specified, then ``entry_point`` must point to a file located at the root of ``source_dir``. - image (str): A Docker image URI (default: None). If not specified, a - default image for Chainer will be used. - py_version (str): Python version you want to use for executing your - model training code (default: 'py2'). + image_uri (str): A Docker image URI (default: None). If not specified, a + default image for Chainer will be used. If ``framework_version`` + or ``py_version`` are ``None``, then ``image_uri`` is required. If + also ``None``, then a ``ValueError`` will be raised. framework_version (str): Chainer version you want to use for - executing your model training code. + executing your model training code. Defaults to ``None``. Required + unless ``image_uri`` is provided. + py_version (str): Python version you want to use for executing your + model training code. Defaults to ``None``. Required unless + ``image_uri`` is provided. predictor_cls (callable[str, sagemaker.session.Session]): A function to call to create a predictor with an endpoint name and SageMaker ``Session``. If specified, ``deploy()`` returns the @@ -107,21 +113,18 @@ def __init__( :class:`~sagemaker.model.FrameworkModel` and :class:`~sagemaker.model.Model`. """ - super(ChainerModel, self).__init__( - model_data, image, role, entry_point, predictor_cls=predictor_cls, **kwargs - ) + validate_version_or_image_args(framework_version, py_version, image_uri) if py_version == "py2": logger.warning( - python_deprecation_warning(self.__framework_name__, defaults.LATEST_PY2_VERSION) + python_deprecation_warning(self._framework_name, defaults.LATEST_PY2_VERSION) ) + self.framework_version = framework_version + self.py_version = py_version - if framework_version is None: - logger.warning( - empty_framework_version_warning(defaults.CHAINER_VERSION, defaults.LATEST_VERSION) - ) + super(ChainerModel, self).__init__( + model_data, image_uri, role, entry_point, predictor_cls=predictor_cls, **kwargs + ) - self.py_version = py_version - self.framework_version = framework_version or defaults.CHAINER_VERSION self.model_server_workers = model_server_workers def prepare_container_def(self, instance_type=None, accelerator_type=None): @@ -139,7 +142,7 @@ def prepare_container_def(self, instance_type=None, accelerator_type=None): dict[str, str]: A container definition object usable with the CreateModel API. """ - deploy_image = self.image + deploy_image = self.image_uri if not deploy_image: if instance_type is None: raise ValueError( @@ -172,11 +175,12 @@ def serving_image_uri(self, region_name, instance_type, accelerator_type=None): str: The appropriate image URI based on the given parameters. """ - return create_image_uri( + return image_uris.retrieve( + self._framework_name, region_name, - self.__framework_name__, - instance_type, - self.framework_version, - self.py_version, + version=self.framework_version, + py_version=self.py_version, + instance_type=instance_type, accelerator_type=accelerator_type, + image_scope="inference", ) diff --git a/src/sagemaker/cli/__init__.py b/src/sagemaker/cli/__init__.py index 77f4efcba5..4950fdd3ca 100644 --- a/src/sagemaker/cli/__init__.py +++ b/src/sagemaker/cli/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of @@ -6,7 +6,9 @@ # # http://aws.amazon.com/apache2.0/ # -# or in the "license" file accompanying this file. This file is +# or in the "license" file accompanying athis file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +"""Tools for automating code updates""" +from __future__ import absolute_import diff --git a/src/sagemaker/cli/common.py b/src/sagemaker/cli/common.py deleted file mode 100644 index 7256937bc7..0000000000 --- a/src/sagemaker/cli/common.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Placeholder docstring""" -from __future__ import absolute_import - -import json -import logging -import os -import shutil -import tarfile -import tempfile - -import sagemaker - -logger = logging.getLogger(__name__) - - -class HostCommand(object): - """Placeholder docstring""" - - def __init__(self, args): - """ - Args: - args: - """ - self.endpoint_name = args.job_name - self.bucket = args.bucket_name # may be None - self.role_name = args.role_name - self.python = args.python - self.data = args.data - self.script = args.script - self.instance_type = args.instance_type - self.instance_count = args.instance_count - self.environment = dict((kv.split("=") for kv in args.env)) - - self.session = sagemaker.Session() - - def upload_model(self): - """Placeholder docstring""" - prefix = "{}/model".format(self.endpoint_name) - - archive = self.create_model_archive(self.data) - model_uri = self.session.upload_data(path=archive, bucket=self.bucket, key_prefix=prefix) - shutil.rmtree(os.path.dirname(archive)) - - return model_uri - - @staticmethod - def create_model_archive(src): - """ - Args: - src: - """ - if os.path.isdir(src): - arcname = "." - else: - arcname = os.path.basename(src) - - tmp = tempfile.mkdtemp() - archive = os.path.join(tmp, "model.tar.gz") - - with tarfile.open(archive, mode="w:gz") as t: - t.add(src, arcname=arcname) - return archive - - def create_model(self, model_url): - """ - Args: - model_url: - """ - raise NotImplementedError # subclasses must override - - def start(self): - """Placeholder docstring""" - model_url = self.upload_model() - model = self.create_model(model_url) - predictor = model.deploy( - initial_instance_count=self.instance_count, instance_type=self.instance_type - ) - - return predictor - - -class TrainCommand(object): - """Placeholder docstring""" - - def __init__(self, args): - """ - Args: - args: - """ - self.job_name = args.job_name - self.bucket = args.bucket_name # may be None - self.role_name = args.role_name - self.python = args.python - self.data = args.data - self.script = args.script - self.instance_type = args.instance_type - self.instance_count = args.instance_count - self.hyperparameters = self.load_hyperparameters(args.hyperparameters) - - self.session = sagemaker.Session() - - @staticmethod - def load_hyperparameters(src): - """ - Args: - src: - """ - hp = {} - if src and os.path.exists(src): - with open(src, "r") as f: - hp = json.load(f) - return hp - - def upload_training_data(self): - """Placeholder docstring""" - prefix = "{}/data".format(self.job_name) - data_url = self.session.upload_data(path=self.data, bucket=self.bucket, key_prefix=prefix) - return data_url - - def create_estimator(self): - """Placeholder docstring""" - raise NotImplementedError # subclasses must override - - def start(self): - """Placeholder docstring""" - data_url = self.upload_training_data() - estimator = self.create_estimator() - estimator.fit(data_url) - logger.debug("code location: %s", estimator.uploaded_code.s3_prefix) - logger.debug( - "model location: %s%s/output/model.tar.gz", - estimator.output_path, - estimator._current_job_name, - ) diff --git a/src/sagemaker/cli/compatibility/__init__.py b/src/sagemaker/cli/compatibility/__init__.py new file mode 100644 index 0000000000..e3a46fe406 --- /dev/null +++ b/src/sagemaker/cli/compatibility/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Tools to assist with compatibility between SageMaker Python SDK versions.""" +from __future__ import absolute_import diff --git a/src/sagemaker/cli/compatibility/v2/__init__.py b/src/sagemaker/cli/compatibility/v2/__init__.py new file mode 100644 index 0000000000..a19ed16295 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Tools to assist with upgrading to version 2.0 and later of the SageMaker Python SDK.""" +from __future__ import absolute_import diff --git a/src/sagemaker/cli/compatibility/v2/ast_transformer.py b/src/sagemaker/cli/compatibility/v2/ast_transformer.py new file mode 100644 index 0000000000..c39fe9f543 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/ast_transformer.py @@ -0,0 +1,156 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""An ast.NodeTransformer subclass for updating SageMaker Python SDK code.""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2 import modifiers + +FUNCTION_CALL_MODIFIERS = [ + modifiers.renamed_params.EstimatorImageURIRenamer(), + modifiers.renamed_params.ModelImageURIRenamer(), + modifiers.framework_version.FrameworkVersionEnforcer(), + modifiers.tf_legacy_mode.TensorFlowLegacyModeConstructorUpgrader(), + modifiers.tf_legacy_mode.TensorBoardParameterRemover(), + modifiers.deprecated_params.TensorFlowScriptModeParameterRemover(), + modifiers.tfs.TensorFlowServingConstructorRenamer(), + modifiers.predictors.PredictorConstructorRefactor(), + modifiers.airflow.ModelConfigArgModifier(), + modifiers.airflow.ModelConfigImageURIRenamer(), + modifiers.renamed_params.DistributionParameterRenamer(), + modifiers.renamed_params.S3SessionRenamer(), + modifiers.renamed_params.EstimatorCreateModelImageURIRenamer(), + modifiers.renamed_params.SessionCreateModelImageURIRenamer(), + modifiers.renamed_params.SessionCreateEndpointImageURIRenamer(), + modifiers.training_params.TrainPrefixRemover(), + modifiers.training_input.TrainingInputConstructorRefactor(), + modifiers.training_input.ShuffleConfigModuleRenamer(), + modifiers.serde.SerdeConstructorRenamer(), + modifiers.serde.SerdeKeywordRemover(), + modifiers.image_uris.ImageURIRetrieveRefactor(), +] + +IMPORT_MODIFIERS = [modifiers.tfs.TensorFlowServingImportRenamer()] + +NAME_MODIFIERS = [modifiers.serde.SerdeObjectRenamer()] + +MODULE_MODIFIERS = [ + modifiers.serde.SerializerImportInserter(), + modifiers.serde.DeserializerImportInserter(), +] + +IMPORT_FROM_MODIFIERS = [ + modifiers.predictors.PredictorImportFromRenamer(), + modifiers.tfs.TensorFlowServingImportFromRenamer(), + modifiers.training_input.TrainingInputImportFromRenamer(), + modifiers.training_input.ShuffleConfigImportFromRenamer(), + modifiers.serde.SerdeImportFromAmazonCommonRenamer(), + modifiers.serde.SerdeImportFromPredictorRenamer(), + modifiers.image_uris.ImageURIRetrieveImportFromRenamer(), +] + + +class ASTTransformer(ast.NodeTransformer): + """An ``ast.NodeTransformer`` subclass that walks the abstract syntax tree and + modifies nodes to upgrade the given SageMaker Python SDK code. + """ + + def visit_Call(self, node): + """Visits an ``ast.Call`` node and returns a modified node or None. + + See https://docs.python.org/3/library/ast.html#ast.NodeTransformer. + + Args: + node (ast.Call): a node that represents a function call. + + Returns: + ast.AST: if the returned node is None, the original node is removed + from its location. Otherwise, the original node is replaced with + the returned node. + """ + for function_checker in FUNCTION_CALL_MODIFIERS: + node = function_checker.check_and_modify_node(node) + return ast.fix_missing_locations(node) if node else None + + def visit_Name(self, node): + """Visits an ``ast.Name`` node and returns a modified node or None. + + See https://docs.python.org/3/library/ast.html#ast.NodeTransformer. + + Args: + node (ast.Name): a node that represents an identifier. + + Returns: + ast.AST: if the returned node is None, the original node is removed + from its location. Otherwise, the original node is replaced with + the returned node. + """ + for name_checker in NAME_MODIFIERS: + node = name_checker.check_and_modify_node(node) + return ast.fix_missing_locations(node) if node else None + + def visit_Import(self, node): + """Visits an ``ast.Import`` node and returns a modified node or None. + + See https://docs.python.org/3/library/ast.html#ast.NodeTransformer. + + Args: + node (ast.Import): a node that represents an import statement. + + Returns: + ast.AST: if the returned node is None, the original node is removed + from its location. Otherwise, the original node is replaced with + the returned node. + """ + for import_checker in IMPORT_MODIFIERS: + node = import_checker.check_and_modify_node(node) + return ast.fix_missing_locations(node) if node else None + + def visit_Module(self, node): + """Visits an ``ast.Module`` node and returns a modified node or None. + + See https://docs.python.org/3/library/ast.html#ast.NodeTransformer. + + The ``ast.NodeTransformer`` walks the abstract syntax tree and modifies + all other nodes before modifying the ``ast.Module`` node. + + Args: + node (ast.Module): a node that represents a Python module. + + Returns: + ast.AST: if the returned node is None, the original node is removed + from its location. Otherwise, the original node is replaced with + the returned node. + """ + self.generic_visit(node) + for module_checker in MODULE_MODIFIERS: + node = module_checker.check_and_modify_node(node) + return ast.fix_missing_locations(node) if node else None + + def visit_ImportFrom(self, node): + """Visits an ``ast.ImportFrom`` node and returns a modified node or None. + + See https://docs.python.org/3/library/ast.html#ast.NodeTransformer. + + Args: + node (ast.ImportFrom): a node that represents an import statement. + + Returns: + ast.AST: if the returned node is None, the original node is removed + from its location. Otherwise, the original node is replaced with + the returned node. + """ + for import_checker in IMPORT_FROM_MODIFIERS: + node = import_checker.check_and_modify_node(node) + return ast.fix_missing_locations(node) if node else None diff --git a/src/sagemaker/cli/compatibility/v2/files.py b/src/sagemaker/cli/compatibility/v2/files.py new file mode 100644 index 0000000000..45c58de1b4 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/files.py @@ -0,0 +1,201 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes for updating code in files.""" +from __future__ import absolute_import + +from abc import abstractmethod +import json +import logging +import os + +import pasta + +from sagemaker.cli.compatibility.v2.ast_transformer import ASTTransformer + +LOGGER = logging.getLogger(__name__) + + +class FileUpdater(object): + """An abstract class for updating files.""" + + def __init__(self, input_path, output_path): + """Creates a ``FileUpdater`` for updating a file so that + it is compatible with version 2.0 and later of the SageMaker Python SDK. + + Args: + input_path (str): Location of the input file. + output_path (str): Desired location for the output file. + If the directories don't already exist, then they are created. + If a file exists at ``output_path``, then it is overwritten. + """ + self.input_path = input_path + self.output_path = output_path + + @abstractmethod + def update(self): + """Reads the input file, updates the code so that it is + compatible with version 2.0 and later of the SageMaker Python SDK, + and writes the updated code to an output file. + """ + + def _make_output_dirs_if_needed(self): + """Checks if the directory path for ``self.output_path`` exists, + and creates the directories if not. This function also logs a warning if + ``self.output_path`` already exists. + """ + output_dir = os.path.dirname(self.output_path) + if output_dir and not os.path.exists(output_dir): + os.makedirs(output_dir) + + if os.path.exists(self.output_path): + LOGGER.warning("Overwriting file %s", self.output_path) + + +class PyFileUpdater(FileUpdater): + """A class for updating Python (``*.py``) files.""" + + def update(self): + """Reads the input Python file, updates the code so that it is + compatible with version 2.0 and later of the SageMaker Python SDK, + and writes the updated code to an output file. + """ + output = self._update_ast(self._read_input_file()) + self._write_output_file(output) + + def _update_ast(self, input_ast): + """Updates an abstract syntax tree (AST) so that it is compatible + with version 2.0 and later of the SageMaker Python SDK. + + Args: + input_ast (ast.Module): AST to be updated for use with + the Python SDK version 2.0 and later. + + Returns: + ast.Module: Updated AST that is compatible with the Python SDK version 2.0 and later. + """ + return ASTTransformer().visit(input_ast) + + def _read_input_file(self): + """Reads input file and parses it as an abstract syntax tree (AST). + + Returns: + ast.Module: AST representation of the input file. + """ + with open(self.input_path) as input_file: + return pasta.parse(input_file.read()) + + def _write_output_file(self, output): + """Writes abstract syntax tree (AST) to output file. + Creates the directories for the output path, if needed. + + Args: + output (ast.Module): AST to save as the output file. + """ + self._make_output_dirs_if_needed() + + with open(self.output_path, "w") as output_file: + output_file.write(pasta.dump(output)) + + +class JupyterNotebookFileUpdater(FileUpdater): + """A class for updating Jupyter notebook (``*.ipynb``) files. + + For more on this file format, see + https://ipython.org/ipython-doc/dev/notebook/nbformat.html#nbformat. + """ + + def update(self): + """Reads the input Jupyter notebook file, updates the code so that it is + compatible with version 2.0 and later of the SageMaker Python SDK, and writes the + updated code to an output file. + """ + nb_json = self._read_input_file() + for cell in nb_json["cells"]: + if cell["cell_type"] == "code" and not self._contains_shell_cmds(cell): + updated_source = self._update_code_from_cell(cell) + cell["source"] = updated_source + + self._write_output_file(nb_json) + + def _contains_shell_cmds(self, cell): + """Checks if the cell's source uses either ``%%`` or ``!`` to execute shell commands. + + Args: + cell (dict): A dictionary representation of a code cell from + a Jupyter notebook. For more info, see + https://ipython.org/ipython-doc/dev/notebook/nbformat.html#code-cells. + + Returns: + bool: If the first line starts with ``%%`` or any line starts with ``!``. + """ + source = cell["source"] + + if source[0].startswith("%%"): + return True + + return any(line.startswith("!") for line in source) + + def _update_code_from_cell(self, cell): + """Updates the code from a code cell so that it is + compatible with version 2.0 and later of the SageMaker Python SDK. + + Args: + cell (dict): A dictionary representation of a code cell from + a Jupyter notebook. For more info, see + https://ipython.org/ipython-doc/dev/notebook/nbformat.html#code-cells. + + Returns: + list[str]: A list of strings containing the lines of updated code that + can be used for the "source" attribute of a Jupyter notebook code cell. + """ + code = "".join(cell["source"]) + updated_ast = ASTTransformer().visit(pasta.parse(code)) + updated_code = pasta.dump(updated_ast) + return self._code_str_to_source_list(updated_code) + + def _code_str_to_source_list(self, code): + """Converts a string of code into a list for a Jupyter notebook code cell. + + Args: + code (str): Code to be converted. + + Returns: + list[str]: A list of strings containing the lines of code that + can be used for the "source" attribute of a Jupyter notebook code cell. + Each element of the list (i.e. line of code) contains a + trailing newline character ("\n") except for the last element. + """ + source_list = ["{}\n".format(s) for s in code.split("\n")] + source_list[-1] = source_list[-1].rstrip("\n") + return source_list + + def _read_input_file(self): + """Reads input file and parses it as JSON. + + Returns: + dict: JSON representation of the input file. + """ + with open(self.input_path) as input_file: + return json.load(input_file) + + def _write_output_file(self, output): + """Writes JSON to output file. Creates the directories for the output path, if needed. + + Args: + output (dict): JSON to save as the output file. + """ + self._make_output_dirs_if_needed() + + with open(self.output_path, "w") as output_file: + json.dump(output, output_file, indent=1) + output_file.write("\n") # json.dump does not write trailing newline diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/__init__.py b/src/sagemaker/cli/compatibility/v2/modifiers/__init__.py new file mode 100644 index 0000000000..75f5e1dbeb --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes for modifying AST nodes""" +from __future__ import absolute_import + +from sagemaker.cli.compatibility.v2.modifiers import ( # noqa: F401 (imported but unused) + airflow, + deprecated_params, + framework_version, + predictors, + renamed_params, + serde, + tf_legacy_mode, + tfs, + training_params, + training_input, + image_uris, +) diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/airflow.py b/src/sagemaker/cli/compatibility/v2/modifiers/airflow.py new file mode 100644 index 0000000000..13b06b9230 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/airflow.py @@ -0,0 +1,96 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""A class to handle argument changes for Airflow functions.""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2.modifiers import matching, renamed_params +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +FUNCTION_NAMES = ("model_config", "model_config_from_estimator") +NAMESPACES = ("sagemaker.workflow.airflow", "workflow.airflow", "airflow") +FUNCTIONS = {name: NAMESPACES for name in FUNCTION_NAMES} + + +class ModelConfigArgModifier(Modifier): + """A class to handle argument changes for Airflow model config functions.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node creates an Airflow model config and + contains positional arguments. + + This looks for the following formats: + + - ``model_config`` + - ``airflow.model_config`` + - ``workflow.airflow.model_config`` + - ``sagemaker.workflow.airflow.model_config`` + + where ``model_config`` is either ``model_config`` or ``model_config_from_estimator``. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` is either a ``model_config`` call or + a ``model_config_from_estimator`` call and has positional arguments. + """ + return matching.matches_any(node, FUNCTIONS) and len(node.args) > 0 + + def modify_node(self, node): + """Modifies the ``ast.Call`` node's arguments. + + The first argument, the instance type, is turned into a keyword arg, + leaving the second argument, the model, to be the first argument. + + Args: + node (ast.Call): a node that represents either a ``model_config`` call or + a ``model_config_from_estimator`` call. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + instance_type = node.args.pop(0) + node.keywords.append(ast.keyword(arg="instance_type", value=instance_type)) + return node + + +class ModelConfigImageURIRenamer(renamed_params.ParamRenamer): + """A class to rename the ``image`` attribute to ``image_uri`` in Airflow model config functions. + + This looks for the following formats: + + - ``model_config`` + - ``airflow.model_config`` + - ``workflow.airflow.model_config`` + - ``sagemaker.workflow.airflow.model_config`` + + where ``model_config`` is either ``model_config`` or ``model_config_from_estimator``. + """ + + @property + def calls_to_modify(self): + """A dictionary mapping Airflow model config functions to their respective namespaces.""" + return FUNCTIONS + + @property + def old_param_name(self): + """The previous name for the image URI argument.""" + return "image" + + @property + def new_param_name(self): + """The new name for the image URI argument.""" + return "image_uri" diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/deprecated_params.py b/src/sagemaker/cli/compatibility/v2/modifiers/deprecated_params.py new file mode 100644 index 0000000000..f209b7a45c --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/deprecated_params.py @@ -0,0 +1,64 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to remove deprecated parameters.""" +from __future__ import absolute_import + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +TF_NAMESPACES = ("sagemaker.tensorflow", "sagemaker.tensorflow.estimator") + + +class TensorFlowScriptModeParameterRemover(Modifier): + """A class to remove ``script_mode`` from TensorFlow estimators (because it's the only mode).""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a TensorFlow estimator with + ``script_mode`` set. + + This looks for the following formats: + + - ``TensorFlow`` + - ``sagemaker.tensorflow.TensorFlow`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` is instantiating a TensorFlow estimator with ``script_mode``. + """ + is_tf_constructor = matching.matches_name_or_namespaces(node, "TensorFlow", TF_NAMESPACES) + return is_tf_constructor and self._has_script_mode_param(node) + + def _has_script_mode_param(self, node): + """Checks if the ``ast.Call`` node's keywords include ``script_mode``.""" + for kw in node.keywords: + if kw.arg == "script_mode": + return True + + return False + + def modify_node(self, node): + """Modifies the ``ast.Call`` node's keywords to remove ``script_mode``. + + Args: + node (ast.Call): a node that represents a TensorFlow constructor. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for kw in node.keywords: + if kw.arg == "script_mode": + node.keywords.remove(kw) + return node diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/framework_version.py b/src/sagemaker/cli/compatibility/v2/modifiers/framework_version.py new file mode 100644 index 0000000000..f1da388361 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/framework_version.py @@ -0,0 +1,202 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""A class to ensure that ``framework_version`` is defined when constructing framework classes.""" +from __future__ import absolute_import + +import ast + +from packaging.version import InvalidVersion, Version + +from sagemaker.cli.compatibility.v2.modifiers import matching, parsing +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +FRAMEWORK_ARG = "framework_version" +IMAGE_ARG = "image_uri" +PY_ARG = "py_version" + +FRAMEWORK_DEFAULTS = { + "Chainer": "4.1.0", + "MXNet": "1.2.0", + "PyTorch": "0.4.0", + "SKLearn": "0.20.0", + "TensorFlow": "1.11.0", +} + +FRAMEWORK_CLASSES = list(FRAMEWORK_DEFAULTS.keys()) + +ESTIMATORS = { + fw: ("sagemaker.{}".format(fw.lower()), "sagemaker.{}.estimator".format(fw.lower())) + for fw in FRAMEWORK_CLASSES +} +# TODO: check for sagemaker.tensorflow.serving.Model +MODELS = { + "{}Model".format(fw): ( + "sagemaker.{}".format(fw.lower()), + "sagemaker.{}.model".format(fw.lower()), + ) + for fw in FRAMEWORK_CLASSES +} + + +class FrameworkVersionEnforcer(Modifier): + """A class to ensure that ``framework_version`` is defined when + instantiating a framework estimator or model. + """ + + def node_should_be_modified(self, node): + """Checks if the ast.Call node instantiates a framework estimator or model, + but doesn't specify the ``framework_version`` and ``py_version`` parameter, + as appropriate. + + This looks for the following formats: + + - ``TensorFlow`` + - ``sagemaker.tensorflow.TensorFlow`` + + where "TensorFlow" can be Chainer, MXNet, PyTorch, SKLearn, or TensorFlow. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` is instantiating a framework class that + should specify ``framework_version``, but doesn't. + """ + if matching.matches_any(node, ESTIMATORS) or matching.matches_any(node, MODELS): + return _version_args_needed(node) + + return False + + def modify_node(self, node): + """Modifies the ``ast.Call`` node's keywords to include ``framework_version``. + + The ``framework_version`` value is determined by the framework: + + - Chainer: "4.1.0" + - MXNet: "1.2.0" + - PyTorch: "0.4.0" + - SKLearn: "0.20.0" + - TensorFlow: "1.11.0" + + The ``py_version`` value is determined by the framework, framework_version, and if it is a + model, whether the model accepts a py_version + + Args: + node (ast.Call): a node that represents the constructor of a framework class. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + framework, is_model = _framework_from_node(node) + + # if framework_version is not supplied, get default and append keyword + if matching.has_arg(node, FRAMEWORK_ARG): + framework_version = parsing.arg_value(node, FRAMEWORK_ARG) + else: + framework_version = FRAMEWORK_DEFAULTS[framework] + node.keywords.append(ast.keyword(arg=FRAMEWORK_ARG, value=ast.Str(s=framework_version))) + + # if py_version is not supplied, get a conditional default, and if not None, append keyword + if not matching.has_arg(node, PY_ARG): + py_version = _py_version_defaults(framework, framework_version, is_model) + if py_version: + node.keywords.append(ast.keyword(arg=PY_ARG, value=ast.Str(s=py_version))) + return node + + +def _py_version_defaults(framework, framework_version, is_model=False): + """Gets the py_version required for the framework_version and if it's a model + + Args: + framework (str): name of the framework + framework_version (str): version of the framework + is_model (bool): whether it is a constructor for a model or not + + Returns: + str: the default py version, as appropriate. None if no default py_version + """ + if framework in ("Chainer", "PyTorch"): + return "py3" + if framework == "SKLearn" and not is_model: + return "py3" + if framework == "MXNet": + return "py2" + if framework == "TensorFlow" and not is_model: + return _tf_py_version_default(framework_version) + return None + + +def _tf_py_version_default(framework_version): + """Gets the py_version default based on framework_version for TensorFlow.""" + if not framework_version: + return "py2" + + try: + version = Version(framework_version) + except InvalidVersion: + return "py2" + + if version < Version("1.12"): + return "py2" + if version < Version("2.2"): + return "py3" + return "py37" + + +def _framework_from_node(node): + """Retrieves the framework class name based on the function call, and if it was a model + + Args: + node (ast.Call): a node that represents the constructor of a framework class. + This can represent either or sagemaker... + + Returns: + str, bool: the (capitalized) framework class name, and if it is a model class + """ + if isinstance(node.func, ast.Name): + framework = node.func.id + elif isinstance(node.func, ast.Attribute): + framework = node.func.attr + else: + framework = "" + + is_model = framework.endswith("Model") + if is_model: + framework = framework[: framework.find("Model")] + + return framework, is_model + + +def _version_args_needed(node): + """Determines if image_arg or version_arg was supplied + + Applies similar logic as ``validate_version_or_image_args`` + """ + # if image_arg is present, no need to supply version arguments + if matching.has_arg(node, IMAGE_ARG): + return False + + # if framework_version is None, need args + if matching.has_arg(node, FRAMEWORK_ARG): + framework_version = parsing.arg_value(node, FRAMEWORK_ARG) + else: + return True + + # check if we expect py_version and we don't get it -- framework and model dependent + framework, is_model = _framework_from_node(node) + expecting_py_version = _py_version_defaults(framework, framework_version, is_model) + if expecting_py_version: + return not matching.has_arg(node, PY_ARG) + + return False diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/image_uris.py b/src/sagemaker/cli/compatibility/v2/modifiers/image_uris.py new file mode 100644 index 0000000000..fe0ba9df2d --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/image_uris.py @@ -0,0 +1,134 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to modify image uri retrieve methods for Python SDK v2.0 and later.""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +GET_IMAGE_URI_NAME = "get_image_uri" +GET_IMAGE_URI_NAMESPACES = ( + "sagemaker", + "sagemaker.amazon_estimator", + "sagemaker.amazon.amazon_estimator", + "amazon_estimator", + "amazon.amazon_estimator", +) + + +class ImageURIRetrieveRefactor(Modifier): + """A class to refactor *get_image_uri() method.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node calls a function of interest. + + This looks for the following calls: + + - ``sagemaker.get_image_uri`` + - ``sagemaker.amazon_estimator.get_image_uri`` + - ``get_image_uri`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a class of interest. + """ + return matching.matches_name_or_namespaces( + node, GET_IMAGE_URI_NAME, GET_IMAGE_URI_NAMESPACES + ) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to call ``image_uris.retrieve`` instead. + And switch the first two parameters from (region, repo) to (framework, region) + + Args: + node (ast.Call): a node that represents a *image_uris.retrieve call. + """ + original_args = [None] * 3 + for kw in node.keywords: + if kw.arg == "repo_name": + original_args[0] = ast.Str(kw.value.s) + elif kw.arg == "repo_region": + original_args[1] = ast.Str(kw.value.s) + elif kw.arg == "repo_version": + original_args[2] = ast.Str(kw.value.s) + + if len(node.args) > 0: + original_args[1] = ast.Str(node.args[0].s) + if len(node.args) > 1: + original_args[0] = ast.Str(node.args[1].s) + if len(node.args) > 2: + original_args[2] = ast.Str(node.args[2].s) + + args = [] + for arg in original_args: + if arg: + args.append(arg) + + func = node.func + has_sagemaker = False + while hasattr(func, "value"): + if hasattr(func.value, "id") and func.value.id == "sagemaker": + has_sagemaker = True + break + func = func.value + + if has_sagemaker: + node.func = ast.Attribute( + value=ast.Attribute(attr="image_uris", value=ast.Name(id="sagemaker")), + attr="retrieve", + ) + else: + node.func = ast.Attribute(value=ast.Name(id="image_uris"), attr="retrieve") + node.args = args + node.keywords = [] + return node + + +class ImageURIRetrieveImportFromRenamer(Modifier): + """A class to update import statements of ``get_image_uri``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports ``get_image_uri`` from the correct module. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the import statement imports ``get_image_uri`` from the correct module. + """ + return node.module in GET_IMAGE_URI_NAMESPACES and any( + name.name == GET_IMAGE_URI_NAME for name in node.names + ) + + def modify_node(self, node): + """Changes the ``ast.ImportFrom`` node's name from ``get_image_uri`` to ``image_uris``. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for name in node.names: + if name.name == GET_IMAGE_URI_NAME: + name.name = "image_uris" + if node.module in GET_IMAGE_URI_NAMESPACES: + node.module = "sagemaker" + return node diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/matching.py b/src/sagemaker/cli/compatibility/v2/modifiers/matching.py new file mode 100644 index 0000000000..a84a6b9ca9 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/matching.py @@ -0,0 +1,122 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Functions for checking AST nodes for matches.""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2.modifiers import parsing + + +def matches_any(node, name_to_namespaces_dict): + """Determines if the ``ast.Call`` node matches any of the provided names and namespaces. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + name_to_namespaces_dict (dict[str, tuple]): a mapping of names to appropriate namespaces. + + Returns: + bool: if the node matches any of the names and namespaces. + """ + return any( + matches_name_or_namespaces(node, name, namespaces) + for name, namespaces in name_to_namespaces_dict.items() + ) + + +def matches_name_or_namespaces(node, name, namespaces): + """Determines if the ``ast.Call`` node matches the function name in the right namespace. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + name (str): the function name. + namespaces (tuple): the possible namespaces to match to. + + Returns: + bool: if the node matches the name and any of the namespaces. + """ + if matches_name(node, name): + return True + + if not matches_attr(node, name): + return False + + return any(matches_namespace(node, namespace) for namespace in namespaces) + + +def matches_name(node, name): + """Determines if the ``ast.Call`` node points to an ``ast.Name`` node with a matching name. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + name (str): the function name. + + Returns: + bool: if ``node.func`` is an ``ast.Name`` node with a matching name. + """ + return isinstance(node.func, ast.Name) and node.func.id == name + + +def matches_attr(node, name): + """Determines if the ``ast.Call`` node points to an ``ast.Attribute`` node with a matching name. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + name (str): the function name. + + Returns: + bool: if ``node.func`` is an ``ast.Attribute`` node with a matching name. + """ + return isinstance(node.func, ast.Attribute) and node.func.attr == name + + +def matches_namespace(node, namespace): + """Determines if the ``ast.Call`` node corresponds to a matching namespace. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + namespace (str): the namespace. + + Returns: + bool: if the node's namespaces matches the given namespace. + """ + names = namespace.split(".") + name, value = names.pop(), node.func.value + while isinstance(value, ast.Attribute) and len(names) > 0: + if value.attr != name: + return False + name, value = names.pop(), value.value + + return isinstance(value, ast.Name) and value.id == name + + +def has_arg(node, arg): + """Checks if the call has the given argument. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + arg (str): the name of the argument. + + Returns: + bool: if the node has the given argument. + """ + try: + return parsing.arg_value(node, arg) is not None + except KeyError: + return False diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/modifier.py b/src/sagemaker/cli/compatibility/v2/modifiers/modifier.py new file mode 100644 index 0000000000..3b5d47a412 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/modifier.py @@ -0,0 +1,36 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Abstract class for modifying AST nodes.""" +from __future__ import absolute_import + +from abc import abstractmethod + + +class Modifier(object): + """Abstract class to take in an AST node, check if it needs modification, + and potentially modify the node. + """ + + def check_and_modify_node(self, node): + """Check an AST node, and modify, replace, or remove it if applicable.""" + if self.node_should_be_modified(node): + node = self.modify_node(node) + return node + + @abstractmethod + def node_should_be_modified(self, node): + """Check if an AST node should be modified.""" + + @abstractmethod + def modify_node(self, node): + """Modify an AST node.""" diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/parsing.py b/src/sagemaker/cli/compatibility/v2/modifiers/parsing.py new file mode 100644 index 0000000000..02c33117e6 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/parsing.py @@ -0,0 +1,55 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Functions for parsing AST nodes.""" +from __future__ import absolute_import + +import pasta + + +def arg_from_keywords(node, arg): + """Retrieves a keyword argument from the node's keywords. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + arg (str): the name of the argument. + + Returns: + ast.keyword: the keyword argument if it is present. Otherwise, this returns ``None``. + """ + for kw in node.keywords: + if kw.arg == arg: + return kw + + return None + + +def arg_value(node, arg): + """Retrieves a keyword argument's value from the node's keywords. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + arg (str): the name of the argument. + + Returns: + obj: the keyword argument's value. + + Raises: + KeyError: if the node's keywords do not contain the argument. + """ + keyword = arg_from_keywords(node, arg) + if keyword is None: + raise KeyError("arg '{}' not found in call: {}".format(arg, pasta.dump(node))) + + return getattr(keyword.value, keyword.value._fields[0], None) if keyword.value else None diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/predictors.py b/src/sagemaker/cli/compatibility/v2/modifiers/predictors.py new file mode 100644 index 0000000000..90ade836a5 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/predictors.py @@ -0,0 +1,120 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to modify Predictor code to be compatible +with version 2.0 and later of the SageMaker Python SDK. +""" +from __future__ import absolute_import + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +BASE_PREDICTOR = "RealTimePredictor" +PREDICTORS = { + "FactorizationMachinesPredictor": ("sagemaker", "sagemaker.amazon.factorization_machines"), + "IPInsightsPredictor": ("sagemaker", "sagemaker.amazon.ipinsights"), + "KMeansPredictor": ("sagemaker", "sagemaker.amazon.kmeans"), + "KNNPredictor": ("sagemaker", "sagemaker.amazon.knn"), + "LDAPredictor": ("sagemaker", "sagemaker.amazon.lda"), + "LinearLearnerPredictor": ("sagemaker", "sagemaker.amazon.linear_learner"), + "NTMPredictor": ("sagemaker", "sagemaker.amazon.ntm"), + "PCAPredictor": ("sagemaker", "sagemaker.amazon.pca"), + "RandomCutForestPredictor": ("sagemaker", "sagemaker.amazon.randomcutforest"), + "RealTimePredictor": ("sagemaker", "sagemaker.predictor"), + "SparkMLPredictor": ("sagemaker.sparkml", "sagemaker.sparkml.model"), +} + + +class PredictorConstructorRefactor(Modifier): + """A class to refactor *Predictor class and refactor endpoint attribute.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a class of interest. + + This looks for the following calls: + + - ``sagemaker...`` + - ``sagemaker..`` + - ```` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a class of interest. + """ + return matching.matches_any(node, PREDICTORS) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to call ``Predictor`` instead. + + Also renames ``endpoint`` attribute to ``endpoint_name``. + + Args: + node (ast.Call): a node that represents a *Predictor constructor. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + _rename_class(node) + _rename_endpoint(node) + return node + + +def _rename_class(node): + """Renames the RealTimePredictor base class to Predictor""" + if matching.matches_name(node, BASE_PREDICTOR): + node.func.id = "Predictor" + elif matching.matches_attr(node, BASE_PREDICTOR): + node.func.attr = "Predictor" + + +def _rename_endpoint(node): + """Renames keyword endpoint argument to endpoint_name""" + for keyword in node.keywords: + if keyword.arg == "endpoint": + keyword.arg = "endpoint_name" + break + + +class PredictorImportFromRenamer(Modifier): + """A class to update import statements of ``RealTimePredictor``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports ``RealTimePredictor`` from the correct module. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the import statement imports ``RealTimePredictor`` from the correct module. + """ + return node.module in PREDICTORS[BASE_PREDICTOR] and any( + name.name == BASE_PREDICTOR for name in node.names + ) + + def modify_node(self, node): + """Changes the ``ast.ImportFrom`` node's name from ``RealTimePredictor`` to ``Predictor``. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for name in node.names: + if name.name == BASE_PREDICTOR: + name.name = "Predictor" + return node diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/renamed_params.py b/src/sagemaker/cli/compatibility/v2/modifiers/renamed_params.py new file mode 100644 index 0000000000..7a19bc6597 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/renamed_params.py @@ -0,0 +1,322 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to handle renames for version 2.0 and later of the SageMaker Python SDK.""" +from __future__ import absolute_import + +import ast +from abc import abstractmethod + +from sagemaker.cli.compatibility.v2.modifiers import matching, parsing +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + + +class ParamRenamer(Modifier): + """Abstract class to take in an AST node, check if it is a function call with + an argument that needs to be renamed, and rename the argument if needed. + """ + + @property + @abstractmethod + def calls_to_modify(self): + """A dictionary mapping function names to possible namespaces.""" + + @property + @abstractmethod + def old_param_name(self): + """The parameter name used in previous versions of the SageMaker Python SDK.""" + + @property + @abstractmethod + def new_param_name(self): + """The parameter name used in version 2.0 and later of the SageMaker Python SDK.""" + + def node_should_be_modified(self, node): + """Checks if the node matches any of the relevant functions and + contains the parameter to be renamed. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` matches the relevant function calls and + contains the parameter to be renamed. + """ + return matching.matches_any(node, self.calls_to_modify) and matching.has_arg( + node, self.old_param_name + ) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to rename the attribute. + + Args: + node (ast.Call): a node that represents the relevant function call. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + keyword = parsing.arg_from_keywords(node, self.old_param_name) + keyword.arg = self.new_param_name + return node + + +class MethodParamRenamer(ParamRenamer): + """Abstract class to handle parameter renames for methods that belong to objects. + + This differs from ``ParamRenamer`` in that a node for a standalone function call + (i.e. where ``node.func`` is an ``ast.Name`` rather than an ``ast.Attribute``) is not modified. + """ + + def node_should_be_modified(self, node): + """Checks if the node matches any of the relevant functions and + contains the parameter to be renamed. + + This looks for a call of the form ``.``, and + assumes the method cannot be called on its own. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` matches the relevant function calls and + contains the parameter to be renamed. + """ + if isinstance(node.func, ast.Name): + return False + + return super(MethodParamRenamer, self).node_should_be_modified(node) + + +class DistributionParameterRenamer(ParamRenamer): + """A class to rename the ``distributions`` attribute to ``distrbution`` in + MXNet and TensorFlow estimators. + + This looks for the following calls: + + - ```` + - ``sagemaker..`` + - ``sagemaker..estimator.`` + + where ```` is either ``TensorFlow`` or ``MXNet``. + """ + + @property + def calls_to_modify(self): + """A dictionary mapping ``MXNet`` and ``TensorFlow`` to their respective namespaces.""" + return { + "TensorFlow": ("sagemaker.tensorflow", "sagemaker.tensorflow.estimator"), + "MXNet": ("sagemaker.mxnet", "sagemaker.mxnet.estimator"), + } + + @property + def old_param_name(self): + """The previous name for the distribution argument.""" + return "distributions" + + @property + def new_param_name(self): + """The new name for the distribution argument.""" + return "distribution" + + +class S3SessionRenamer(MethodParamRenamer): + """A class to rename the ``session`` attribute to ``sagemaker_session`` in + ``S3Uploader`` and ``S3Downloader``. + + This looks for the following calls: + + - ``sagemaker.s3.S3Uploader.`` + - ``s3.S3Uploader.`` + - ``S3Uploader.`` + + where ``S3Uploader`` is either ``S3Uploader`` or ``S3Downloader``, and where + ```` is any of the functions belonging to those two classes. + """ + + @property + def calls_to_modify(self): + """A dictionary mapping S3 utility functions to their respective namespaces.""" + return { + "download": ("sagemaker.s3.S3Downloader", "s3.S3Downloader", "S3Downloader"), + "list": ("sagemaker.s3.S3Downloader", "s3.S3Downloader", "S3Downloader"), + "read_file": ("sagemaker.s3.S3Downloader", "s3.S3Downloader", "S3Downloader"), + "upload": ("sagemaker.s3.S3Uploader", "s3.S3Uploader", "S3Uploader"), + "upload_string_as_file_body": ( + "sagemaker.s3.S3Uploader", + "s3.S3Uploader", + "S3Uploader", + ), + } + + @property + def old_param_name(self): + """The previous name for the SageMaker session argument.""" + return "session" + + @property + def new_param_name(self): + """The new name for the SageMaker session argument.""" + return "sagemaker_session" + + +class EstimatorImageURIRenamer(ParamRenamer): + """A class to rename the ``image_name`` attribute to ``image_uri`` in estimators.""" + + @property + def calls_to_modify(self): + """A dictionary mapping estimators with the ``image_name`` attribute to their + respective namespaces. + """ + return { + "Chainer": ("sagemaker.chainer", "sagemaker.chainer.estimator"), + "Estimator": ("sagemaker.estimator",), + "Framework": ("sagemaker.estimator",), + "MXNet": ("sagemaker.mxnet", "sagemaker.mxnet.estimator"), + "PyTorch": ("sagemaker.pytorch", "sagemaker.pytorch.estimator"), + "RLEstimator": ("sagemaker.rl", "sagemaker.rl.estimator"), + "SKLearn": ("sagemaker.sklearn", "sagemaker.sklearn.estimator"), + "TensorFlow": ("sagemaker.tensorflow", "sagemaker.tensorflow.estimator"), + "XGBoost": ("sagemaker.xgboost", "sagemaker.xgboost.estimator"), + } + + @property + def old_param_name(self): + """The previous name for the image URI argument.""" + return "image_name" + + @property + def new_param_name(self): + """The new name for the image URI argument.""" + return "image_uri" + + +class ModelImageURIRenamer(ParamRenamer): + """A class to rename the ``image`` attribute to ``image_uri`` in models.""" + + @property + def calls_to_modify(self): + """A dictionary mapping models with the ``image`` attribute to their + respective namespaces. + """ + return { + "ChainerModel": ("sagemaker.chainer", "sagemaker.chainer.model"), + "Model": ("sagemaker.model",), + "MultiDataModel": ("sagemaker.multidatamodel",), + "FrameworkModel": ("sagemaker.model",), + "MXNetModel": ("sagemaker.mxnet", "sagemaker.mxnet.model"), + "PyTorchModel": ("sagemaker.pytorch", "sagemaker.pytorch.model"), + "SKLearnModel": ("sagemaker.sklearn", "sagemaker.sklearn.model"), + "TensorFlowModel": ("sagemaker.tensorflow", "sagemaker.tensorflow.model"), + "XGBoostModel": ("sagemaker.xgboost", "sagemaker.xgboost.model"), + } + + @property + def old_param_name(self): + """The previous name for the image URI argument.""" + return "image" + + @property + def new_param_name(self): + """The new name for the image URI argument.""" + return "image_uri" + + +class EstimatorCreateModelImageURIRenamer(MethodParamRenamer): + """A class to rename ``image`` to ``image_uri`` in estimator ``create_model()`` methods.""" + + @property + def calls_to_modify(self): + """A mapping of ``create_model`` to common variable names for estimators.""" + return { + "create_model": ( + "estimator", + "chainer", + "mxnet", + "mx", + "pytorch", + "rl", + "sklearn", + "tensorflow", + "tf", + "xgboost", + "xgb", + ) + } + + @property + def old_param_name(self): + """The previous name for the image URI argument.""" + return "image" + + @property + def new_param_name(self): + """The new name for the the image URI argument.""" + return "image_uri" + + +class SessionCreateModelImageURIRenamer(MethodParamRenamer): + """A class to rename ``primary_container_image`` to ``image_uri``. + + This looks for the following calls: + + - ``sagemaker_session.create_model_from_job()`` + - ``sess.create_model_from_job()`` + """ + + @property + def calls_to_modify(self): + """A mapping of ``create_model_from_job`` to common variable names for Session.""" + return { + "create_model_from_job": ("sagemaker_session", "sess"), + } + + @property + def old_param_name(self): + """The previous name for the image URI argument.""" + return "primary_container_image" + + @property + def new_param_name(self): + """The new name for the the image URI argument.""" + return "image_uri" + + +class SessionCreateEndpointImageURIRenamer(MethodParamRenamer): + """A class to rename ``deployment_image`` to ``image_uri``. + + This looks for the following calls: + + - ``sagemaker_session.endpoint_from_job()`` + - ``sess.endpoint_from_job()`` + - ``sagemaker_session.endpoint_from_model_data()`` + - ``sess.endpoint_from_model_data()`` + """ + + @property + def calls_to_modify(self): + """A mapping of the ``endpoint_from_*`` functions to common variable names for Session.""" + return { + "endpoint_from_job": ("sagemaker_session", "sess"), + "endpoint_from_model_data": ("sagemaker_session", "sess"), + } + + @property + def old_param_name(self): + """The previous name for the image URI argument.""" + return "deployment_image" + + @property + def new_param_name(self): + """The new name for the the image URI argument.""" + return "image_uri" diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/serde.py b/src/sagemaker/cli/compatibility/v2/modifiers/serde.py new file mode 100644 index 0000000000..83a173c392 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/serde.py @@ -0,0 +1,460 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to modify SerDe code to be compatibile with version 2.0 and later.""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +OLD_AMAZON_CLASS_NAMES = {"numpy_to_record_serializer", "record_deserializer"} +NEW_AMAZON_CLASS_NAMES = {"RecordSerializer", "RecordDeserializer"} +OLD_PREDICTOR_CLASS_NAMES = { + "_CsvSerializer", + "_JsonSerializer", + "_NpySerializer", + "_CsvDeserializer", + "BytesDeserializer", + "StringDeserializer", + "StreamDeserializer", + "_NumpyDeserializer", + "_JsonDeserializer", +} + +# The values are tuples so that the object can be passed to matching.matches_any. +OLD_CLASS_NAME_TO_NAMESPACES = { + class_name: ("sagemaker.predictor",) for class_name in OLD_PREDICTOR_CLASS_NAMES +} +OLD_CLASS_NAME_TO_NAMESPACES.update( + {class_name: ("sagemaker.amazon.common",) for class_name in OLD_AMAZON_CLASS_NAMES} +) + +# The values are tuples so that the object can be passed to matching.matches_any. +NEW_CLASS_NAME_TO_NAMESPACES = { + "CSVSerializer": ("sagemaker.serializers",), + "JSONSerializer": ("sagemaker.serializers",), + "NumpySerializer": ("sagemaker.serializers",), + "CSVDeserializer": ("sagemaker.deserializers",), + "BytesDeserializer": ("sagemaker.deserializers",), + "StringDeserializer": ("sagemaker.deserializers",), + "StreamDeserializer": ("sagemaker.deserializers",), + "NumpyDeserializer": ("sagemaker.deserializers",), + "JSONDeserializer": ("sagemaker.deserializers",), + "RecordSerializer ": ("sagemaker.amazon.common",), + "RecordDeserializer": ("sagemaker.amazon.common",), +} + +OLD_CLASS_NAME_TO_NEW_CLASS_NAME = { + "_CsvSerializer": "CSVSerializer", + "_JsonSerializer": "JSONSerializer", + "_NpySerializer": "NumpySerializer", + "_CsvDeserializer": "CSVDeserializer", + "BytesDeserializer": "BytesDeserializer", + "StringDeserializer": "StringDeserializer", + "StreamDeserializer": "StreamDeserializer", + "_NumpyDeserializer": "NumpyDeserializer", + "_JsonDeserializer": "JSONDeserializer", + "numpy_to_record_serializer": "RecordSerializer", + "record_deserializer": "RecordDeserializer", +} + +OLD_OBJECT_NAME_TO_NEW_CLASS_NAME = { + "csv_serializer": "CSVSerializer", + "json_serializer": "JSONSerializer", + "npy_serializer": "NumpySerializer", + "csv_deserializer": "CSVDeserializer", + "json_deserializer": "JSONDeserializer", + "numpy_deserializer": "NumpyDeserializer", +} + +NEW_CLASS_NAMES = set(OLD_CLASS_NAME_TO_NEW_CLASS_NAME.values()) +OLD_CLASS_NAMES = set(OLD_CLASS_NAME_TO_NEW_CLASS_NAME.keys()) + +OLD_OBJECT_NAMES = set(OLD_OBJECT_NAME_TO_NEW_CLASS_NAME.keys()) + + +class SerdeConstructorRenamer(Modifier): + """A class to rename SerDe classes.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a SerDe class. + + This looks for the following calls (both with and without namespaces): + + - ``sagemaker.predictor._CsvSerializer`` + - ``sagemaker.predictor._JsonSerializer`` + - ``sagemaker.predictor._NpySerializer`` + - ``sagemaker.predictor._CsvDeserializer`` + - ``sagemaker.predictor.BytesDeserializer`` + - ``sagemaker.predictor.StringDeserializer`` + - ``sagemaker.predictor.StreamDeserializer`` + - ``sagemaker.predictor._NumpyDeserializer`` + - ``sagemaker.predictor._JsonDeserializer`` + - ``sagemaker.amazon.common.numpy_to_record_serializer`` + - ``sagemaker.amazon.common.record_deserializer`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a SerDe class. + """ + return matching.matches_any(node, OLD_CLASS_NAME_TO_NAMESPACES) + + def modify_node(self, node): + """Updates the name and namespace of the ``ast.Call`` node, as applicable. + + This method modifies the ``ast.Call`` node to use the SerDe classes + available in version 2.0 and later of the Python SDK: + + - ``sagemaker.serializers.CSVSerializer`` + - ``sagemaker.serializers.JSONSerializer`` + - ``sagemaker.serializers.NumpySerializer`` + - ``sagemaker.deserializers.CSVDeserializer`` + - ``sagemaker.deserializers.BytesDeserializer`` + - ``sagemaker.deserializers.StringDeserializer`` + - ``sagemaker.deserializers.StreamDeserializer`` + - ``sagemaker.deserializers.NumpyDeserializer`` + - ``sagemaker.deserializers._JsonDeserializer`` + - ``sagemaker.amazon.common.RecordSerializer`` + - ``sagemaker.amazon.common.RecordDeserializer`` + + Args: + node (ast.Call): a node that represents a SerDe constructor. + + Returns: + ast.Call: a node that represents the instantiation of a SerDe object. + """ + class_name = node.func.id if isinstance(node.func, ast.Name) else node.func.attr + new_class_name = OLD_CLASS_NAME_TO_NEW_CLASS_NAME[class_name] + + # We don't change the namespace for Amazon SerDe. + if class_name in OLD_AMAZON_CLASS_NAMES: + if isinstance(node.func, ast.Name): + node.func.id = new_class_name + elif isinstance(node.func, ast.Attribute): + node.func.attr = new_class_name + return node + + namespace_name = NEW_CLASS_NAME_TO_NAMESPACES[new_class_name][0] + subpackage_name = namespace_name.split(".")[1] + return ast.Call( + func=ast.Attribute(value=ast.Name(id=subpackage_name), attr=new_class_name), + args=[], + keywords=[], + ) + + +class SerdeKeywordRemover(Modifier): + """A class to remove Serde-related keyword arguments from call expressions.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node uses deprecated keywords. + + In particular, this function checks if: + + - The ``ast.Call`` represents the ``create_model`` method. + - Either the serializer or deserializer keywords are used. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` contains keywords that should be removed. + """ + if not isinstance(node.func, ast.Attribute) or node.func.attr != "create_model": + return False + return any(keyword.arg in {"serializer", "deserializer"} for keyword in node.keywords) + + def modify_node(self, node): + """Removes the serializer and deserializer keywords, as applicable. + + Args: + node (ast.Call): a node that represents a ``create_model`` call. + + Returns: + ast.Call: the node that represents a ``create_model`` call without + serializer or deserializers keywords. + """ + i = 0 + while i < len(node.keywords): + keyword = node.keywords[i] + if keyword.arg in {"serializer", "deserializer"}: + node.keywords.pop(i) + else: + i += 1 + return node + + +class SerdeObjectRenamer(Modifier): + """A class to rename SerDe objects imported from ``sagemaker.predictor``.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Name`` node identifies a SerDe object. + + This looks for the following objects: + + - ``sagemaker.predictor.csv_serializer`` + - ``sagemaker.predictor.json_serializer`` + - ``sagemaker.predictor.npy_serializer`` + - ``sagemaker.predictor.csv_deserializer`` + - ``sagemaker.predictor.json_deserializer`` + - ``sagemaker.predictor.numpy_deserializer`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a SerDe class. + """ + name = node.id if isinstance(node, ast.Name) else node.attr + return name in OLD_OBJECT_NAMES + + def modify_node(self, node): + """Replaces the ``ast.Name`` node with a ``ast.Call`` node that + instantiates a class available in version 2.0 and later of the Python SDK: + + - ``sagemaker.serializers.CSVSerializer()`` + - ``sagemaker.serializers.JSONSerializer()`` + - ``sagemaker.serializers.NumpySerializer()`` + - ``sagemaker.deserializers.CSVDeserializer()`` + - ``sagemaker.deserializers.JSONDeserializer()`` + - ``sagemaker.deserializers.NumpyDeserializer()`` + + The ``sagemaker`` prefix is excluded from the returned node. + + Args: + node (ast.Name): a node that represents a Python identifier. + + Returns: + ast.Call: a node that represents the instantiation of a SerDe object. + """ + object_name = node.id if isinstance(node, ast.Name) else node.attr + new_class_name = OLD_OBJECT_NAME_TO_NEW_CLASS_NAME[object_name] + namespace_name = NEW_CLASS_NAME_TO_NAMESPACES[new_class_name][0] + subpackage_name = namespace_name.split(".")[1] + return ast.Call( + func=ast.Attribute(value=ast.Name(id=subpackage_name), attr=new_class_name), + args=[], + keywords=[], + ) + + +class SerdeImportFromPredictorRenamer(Modifier): + """A class to update import statements starting with ``from sagemaker.predictor``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports a SerDe from the + ``sagemaker.predictor`` module. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: True if and only if the ``ast.ImportFrom`` imports a SerDe + from the ``sagemaker.predictor`` module. + """ + return node.module == "sagemaker.predictor" and any( + name.name in (OLD_CLASS_NAMES | OLD_OBJECT_NAMES) for name in node.names + ) + + def modify_node(self, node): + """Removes the imported SerDe classes, as applicable. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.ImportFrom: a node that represents a import statement, which has + been modified to remove imported serializers. If nothing is + imported, None is returned. + """ + i = 0 + while i < len(node.names): + name = node.names[i].name + if name in OLD_CLASS_NAMES | OLD_OBJECT_NAMES: + node.names.pop(i) + else: + i += 1 + + return node if node.names else None + + +class SerdeImportFromAmazonCommonRenamer(Modifier): + """A class to update import statements starting with ``from sagemaker.amazon.common``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports a SerDe from the + ``sagemaker.amazon.common`` module. + + This checks for: + - ``sagemaker.amazon.common.numpy_to_record_serializer`` + - ``sagemaker.amazon.common.record_deserializer`` + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: True if and only if the ``ast.ImportFrom`` imports a SerDe from + the ``sagemaker.amazon.common`` module. + """ + return node.module == "sagemaker.amazon.common" and any( + alias.name in OLD_AMAZON_CLASS_NAMES for alias in node.names + ) + + def modify_node(self, node): + """Upgrades the ``numpy_to_record_serializer`` and ``record_deserializer`` + imports, as applicable. + + This upgrades the classes to: + - ``sagemaker.amazon.common.RecordSerializer`` + - ``sagemaker.amazon.common.RecordDeserializer`` + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.ImportFrom: a node that represents a import statement, which has + been modified to import the upgraded class name. + """ + for alias in node.names: + if alias.name in OLD_AMAZON_CLASS_NAMES: + alias.name = OLD_CLASS_NAME_TO_NEW_CLASS_NAME[alias.name] + return node + + +class _ImportInserter(Modifier): + """A class to insert import statements into the Python module.""" + + def __init__(self, class_names, import_node): + """Initialize the ``class_names`` and ``import_node`` attributes. + + Args: + class_names (set): If any of these class names are referenced in the + module, then ``import_node`` is inserted. + import_node (ast.ImportFrom): The AST node to insert. + """ + self.class_names = class_names + self.import_node = import_node + + def node_should_be_modified(self, module): + """Checks if the ``ast.Module`` node contains references to the + specified class names. + + Args: + node (ast.Module): a node that represents a Python module. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Module`` references one of the specified classes. + """ + for node in ast.walk(module): + if isinstance(node, ast.Name) and node.id in self.class_names: + return True + if isinstance(node, ast.Attribute) and node.attr in self.class_names: + return True + return False + + def modify_node(self, module): + """Modifies the ``ast.Module`` node by inserted the specified node. + + The ``import_node`` object is inserted immediately before the first + import statement. + + Args: + node (ast.Module): a node that represents a Python module. + + Returns: + ast.Module: a node that represents a Python module, which has been + modified to import a module. + """ + for i, node in enumerate(module.body): + if isinstance(node, (ast.Import, ast.ImportFrom)): + module.body.insert(i, self.import_node) + return module + + module.body.insert(0, self.import_node) + return module + + +class SerializerImportInserter(_ImportInserter): + """A class to import the ``sagemaker.serializers`` module, if necessary. + + This looks for references to the following classes: + + - ``sagemaker.serializers.CSVSerializer`` + - ``sagemaker.serializers.JSONSerializer`` + - ``sagemaker.serializer.NumpySerializer`` + + Because ``SerializerImportInserter`` is gauranteed to run after + ``SerdeConstructorRenamer`` and ``SerdeObjectRenamer``, + we only need to check for the new serializer class names. + """ + + def __init__(self): + """Initialize the ``class_names`` and ``import_node`` attributes. + + Amazon-specific serializers are ignored because they are not defined in + the ``sagemaker.serializers`` module. + """ + class_names = { + class_name + for class_name in NEW_CLASS_NAMES - NEW_AMAZON_CLASS_NAMES + if "Serializer" in class_name + } + import_node = ast.ImportFrom( + module="sagemaker", names=[ast.alias(name="serializers", asname=None)], level=0 + ) + super().__init__(class_names, import_node) + + +class DeserializerImportInserter(_ImportInserter): + """A class to import the ``sagemaker.deserializers`` module, if necessary. + + This looks for references to the following classes: + + - ``sagemaker.serializers.CSVDeserializer`` + - ``sagemaker.serializers.BytesDeserializer`` + - ``sagemaker.serializers.StringDeserializer`` + - ``sagemaker.serializers.StreamDeserializer`` + - ``sagemaker.serializers.NumpyDeserializer`` + - ``sagemaker.serializer.JSONDeserializer`` + + Because ``DeserializerImportInserter`` is gauranteed to run after + ``SerdeConstructorRenamer`` and ``SerdeObjectRenamer``, + we only need to check for the new deserializer class names. + """ + + def __init__(self): + """Initialize the ``class_names`` and ``import_node`` attributes. + + Amazon-specific deserializers are ignored because they are not defined + in the ``sagemaker.deserializers`` module. + """ + class_names = { + class_name + for class_name in NEW_CLASS_NAMES - NEW_AMAZON_CLASS_NAMES + if "Deserializer" in class_name + } + import_node = ast.ImportFrom( + module="sagemaker", names=[ast.alias(name="deserializers", asname=None)], level=0 + ) + super().__init__(class_names, import_node) diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/tf_legacy_mode.py b/src/sagemaker/cli/compatibility/v2/modifiers/tf_legacy_mode.py new file mode 100644 index 0000000000..2852d0ecdc --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/tf_legacy_mode.py @@ -0,0 +1,224 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to modify TensorFlow legacy mode code to be compatible +with version 2.0 and later of the SageMaker Python SDK. +""" +from __future__ import absolute_import + +import ast + +import boto3 +import six + +from sagemaker.cli.compatibility.v2.modifiers import framework_version, matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier +from sagemaker import image_uris + +TF_NAMESPACES = ("sagemaker.tensorflow", "sagemaker.tensorflow.estimator") +LEGACY_MODE_PARAMETERS = ( + "checkpoint_path", + "evaluation_steps", + "requirements_file", + "training_steps", +) + + +class TensorFlowLegacyModeConstructorUpgrader(Modifier): + """A class to turn legacy mode parameters into hyperparameters, disable the ``model_dir`` + hyperparameter, and set the image URI when instantiating a TensorFlow estimator. + """ + + def __init__(self): + """Initializes a ``TensorFlowLegacyModeConstructorUpgrader``.""" + self._region = None + + @property + def region(self): + """Returns the AWS region for constructing an ECR image URI.""" + if self._region is None: + self._region = boto3.Session().region_name + + return self._region + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a TensorFlow estimator with legacy mode. + + This looks for the following formats: + + - ``TensorFlow`` + - ``sagemaker.tensorflow.TensorFlow`` + - ``sagemaker.tensorflow.estimator.TensorFlow`` + + Legacy mode is enabled if (1) ``script_mode`` is ``False``, ``None``, or not specified, + and (2) if ``py_version`` is ``py2`` or not specified. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` is instantiating a TensorFlow estimator with legacy mode. + """ + is_tf_constructor = matching.matches_name_or_namespaces(node, "TensorFlow", TF_NAMESPACES) + return is_tf_constructor and self._is_legacy_mode(node) + + def _is_legacy_mode(self, node): + """Checks if the ``ast.Call`` node's keywords signal using legacy mode.""" + script_mode = False + py_version = "py2" + + for kw in node.keywords: + if kw.arg == "script_mode": + script_mode = ( + bool(kw.value.value) if isinstance(kw.value, ast.NameConstant) else True + ) + if kw.arg == "py_version": + py_version = kw.value.s if isinstance(kw.value, ast.Str) else "py3" + + return not (py_version.startswith("py3") or script_mode) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node's keywords to turn TensorFlow legacy mode parameters + into hyperparameters and sets ``model_dir=False``. + + The parameters that are converted into hyperparameters: + + - ``training_steps`` + - ``evaluation_steps`` + - ``checkpoint_path`` + - ``requirements_file`` + + Args: + node (ast.Call): a node that represents a TensorFlow constructor. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + base_hps = {} + additional_hps = {} + kw_to_remove = [] # remove keyword args after so that none are skipped during iteration + + add_image_uri = True + + for kw in node.keywords: + if kw.arg in ("script_mode", "model_dir"): + # model_dir is removed so that it can be set to False later + kw_to_remove.append(kw) + if kw.arg == "hyperparameters" and kw.value: + base_hps = dict(zip(kw.value.keys, kw.value.values)) + kw_to_remove.append(kw) + if kw.arg in LEGACY_MODE_PARAMETERS and kw.value: + hp_key = self._hyperparameter_key_for_param(kw.arg) + additional_hps[hp_key] = kw.value + kw_to_remove.append(kw) + if kw.arg == "image_uri": + add_image_uri = False + + self._remove_keywords(node, kw_to_remove) + self._add_updated_hyperparameters(node, base_hps, additional_hps) + + if add_image_uri: + image_uri = self._image_uri_from_args(node.keywords) + if image_uri: + node.keywords.append(ast.keyword(arg="image_uri", value=ast.Str(s=image_uri))) + + node.keywords.append(ast.keyword(arg="model_dir", value=ast.NameConstant(value=False))) + return node + + def _hyperparameter_key_for_param(self, arg): + """Returns an ``ast.Str`` for a hyperparameter key replacing a legacy mode parameter.""" + name = "sagemaker_requirements" if arg == "requirements_file" else arg + return ast.Str(s=name) + + def _remove_keywords(self, node, keywords): + """Removes the keywords from the ``ast.Call`` node.""" + for kw in keywords: + node.keywords.remove(kw) + + def _add_updated_hyperparameters(self, node, base_hps, additional_hps): + """Combines and adds the hyperparameters to the ``ast.Call`` node's keywords.""" + base_hps.update(additional_hps) + updated_hp_keyword = self._to_ast_keyword(base_hps) + + if updated_hp_keyword: + node.keywords.append(updated_hp_keyword) + + def _to_ast_keyword(self, hps): + """Returns an ``ast.keyword`` for the ``hyperparameters`` kwarg if there are any.""" + if hps: + keys, values = zip(*six.iteritems(hps)) + return ast.keyword(arg="hyperparameters", value=ast.Dict(keys=keys, values=values)) + + return None + + def _image_uri_from_args(self, keywords): + """Returns a legacy TensorFlow image URI based on the estimator arguments if possible.""" + tf_version = framework_version.FRAMEWORK_DEFAULTS["TensorFlow"] + instance_type = "ml.m4.xlarge" # CPU default (exact type doesn't matter) + + for kw in keywords: + if kw.arg == "framework_version": + tf_version = kw.value.s if isinstance(kw.value, ast.Str) else None + if kw.arg == "train_instance_type": + instance_type = kw.value.s if isinstance(kw.value, ast.Str) else None + + if tf_version and instance_type: + return image_uris.retrieve( + "tensorflow", + self.region, + version=tf_version, + py_version="py2", + instance_type=instance_type, + image_scope="training", + ).replace("-scriptmode", "") + + return None + + +class TensorBoardParameterRemover(Modifier): + """A class for removing the ``run_tensorboard_locally`` parameter from ``fit()``.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node invokes a function named "fit" and + contains a keyword argument named "run_tensorboard_locally". + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` is invoking a function named "fit" with + a parameter named "run_tensorboard_locally". + """ + is_fit_call = isinstance(node.func, ast.Attribute) and node.func.attr == "fit" + if is_fit_call: + for kw in node.keywords: + if kw.arg == "run_tensorboard_locally": + return True + + return False + + def modify_node(self, node): + """Removes ``run_tensorboard_locally`` from the ``ast.Call`` node's keywords. + + Args: + node (ast.Call): a node that represents ``fit`` being called with + ``run_tensorboard_locally`` set. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for kw in node.keywords: + if kw.arg == "run_tensorboard_locally": + node.keywords.remove(kw) + return node diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/tfs.py b/src/sagemaker/cli/compatibility/v2/modifiers/tfs.py new file mode 100644 index 0000000000..5c80fcb2d4 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/tfs.py @@ -0,0 +1,130 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to modify TensorFlow Serving code to be compatible +with version 2.0 and later of the SageMaker Python SDK. +""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +CLASS_NAMES = ("Model", "Predictor") +TFS_CLASSES = {name: ("sagemaker.tensorflow.serving",) for name in CLASS_NAMES} + + +class TensorFlowServingConstructorRenamer(Modifier): + """A class to rename TensorFlow Serving classes.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a TensorFlow Serving class. + + This looks for the following calls: + + - ``sagemaker.tensorflow.serving.Model`` + - ``sagemaker.tensorflow.serving.Predictor`` + - ``Predictor`` + + Because ``Model`` can refer to either ``sagemaker.tensorflow.serving.Model`` + or :class:`~sagemaker.model.Model`, ``Model`` on its own is not sufficient + for indicating a TFS Model object. + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a TensorFlow Serving class. + """ + if isinstance(node.func, ast.Name): + return node.func.id == "Predictor" + + return matching.matches_any(node, TFS_CLASSES) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to use the classes for TensorFlow Serving available in + version 2.0 and later of the Python SDK: + + - ``sagemaker.tensorflow.TensorFlowModel`` + - ``sagemaker.tensorflow.TensorFlowPredictor`` + + Args: + node (ast.Call): a node that represents a TensorFlow Serving constructor. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + if isinstance(node.func, ast.Name): + node.func.id = self._new_cls_name(node.func.id) + else: + node.func.attr = self._new_cls_name(node.func.attr) + node.func.value = node.func.value.value + return node + + def _new_cls_name(self, cls_name): + """Returns the updated class name.""" + return "TensorFlow{}".format(cls_name) + + +class TensorFlowServingImportFromRenamer(Modifier): + """A class to update import statements starting with ``from sagemaker.tensorflow.serving``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports from the ``sagemaker.tensorflow.serving`` module. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.ImportFrom`` uses the ``sagemaker.tensorflow.serving`` module. + """ + return node.module == "sagemaker.tensorflow.serving" + + def modify_node(self, node): + """Changes the ``ast.ImportFrom`` node's module to ``sagemaker.tensorflow`` and updates the + imported class names to ``TensorFlowModel`` and ``TensorFlowPredictor``, as applicable. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + node.module = "sagemaker.tensorflow" + + for cls in node.names: + cls.name = "TensorFlow{}".format(cls.name) + return node + + +class TensorFlowServingImportRenamer(Modifier): + """A class to update ``import sagemaker.tensorflow.serving``.""" + + def check_and_modify_node(self, node): + """Checks if the ``ast.Import`` node imports the ``sagemaker.tensorflow.serving`` module + and, if so, changes it to ``sagemaker.tensorflow``. + + Args: + node (ast.Import): a node that represents an import statement. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for module in node.names: + if module.name == "sagemaker.tensorflow.serving": + module.name = "sagemaker.tensorflow" + return node diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/training_input.py b/src/sagemaker/cli/compatibility/v2/modifiers/training_input.py new file mode 100644 index 0000000000..b181cdf43e --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/training_input.py @@ -0,0 +1,172 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to modify TrainingInput code to be compatible +with version 2.0 and later of the SageMaker Python SDK. +""" +from __future__ import absolute_import + +import ast + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +S3_INPUT_NAME = "s3_input" +S3_INPUT_NAMESPACES = ("sagemaker", "sagemaker.inputs", "sagemaker.session") + + +class TrainingInputConstructorRefactor(Modifier): + """A class to refactor *s3_input class.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a class of interest. + + This looks for the following calls: + + - ``sagemaker.s3_input`` + - ``sagemaker.session.s3_input`` + - ``s3_input`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a class of interest. + """ + return matching.matches_name_or_namespaces(node, S3_INPUT_NAME, S3_INPUT_NAMESPACES) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to call ``TrainingInput`` instead. + + Args: + node (ast.Call): a node that represents a *TrainingInput constructor. + """ + if matching.matches_name(node, S3_INPUT_NAME): + node.func.id = "TrainingInput" + elif matching.matches_attr(node, S3_INPUT_NAME): + node.func.attr = "TrainingInput" + _rename_namespace(node, "session") + return node + + +def _rename_namespace(node, name): + """Rename namespace ``session`` to ``inputs`` """ + if isinstance(node.func.value, ast.Attribute) and node.func.value.attr == name: + node.func.value.attr = "inputs" + elif isinstance(node.func.value, ast.Name) and node.func.value.id == name: + node.func.value.id = "inputs" + + +class TrainingInputImportFromRenamer(Modifier): + """A class to update import statements of ``s3_input``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports ``s3_input`` from the correct module. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the import statement imports ``s3_input`` from the correct module. + """ + return node.module in S3_INPUT_NAMESPACES and any( + name.name == S3_INPUT_NAME for name in node.names + ) + + def modify_node(self, node): + """Changes the ``ast.ImportFrom`` node's name from ``s3_input`` to ``TrainingInput``. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for name in node.names: + if name.name == S3_INPUT_NAME: + name.name = "TrainingInput" + if node.module == "sagemaker.session": + node.module = "sagemaker.inputs" + return node + + +class ShuffleConfigModuleRenamer(Modifier): + """A class to change ``ShuffleConfig`` usage to use ``sagemaker.inputs.ShuffleConfig``.""" + + def node_should_be_modified(self, node): + """Checks if the ``ast.Call`` node instantiates a class of interest. + + This looks for the following calls: + + - ``sagemaker.session.ShuffleConfig`` + - ``session.ShuffleConfig`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` instantiates a class of interest. + """ + if isinstance(node.func, ast.Name): + return False + + return matching.matches_name_or_namespaces( + node, "ShuffleConfig", ("sagemaker.session", "session") + ) + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to call ``sagemaker.inputs.ShuffleConfig``. + + Args: + node (ast.Call): a node that represents a ``sagemaker.session.ShuffleConfig`` + constructor. + + Returns: + ast.Call: the original node, with its namespace changed to use the ``inputs`` module. + """ + _rename_namespace(node, "session") + return node + + +class ShuffleConfigImportFromRenamer(Modifier): + """A class to update import statements of ``ShuffleConfig``.""" + + def node_should_be_modified(self, node): + """Checks if the import statement imports ``sagemaker.session.ShuffleConfig``. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the import statement imports ``sagemaker.session.ShuffleConfig``. + """ + return node.module == "sagemaker.session" and any( + name.name == "ShuffleConfig" for name in node.names + ) + + def modify_node(self, node): + """Changes the ``ast.ImportFrom`` node's namespace to ``sagemaker.inputs``. + + Args: + node (ast.ImportFrom): a node that represents a ``from ... import ... `` statement. + For more, see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + ast.ImportFrom: the original node, with its module modified to ``"sagemaker.inputs"``. + """ + node.module = "sagemaker.inputs" + return node diff --git a/src/sagemaker/cli/compatibility/v2/modifiers/training_params.py b/src/sagemaker/cli/compatibility/v2/modifiers/training_params.py new file mode 100644 index 0000000000..48ddf14201 --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/modifiers/training_params.py @@ -0,0 +1,101 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Classes to handle training renames for version 2.0 and later of the SageMaker Python SDK.""" +from __future__ import absolute_import + +from sagemaker.cli.compatibility.v2.modifiers import matching +from sagemaker.cli.compatibility.v2.modifiers.modifier import Modifier + +ESTIMATORS = { + "AlgorithmEstimator": ("sagemaker", "sagemaker.algorithm"), + "AmazonAlgorithmEstimatorBase": ("sagemaker.amazon.amazon_estimator",), + "Chainer": ("sagemaker.chainer", "sagemaker.chainer.estimator"), + "Estimator": ("sagemaker.estimator",), + "EstimatorBase": ("sagemaker.estimator",), + "FactorizationMachines": ("sagemaker", "sagemaker.amazon.factorization_machines"), + "Framework": ("sagemaker.estimator",), + "IPInsights": ("sagemaker", "sagemaker.amazon.ipinsights"), + "KMeans": ("sagemaker", "sagemaker.amazon.kmeans"), + "KNN": ("sagemaker", "sagemaker.amazon.knn"), + "LDA": ("sagemaker", "sagemaker.amazon.lda"), + "LinearLearner": ("sagemaker", "sagemaker.amazon.linear_learner"), + "MXNet": ("sagemaker.mxnet", "sagemaker.mxnet.estimator"), + "NTM": ("sagemaker", "sagemaker.amazon.ntm"), + "Object2Vec": ("sagemaker", "sagemaker.amazon.object2vec"), + "PCA": ("sagemaker", "sagemaker.amazon.pca"), + "PyTorch": ("sagemaker.pytorch", "sagemaker.pytorch.estimator"), + "RandomCutForest": ("sagemaker", "sagemaker.amazon.randomcutforest"), + "RLEstimator": ("sagemaker.rl", "sagemaker.rl.estimator"), + "SKLearn": ("sagemaker.sklearn", "sagemaker.sklearn.estimator"), + "TensorFlow": ("sagemaker.tensorflow", "sagemaker.tensorflow.estimator"), + "XGBoost": ("sagemaker.xgboost", "sagemaker.xgboost.estimator"), +} + +PARAMS = ( + "train_instance_count", + "train_instance_type", + "train_max_run", + "train_max_run_wait", + "train_use_spot_instances", + "train_volume_size", + "train_volume_kms_key", +) + + +class TrainPrefixRemover(Modifier): + """A class to remove the redundant 'train' prefix in estimator parameters.""" + + def node_should_be_modified(self, node): + """Checks if the node is an estimator constructor and contains any relevant parameters. + + This looks for the following parameters: + + - ``train_instance_count`` + - ``train_instance_type`` + - ``train_max_run`` + - ``train_max_run_wait`` + - ``train_use_spot_instances`` + - ``train_volume_kms_key`` + - ``train_volume_size`` + + Args: + node (ast.Call): a node that represents a function call. For more, + see https://docs.python.org/3/library/ast.html#abstract-grammar. + + Returns: + bool: If the ``ast.Call`` matches the relevant function calls and + contains the parameter to be renamed. + """ + return matching.matches_any(node, ESTIMATORS) and self._has_train_parameter(node) + + def _has_train_parameter(self, node): + """Checks if at least one of the node's keywords is prefixed with 'train'.""" + for kw in node.keywords: + if kw.arg in PARAMS: + return True + + return False + + def modify_node(self, node): + """Modifies the ``ast.Call`` node to remove the 'train' prefix from its keywords. + + Args: + node (ast.Call): a node that represents an estimator constructor. + + Returns: + ast.AST: the original node, which has been potentially modified. + """ + for kw in node.keywords: + if kw.arg in PARAMS: + kw.arg = kw.arg.replace("train_", "") + return node diff --git a/src/sagemaker/cli/compatibility/v2/sagemaker_upgrade_v2.py b/src/sagemaker/cli/compatibility/v2/sagemaker_upgrade_v2.py new file mode 100644 index 0000000000..e72fc8226f --- /dev/null +++ b/src/sagemaker/cli/compatibility/v2/sagemaker_upgrade_v2.py @@ -0,0 +1,78 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""A tool to upgrade SageMaker Python SDK code to be compatible with version 2.0 and later.""" +from __future__ import absolute_import + +import argparse +import os + +from sagemaker.cli.compatibility.v2 import files + +_EXT_TO_UPDATER_CLS = {".py": files.PyFileUpdater, ".ipynb": files.JupyterNotebookFileUpdater} + + +def _update_file(input_file, output_file): + """Updates a file to be compatible with version 2.0 and later of the SageMaker Python SDK, + and write the updated source to the output file. + + Args: + input_file (str): The path to the file to be updated. + output_file (str): The output file destination. + + Raises: + ValueError: If the input and output filename extensions don't match, + or if the file extensions are neither ".py" nor ".ipynb". + """ + input_file_ext = os.path.splitext(input_file)[1] + output_file_ext = os.path.splitext(output_file)[1] + + if input_file_ext != output_file_ext: + raise ValueError( + "Mismatched file extensions: input: {}, output: {}".format( + input_file_ext, output_file_ext + ) + ) + + if input_file_ext not in _EXT_TO_UPDATER_CLS: + raise ValueError("Unrecognized file extension: {}".format(input_file_ext)) + + updater_cls = _EXT_TO_UPDATER_CLS[input_file_ext] + updater_cls(input_path=input_file, output_path=output_file).update() + + +def _parse_args(): + """Parses CLI arguments.""" + parser = argparse.ArgumentParser( + description="A tool to convert files to be compatible with " + "version 2.0 and later of the SageMaker Python SDK. " + "Simple usage: sagemaker-upgrade-v2 --in-file foo.py --out-file bar.py" + ) + parser.add_argument( + "--in-file", + help="If converting a single file, the file to convert. The file's extension " + "must be either '.py' or '.ipynb'.", + ) + parser.add_argument( + "--out-file", + help="If converting a single file, the output file destination. The file's extension " + "must be either '.py' or '.ipynb'. If needed, directories in the output path are created. " + "If the output file already exists, it is overwritten.", + ) + + return parser.parse_args() + + +def main(): + """Parses the CLI arguments and executes the file update.""" + args = _parse_args() + _update_file(args.in_file, args.out_file) diff --git a/src/sagemaker/cli/framework_upgrade.py b/src/sagemaker/cli/framework_upgrade.py new file mode 100644 index 0000000000..81247c5325 --- /dev/null +++ b/src/sagemaker/cli/framework_upgrade.py @@ -0,0 +1,251 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""A Python script to upgrade framework versions and regions""" +from __future__ import absolute_import + +import argparse +import json +import os + +from sagemaker.image_uris import config_for_framework + +IMAGE_URI_CONFIG_DIR = os.path.join("..", "image_uri_config") + + +def get_latest_values(existing_content, scope=None): + """Get the latest "registries", "py_versions" and "repository" values + + Args: + existing_content (dict): Dictionary of complete framework image information. + scope (str): Type of the image, required if the target is DLC + framework (Default: None). + """ + if scope in existing_content: + existing_content = existing_content[scope] + else: + if "versions" not in existing_content: + raise ValueError( + "Invalid image scope: {}. Valid options: {}.".format( + scope, ", ".join(existing_content.key()) + ) + ) + + latest_version = list(existing_content["versions"].keys())[-1] + registries = existing_content["versions"][latest_version].get("registries", None) + py_versions = existing_content["versions"][latest_version].get("py_versions", None) + repository = existing_content["versions"][latest_version].get("repository", None) + + return registries, py_versions, repository + + +def _write_dict_to_json(filename, existing_content): + """Write a Python dictionary to a json file. + + Args: + filename (str): Name of the target json file. + existing_content (dict): Dictionary to be written to the json file. + """ + with open(filename, "w") as f: + json.dump(existing_content, f, sort_keys=True, indent=4) + + +def add_dlc_framework_version( + existing_content, + short_version, + full_version, + scope, + processors, + py_versions, + registries, + repository, +): + """Update DLC framework image uri json file with new version information. + + Args: + existing_content (dict): Existing framework image uri information read from + ".json" file. + framework (str): Framework name (e.g. tensorflow, pytorch, mxnet) + short_version (str): Abbreviated framework version (e.g. 1.0, 1.5) + full_version (str): Complete framework version (e.g. 1.0.0, 1.5.2) + scope (str): Framework image type, it could be "training", "inference" + or "eia" + processors (list): Supported processors (e.g. ["cpu", "gpu"]) + py_versions (list): Supported Python versions (e.g. ["py3", "py37"]) + registries (dict): Framework image's region to account mapping. + repository (str): Framework image's ECR repository. + """ + for processor in processors: + if processor not in existing_content[scope]["processors"]: + existing_content[scope]["processors"].append(processor) + existing_content[scope]["version_aliases"][short_version] = full_version + + new_version = { + "registries": registries, + "repository": repository, + } + if py_versions: + new_version["py_versions"] = py_versions + existing_content[scope]["versions"][full_version] = new_version + + +def add_algo_version( + existing_content, + processors, + scopes, + full_version, + py_versions, + registries, + repository, + tag_prefix, +): + """Update Algorithm image uri json file with new version information. + + Args: + existing_content (dict): Existing algorithm image uri information read from + ".json" file. + processors (list): Supported processors (e.g. ["cpu", "gpu"]) + scopes (list): Framework image type, it could be "training", "inference + full_version (str): Complete framework version (e.g. 1.0.0, 1.5.2) + py_versions (list): Supported Python versions (e.g. ["py3", "py37"]) + registries (dict): Algorithm image's region to account mapping. + repository (str): Algorithm's corresponding repository name. + tag_prefix (str): Algorithm image's tag prefix. + """ + for processor in processors: + if processor not in existing_content["processors"]: + existing_content["processors"].append(processor) + for scope in scopes: + if scope not in existing_content["scope"]: + existing_content["scope"].append(scope) + + new_version = { + "registries": registries, + "repository": repository, + } + if py_versions: + new_version["py_versions"] = py_versions + if tag_prefix: + new_version["tag_prefix"] = tag_prefix + existing_content["versions"][full_version] = new_version + + +def add_region(existing_content, region, account): + """Add region account to framework/algorithm registries. + + Args: + existing_content (dict): Existing framework/algorithm image uri information read from + json file. + region (str): New region to be added to framework/algorithm registries (e.g. us-west-2). + account (str): Region registry account number. + """ + if "scope" not in existing_content: + for scope in existing_content: + for version in existing_content[scope]["versions"]: + existing_content[scope]["versions"][version]["registries"][region] = account + else: + for version in existing_content["versions"]: + existing_content["versions"][version]["registries"][region] = account + + +def add_version( + existing_content, short_version, full_version, scope, processors, py_versions, tag_prefix, +): + """Read framework image uri information from json file to a dictionary, update it with new + framework version information, then write the dictionary back to json file. + + Args: + existing_content (dict): Existing framework/algorithm image uri information read from + json file. + short_version (str): Abbreviated framework version (e.g. 1.0, 1.5). + full_version (str): Complete framework version (e.g. 1.0.0, 1.5.2). + scope (str): Framework image type, it could be "training", "inference". + processors (str): Supported processors (e.g. "cpu,gpu"). + py_versions (str): Supported Python versions (e.g. "py3,py37"). + tag_prefix (str): Algorithm image's tag prefix. + """ + if py_versions: + py_versions = py_versions.split(",") + processors = processors.split(",") + latest_registries, latest_py_versions, latest_repository = get_latest_values( + existing_content, scope + ) + if not py_versions: + py_versions = latest_py_versions + + if scope in existing_content: + add_dlc_framework_version( + existing_content, + short_version, + full_version, + scope, + processors, + py_versions, + latest_registries, + latest_repository, + ) + else: + scopes = scope.split(",") + add_algo_version( + existing_content, + processors, + scopes, + full_version, + py_versions, + latest_registries, + latest_repository, + tag_prefix, + ) + + +def main(): + """Parse command line arguments, call corresponding methods.""" + parser = argparse.ArgumentParser(description="Framework upgrade tool.") + parser.add_argument( + "--framework", required=True, help="Name of the framework (e.g. tensorflow, mxnet, etc.)" + ) + parser.add_argument("--short-version", help="Abbreviated framework version (e.g. 2.0)") + parser.add_argument("--full-version", help="Full framework version (e.g. 2.0.1)") + parser.add_argument("--processors", help="Suppoted processors (e.g. cpu, gpu)") + parser.add_argument("--py-versions", help="Supported Python versions (e.g. py3,py37)") + parser.add_argument("--scope", help="Scope for the Algorithm image (e.g. inference, training)") + parser.add_argument( + "--tag-prefix", help="Tag prefix of the Algorithm image (e.g. ray-0.8.5-torch)" + ) + parser.add_argument("--region", help="New region to be added (e.g. us-west-2)") + parser.add_argument("--account", help="Registry account of new region") + + args = parser.parse_args() + + content = config_for_framework(args.framework) + + if args.region or args.account: + if args.region and not args.account or args.account and not args.region: + raise ValueError("--region and --account must be used together to expand region.") + add_region(content, args.region, args.account) + else: + add_version( + content, + args.short_version, + args.full_version, + args.scope, + args.processors, + args.py_versions, + args.tag_prefix, + ) + + file = os.path.join(IMAGE_URI_CONFIG_DIR, "{}.json".format(args.framework)) + _write_dict_to_json(file, content) + + +if __name__ == "__main__": + main() diff --git a/src/sagemaker/cli/main.py b/src/sagemaker/cli/main.py deleted file mode 100644 index 5328596ba6..0000000000 --- a/src/sagemaker/cli/main.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Placeholder docstring""" -from __future__ import absolute_import - -import argparse -import logging -import sys - -import sagemaker -import sagemaker.cli.mxnet -import sagemaker.cli.tensorflow - -logger = logging.getLogger(__name__) - -DEFAULT_LOG_LEVEL = "info" -DEFAULT_BOTOCORE_LOG_LEVEL = "warning" - - -def parse_arguments(args): - """ - Args: - args: - """ - parser = argparse.ArgumentParser( - description="Launch SageMaker training jobs or hosting endpoints" - ) - parser.set_defaults(func=lambda x: parser.print_usage()) - - # common args for training/hosting/all frameworks - common_parser = argparse.ArgumentParser(add_help=False) - common_parser.add_argument( - "--role-name", help="SageMaker execution role name", type=str, required=True - ) - common_parser.add_argument( - "--data", help="path to training data or model files", type=str, default="./data" - ) - common_parser.add_argument("--script", help="path to script", type=str, default="./script.py") - common_parser.add_argument("--job-name", help="job or endpoint name", type=str, default=None) - common_parser.add_argument( - "--bucket-name", - help="S3 bucket for training/model data and script files", - type=str, - default=None, - ) - common_parser.add_argument("--python", help="python version", type=str, default="py2") - - instance_group = common_parser.add_argument_group("instance settings") - instance_group.add_argument( - "--instance-type", type=str, help="instance type", default="ml.m4.xlarge" - ) - instance_group.add_argument("--instance-count", type=int, help="instance count", default=1) - - # common training args - common_train_parser = argparse.ArgumentParser(add_help=False) - common_train_parser.add_argument( - "--hyperparameters", - help="path to training hyperparameters file", - type=str, - default="./hyperparameters.json", - ) - - # common hosting args - common_host_parser = argparse.ArgumentParser(add_help=False) - common_host_parser.add_argument( - "--env", help="hosting environment variable(s)", type=str, nargs="*", default=[] - ) - - subparsers = parser.add_subparsers() - - # framework/algo subcommands - mxnet_parser = subparsers.add_parser("mxnet", help="use MXNet", parents=[]) - mxnet_subparsers = mxnet_parser.add_subparsers() - mxnet_train_parser = mxnet_subparsers.add_parser( - "train", help="start a training job", parents=[common_parser, common_train_parser] - ) - mxnet_train_parser.set_defaults(func=sagemaker.cli.mxnet.train) - - mxnet_host_parser = mxnet_subparsers.add_parser( - "host", help="start a hosting endpoint", parents=[common_parser, common_host_parser] - ) - mxnet_host_parser.set_defaults(func=sagemaker.cli.mxnet.host) - - tensorflow_parser = subparsers.add_parser("tensorflow", help="use TensorFlow", parents=[]) - tensorflow_subparsers = tensorflow_parser.add_subparsers() - tensorflow_train_parser = tensorflow_subparsers.add_parser( - "train", help="start a training job", parents=[common_parser, common_train_parser] - ) - tensorflow_train_parser.add_argument( - "--training-steps", - help="number of training steps (tensorflow only)", - type=int, - default=None, - ) - tensorflow_train_parser.add_argument( - "--evaluation-steps", - help="number of evaluation steps (tensorflow only)", - type=int, - default=None, - ) - tensorflow_train_parser.set_defaults(func=sagemaker.cli.tensorflow.train) - - tensorflow_host_parser = tensorflow_subparsers.add_parser( - "host", help="start a hosting endpoint", parents=[common_parser, common_host_parser] - ) - tensorflow_host_parser.set_defaults(func=sagemaker.cli.tensorflow.host) - - log_group = parser.add_argument_group("optional log settings") - log_group.add_argument( - "--log-level", help="log level for this command", type=str, default=DEFAULT_LOG_LEVEL - ) - log_group.add_argument( - "--botocore-log-level", - help="log level for botocore", - type=str, - default=DEFAULT_BOTOCORE_LOG_LEVEL, - ) - - return parser.parse_args(args) - - -def configure_logging(args): - """ - Args: - args: - """ - log_format = "%(asctime)s %(levelname)s %(name)s: %(message)s" - log_level = logging.getLevelName(args.log_level.upper()) - logging.basicConfig(format=log_format, level=log_level) - logging.getLogger("botocore").setLevel(args.botocore_log_level.upper()) - - -def main(): - """Placeholder docstring""" - args = parse_arguments(sys.argv[1:]) - configure_logging(args) - logger.debug("args: %s", args) - args.func(args) - - -if __name__ == "__main__": - main() diff --git a/src/sagemaker/cli/mxnet.py b/src/sagemaker/cli/mxnet.py deleted file mode 100644 index 244c69d7ee..0000000000 --- a/src/sagemaker/cli/mxnet.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Placeholder docstring""" -from __future__ import absolute_import - -from sagemaker.cli.common import HostCommand, TrainCommand - - -def train(args): - """ - Args: - args: - """ - MXNetTrainCommand(args).start() - - -def host(args): - """ - Args: - args: - """ - MXNetHostCommand(args).start() - - -class MXNetTrainCommand(TrainCommand): - """Placeholder docstring""" - - def create_estimator(self): - """Placeholder docstring""" - from sagemaker.mxnet.estimator import MXNet - - return MXNet( - self.script, - role=self.role_name, - base_job_name=self.job_name, - train_instance_count=self.instance_count, - train_instance_type=self.instance_type, - hyperparameters=self.hyperparameters, - py_version=self.python, - ) - - -class MXNetHostCommand(HostCommand): - """Placeholder docstring""" - - def create_model(self, model_url): - """ - Args: - model_url: - """ - from sagemaker.mxnet.model import MXNetModel - - return MXNetModel( - model_data=model_url, - role=self.role_name, - entry_point=self.script, - py_version=self.python, - name=self.endpoint_name, - env=self.environment, - ) diff --git a/src/sagemaker/cli/tensorflow.py b/src/sagemaker/cli/tensorflow.py deleted file mode 100644 index 61af24f1ca..0000000000 --- a/src/sagemaker/cli/tensorflow.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Placeholder docstring""" -from __future__ import absolute_import - -from sagemaker.cli.common import HostCommand, TrainCommand - - -def train(args): - """ - Args: - args: - """ - TensorFlowTrainCommand(args).start() - - -def host(args): - """ - Args: - args: - """ - TensorFlowHostCommand(args).start() - - -class TensorFlowTrainCommand(TrainCommand): - """Placeholder docstring""" - - def __init__(self, args): - """ - Args: - args: - """ - super(TensorFlowTrainCommand, self).__init__(args) - self.training_steps = args.training_steps - self.evaluation_steps = args.evaluation_steps - - def create_estimator(self): - from sagemaker.tensorflow import TensorFlow - - return TensorFlow( - training_steps=self.training_steps, - evaluation_steps=self.evaluation_steps, - py_version=self.python, - entry_point=self.script, - role=self.role_name, - base_job_name=self.job_name, - train_instance_count=self.instance_count, - train_instance_type=self.instance_type, - hyperparameters=self.hyperparameters, - ) - - -class TensorFlowHostCommand(HostCommand): - """Placeholder docstring""" - - def create_model(self, model_url): - """ - Args: - model_url: - """ - from sagemaker.tensorflow.model import TensorFlowModel - - return TensorFlowModel( - model_data=model_url, - role=self.role_name, - entry_point=self.script, - py_version=self.python, - name=self.endpoint_name, - env=self.environment, - ) diff --git a/src/sagemaker/debugger.py b/src/sagemaker/debugger.py index 8e040ca890..d6403eb342 100644 --- a/src/sagemaker/debugger.py +++ b/src/sagemaker/debugger.py @@ -23,32 +23,9 @@ import smdebug_rulesconfig as rule_configs # noqa: F401 # pylint: disable=unused-import -from sagemaker.utils import get_ecr_image_uri_prefix - -RULES_ECR_REPO_NAME = "sagemaker-debugger-rules" - -SAGEMAKER_RULE_CONTAINERS_ACCOUNTS_MAP = { - "eu-north-1": {RULES_ECR_REPO_NAME: "314864569078"}, - "me-south-1": {RULES_ECR_REPO_NAME: "986000313247"}, - "ap-south-1": {RULES_ECR_REPO_NAME: "904829902805"}, - "eu-west-3": {RULES_ECR_REPO_NAME: "447278800020"}, - "us-east-2": {RULES_ECR_REPO_NAME: "915447279597"}, - "eu-west-1": {RULES_ECR_REPO_NAME: "929884845733"}, - "eu-central-1": {RULES_ECR_REPO_NAME: "482524230118"}, - "sa-east-1": {RULES_ECR_REPO_NAME: "818342061345"}, - "ap-east-1": {RULES_ECR_REPO_NAME: "199566480951"}, - "us-east-1": {RULES_ECR_REPO_NAME: "503895931360"}, - "ap-northeast-2": {RULES_ECR_REPO_NAME: "578805364391"}, - "eu-west-2": {RULES_ECR_REPO_NAME: "250201462417"}, - "ap-northeast-1": {RULES_ECR_REPO_NAME: "430734990657"}, - "us-west-2": {RULES_ECR_REPO_NAME: "895741380848"}, - "us-west-1": {RULES_ECR_REPO_NAME: "685455198987"}, - "ap-southeast-1": {RULES_ECR_REPO_NAME: "972752614525"}, - "ap-southeast-2": {RULES_ECR_REPO_NAME: "184798709955"}, - "ca-central-1": {RULES_ECR_REPO_NAME: "519511493484"}, - "cn-north-1": {RULES_ECR_REPO_NAME: "618459771430"}, - "cn-northwest-1": {RULES_ECR_REPO_NAME: "658757709296"}, -} +from sagemaker import image_uris + +framework_name = "debugger" def get_rule_container_image_uri(region): @@ -61,9 +38,7 @@ def get_rule_container_image_uri(region): Returns: str: Formatted image uri for the given region and the rule container type """ - registry_id = SAGEMAKER_RULE_CONTAINERS_ACCOUNTS_MAP.get(region).get(RULES_ECR_REPO_NAME) - image_uri_prefix = get_ecr_image_uri_prefix(registry_id, region) - return "{}/{}:latest".format(image_uri_prefix, RULES_ECR_REPO_NAME) + return image_uris.retrieve(framework_name, region) class Rule(object): diff --git a/src/sagemaker/deserializers.py b/src/sagemaker/deserializers.py new file mode 100644 index 0000000000..bffdfa8bb7 --- /dev/null +++ b/src/sagemaker/deserializers.py @@ -0,0 +1,284 @@ +# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Implements methods for deserializing data returned from an inference endpoint.""" +from __future__ import absolute_import + +import csv + +import abc +import codecs +import io +import json + +import numpy as np + +from sagemaker.utils import DeferredError + +try: + import pandas +except ImportError as e: + pandas = DeferredError(e) + + +class BaseDeserializer(abc.ABC): + """Abstract base class for creation of new deserializers. + + Provides a skeleton for customization requiring the overriding of the method + deserialize and the class attribute ACCEPT. + """ + + @abc.abstractmethod + def deserialize(self, stream, content_type): + """Deserialize data received from an inference endpoint. + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + object: The data deserialized into an object. + """ + + @property + @abc.abstractmethod + def ACCEPT(self): + """The content types that are expected from the inference endpoint.""" + + +class StringDeserializer(BaseDeserializer): + """Deserialize data from an inference endpoint into a decoded string.""" + + ACCEPT = ("application/json",) + + def __init__(self, encoding="UTF-8"): + """Initialize the string encoding. + + Args: + encoding (str): The string encoding to use (default: UTF-8). + """ + self.encoding = encoding + + def deserialize(self, stream, content_type): + """Deserialize data from an inference endpoint into a decoded string. + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + str: The data deserialized into a decoded string. + """ + try: + return stream.read().decode(self.encoding) + finally: + stream.close() + + +class BytesDeserializer(BaseDeserializer): + """Deserialize a stream of bytes into a bytes object.""" + + ACCEPT = ("*/*",) + + def deserialize(self, stream, content_type): + """Read a stream of bytes returned from an inference endpoint. + + Args: + stream (botocore.response.StreamingBody): A stream of bytes. + content_type (str): The MIME type of the data. + + Returns: + bytes: The bytes object read from the stream. + """ + try: + return stream.read() + finally: + stream.close() + + +class CSVDeserializer(BaseDeserializer): + """Deserialize a stream of bytes into a list of lists.""" + + ACCEPT = ("text/csv",) + + def __init__(self, encoding="utf-8"): + """Initialize the string encoding. + + Args: + encoding (str): The string encoding to use (default: "utf-8"). + """ + self.encoding = encoding + + def deserialize(self, stream, content_type): + """Deserialize data from an inference endpoint into a list of lists. + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + list: The data deserialized into a list of lists representing the + contents of a CSV file. + """ + try: + decoded_string = stream.read().decode(self.encoding) + return list(csv.reader(decoded_string.splitlines())) + finally: + stream.close() + + +class StreamDeserializer(BaseDeserializer): + """Returns the data and content-type received from an inference endpoint. + + It is the user's responsibility to close the data stream once they're done + reading it. + """ + + ACCEPT = ("*/*",) + + def deserialize(self, stream, content_type): + """Returns a stream of the response body and the MIME type of the data. + + Args: + stream (botocore.response.StreamingBody): A stream of bytes. + content_type (str): The MIME type of the data. + + Returns: + tuple: A two-tuple containing the stream and content-type. + """ + return stream, content_type + + +class NumpyDeserializer(BaseDeserializer): + """Deserialize a stream of data in the .npy format.""" + + def __init__(self, dtype=None, accept="application/x-npy", allow_pickle=True): + """Initialize the dtype and allow_pickle arguments. + + Args: + dtype (str): The dtype of the data (default: None). + accept (str): The MIME type that is expected from the inference + endpoint (default: "application/x-npy"). + allow_pickle (bool): Allow loading pickled object arrays (default: True). + """ + self.dtype = dtype + self.accept = accept + self.allow_pickle = allow_pickle + + def deserialize(self, stream, content_type): + """Deserialize data from an inference endpoint into a NumPy array. + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + numpy.ndarray: The data deserialized into a NumPy array. + """ + try: + if content_type == "text/csv": + return np.genfromtxt( + codecs.getreader("utf-8")(stream), delimiter=",", dtype=self.dtype + ) + if content_type == "application/json": + return np.array(json.load(codecs.getreader("utf-8")(stream)), dtype=self.dtype) + if content_type == "application/x-npy": + return np.load(io.BytesIO(stream.read()), allow_pickle=self.allow_pickle) + finally: + stream.close() + + raise ValueError("%s cannot read content type %s." % (__class__.__name__, content_type)) + + @property + def ACCEPT(self): + """The content types that are expected from the inference endpoint. + + To maintain backwards compatability with legacy images, the + NumpyDeserializer supports sending only one content type in the Accept + header. + """ + return (self.accept,) + + +class JSONDeserializer(BaseDeserializer): + """Deserialize JSON data from an inference endpoint into a Python object.""" + + ACCEPT = ("application/json",) + + def deserialize(self, stream, content_type): + """Deserialize JSON data from an inference endpoint into a Python object. + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + object: The JSON-formatted data deserialized into a Python object. + """ + try: + return json.load(codecs.getreader("utf-8")(stream)) + finally: + stream.close() + + +class PandasDeserializer(BaseDeserializer): + """Deserialize CSV or JSON data from an inference endpoint into a pandas dataframe.""" + + ACCEPT = ("text/csv", "application/json") + + def deserialize(self, stream, content_type): + """Deserialize CSV or JSON data from an inference endpoint into a pandas + dataframe. + + If the data is JSON, the data should be formatted in the 'columns' orient. + See https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_json.html + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + pandas.DataFrame: The data deserialized into a pandas DataFrame. + """ + if content_type == "text/csv": + return pandas.read_csv(stream) + + if content_type == "application/json": + return pandas.read_json(stream) + + raise ValueError("%s cannot read content type %s." % (__class__.__name__, content_type)) + + +class JSONLinesDeserializer(BaseDeserializer): + """Deserialize JSON lines data from an inference endpoint.""" + + ACCEPT = ("application/jsonlines",) + + def deserialize(self, stream, content_type): + """Deserialize JSON lines data from an inference endpoint. + + See https://docs.python.org/3/library/json.html#py-to-json-table to + understand how JSON values are converted to Python objects. + + Args: + stream (botocore.response.StreamingBody): Data to be deserialized. + content_type (str): The MIME type of the data. + + Returns: + list: A list of JSON serializable objects. + """ + try: + body = stream.read().decode("utf-8") + lines = body.rstrip().split("\n") + return [json.loads(line) for line in lines] + finally: + stream.close() diff --git a/src/sagemaker/estimator.py b/src/sagemaker/estimator.py index 660085c089..c05f51f319 100644 --- a/src/sagemaker/estimator.py +++ b/src/sagemaker/estimator.py @@ -17,7 +17,6 @@ import logging import os import uuid -import warnings from abc import ABCMeta from abc import abstractmethod @@ -25,38 +24,34 @@ from six import string_types from six.moves.urllib.parse import urlparse import sagemaker -from sagemaker import git_utils +from sagemaker import git_utils, image_uris from sagemaker.analytics import TrainingJobAnalytics from sagemaker.debugger import DebuggerHookConfig from sagemaker.debugger import TensorBoardOutputConfig # noqa: F401 # pylint: disable=unused-import from sagemaker.debugger import get_rule_container_image_uri -from sagemaker.s3 import S3Uploader +from sagemaker.s3 import S3Uploader, parse_s3_url from sagemaker.fw_utils import ( - create_image_uri, tar_and_upload_dir, - parse_s3_url, UploadedCode, validate_source_dir, _region_supports_debugger, - parameter_v2_rename_warning, ) +from sagemaker.inputs import TrainingInput from sagemaker.job import _Job from sagemaker.local import LocalSession from sagemaker.model import Model, NEO_ALLOWED_FRAMEWORKS from sagemaker.model import ( SCRIPT_PARAM_NAME, DIR_PARAM_NAME, - CLOUDWATCH_METRICS_PARAM_NAME, CONTAINER_LOG_LEVEL_PARAM_NAME, JOB_NAME_PARAM_NAME, SAGEMAKER_REGION_PARAM_NAME, ) -from sagemaker.predictor import RealTimePredictor +from sagemaker.predictor import Predictor from sagemaker.session import Session -from sagemaker.session import s3_input from sagemaker.transformer import Transformer -from sagemaker.utils import base_name_from_image, name_from_base, get_config_value +from sagemaker.utils import base_from_name, base_name_from_image, name_from_base, get_config_value from sagemaker import vpc_utils logger = logging.getLogger(__name__) @@ -76,11 +71,11 @@ class EstimatorBase(with_metaclass(ABCMeta, object)): def __init__( self, role, - train_instance_count, - train_instance_type, - train_volume_size=30, - train_volume_kms_key=None, - train_max_run=24 * 60 * 60, + instance_count, + instance_type, + volume_size=30, + volume_kms_key=None, + max_run=24 * 60 * 60, input_mode="File", output_path=None, output_kms_key=None, @@ -93,8 +88,8 @@ def __init__( model_channel_name="model", metric_definitions=None, encrypt_inter_container_traffic=False, - train_use_spot_instances=False, - train_max_wait=None, + use_spot_instances=False, + max_wait=None, checkpoint_s3_uri=None, checkpoint_local_path=None, rules=None, @@ -111,17 +106,17 @@ def __init__( endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if it needs to access an AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. - train_volume_size (int): Size in GB of the EBS volume to use for + volume_size (int): Size in GB of the EBS volume to use for storing input data during training (default: 30). Must be large enough to store training data if File Mode is used (which is the default). - train_volume_kms_key (str): Optional. KMS key ID for encrypting EBS + volume_kms_key (str): Optional. KMS key ID for encrypting EBS volume attached to the training instance (default: None). - train_max_run (int): Timeout in seconds for training (default: 24 * + max_run (int): Timeout in seconds for training (default: 24 * 60 * 60). After this amount of time Amazon SageMaker terminates the job regardless of its current status. input_mode (str): The input mode that the algorithm supports @@ -130,7 +125,7 @@ def __init__( 'Pipe' - Amazon SageMaker streams data directly from S3 to the container via a Unix-named pipe. This argument can be overriden on a per-channel basis using - ``sagemaker.session.s3_input.input_mode``. + ``sagemaker.inputs.TrainingInput.input_mode``. output_path (str): S3 location for saving the training result (model artifacts and output files). If not specified, results are stored to a default bucket. If the bucket with the specific name @@ -142,7 +137,7 @@ def __init__( training output (default: None). base_job_name (str): Prefix for training job name when the :meth:`~sagemaker.estimator.EstimatorBase.fit` method launches. - If not specified, the estimator generates a default job name, + If not specified, the estimator generates a default job name based on the training image name and current timestamp. sagemaker_session (sagemaker.session.Session): Session object which manages interactions with Amazon SageMaker APIs and any other @@ -179,14 +174,14 @@ def __init__( encrypt_inter_container_traffic (bool): Specifies whether traffic between training containers is encrypted for the training job (default: ``False``). - train_use_spot_instances (bool): Specifies whether to use SageMaker + use_spot_instances (bool): Specifies whether to use SageMaker Managed Spot instances for training. If enabled then the - `train_max_wait` arg should also be set. + ``max_wait`` arg should also be set. More information: https://docs.aws.amazon.com/sagemaker/latest/dg/model-managed-spot-training.html (default: ``False``). - train_max_wait (int): Timeout in seconds waiting for spot training + max_wait (int): Timeout in seconds waiting for spot training instances (default: None). After this amount of time Amazon SageMaker will stop waiting for Spot instances to become available (default: ``None``). @@ -228,11 +223,11 @@ def __init__( outbound network calls. Also known as Internet-free mode. """ self.role = role - self.train_instance_count = train_instance_count - self.train_instance_type = train_instance_type - self.train_volume_size = train_volume_size - self.train_volume_kms_key = train_volume_kms_key - self.train_max_run = train_max_run + self.instance_count = instance_count + self.instance_type = instance_type + self.volume_size = volume_size + self.volume_kms_key = volume_kms_key + self.max_run = max_run self.input_mode = input_mode self.tags = tags self.metric_definitions = metric_definitions @@ -241,8 +236,8 @@ def __init__( self.code_uri = None self.code_channel_name = "code" - if self.train_instance_type in ("local", "local_gpu"): - if self.train_instance_type == "local_gpu" and self.train_instance_count > 1: + if self.instance_type in ("local", "local_gpu"): + if self.instance_type == "local_gpu" and self.instance_count > 1: raise RuntimeError("Distributed Training in Local GPU is not supported") self.sagemaker_session = sagemaker_session or LocalSession() if not isinstance(self.sagemaker_session, sagemaker.local.LocalSession): @@ -274,8 +269,8 @@ def __init__( self.security_group_ids = security_group_ids self.encrypt_inter_container_traffic = encrypt_inter_container_traffic - self.train_use_spot_instances = train_use_spot_instances - self.train_max_wait = train_max_wait + self.use_spot_instances = use_spot_instances + self.max_wait = max_wait self.checkpoint_s3_uri = checkpoint_s3_uri self.checkpoint_local_path = checkpoint_local_path @@ -290,7 +285,7 @@ def __init__( self._enable_network_isolation = enable_network_isolation @abstractmethod - def train_image(self): + def training_image_uri(self): """Return the Docker image to use for training. The :meth:`~sagemaker.estimator.EstimatorBase.fit` method, which does @@ -330,6 +325,28 @@ def prepare_workflow_for_training(self, job_name=None): """ self._prepare_for_training(job_name=job_name) + def _ensure_base_job_name(self): + """Set ``self.base_job_name`` if it is not set already.""" + # honor supplied base_job_name or generate it + if self.base_job_name is None: + self.base_job_name = base_name_from_image(self.training_image_uri()) + + def _get_or_create_name(self, name=None): + """Generate a name based on the base job name or training image if needed. + + Args: + name (str): User-supplied name. If not specified, a name is generated from + the base job name or training image. + + Returns: + str: Either the user-supplied name or a generated name. + """ + if name: + return name + + self._ensure_base_job_name() + return name_from_base(self.base_job_name) + def _prepare_for_training(self, job_name=None): """Set any values in the estimator that need to be set before training. @@ -338,18 +355,7 @@ def _prepare_for_training(self, job_name=None): specified, one is generated, using the base name given to the constructor if applicable. """ - if job_name is not None: - self._current_job_name = job_name - else: - # honor supplied base_job_name or generate it - if self.base_job_name: - base_name = self.base_job_name - elif isinstance(self, sagemaker.algorithm.AlgorithmEstimator): - base_name = self.algorithm_arn.split("/")[-1] # pylint: disable=no-member - else: - base_name = base_name_from_image(self.train_image()) - - self._current_job_name = name_from_base(base_name) + self._current_job_name = self._get_or_create_name(job_name) # if output_path was specified we use it otherwise initialize here. # For Local Mode with local_code=True we don't need an explicit output_path @@ -395,7 +401,7 @@ def _prepare_rules(self): s3_uri = S3Uploader.upload( local_path=rule.rule_parameters["source_s3_uri"], desired_s3_uri=desired_s3_uri, - session=self.sagemaker_session, + sagemaker_session=self.sagemaker_session, ) rule.rule_parameters["source_s3_uri"] = s3_uri # Save the request dictionary for the rule. @@ -464,17 +470,18 @@ def fit(self, inputs=None, wait=True, logs="All", job_name=None, experiment_conf model using the Amazon SageMaker hosting services. Args: - inputs (str or dict or sagemaker.session.s3_input): Information + inputs (str or dict or sagemaker.inputs.TrainingInput): Information about the training data. This can be one of three types: * (str) the S3 location where training data is saved, or a file:// path in local mode. - * (dict[str, str] or dict[str, sagemaker.session.s3_input]) If using multiple + * (dict[str, str] or dict[str, sagemaker.inputs.TrainingInput]) If using multiple channels for training data, you can specify a dict mapping channel names to - strings or :func:`~sagemaker.session.s3_input` objects. - * (sagemaker.session.s3_input) - channel configuration for S3 data sources that can - provide additional information as well as the path to the training dataset. - See :func:`sagemaker.session.s3_input` for full details. + strings or :func:`~sagemaker.inputs.TrainingInput` objects. + * (sagemaker.inputs.TrainingInput) - channel configuration for S3 data sources + that can provide additional information as well as the path to the training + dataset. + See :func:`sagemaker.inputs.TrainingInput` for full details. * (sagemaker.session.FileSystemInput) - channel configuration for a file system data source that can provide additional information as well as the path to the training dataset. @@ -485,7 +492,7 @@ def fit(self, inputs=None, wait=True, logs="All", job_name=None, experiment_conf compatibility, boolean values are also accepted and converted to strings. Only meaningful when wait is True. job_name (str): Training job name. If not specified, the estimator generates - a default job name, based on the training image name and current timestamp. + a default job name based on the training image name and current timestamp. experiment_config (dict[str, str]): Experiment management configuration. Dictionary contains three optional keys, 'ExperimentName', 'TrialName', and 'TrialComponentDisplayName'. @@ -500,7 +507,7 @@ def fit(self, inputs=None, wait=True, logs="All", job_name=None, experiment_conf def _compilation_job_name(self): """Placeholder docstring""" - base_name = self.base_job_name or base_name_from_image(self.train_image()) + base_name = self.base_job_name or base_name_from_image(self.training_image_uri()) return name_from_base("compilation-" + base_name) def compile_model( @@ -603,14 +610,16 @@ def attach(cls, training_job_name, sagemaker_session=None, model_channel_name="m has a Complete status, it can be ``deploy()`` ed to create a SageMaker Endpoint and return a ``Predictor``. - If the training job is in progress, attach will block and display log - messages from the training job, until the training job completes. + If the training job is in progress, attach will block until the training job + completes, but logs of the training job will not display. To see the logs + content, please call ``logs()`` Examples: >>> my_estimator.fit(wait=False) >>> training_job_name = my_estimator.latest_training_job.name Later on: >>> attached_estimator = Estimator.attach(training_job_name) + >>> attached_estimator.logs() >>> attached_estimator.deploy() Args: @@ -641,20 +650,29 @@ def attach(cls, training_job_name, sagemaker_session=None, model_channel_name="m estimator = cls(sagemaker_session=sagemaker_session, **init_params) estimator.latest_training_job = _TrainingJob( - sagemaker_session=sagemaker_session, job_name=init_params["base_job_name"] + sagemaker_session=sagemaker_session, job_name=training_job_name ) estimator._current_job_name = estimator.latest_training_job.name - estimator.latest_training_job.wait() + estimator.latest_training_job.wait(logs="None") return estimator + def logs(self): + """Display the logs for Estimator's training job. + + If the output is a tty or a Jupyter cell, it will be color-coded based + on which instance the log entry is from. + """ + self.sagemaker_session.logs_for_job(self.latest_training_job, wait=True) + def deploy( self, initial_instance_count, instance_type, + serializer=None, + deserializer=None, accelerator_type=None, endpoint_name=None, use_compiled_model=False, - update_endpoint=False, wait=True, model_name=None, kms_key=None, @@ -663,7 +681,7 @@ def deploy( **kwargs ): """Deploy the trained model to an Amazon SageMaker endpoint and return a - ``sagemaker.RealTimePredictor`` object. + ``sagemaker.Predictor`` object. More information: http://docs.aws.amazon.com/sagemaker/latest/dg/how-it-works-training.html @@ -673,6 +691,16 @@ def deploy( deploy to an endpoint for prediction. instance_type (str): Type of EC2 instance to deploy to an endpoint for prediction, for example, 'ml.c4.xlarge'. + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: None). If ``serializer`` is not None, then + ``serializer`` will override the default serializer. The + default serializer is set by the ``predictor_cls``. + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: None). If ``deserializer`` is not None, then + ``deserializer`` will override the default deserializer. The + default deserializer is set by the ``predictor_cls``. accelerator_type (str): Type of Elastic Inference accelerator to attach to an endpoint for model loading and inference, for example, 'ml.eia1.medium'. If not specified, no Elastic @@ -684,15 +712,11 @@ def deploy( used. use_compiled_model (bool): Flag to select whether to use compiled (optimized) model. Default: False. - update_endpoint (bool): Flag to update the model in an existing - Amazon SageMaker endpoint. If True, this will deploy a new - EndpointConfig to an already existing endpoint and delete - resources corresponding to the previous EndpointConfig. Default: - False wait (bool): Whether the call should wait until the deployment of model completes (default: True). model_name (str): Name to use for creating an Amazon SageMaker - model. If not specified, the name of the training job is used. + model. If not specified, the estimator generates a default job name + based on the training image name and current timestamp. kms_key (str): The ARN of the KMS key that is used to encrypt the data on the storage volume attached to the instance hosting the endpoint. @@ -711,13 +735,16 @@ def deploy( For more, see the implementation docs. Returns: - sagemaker.predictor.RealTimePredictor: A predictor that provides a ``predict()`` method, + sagemaker.predictor.Predictor: A predictor that provides a ``predict()`` method, which can be used to send requests to the Amazon SageMaker endpoint and obtain inferences. """ self._ensure_latest_training_job() - endpoint_name = endpoint_name or self.latest_training_job.name - model_name = model_name or self.latest_training_job.name + self._ensure_base_job_name() + default_name = name_from_base(self.base_job_name) + endpoint_name = endpoint_name or default_name + model_name = model_name or default_name + self.deploy_instance_type = instance_type if use_compiled_model: family = "_".join(instance_type.split(".")[:-1]) @@ -736,9 +763,10 @@ def deploy( return model.deploy( instance_type=instance_type, initial_instance_count=initial_instance_count, + serializer=serializer, + deserializer=deserializer, accelerator_type=accelerator_type, endpoint_name=endpoint_name, - update_endpoint=update_endpoint, tags=tags or self.tags, wait=wait, kms_key=kms_key, @@ -796,12 +824,12 @@ class constructor init_params = dict() init_params["role"] = job_details["RoleArn"] - init_params["train_instance_count"] = job_details["ResourceConfig"]["InstanceCount"] - init_params["train_instance_type"] = job_details["ResourceConfig"]["InstanceType"] - init_params["train_volume_size"] = job_details["ResourceConfig"]["VolumeSizeInGB"] - init_params["train_max_run"] = job_details["StoppingCondition"]["MaxRuntimeInSeconds"] + init_params["instance_count"] = job_details["ResourceConfig"]["InstanceCount"] + init_params["instance_type"] = job_details["ResourceConfig"]["InstanceType"] + init_params["volume_size"] = job_details["ResourceConfig"]["VolumeSizeInGB"] + init_params["max_run"] = job_details["StoppingCondition"]["MaxRuntimeInSeconds"] init_params["input_mode"] = job_details["AlgorithmSpecification"]["TrainingInputMode"] - init_params["base_job_name"] = job_details["TrainingJobName"] + init_params["base_job_name"] = base_from_name(job_details["TrainingJobName"]) init_params["output_path"] = job_details["OutputDataConfig"]["S3OutputPath"] init_params["output_kms_key"] = job_details["OutputDataConfig"]["KmsKeyId"] if "EnableNetworkIsolation" in job_details: @@ -813,7 +841,7 @@ class constructor if "AlgorithmName" in job_details["AlgorithmSpecification"]: init_params["algorithm_arn"] = job_details["AlgorithmSpecification"]["AlgorithmName"] elif "TrainingImage" in job_details["AlgorithmSpecification"]: - init_params["image"] = job_details["AlgorithmSpecification"]["TrainingImage"] + init_params["image_uri"] = job_details["AlgorithmSpecification"]["TrainingImage"] else: raise RuntimeError( "Invalid AlgorithmSpecification. Either TrainingImage or " @@ -845,20 +873,6 @@ class constructor return init_params - def delete_endpoint(self): - """Delete an Amazon SageMaker ``Endpoint``. - - Raises: - botocore.exceptions.ClientError: If the endpoint does not exist. - """ - logger.warning( - "estimator.delete_endpoint() will be deprecated in SageMaker Python SDK v2. " - "Please use the delete_endpoint() function on your predictor instead." - ) - - self._ensure_latest_training_job(error_message="Endpoint was not created yet") - self.sagemaker_session.delete_endpoint(self.latest_training_job.name) - def transformer( self, instance_count, @@ -928,18 +942,18 @@ def transformer( If not specified, this setting is taken from the estimator's current configuration. model_name (str): Name to use for creating an Amazon SageMaker - model. If not specified, the name of the training job is used. + model. If not specified, the estimator generates a default job name + based on the training image name and current timestamp. """ tags = tags or self.tags + model_name = self._get_or_create_name(model_name) if self.latest_training_job is None: logger.warning( "No finished training job found associated with this estimator. Please make sure " "this estimator is only used for building workflow config" ) - model_name = model_name or self._current_job_name else: - model_name = model_name or self.latest_training_job.name if enable_network_isolation is None: enable_network_isolation = self.enable_network_isolation() @@ -1052,10 +1066,10 @@ def start_new(cls, estimator, inputs, experiment_config): train_args["metric_definitions"] = estimator.metric_definitions train_args["experiment_config"] = experiment_config - if isinstance(inputs, s3_input): + if isinstance(inputs, TrainingInput): if "InputMode" in inputs.config: logger.debug( - "Selecting s3_input's input_mode (%s) for TrainingInputMode.", + "Selecting TrainingInput's input_mode (%s) for TrainingInputMode.", inputs.config["InputMode"], ) train_args["input_mode"] = inputs.config["InputMode"] @@ -1069,7 +1083,7 @@ def start_new(cls, estimator, inputs, experiment_config): if isinstance(estimator, sagemaker.algorithm.AlgorithmEstimator): train_args["algorithm_arn"] = estimator.algorithm_arn else: - train_args["image"] = estimator.train_image() + train_args["image_uri"] = estimator.training_image_uri() if estimator.debugger_rule_configs: train_args["debugger_rule_configs"] = estimator.debugger_rule_configs @@ -1100,10 +1114,10 @@ def _add_spot_checkpoint_args(cls, local_mode, estimator, train_args): estimator: train_args: """ - if estimator.train_use_spot_instances: + if estimator.use_spot_instances: if local_mode: raise ValueError("Spot training is not supported in local mode.") - train_args["train_use_spot_instances"] = True + train_args["use_spot_instances"] = True if estimator.checkpoint_s3_uri: if local_mode: @@ -1162,13 +1176,13 @@ class Estimator(EstimatorBase): def __init__( self, - image_name, + image_uri, role, - train_instance_count, - train_instance_type, - train_volume_size=30, - train_volume_kms_key=None, - train_max_run=24 * 60 * 60, + instance_count, + instance_type, + volume_size=30, + volume_kms_key=None, + max_run=24 * 60 * 60, input_mode="File", output_path=None, output_kms_key=None, @@ -1182,8 +1196,8 @@ def __init__( model_channel_name="model", metric_definitions=None, encrypt_inter_container_traffic=False, - train_use_spot_instances=False, - train_max_wait=None, + use_spot_instances=False, + max_wait=None, checkpoint_s3_uri=None, checkpoint_local_path=None, enable_network_isolation=False, @@ -1195,23 +1209,23 @@ def __init__( """Initialize an ``Estimator`` instance. Args: - image_name (str): The container image to use for training. + image_uri (str): The container image to use for training. role (str): An AWS IAM role (either name or full ARN). The Amazon SageMaker training jobs and APIs that create Amazon SageMaker endpoints use this role to access training data and model artifacts. After the endpoint is created, the inference code might use the IAM role, if it needs to access an AWS resource. - train_instance_count (int): Number of Amazon EC2 instances to use + instance_count (int): Number of Amazon EC2 instances to use for training. - train_instance_type (str): Type of EC2 instance to use for training, + instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. - train_volume_size (int): Size in GB of the EBS volume to use for + volume_size (int): Size in GB of the EBS volume to use for storing input data during training (default: 30). Must be large enough to store training data if File Mode is used (which is the default). - train_volume_kms_key (str): Optional. KMS key ID for encrypting EBS + volume_kms_key (str): Optional. KMS key ID for encrypting EBS volume attached to the training instance (default: None). - train_max_run (int): Timeout in seconds for training (default: 24 * + max_run (int): Timeout in seconds for training (default: 24 * 60 * 60). After this amount of time Amazon SageMaker terminates the job regardless of its current status. input_mode (str): The input mode that the algorithm supports @@ -1223,7 +1237,7 @@ def __init__( container via a Unix-named pipe. This argument can be overriden on a per-channel basis using - ``sagemaker.session.s3_input.input_mode``. + ``sagemaker.inputs.TrainingInput.input_mode``. output_path (str): S3 location for saving the training result (model artifacts and output files). If not specified, results are stored to a default bucket. If the bucket with the specific name @@ -1272,14 +1286,14 @@ def __init__( encrypt_inter_container_traffic (bool): Specifies whether traffic between training containers is encrypted for the training job (default: ``False``). - train_use_spot_instances (bool): Specifies whether to use SageMaker + use_spot_instances (bool): Specifies whether to use SageMaker Managed Spot instances for training. If enabled then the - `train_max_wait` arg should also be set. + ``max_wait`` arg should also be set. More information: https://docs.aws.amazon.com/sagemaker/latest/dg/model-managed-spot-training.html (default: ``False``). - train_max_wait (int): Timeout in seconds waiting for spot training + max_wait (int): Timeout in seconds waiting for spot training instances (default: None). After this amount of time Amazon SageMaker will stop waiting for Spot instances to become available (default: ``None``). @@ -1304,16 +1318,15 @@ def __init__( https://docs.aws.amazon.com/sagemaker/latest/dg/API_AlgorithmSpecification.html#SageMaker-Type-AlgorithmSpecification-EnableSageMakerMetricsTimeSeries (default: ``None``). """ - logger.warning(parameter_v2_rename_warning("image_name", "image_uri")) - self.image_name = image_name + self.image_uri = image_uri self.hyperparam_dict = hyperparameters.copy() if hyperparameters else {} super(Estimator, self).__init__( role, - train_instance_count, - train_instance_type, - train_volume_size, - train_volume_kms_key, - train_max_run, + instance_count, + instance_type, + volume_size, + volume_kms_key, + max_run, input_mode, output_path, output_kms_key, @@ -1326,8 +1339,8 @@ def __init__( model_channel_name=model_channel_name, metric_definitions=metric_definitions, encrypt_inter_container_traffic=encrypt_inter_container_traffic, - train_use_spot_instances=train_use_spot_instances, - train_max_wait=train_max_wait, + use_spot_instances=use_spot_instances, + max_wait=max_wait, checkpoint_s3_uri=checkpoint_s3_uri, checkpoint_local_path=checkpoint_local_path, rules=rules, @@ -1337,13 +1350,13 @@ def __init__( enable_network_isolation=enable_network_isolation, ) - def train_image(self): + def training_image_uri(self): """Returns the docker image to use for training. The fit() method, that does the model training, calls this method to find the image to use for model training. """ - return self.image_name + return self.image_uri def set_hyperparameters(self, **kwargs): """ @@ -1364,40 +1377,25 @@ def hyperparameters(self): def create_model( self, role=None, - image=None, + image_uri=None, predictor_cls=None, - serializer=None, - deserializer=None, - content_type=None, - accept=None, vpc_config_override=vpc_utils.VPC_CONFIG_DEFAULT, **kwargs ): """Create a model to deploy. - The serializer, deserializer, content_type, and accept arguments are only used to define a - default RealTimePredictor. They are ignored if an explicit predictor class is passed in. + The serializer and deserializer arguments are only used to define a + default Predictor. They are ignored if an explicit predictor class is passed in. Other arguments are passed through to the Model class. Args: role (str): The ``ExecutionRoleArn`` IAM Role ARN for the ``Model``, which is also used during transform jobs. If not specified, the role from the Estimator will be used. - image (str): An container image to use for deploying the model. + image_uri (str): A Docker image URI to use for deploying the model. Defaults to the image used for training. - predictor_cls (RealTimePredictor): The predictor class to use when + predictor_cls (Predictor): The predictor class to use when deploying the model. - serializer (callable): Should accept a single argument, the input - data, and return a sequence of bytes. May provide a content_type - attribute that defines the endpoint request content type - deserializer (callable): Should accept two arguments, the result - data and the response content type, and return a sequence of - bytes. May provide a content_type attribute that defines th - endpoint response Accept content type. - content_type (str): The invocation ContentType, overriding any - content_type from the serializer - accept (str): The invocation Accept, overriding any accept from the - deserializer. vpc_config_override (dict[str, list[str]]): Optional override for VpcConfig set on the model. Default: use subnets and security groups from this Estimator. @@ -1416,9 +1414,7 @@ def create_model( if predictor_cls is None: def predict_wrapper(endpoint, session): - return RealTimePredictor( - endpoint, session, serializer, deserializer, content_type, accept - ) + return Predictor(endpoint, session) predictor_cls = predict_wrapper @@ -1428,8 +1424,8 @@ def predict_wrapper(endpoint, session): kwargs["enable_network_isolation"] = self.enable_network_isolation() return Model( + image_uri or self.training_image_uri(), self.model_data, - image or self.train_image(), role, vpc_config=self.get_vpc_config(vpc_config_override), sagemaker_session=self.sagemaker_session, @@ -1437,27 +1433,6 @@ def predict_wrapper(endpoint, session): **kwargs ) - @classmethod - def _prepare_init_params_from_job_description(cls, job_details, model_channel_name=None): - """Convert the job description to init params that can be handled by the - class constructor - - Args: - job_details: the returned job details from a describe_training_job - API call. - model_channel_name (str): Name of the channel where pre-trained - model data will be downloaded - - Returns: - dictionary: The transformed init_params - """ - init_params = super(Estimator, cls)._prepare_init_params_from_job_description( - job_details, model_channel_name - ) - - init_params["image_name"] = init_params.pop("image") - return init_params - class Framework(EstimatorBase): """Base class that cannot be instantiated directly. @@ -1466,7 +1441,7 @@ class Framework(EstimatorBase): such as training/deployment images and predictor instances. """ - __framework_name__ = None + _framework_name = None LAUNCH_PS_ENV_NAME = "sagemaker_parameter_server_enabled" LAUNCH_MPI_ENV_NAME = "sagemaker_mpi_enabled" @@ -1479,10 +1454,9 @@ def __init__( entry_point, source_dir=None, hyperparameters=None, - enable_cloudwatch_metrics=False, container_log_level=logging.INFO, code_location=None, - image_name=None, + image_uri=None, dependencies=None, enable_network_isolation=False, git_config=None, @@ -1537,9 +1511,6 @@ def __init__( SageMaker. For convenience, this accepts other types for keys and values, but ``str()`` will be called to convert them before training. - enable_cloudwatch_metrics (bool): [DEPRECATED] Now there are - cloudwatch metrics emitted by all SageMaker training jobs. This - will be ignored for now and removed in a further release. container_log_level (int): Log level to use within the container (default: logging.INFO). Valid values are defined in the Python logging module. @@ -1548,7 +1519,7 @@ def __init__( a string prepended with a "/" is appended to ``code_location``. The code file uploaded to S3 is 'code_location/job-name/source/sourcedir.tar.gz'. If not specified, the default ``code location`` is s3://output_bucket/job-name/. - image_name (str): An alternate image name to use instead of the + image_uri (str): An alternate image name to use instead of the official Sagemaker image for the framework. This is useful to run one of the Sagemaker supported frameworks with an image containing custom dependencies. @@ -1668,19 +1639,11 @@ def __init__( self.git_config = git_config self.source_dir = source_dir self.dependencies = dependencies or [] - if enable_cloudwatch_metrics: - warnings.warn( - "enable_cloudwatch_metrics is now deprecated and will be removed in the future.", - DeprecationWarning, - ) - self.enable_cloudwatch_metrics = False + self.uploaded_code = None + self.container_log_level = container_log_level self.code_location = code_location - self.image_name = image_name - if image_name is not None: - logger.warning(parameter_v2_rename_warning("image_name", "image_uri")) - - self.uploaded_code = None + self.image_uri = image_uri self._hyperparameters = hyperparameters or {} self.checkpoint_s3_uri = checkpoint_s3_uri @@ -1735,7 +1698,6 @@ def _prepare_for_training(self, job_name=None): # Modify hyperparameters in-place to point to the right code directory and script URIs self._hyperparameters[DIR_PARAM_NAME] = code_dir self._hyperparameters[SCRIPT_PARAM_NAME] = script - self._hyperparameters[CLOUDWATCH_METRICS_PARAM_NAME] = self.enable_cloudwatch_metrics self._hyperparameters[CONTAINER_LOG_LEVEL_PARAM_NAME] = self.container_log_level self._hyperparameters[JOB_NAME_PARAM_NAME] = self._current_job_name self._hyperparameters[SAGEMAKER_REGION_PARAM_NAME] = self.sagemaker_session.boto_region_name @@ -1791,17 +1753,28 @@ def _stage_user_code_in_s3(self): ) def _model_source_dir(self): - """Get the appropriate value to pass as source_dir to model constructor - on deploying + """Get the appropriate value to pass as ``source_dir`` to a model constructor. Returns: - str: Either a local or an S3 path pointing to the source_dir to be - used for code by the model to be deployed + str: Either a local or an S3 path pointing to the ``source_dir`` to be + used for code by the model to be deployed """ return ( self.source_dir if self.sagemaker_session.local_mode else self.uploaded_code.s3_prefix ) + def _model_entry_point(self): + """Get the appropriate value to pass as ``entry_point`` to a model constructor. + + Returns: + str: The path to the entry point script. This can be either an absolute path or + a path relative to ``self._model_source_dir()``. + """ + if self.sagemaker_session.local_mode or (self._model_source_dir() is None): + return self.entry_point + + return self.uploaded_code.script_name + def hyperparameters(self): """Return the hyperparameters as a dictionary to use for training. @@ -1835,9 +1808,6 @@ class constructor init_params["hyperparameters"].get(SCRIPT_PARAM_NAME) ) init_params["source_dir"] = json.loads(init_params["hyperparameters"].get(DIR_PARAM_NAME)) - init_params["enable_cloudwatch_metrics"] = json.loads( - init_params["hyperparameters"].get(CLOUDWATCH_METRICS_PARAM_NAME) - ) init_params["container_log_level"] = json.loads( init_params["hyperparameters"].get(CONTAINER_LOG_LEVEL_PARAM_NAME) ) @@ -1856,7 +1826,7 @@ class constructor return init_params - def train_image(self): + def training_image_uri(self): """Return the Docker image to use for training. The :meth:`~sagemaker.estimator.EstimatorBase.fit` method, which does @@ -1866,14 +1836,15 @@ def train_image(self): Returns: str: The URI of the Docker image. """ - if self.image_name: - return self.image_name - return create_image_uri( + if self.image_uri: + return self.image_uri + return image_uris.retrieve( + self._framework_name, self.sagemaker_session.boto_region_name, - self.__framework_name__, - self.train_instance_type, - self.framework_version, # pylint: disable=no-member + instance_type=self.instance_type, + version=self.framework_version, # pylint: disable=no-member py_version=self.py_version, # pylint: disable=no-member + image_scope="training", ) @classmethod @@ -1888,14 +1859,16 @@ def attach(cls, training_job_name, sagemaker_session=None, model_channel_name="m has a Complete status, it can be ``deploy()`` ed to create a SageMaker Endpoint and return a ``Predictor``. - If the training job is in progress, attach will block and display log - messages from the training job, until the training job completes. + If the training job is in progress, attach will block until the training job + completes, but logs of the training job will not display. To see the logs + content, please call ``logs()`` Examples: >>> my_estimator.fit(wait=False) >>> training_job_name = my_estimator.latest_training_job.name Later on: >>> attached_estimator = Estimator.attach(training_job_name) + >>> attached_estimator.logs() >>> attached_estimator.deploy() Args: @@ -2025,7 +1998,8 @@ def transformer( If not specified, this setting is taken from the estimator's current configuration. model_name (str): Name to use for creating an Amazon SageMaker - model. If not specified, the name of the training job is used. + model. If not specified, the estimator generates a default job name + based on the training image name and current timestamp. Returns: sagemaker.transformer.Transformer: a ``Transformer`` object that can be used to start a @@ -2033,6 +2007,7 @@ def transformer( """ role = role or self.role tags = tags or self.tags + model_name = self._get_or_create_name(model_name) if self.latest_training_job is not None: if enable_network_isolation is None: @@ -2049,7 +2024,6 @@ def transformer( ) model._create_sagemaker_model(instance_type, tags=tags) - model_name = model.name transform_env = model.env.copy() if env is not None: transform_env.update(env) @@ -2058,7 +2032,6 @@ def transformer( "No finished training job found associated with this estimator. Please make sure " "this estimator is only used for building workflow config" ) - model_name = model_name or self._current_job_name transform_env = env or {} return Transformer( @@ -2086,7 +2059,7 @@ def _s3_uri_prefix(channel_name, s3_data): channel_name: s3_data: """ - if isinstance(s3_data, s3_input): + if isinstance(s3_data, TrainingInput): s3_uri = s3_data.config["DataSource"]["S3DataSource"]["S3Uri"] else: s3_uri = s3_data @@ -2096,7 +2069,7 @@ def _s3_uri_prefix(channel_name, s3_data): # E.g. 's3://bucket/data' would return 'bucket/data'. -# Also accepts other valid input types, e.g. dict and s3_input. +# Also accepts other valid input types, e.g. dict and TrainingInput. def _s3_uri_without_prefix_from_input(input_data): # Unpack an input_config object from a dict if a dict was passed in. """ @@ -2110,8 +2083,10 @@ def _s3_uri_without_prefix_from_input(input_data): return response if isinstance(input_data, str): return _s3_uri_prefix("training", input_data) - if isinstance(input_data, s3_input): + if isinstance(input_data, TrainingInput): return _s3_uri_prefix("training", input_data) raise ValueError( - "Unrecognized type for S3 input data config - not str or s3_input: {}".format(input_data) + "Unrecognized type for S3 input data config - not str or TrainingInput: {}".format( + input_data + ) ) diff --git a/src/sagemaker/fw_registry.py b/src/sagemaker/fw_registry.py deleted file mode 100644 index f0310c926b..0000000000 --- a/src/sagemaker/fw_registry.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -"""Placeholder docstring""" -from __future__ import absolute_import - -import logging - -from sagemaker.utils import get_ecr_image_uri_prefix - -logger = logging.getLogger(__name__) - -image_registry_map = { - "us-west-1": { - "sparkml-serving": "746614075791", - "scikit-learn": "746614075791", - "xgboost": "746614075791", - }, - "us-west-2": { - "sparkml-serving": "246618743249", - "scikit-learn": "246618743249", - "xgboost": "246618743249", - }, - "us-east-1": { - "sparkml-serving": "683313688378", - "scikit-learn": "683313688378", - "xgboost": "683313688378", - }, - "us-east-2": { - "sparkml-serving": "257758044811", - "scikit-learn": "257758044811", - "xgboost": "257758044811", - }, - "ap-northeast-1": { - "sparkml-serving": "354813040037", - "scikit-learn": "354813040037", - "xgboost": "354813040037", - }, - "ap-northeast-2": { - "sparkml-serving": "366743142698", - "scikit-learn": "366743142698", - "xgboost": "366743142698", - }, - "ap-southeast-1": { - "sparkml-serving": "121021644041", - "scikit-learn": "121021644041", - "xgboost": "121021644041", - }, - "ap-southeast-2": { - "sparkml-serving": "783357654285", - "scikit-learn": "783357654285", - "xgboost": "783357654285", - }, - "ap-south-1": { - "sparkml-serving": "720646828776", - "scikit-learn": "720646828776", - "xgboost": "720646828776", - }, - "eu-west-1": { - "sparkml-serving": "141502667606", - "scikit-learn": "141502667606", - "xgboost": "141502667606", - }, - "eu-west-2": { - "sparkml-serving": "764974769150", - "scikit-learn": "764974769150", - "xgboost": "764974769150", - }, - "eu-central-1": { - "sparkml-serving": "492215442770", - "scikit-learn": "492215442770", - "xgboost": "492215442770", - }, - "ca-central-1": { - "sparkml-serving": "341280168497", - "scikit-learn": "341280168497", - "xgboost": "341280168497", - }, - "us-gov-west-1": { - "sparkml-serving": "414596584902", - "scikit-learn": "414596584902", - "xgboost": "414596584902", - }, - "us-iso-east-1": { - "sparkml-serving": "833128469047", - "scikit-learn": "833128469047", - "xgboost": "833128469047", - }, - "ap-east-1": { - "sparkml-serving": "651117190479", - "scikit-learn": "651117190479", - "xgboost": "651117190479", - }, - "sa-east-1": { - "sparkml-serving": "737474898029", - "scikit-learn": "737474898029", - "xgboost": "737474898029", - }, - "eu-north-1": { - "sparkml-serving": "662702820516", - "scikit-learn": "662702820516", - "xgboost": "662702820516", - }, - "eu-west-3": { - "sparkml-serving": "659782779980", - "scikit-learn": "659782779980", - "xgboost": "659782779980", - }, - "me-south-1": { - "sparkml-serving": "801668240914", - "scikit-learn": "801668240914", - "xgboost": "801668240914", - }, - "cn-north-1": { - "sparkml-serving": "450853457545", - "scikit-learn": "450853457545", - "xgboost": "450853457545", - }, - "cn-northwest-1": { - "sparkml-serving": "451049120500", - "scikit-learn": "451049120500", - "xgboost": "451049120500", - }, -} - - -def registry(region_name, framework=None): - """Return docker registry for the given AWS region for the given framework. - This is only used for SparkML and Scikit-learn for now. - - Args: - region_name: - framework: - """ - try: - account_id = image_registry_map[region_name][framework] - return get_ecr_image_uri_prefix(account_id, region_name) - except KeyError: - logger.error("The specific image or region does not exist") - raise - - -def default_framework_uri(framework, region_name, image_tag): - """ - Args: - framework: - region_name: - image_tag: - """ - repository_name = "sagemaker-{}".format(framework) - account_name = registry(region_name, framework) - return "{}/{}:{}".format(account_name, repository_name, image_tag) diff --git a/src/sagemaker/fw_utils.py b/src/sagemaker/fw_utils.py index f2b9ff629e..f8d316fe3a 100644 --- a/src/sagemaker/fw_utils.py +++ b/src/sagemaker/fw_utils.py @@ -21,8 +21,6 @@ from collections import namedtuple import sagemaker.utils -from sagemaker import s3 -from sagemaker.utils import get_ecr_image_uri_prefix, ECR_URI_PATTERN logger = logging.getLogger("sagemaker") @@ -34,15 +32,6 @@ instantiated with positional or keyword arguments. """ -EMPTY_FRAMEWORK_VERSION_WARNING = ( - "No framework_version specified, defaulting to version {}. " - "framework_version will be required in SageMaker Python SDK v2." -) -LATER_FRAMEWORK_VERSION_WARNING = ( - "This is not the latest supported version. " - "If you would like to use version {latest}, " - "please add framework_version={latest} to your constructor." -) PYTHON_2_DEPRECATION_WARNING = ( "{latest_supported_version} is the latest version of {framework} that supports " "Python 2. Newer versions of {framework} will only be available for Python 3." @@ -55,339 +44,10 @@ "fully leverage all GPU cores; the parameter server will be configured to run " "only one worker per host regardless of the number of GPUs." ) -PARAMETER_V2_RENAME_WARNING = ( - "Parameter {v1_parameter_name} will be renamed to {v2_parameter_name} " - "in SageMaker Python SDK v2." -) - - -EMPTY_FRAMEWORK_VERSION_ERROR = ( - "framework_version is required for script mode estimator. " - "Please add framework_version={} to your constructor to avoid this error." -) -UNSUPPORTED_FRAMEWORK_VERSION_ERROR = ( - "{} framework does not support version {}. Please use one of the following: {}." -) -VALID_PY_VERSIONS = ["py2", "py3", "py37"] -VALID_EIA_FRAMEWORKS = [ - "tensorflow", - "tensorflow-serving", - "mxnet", - "mxnet-serving", - "pytorch-serving", -] -PY2_RESTRICTED_EIA_FRAMEWORKS = ["pytorch-serving"] -PY37_SUPPORTED_FRAMEWORKS = ["tensorflow-scriptmode"] -VALID_ACCOUNTS_BY_REGION = { - "us-gov-west-1": "246785580436", - "us-iso-east-1": "744548109606", - "cn-north-1": "422961961927", - "cn-northwest-1": "423003514399", -} -ASIMOV_VALID_ACCOUNTS_BY_REGION = { - "us-gov-west-1": "442386744353", - "us-iso-east-1": "886529160074", - "cn-north-1": "727897471807", - "cn-northwest-1": "727897471807", -} -OPT_IN_ACCOUNTS_BY_REGION = {"ap-east-1": "057415533634", "me-south-1": "724002660598"} -ASIMOV_OPT_IN_ACCOUNTS_BY_REGION = {"ap-east-1": "871362719292", "me-south-1": "217643126080"} -DEFAULT_ACCOUNT = "520713654638" -ASIMOV_PROD_ACCOUNT = "763104351884" -ASIMOV_DEFAULT_ACCOUNT = ASIMOV_PROD_ACCOUNT +DEBUGGER_UNSUPPORTED_REGIONS = ("us-gov-west-1", "us-iso-east-1") SINGLE_GPU_INSTANCE_TYPES = ("ml.p2.xlarge", "ml.p3.2xlarge") -MERGED_FRAMEWORKS_REPO_MAP = { - "tensorflow-scriptmode": "tensorflow-training", - "tensorflow-serving": "tensorflow-inference", - "tensorflow-serving-eia": "tensorflow-inference-eia", - "mxnet": "mxnet-training", - "mxnet-serving": "mxnet-inference", - "mxnet-serving-eia": "mxnet-inference-eia", - "pytorch": "pytorch-training", - "pytorch-serving": "pytorch-inference", - "pytorch-serving-eia": "pytorch-inference-eia", -} - -MERGED_FRAMEWORKS_LOWEST_VERSIONS = { - "tensorflow-scriptmode": {"py3": [1, 13, 1], "py2": [1, 14, 0], "py37": [1, 15, 2]}, - "tensorflow-serving": [1, 13, 0], - "tensorflow-serving-eia": [1, 14, 0], - "mxnet": {"py3": [1, 4, 1], "py2": [1, 6, 0]}, - "mxnet-serving": {"py3": [1, 4, 1], "py2": [1, 6, 0]}, - "mxnet-serving-eia": [1, 4, 1], - "pytorch": [1, 2, 0], - "pytorch-serving": [1, 2, 0], - "pytorch-serving-eia": [1, 3, 1], -} - -INFERENTIA_VERSION_RANGES = { - "neo-mxnet": [[1, 5, 1], [1, 5, 1]], - "neo-tensorflow": [[1, 15, 0], [1, 15, 0]], -} - -INFERENTIA_SUPPORTED_REGIONS = ["us-east-1", "us-west-2"] - -DEBUGGER_UNSUPPORTED_REGIONS = ["us-gov-west-1", "us-iso-east-1"] - - -def is_version_equal_or_higher(lowest_version, framework_version): - """Determine whether the ``framework_version`` is equal to or higher than - ``lowest_version`` - - Args: - lowest_version (List[int]): lowest version represented in an integer - list - framework_version (str): framework version string - - Returns: - bool: Whether or not ``framework_version`` is equal to or higher than - ``lowest_version`` - """ - version_list = [int(s) for s in framework_version.split(".")] - return version_list >= lowest_version[0 : len(version_list)] - - -def is_version_equal_or_lower(highest_version, framework_version): - """Determine whether the ``framework_version`` is equal to or lower than - ``highest_version`` - - Args: - highest_version (List[int]): highest version represented in an integer - list - framework_version (str): framework version string - - Returns: - bool: Whether or not ``framework_version`` is equal to or lower than - ``highest_version`` - """ - version_list = [int(s) for s in framework_version.split(".")] - return version_list <= highest_version[0 : len(version_list)] - - -def _is_dlc_version(framework, framework_version, py_version): - """Return if the framework's version uses the corresponding DLC image. - - Args: - framework (str): The framework name, e.g. "tensorflow-scriptmode" - framework_version (str): The framework version - py_version (str): The Python version, e.g. "py3" - - Returns: - bool: Whether or not the framework's version uses the DLC image. - """ - lowest_version_list = MERGED_FRAMEWORKS_LOWEST_VERSIONS.get(framework) - if isinstance(lowest_version_list, dict): - lowest_version_list = lowest_version_list[py_version] - - if lowest_version_list: - return is_version_equal_or_higher(lowest_version_list, framework_version) - return False - - -def _is_inferentia_supported(framework, framework_version): - """Return if Inferentia supports the framework and its version. - - Args: - framework (str): The framework name, e.g. "tensorflow" - framework_version (str): The framework version - - Returns: - bool: Whether or not Inferentia supports the framework and its version. - """ - lowest_version_list = INFERENTIA_VERSION_RANGES.get(framework)[0] - highest_version_list = INFERENTIA_VERSION_RANGES.get(framework)[1] - return is_version_equal_or_higher( - lowest_version_list, framework_version - ) and is_version_equal_or_lower(highest_version_list, framework_version) - - -def _registry_id(region, framework, py_version, account, framework_version): - """Return the Amazon ECR registry number (or AWS account ID) for - the given framework, framework version, Python version, and region. - - Args: - region (str): The AWS region. - framework (str): The framework name, e.g. "tensorflow-scriptmode". - py_version (str): The Python version, e.g. "py3". - account (str): The AWS account ID to use as a default. - framework_version (str): The framework version. - - Returns: - str: The appropriate Amazon ECR registry number. If there is no - specific one for the framework, framework version, Python version, - and region, then ``account`` is returned. - """ - if _is_dlc_version(framework, framework_version, py_version): - if region in ASIMOV_OPT_IN_ACCOUNTS_BY_REGION: - return ASIMOV_OPT_IN_ACCOUNTS_BY_REGION.get(region) - if region in ASIMOV_VALID_ACCOUNTS_BY_REGION: - return ASIMOV_VALID_ACCOUNTS_BY_REGION.get(region) - return ASIMOV_DEFAULT_ACCOUNT - if region in OPT_IN_ACCOUNTS_BY_REGION: - return OPT_IN_ACCOUNTS_BY_REGION.get(region) - return VALID_ACCOUNTS_BY_REGION.get(region, account) - - -def create_image_uri( - region, - framework, - instance_type, - framework_version, - py_version=None, - account=None, - accelerator_type=None, - optimized_families=None, -): - """Return the ECR URI of an image. - - Args: - region (str): AWS region where the image is uploaded. - framework (str): framework used by the image. - instance_type (str): SageMaker instance type. Used to determine device - type (cpu/gpu/family-specific optimized). - framework_version (str): The version of the framework. - py_version (str): Optional. Python version. If specified, should be one - of 'py2' or 'py3'. If not specified, image uri will not include a - python component. - account (str): AWS account that contains the image. (default: - '520713654638') - accelerator_type (str): SageMaker Elastic Inference accelerator type. - optimized_families (str): Instance families for which there exist - specific optimized images. - - Returns: - str: The appropriate image URI based on the given parameters. - """ - logger.warning( - "'create_image_uri' will be deprecated in favor of 'ImageURIProvider' class " - "in SageMaker Python SDK v2." - ) - - optimized_families = optimized_families or [] - - if py_version and py_version not in VALID_PY_VERSIONS: - raise ValueError("invalid py_version argument: {}".format(py_version)) - - if py_version == "py37" and framework not in PY37_SUPPORTED_FRAMEWORKS: - raise ValueError("{} does not support Python 3.7 at this time.".format(framework)) - - if _accelerator_type_valid_for_framework( - framework=framework, - py_version=py_version, - accelerator_type=accelerator_type, - optimized_families=optimized_families, - ): - framework += "-eia" - - # Handle account number for specific cases (e.g. GovCloud, opt-in regions, DLC images etc.) - if account is None: - account = _registry_id( - region=region, - framework=framework, - py_version=py_version, - account=DEFAULT_ACCOUNT, - framework_version=framework_version, - ) - - # Handle Local Mode - if instance_type.startswith("local"): - device_type = "cpu" if instance_type == "local" else "gpu" - elif not instance_type.startswith("ml."): - raise ValueError( - "{} is not a valid SageMaker instance type. See: " - "https://aws.amazon.com/sagemaker/pricing/instance-types/".format(instance_type) - ) - else: - family = instance_type.split(".")[1] - - # For some frameworks, we have optimized images for specific families, e.g c5 or p3. - # In those cases, we use the family name in the image tag. In other cases, we use - # 'cpu' or 'gpu'. - if family in optimized_families: - device_type = family - elif family.startswith("inf"): - device_type = "inf" - elif family[0] in ["g", "p"]: - device_type = "gpu" - else: - device_type = "cpu" - - if device_type == "inf": - if region not in INFERENTIA_SUPPORTED_REGIONS: - raise ValueError( - "Inferentia is not supported in region {}. Supported regions are {}".format( - region, ", ".join(INFERENTIA_SUPPORTED_REGIONS) - ) - ) - if framework not in INFERENTIA_VERSION_RANGES: - raise ValueError( - "Inferentia does not support {}. Currently it supports " - "MXNet and TensorFlow with more frameworks coming soon.".format( - framework.split("-")[-1] - ) - ) - if not _is_inferentia_supported(framework, framework_version): - raise ValueError( - "Inferentia is not supported with {} version {}.".format( - framework.split("-")[-1], framework_version - ) - ) - - use_dlc_image = _is_dlc_version(framework, framework_version, py_version) - - if not py_version or (use_dlc_image and framework == "tensorflow-serving-eia"): - tag = "{}-{}".format(framework_version, device_type) - else: - tag = "{}-{}-{}".format(framework_version, device_type, py_version) - - if use_dlc_image: - ecr_repo = MERGED_FRAMEWORKS_REPO_MAP[framework] - else: - ecr_repo = "sagemaker-{}".format(framework) - - return "{}/{}:{}".format(get_ecr_image_uri_prefix(account, region), ecr_repo, tag) - - -def _accelerator_type_valid_for_framework( - framework, py_version, accelerator_type=None, optimized_families=None -): - """ - Args: - framework: - py_version: - accelerator_type: - optimized_families: - """ - if accelerator_type is None: - return False - - if py_version == "py2" and framework in PY2_RESTRICTED_EIA_FRAMEWORKS: - raise ValueError( - "{} is not supported with Amazon Elastic Inference in Python 2.".format(framework) - ) - - if framework not in VALID_EIA_FRAMEWORKS: - raise ValueError( - "{} is not supported with Amazon Elastic Inference. Currently only " - "Python-based TensorFlow, MXNet, PyTorch are supported.".format(framework) - ) - - if optimized_families: - raise ValueError("Neo does not support Amazon Elastic Inference.") - - if ( - not accelerator_type.startswith("ml.eia") - and not accelerator_type == "local_sagemaker_notebook" - ): - raise ValueError( - "{} is not a valid SageMaker Elastic Inference accelerator type. " - "See: https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html".format(accelerator_type) - ) - - return True - def validate_source_dir(script, directory): """Validate that the source directory exists and it contains the user script @@ -447,7 +107,7 @@ def tar_and_upload_dir( script name. """ if directory and directory.lower().startswith("s3://"): - return UploadedCode(s3_prefix=directory, script_name=os.path.basename(script)) + return UploadedCode(s3_prefix=directory, script_name=script) script_name = script if directory else os.path.basename(script) dependencies = dependencies or [] @@ -490,12 +150,12 @@ def _list_files_to_compress(script, directory): return [os.path.join(basedir, name) for name in os.listdir(basedir)] -def framework_name_from_image(image_name): +def framework_name_from_image(image_uri): # noinspection LongLine """Extract the framework and Python version from the image name. Args: - image_name (str): Image URI, which should be one of the following forms: + image_uri (str): Image URI, which should be one of the following forms: legacy: '.dkr.ecr..amazonaws.com/sagemaker---:' legacy: @@ -515,8 +175,8 @@ def framework_name_from_image(image_name): - str: The image tag - str: If the TensorFlow image is script mode """ - sagemaker_pattern = re.compile(ECR_URI_PATTERN) - sagemaker_match = sagemaker_pattern.match(image_name) + sagemaker_pattern = re.compile(sagemaker.utils.ECR_URI_PATTERN) + sagemaker_match = sagemaker_pattern.match(image_uri) if sagemaker_match is None: return None, None, None, None @@ -562,20 +222,6 @@ def framework_version_from_tag(image_tag): return None if tag_match is None else tag_match.group(1) -def parse_s3_url(url): - """Calls the method with the same name in the s3 module. - - :func:~sagemaker.s3.parse_s3_url - - Args: - url: A URL, expected with an s3 scheme. - - Returns: The return value of s3.parse_s3_url, which is a tuple containing: - str: S3 bucket name str: S3 key - """ - return s3.parse_s3_url(url) - - def model_code_key_prefix(code_location_key_prefix, model_name, image): """Returns the s3 key prefix for uploading code during model deployment The location returned is a potential concatenation of 2 parts @@ -592,27 +238,7 @@ def model_code_key_prefix(code_location_key_prefix, model_name, image): return "/".join(filter(None, [code_location_key_prefix, model_name or training_job_name])) -def empty_framework_version_warning(default_version, latest_version): - """ - Args: - default_version: - latest_version: - """ - msgs = [EMPTY_FRAMEWORK_VERSION_WARNING.format(default_version)] - if default_version != latest_version: - msgs.append(later_framework_version_warning(latest_version)) - return " ".join(msgs) - - -def later_framework_version_warning(latest_version): - """ - Args: - latest_version: - """ - return LATER_FRAMEWORK_VERSION_WARNING.format(latest=latest_version) - - -def warn_if_parameter_server_with_multi_gpu(training_instance_type, distributions): +def warn_if_parameter_server_with_multi_gpu(training_instance_type, distribution): """Warn the user that training will not fully leverage all the GPU cores if parameter server is enabled and a multi-GPU instance is selected. Distributed training with the default parameter server setup doesn't @@ -620,7 +246,7 @@ def warn_if_parameter_server_with_multi_gpu(training_instance_type, distribution Args: training_instance_type (str): A string representing the type of training instance selected. - distributions (dict): A dictionary with information to enable distributed training. + distribution (dict): A dictionary with information to enable distributed training. (Defaults to None if distributed training is not enabled.) For example: .. code:: python @@ -634,7 +260,7 @@ def warn_if_parameter_server_with_multi_gpu(training_instance_type, distribution """ - if training_instance_type == "local" or distributions is None: + if training_instance_type == "local" or distribution is None: return is_multi_gpu_instance = ( @@ -642,7 +268,7 @@ def warn_if_parameter_server_with_multi_gpu(training_instance_type, distribution or training_instance_type.split(".")[1].startswith("p") ) and training_instance_type not in SINGLE_GPU_INSTANCE_TYPES - ps_enabled = "parameter_server" in distributions and distributions["parameter_server"].get( + ps_enabled = "parameter_server" in distribution and distribution["parameter_server"].get( "enabled", False ) @@ -650,25 +276,6 @@ def warn_if_parameter_server_with_multi_gpu(training_instance_type, distribution logger.warning(PARAMETER_SERVER_MULTI_GPU_WARNING) -def get_unsupported_framework_version_error( - framework_name, unsupported_version, supported_versions -): - """Return error message for unsupported framework version. - - This should also return the supported versions for customers. - - :param framework_name: - :param unsupported_version: - :param supported_versions: - :return: - """ - return UNSUPPORTED_FRAMEWORK_VERSION_ERROR.format( - framework_name, - unsupported_version, - ", ".join('"{}"'.format(version) for version in supported_versions), - ) - - def python_deprecation_warning(framework, latest_supported_version): """ Args: @@ -680,17 +287,6 @@ def python_deprecation_warning(framework, latest_supported_version): ) -def parameter_v2_rename_warning(v1_parameter_name, v2_parameter_name): - """ - Args: - v1_parameter_name: parameter name used in SageMaker Python SDK v1 - v2_parameter_name: parameter name used in SageMaker Python SDK v2 - """ - return PARAMETER_V2_RENAME_WARNING.format( - v1_parameter_name=v1_parameter_name, v2_parameter_name=v2_parameter_name - ) - - def _region_supports_debugger(region_name): """Returns boolean indicating whether the region supports Amazon SageMaker Debugger. @@ -702,3 +298,24 @@ def _region_supports_debugger(region_name): """ return region_name.lower() not in DEBUGGER_UNSUPPORTED_REGIONS + + +def validate_version_or_image_args(framework_version, py_version, image_uri): + """Checks if version or image arguments are specified. + + Validates framework and model arguments to enforce version or image specification. + + Args: + framework_version (str): The version of the framework. + py_version (str): The version of Python. + image_uri (str): The URI of the image. + + Raises: + ValueError: if `image_uri` is None and either `framework_version` or `py_version` is + None. + """ + if (framework_version is None or py_version is None) and image_uri is None: + raise ValueError( + "framework_version or py_version was None, yet image_uri was also None. " + "Either specify both framework_version and py_version, or specify image_uri." + ) diff --git a/src/sagemaker/image_uri_config/blazingtext.json b/src/sagemaker/image_uri_config/blazingtext.json new file mode 100644 index 0000000000..c94c9cf214 --- /dev/null +++ b/src/sagemaker/image_uri_config/blazingtext.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "501404015308", + "ap-northeast-2": "306986355934", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "544295431143", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "813361260812", + "eu-north-1": "669576153137", + "eu-west-1": "685385470294", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "811284229777", + "us-east-2": "825641698319", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "433757028032" + }, + "repository": "blazingtext" + } + } +} diff --git a/src/sagemaker/image_uri_config/chainer.json b/src/sagemaker/image_uri_config/chainer.json new file mode 100644 index 0000000000..a2f31a4c3a --- /dev/null +++ b/src/sagemaker/image_uri_config/chainer.json @@ -0,0 +1,95 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["inference", "training"], + "version_aliases": { + "4.0": "4.0.0", + "4.1": "4.1.0", + "5.0": "5.0.0" + }, + "versions": { + "4.0.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-chainer", + "py_versions": ["py2", "py3"] + }, + "4.1.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-chainer", + "py_versions": ["py2", "py3"] + }, + "5.0.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-chainer", + "py_versions": ["py2", "py3"] + } + } +} diff --git a/src/sagemaker/image_uri_config/coach-mxnet.json b/src/sagemaker/image_uri_config/coach-mxnet.json new file mode 100644 index 0000000000..6f0b4dded7 --- /dev/null +++ b/src/sagemaker/image_uri_config/coach-mxnet.json @@ -0,0 +1,64 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["training"], + "versions": { + "0.11": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-mxnet", + "tag_prefix": "coach0.11" + }, + "0.11.0": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-mxnet", + "tag_prefix": "coach0.11.0" + } + } +} diff --git a/src/sagemaker/image_uri_config/coach-tensorflow.json b/src/sagemaker/image_uri_config/coach-tensorflow.json new file mode 100644 index 0000000000..83db68d730 --- /dev/null +++ b/src/sagemaker/image_uri_config/coach-tensorflow.json @@ -0,0 +1,174 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["training"], + "versions": { + "0.10": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "coach0.10" + }, + "0.10.1": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "coach0.10.1" + }, + "0.11": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "coach0.11" + }, + "0.11.0": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "coach0.11.0" + }, + "0.11.1": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "coach0.11.1" + }, + "1.0.0": { + "py_versions": ["py3"], + "registries": { + "ap-northeast-1": "462105765813", + "ap-northeast-2": "462105765813", + "ap-south-1": "462105765813", + "ap-southeast-1": "462105765813", + "ap-southeast-2": "462105765813", + "ca-central-1": "462105765813", + "eu-central-1": "462105765813", + "eu-north-1": "462105765813", + "eu-west-1": "462105765813", + "eu-west-2": "462105765813", + "eu-west-3": "462105765813", + "sa-east-1": "462105765813", + "us-east-1": "462105765813", + "us-east-2": "462105765813", + "us-west-1": "462105765813", + "us-west-2": "462105765813" + }, + "repository": "sagemaker-rl-coach-container", + "tag_prefix": "coach-1.0.0-tf" + } + } +} diff --git a/src/sagemaker/image_uri_config/debugger.json b/src/sagemaker/image_uri_config/debugger.json new file mode 100644 index 0000000000..41fb0ff6f1 --- /dev/null +++ b/src/sagemaker/image_uri_config/debugger.json @@ -0,0 +1,30 @@ +{ + "scope": ["debugger"], + "versions": { + "latest": { + "registries": { + "ap-east-1": "199566480951", + "ap-northeast-1": "430734990657", + "ap-northeast-2": "578805364391", + "ap-south-1": "904829902805", + "ap-southeast-1": "972752614525", + "ap-southeast-2": "184798709955", + "ca-central-1": "519511493484", + "cn-north-1": "618459771430", + "cn-northwest-1": "658757709296", + "eu-central-1": "482524230118", + "eu-north-1": "314864569078", + "eu-west-1": "929884845733", + "eu-west-2": "250201462417", + "eu-west-3": "447278800020", + "me-south-1": "986000313247", + "sa-east-1": "818342061345", + "us-east-1": "199566480951", + "us-east-2": "915447279597", + "us-west-1": "685455198987", + "us-west-2": "895741380848" + }, + "repository": "sagemaker-debugger-rules" + } + } +} \ No newline at end of file diff --git a/src/sagemaker/image_uri_config/factorization-machines.json b/src/sagemaker/image_uri_config/factorization-machines.json new file mode 100644 index 0000000000..56f425c0f3 --- /dev/null +++ b/src/sagemaker/image_uri_config/factorization-machines.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "factorization-machines" + } + } +} diff --git a/src/sagemaker/image_uri_config/forecasting-deepar.json b/src/sagemaker/image_uri_config/forecasting-deepar.json new file mode 100644 index 0000000000..dae3a2b2c3 --- /dev/null +++ b/src/sagemaker/image_uri_config/forecasting-deepar.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "633353088612", + "ap-northeast-2": "204372634319", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "514117268639", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "495149712605", + "eu-north-1": "669576153137", + "eu-west-1": "224300973850", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "522234722520", + "us-east-2": "566113047672", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "156387875391" + }, + "repository": "forecasting-deepar" + } + } +} diff --git a/src/sagemaker/image_uri_config/image-classification-neo.json b/src/sagemaker/image_uri_config/image-classification-neo.json new file mode 100644 index 0000000000..9fba643590 --- /dev/null +++ b/src/sagemaker/image_uri_config/image-classification-neo.json @@ -0,0 +1,31 @@ +{ + "scope": ["inference"], + "versions": { + "latest": { + "registries": { + "ap-east-1": "110948597952", + "ap-northeast-1": "941853720454", + "ap-northeast-2": "151534178276", + "ap-south-1": "763008648453", + "ap-southeast-1": "324986816169", + "ap-southeast-2": "355873309152", + "ca-central-1": "464438896020", + "cn-north-1": "472730292857", + "cn-northwest-1": "474822919863", + "eu-central-1": "746233611703", + "eu-north-1": "601324751636", + "eu-west-1": "802834080501", + "eu-west-2": "205493899709", + "eu-west-3": "254080097072", + "me-south-1": "836785723513", + "sa-east-1": "756306329178", + "us-east-1": "785573368785", + "us-east-2": "007439368137", + "us-gov-west-1": "263933020539", + "us-west-1": "710691900526", + "us-west-2": "301217895009" + }, + "repository": "image-classification-neo" + } + } +} diff --git a/src/sagemaker/image_uri_config/image-classification.json b/src/sagemaker/image_uri_config/image-classification.json new file mode 100644 index 0000000000..b5b9591965 --- /dev/null +++ b/src/sagemaker/image_uri_config/image-classification.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "501404015308", + "ap-northeast-2": "306986355934", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "544295431143", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "813361260812", + "eu-north-1": "669576153137", + "eu-west-1": "685385470294", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "811284229777", + "us-east-2": "825641698319", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "433757028032" + }, + "repository": "image-classification" + } + } +} diff --git a/src/sagemaker/image_uri_config/inferentia-mxnet.json b/src/sagemaker/image_uri_config/inferentia-mxnet.json new file mode 100644 index 0000000000..638892e726 --- /dev/null +++ b/src/sagemaker/image_uri_config/inferentia-mxnet.json @@ -0,0 +1,14 @@ +{ + "processors": ["inf"], + "scope": ["inference"], + "versions": { + "1.5.1": { + "py_versions": ["py3"], + "registries": { + "us-east-1": "785573368785", + "us-west-2": "301217895009" + }, + "repository": "sagemaker-neo-mxnet" + } + } +} diff --git a/src/sagemaker/image_uri_config/inferentia-tensorflow.json b/src/sagemaker/image_uri_config/inferentia-tensorflow.json new file mode 100644 index 0000000000..8b75be9f06 --- /dev/null +++ b/src/sagemaker/image_uri_config/inferentia-tensorflow.json @@ -0,0 +1,14 @@ +{ + "processors": ["inf"], + "scope": ["inference"], + "versions": { + "1.15.0": { + "py_versions": ["py3"], + "registries": { + "us-east-1": "785573368785", + "us-west-2": "301217895009" + }, + "repository": "sagemaker-neo-tensorflow" + } + } +} diff --git a/src/sagemaker/image_uri_config/ipinsights.json b/src/sagemaker/image_uri_config/ipinsights.json new file mode 100644 index 0000000000..c1638a0277 --- /dev/null +++ b/src/sagemaker/image_uri_config/ipinsights.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "ipinsights" + } + } +} diff --git a/src/sagemaker/image_uri_config/kmeans.json b/src/sagemaker/image_uri_config/kmeans.json new file mode 100644 index 0000000000..457936d91b --- /dev/null +++ b/src/sagemaker/image_uri_config/kmeans.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "kmeans" + } + } +} diff --git a/src/sagemaker/image_uri_config/knn.json b/src/sagemaker/image_uri_config/knn.json new file mode 100644 index 0000000000..d8596f1a52 --- /dev/null +++ b/src/sagemaker/image_uri_config/knn.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "knn" + } + } +} diff --git a/src/sagemaker/image_uri_config/lda.json b/src/sagemaker/image_uri_config/lda.json new file mode 100644 index 0000000000..1a0501e814 --- /dev/null +++ b/src/sagemaker/image_uri_config/lda.json @@ -0,0 +1,25 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-northeast-1": "258307448986", + "ap-northeast-2": "293181348795", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "297031611018", + "ca-central-1": "469771592824", + "eu-central-1": "353608530281", + "eu-west-1": "999678624901", + "eu-west-2": "644912444149", + "us-east-1": "766337827248", + "us-east-2": "999911452149", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "266724342769" + }, + "repository": "lda" + } + } +} diff --git a/src/sagemaker/image_uri_config/linear-learner.json b/src/sagemaker/image_uri_config/linear-learner.json new file mode 100644 index 0000000000..ec0ae2ca10 --- /dev/null +++ b/src/sagemaker/image_uri_config/linear-learner.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "linear-learner" + } + } +} diff --git a/src/sagemaker/image_uri_config/model-monitor.json b/src/sagemaker/image_uri_config/model-monitor.json new file mode 100644 index 0000000000..479cd98893 --- /dev/null +++ b/src/sagemaker/image_uri_config/model-monitor.json @@ -0,0 +1,30 @@ +{ + "scope": ["monitoring"], + "versions": { + "": { + "registries": { + "ap-east-1": "001633400207", + "ap-northeast-1": "574779866223", + "ap-northeast-2": "709848358524", + "ap-south-1": "126357580389", + "ap-southeast-1": "245545462676", + "ap-southeast-2": "563025443158", + "ca-central-1": "536280801234", + "cn-north-1": "453000072557", + "cn-northwest-1": "453252182341", + "eu-central-1": "048819808253", + "eu-north-1": "895015795356", + "eu-west-1": "468650794304", + "eu-west-2": "749857270468", + "eu-west-3": "680080141114", + "me-south-1": "607024016150", + "sa-east-1": "539772159869", + "us-east-1": "156813124566", + "us-east-2": "777275614652", + "us-west-1": "890145073186", + "us-west-2": "159807026194" + }, + "repository": "sagemaker-model-monitor-analyzer" + } + } +} diff --git a/src/sagemaker/image_uri_config/mxnet.json b/src/sagemaker/image_uri_config/mxnet.json new file mode 100644 index 0000000000..b878a70c31 --- /dev/null +++ b/src/sagemaker/image_uri_config/mxnet.json @@ -0,0 +1,656 @@ +{ + "training": { + "processors": ["cpu", "gpu"], + "version_aliases": { + "0.12": "0.12.1", + "1.0": "1.0.0", + "1.1": "1.1.0", + "1.2": "1.2.1", + "1.3": "1.3.0", + "1.4": "1.4.1", + "1.6": "1.6.0" + }, + "versions": { + "0.12.1": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.0.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.1.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.2.1": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.3.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.4.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.4.1": { + "py2": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet" + }, + "py3": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "mxnet-training" + } + }, + "1.6.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "mxnet-training", + "py_versions": ["py2", "py3"] + } + } + }, + "inference": { + "processors": ["cpu", "gpu"], + "version_aliases": { + "0.12": "0.12.1", + "1.0": "1.0.0", + "1.1": "1.1.0", + "1.2": "1.2.1", + "1.3": "1.3.0", + "1.4": "1.4.1", + "1.6": "1.6.0" + }, + "versions": { + "0.12.1": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.0.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.1.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.2.1": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.3.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet", + "py_versions": ["py2", "py3"] + }, + "1.4.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet-serving", + "py_versions": ["py2", "py3"] + }, + "1.4.1": { + "py2": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet-serving" + }, + "py3": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "mxnet-inference" + } + }, + "1.6.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "mxnet-inference", + "py_versions": ["py2", "py3"] + } + } + }, + "eia": { + "processors": ["cpu"], + "version_aliases": { + "1.3": "1.3.0", + "1.4": "1.4.1", + "1.5": "1.5.1" + }, + "versions": { + "1.3.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet-eia", + "py_versions": ["py2", "py3"] + }, + "1.4.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-mxnet-serving-eia", + "py_versions": ["py2", "py3"] + }, + "1.4.1": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "mxnet-inference-eia", + "py_versions": ["py2", "py3"] + }, + "1.5.1": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "mxnet-inference-eia", + "py_versions": ["py2", "py3"] + } + } + } +} diff --git a/src/sagemaker/image_uri_config/neo-mxnet.json b/src/sagemaker/image_uri_config/neo-mxnet.json new file mode 100644 index 0000000000..eec9ceb7e9 --- /dev/null +++ b/src/sagemaker/image_uri_config/neo-mxnet.json @@ -0,0 +1,46 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["inference"], + "version_aliases": { + "0.12.1": "1.5", + "1.0.0": "1.5", + "1.1.0": "1.5", + "1.2": "1.5", + "1.2.0": "1.5", + "1.2.1": "1.5", + "1.3": "1.5", + "1.3.0": "1.5", + "1.4": "1.5", + "1.4.0": "1.5", + "1.4.1": "1.5" + }, + "versions": { + "1.5": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "110948597952", + "ap-northeast-1": "941853720454", + "ap-northeast-2": "151534178276", + "ap-south-1": "763008648453", + "ap-southeast-1": "324986816169", + "ap-southeast-2": "355873309152", + "ca-central-1": "464438896020", + "cn-north-1": "472730292857", + "cn-northwest-1": "474822919863", + "eu-central-1": "746233611703", + "eu-north-1": "601324751636", + "eu-west-1": "802834080501", + "eu-west-2": "205493899709", + "eu-west-3": "254080097072", + "me-south-1": "836785723513", + "sa-east-1": "756306329178", + "us-east-1": "785573368785", + "us-east-2": "007439368137", + "us-gov-west-1": "263933020539", + "us-west-1": "710691900526", + "us-west-2": "301217895009" + }, + "repository": "sagemaker-neo-mxnet" + } + } +} diff --git a/src/sagemaker/image_uri_config/neo-pytorch.json b/src/sagemaker/image_uri_config/neo-pytorch.json new file mode 100644 index 0000000000..e53671a3ac --- /dev/null +++ b/src/sagemaker/image_uri_config/neo-pytorch.json @@ -0,0 +1,40 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["inference"], + "version_aliases": { + "0.4.0": "1.4.0", + "1.0.0": "1.4.0", + "1.1.0": "1.4.0", + "1.2.0": "1.4.0", + "1.3.0": "1.4.0" + }, + "versions": { + "1.4.0": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "110948597952", + "ap-northeast-1": "941853720454", + "ap-northeast-2": "151534178276", + "ap-south-1": "763008648453", + "ap-southeast-1": "324986816169", + "ap-southeast-2": "355873309152", + "ca-central-1": "464438896020", + "cn-north-1": "472730292857", + "cn-northwest-1": "474822919863", + "eu-central-1": "746233611703", + "eu-north-1": "601324751636", + "eu-west-1": "802834080501", + "eu-west-2": "205493899709", + "eu-west-3": "254080097072", + "me-south-1": "836785723513", + "sa-east-1": "756306329178", + "us-east-1": "785573368785", + "us-east-2": "007439368137", + "us-gov-west-1": "263933020539", + "us-west-1": "710691900526", + "us-west-2": "301217895009" + }, + "repository": "sagemaker-neo-pytorch" + } + } +} diff --git a/src/sagemaker/image_uri_config/neo-tensorflow.json b/src/sagemaker/image_uri_config/neo-tensorflow.json new file mode 100644 index 0000000000..be902c2129 --- /dev/null +++ b/src/sagemaker/image_uri_config/neo-tensorflow.json @@ -0,0 +1,46 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["inference"], + "version_aliases": { + "1.4.1": "1.15.0", + "1.5.0": "1.15.0", + "1.6.0": "1.15.0", + "1.7.0": "1.15.0", + "1.8.0": "1.15.0", + "1.9.0": "1.15.0", + "1.10.0": "1.15.0", + "1.11.0": "1.15.0", + "1.12.0": "1.15.0", + "1.13.0": "1.15.0", + "1.14.0": "1.15.0" + }, + "versions": { + "1.15.0": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "110948597952", + "ap-northeast-1": "941853720454", + "ap-northeast-2": "151534178276", + "ap-south-1": "763008648453", + "ap-southeast-1": "324986816169", + "ap-southeast-2": "355873309152", + "ca-central-1": "464438896020", + "cn-north-1": "472730292857", + "cn-northwest-1": "474822919863", + "eu-central-1": "746233611703", + "eu-north-1": "601324751636", + "eu-west-1": "802834080501", + "eu-west-2": "205493899709", + "eu-west-3": "254080097072", + "me-south-1": "836785723513", + "sa-east-1": "756306329178", + "us-east-1": "785573368785", + "us-east-2": "007439368137", + "us-gov-west-1": "263933020539", + "us-west-1": "710691900526", + "us-west-2": "301217895009" + }, + "repository": "sagemaker-neo-tensorflow" + } + } +} diff --git a/src/sagemaker/image_uri_config/ntm.json b/src/sagemaker/image_uri_config/ntm.json new file mode 100644 index 0000000000..78fcbdf2fc --- /dev/null +++ b/src/sagemaker/image_uri_config/ntm.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "ntm" + } + } +} diff --git a/src/sagemaker/image_uri_config/object-detection.json b/src/sagemaker/image_uri_config/object-detection.json new file mode 100644 index 0000000000..a439f1043b --- /dev/null +++ b/src/sagemaker/image_uri_config/object-detection.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "501404015308", + "ap-northeast-2": "306986355934", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "544295431143", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "813361260812", + "eu-north-1": "669576153137", + "eu-west-1": "685385470294", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "811284229777", + "us-east-2": "825641698319", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "433757028032" + }, + "repository": "object-detection" + } + } +} diff --git a/src/sagemaker/image_uri_config/object2vec.json b/src/sagemaker/image_uri_config/object2vec.json new file mode 100644 index 0000000000..2052db7cc5 --- /dev/null +++ b/src/sagemaker/image_uri_config/object2vec.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "object2vec" + } + } +} diff --git a/src/sagemaker/image_uri_config/pca.json b/src/sagemaker/image_uri_config/pca.json new file mode 100644 index 0000000000..652a0ae99d --- /dev/null +++ b/src/sagemaker/image_uri_config/pca.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "pca" + } + } +} diff --git a/src/sagemaker/image_uri_config/pytorch.json b/src/sagemaker/image_uri_config/pytorch.json new file mode 100644 index 0000000000..5d7256b3e6 --- /dev/null +++ b/src/sagemaker/image_uri_config/pytorch.json @@ -0,0 +1,538 @@ +{ + "eia": { + "processors": [ + "cpu" + ], + "version_aliases": { + "1.3": "1.3.1" + }, + "versions": { + "1.3.1": { + "py_versions": [ + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-inference-eia" + } + } + }, + "inference": { + "processors": [ + "cpu", + "gpu" + ], + "version_aliases": { + "0.4": "0.4.0", + "1.0": "1.0.0", + "1.1": "1.1.0", + "1.2": "1.2.0", + "1.3": "1.3.1", + "1.4": "1.4.0", + "1.5": "1.5.0" + }, + "versions": { + "0.4.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-pytorch" + }, + "1.0.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-pytorch" + }, + "1.1.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-pytorch" + }, + "1.2.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-inference" + }, + "1.3.1": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-inference" + }, + "1.4.0": { + "py_versions": [ + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-inference" + }, + "1.5.0": { + "py_versions": [ + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-inference" + } + } + }, + "training": { + "processors": [ + "cpu", + "gpu" + ], + "version_aliases": { + "0.4": "0.4.0", + "1.0": "1.0.0", + "1.1": "1.1.0", + "1.2": "1.2.0", + "1.3": "1.3.1", + "1.4": "1.4.0", + "1.5": "1.5.0", + "1.6": "1.6.0" + }, + "versions": { + "0.4.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-pytorch" + }, + "1.0.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-pytorch" + }, + "1.1.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-pytorch" + }, + "1.2.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-training" + }, + "1.3.1": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-training" + }, + "1.4.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-training" + }, + "1.5.0": { + "py_versions": [ + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-training" + }, + "1.6.0": { + "py_versions": [ + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "pytorch-training" + } + } + } +} \ No newline at end of file diff --git a/src/sagemaker/image_uri_config/randomcutforest.json b/src/sagemaker/image_uri_config/randomcutforest.json new file mode 100644 index 0000000000..05fadc0188 --- /dev/null +++ b/src/sagemaker/image_uri_config/randomcutforest.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "351501993468", + "ap-northeast-2": "835164637446", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "712309505854", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "664544806723", + "eu-north-1": "669576153137", + "eu-west-1": "438346466558", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "382416733822", + "us-east-2": "404615174143", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "174872318107" + }, + "repository": "randomcutforest" + } + } +} diff --git a/src/sagemaker/image_uri_config/ray-pytorch.json b/src/sagemaker/image_uri_config/ray-pytorch.json new file mode 100644 index 0000000000..369a9a1d50 --- /dev/null +++ b/src/sagemaker/image_uri_config/ray-pytorch.json @@ -0,0 +1,29 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["training"], + "versions": { + "0.8.5": { + "py_versions": ["py36"], + "registries": { + "ap-northeast-1": "462105765813", + "ap-northeast-2": "462105765813", + "ap-south-1": "462105765813", + "ap-southeast-1": "462105765813", + "ap-southeast-2": "462105765813", + "ca-central-1": "462105765813", + "eu-central-1": "462105765813", + "eu-north-1": "462105765813", + "eu-west-1": "462105765813", + "eu-west-2": "462105765813", + "eu-west-3": "462105765813", + "sa-east-1": "462105765813", + "us-east-1": "462105765813", + "us-east-2": "462105765813", + "us-west-1": "462105765813", + "us-west-2": "462105765813" + }, + "repository": "sagemaker-rl-ray-container", + "tag_prefix": "ray-0.8.5-torch" + } + } +} diff --git a/src/sagemaker/image_uri_config/ray-tensorflow.json b/src/sagemaker/image_uri_config/ray-tensorflow.json new file mode 100644 index 0000000000..22f80b3746 --- /dev/null +++ b/src/sagemaker/image_uri_config/ray-tensorflow.json @@ -0,0 +1,168 @@ +{ + "processors": ["cpu", "gpu"], + "scope": ["training"], + "versions": { + "0.5": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "ray0.5" + }, + "0.5.3": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "ray0.5.3" + }, + "0.6": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "ray0.6" + }, + "0.6.5": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-rl-tensorflow", + "tag_prefix": "ray0.6.5" + }, + "0.8.2": { + "py_versions": ["py36"], + "registries": { + "ap-northeast-1": "462105765813", + "ap-northeast-2": "462105765813", + "ap-south-1": "462105765813", + "ap-southeast-1": "462105765813", + "ap-southeast-2": "462105765813", + "ca-central-1": "462105765813", + "eu-central-1": "462105765813", + "eu-north-1": "462105765813", + "eu-west-1": "462105765813", + "eu-west-2": "462105765813", + "eu-west-3": "462105765813", + "sa-east-1": "462105765813", + "us-east-1": "462105765813", + "us-east-2": "462105765813", + "us-west-1": "462105765813", + "us-west-2": "462105765813" + }, + "repository": "sagemaker-rl-ray-container", + "tag_prefix": "ray-0.8.2-tf" + }, + "0.8.5": { + "py_versions": ["py36"], + "registries": { + "ap-northeast-1": "462105765813", + "ap-northeast-2": "462105765813", + "ap-south-1": "462105765813", + "ap-southeast-1": "462105765813", + "ap-southeast-2": "462105765813", + "ca-central-1": "462105765813", + "eu-central-1": "462105765813", + "eu-north-1": "462105765813", + "eu-west-1": "462105765813", + "eu-west-2": "462105765813", + "eu-west-3": "462105765813", + "sa-east-1": "462105765813", + "us-east-1": "462105765813", + "us-east-2": "462105765813", + "us-west-1": "462105765813", + "us-west-2": "462105765813" + }, + "repository": "sagemaker-rl-ray-container", + "tag_prefix": "ray-0.8.5-tf" + } + } +} diff --git a/src/sagemaker/image_uri_config/semantic-segmentation.json b/src/sagemaker/image_uri_config/semantic-segmentation.json new file mode 100644 index 0000000000..d0dc33df6d --- /dev/null +++ b/src/sagemaker/image_uri_config/semantic-segmentation.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "501404015308", + "ap-northeast-2": "306986355934", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "544295431143", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "813361260812", + "eu-north-1": "669576153137", + "eu-west-1": "685385470294", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "811284229777", + "us-east-2": "825641698319", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "433757028032" + }, + "repository": "semantic-segmentation" + } + } +} diff --git a/src/sagemaker/image_uri_config/seq2seq.json b/src/sagemaker/image_uri_config/seq2seq.json new file mode 100644 index 0000000000..73f4c2dcd6 --- /dev/null +++ b/src/sagemaker/image_uri_config/seq2seq.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference", "training"], + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "501404015308", + "ap-northeast-2": "306986355934", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "544295431143", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "813361260812", + "eu-north-1": "669576153137", + "eu-west-1": "685385470294", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "811284229777", + "us-east-2": "825641698319", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "433757028032" + }, + "repository": "seq2seq" + } + } +} diff --git a/src/sagemaker/image_uri_config/sklearn.json b/src/sagemaker/image_uri_config/sklearn.json new file mode 100644 index 0000000000..962c2632e6 --- /dev/null +++ b/src/sagemaker/image_uri_config/sklearn.json @@ -0,0 +1,62 @@ +{ + "processors": ["cpu"], + "scope": ["inference", "training"], + "versions": { + "0.20.0": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "651117190479", + "ap-northeast-1": "354813040037", + "ap-northeast-2": "366743142698", + "ap-south-1": "720646828776", + "ap-southeast-1": "121021644041", + "ap-southeast-2": "783357654285", + "ca-central-1": "341280168497", + "cn-north-1": "450853457545", + "cn-northwest-1": "451049120500", + "eu-central-1": "492215442770", + "eu-north-1": "662702820516", + "eu-west-1": "141502667606", + "eu-west-2": "764974769150", + "eu-west-3": "659782779980", + "me-south-1": "801668240914", + "sa-east-1": "737474898029", + "us-east-1": "683313688378", + "us-east-2": "257758044811", + "us-gov-west-1": "414596584902", + "us-iso-east-1": "833128469047", + "us-west-1": "746614075791", + "us-west-2": "246618743249" + }, + "repository": "sagemaker-scikit-learn" + }, + "0.23-1": { + "py_versions": ["py3"], + "registries": { + "ap-east-1": "651117190479", + "ap-northeast-1": "354813040037", + "ap-northeast-2": "366743142698", + "ap-south-1": "720646828776", + "ap-southeast-1": "121021644041", + "ap-southeast-2": "783357654285", + "ca-central-1": "341280168497", + "cn-north-1": "450853457545", + "cn-northwest-1": "451049120500", + "eu-central-1": "492215442770", + "eu-north-1": "662702820516", + "eu-west-1": "141502667606", + "eu-west-2": "764974769150", + "eu-west-3": "659782779980", + "me-south-1": "801668240914", + "sa-east-1": "737474898029", + "us-east-1": "683313688378", + "us-east-2": "257758044811", + "us-gov-west-1": "414596584902", + "us-iso-east-1": "833128469047", + "us-west-1": "746614075791", + "us-west-2": "246618743249" + }, + "repository": "sagemaker-scikit-learn" + } + } +} diff --git a/src/sagemaker/image_uri_config/sparkml-serving.json b/src/sagemaker/image_uri_config/sparkml-serving.json new file mode 100644 index 0000000000..e767d75729 --- /dev/null +++ b/src/sagemaker/image_uri_config/sparkml-serving.json @@ -0,0 +1,32 @@ +{ + "scope": ["inference"], + "versions": { + "2.2": { + "registries": { + "ap-east-1": "651117190479", + "ap-northeast-1": "354813040037", + "ap-northeast-2": "366743142698", + "ap-south-1": "720646828776", + "ap-southeast-1": "121021644041", + "ap-southeast-2": "783357654285", + "ca-central-1": "341280168497", + "cn-north-1": "450853457545", + "cn-northwest-1": "451049120500", + "eu-central-1": "492215442770", + "eu-north-1": "662702820516", + "eu-west-1": "141502667606", + "eu-west-2": "764974769150", + "eu-west-3": "659782779980", + "me-south-1": "801668240914", + "sa-east-1": "737474898029", + "us-east-1": "683313688378", + "us-east-2": "257758044811", + "us-gov-west-1": "414596584902", + "us-iso-east-1": "833128469047", + "us-west-1": "746614075791", + "us-west-2": "246618743249" + }, + "repository": "sagemaker-sparkml-serving" + } + } +} diff --git a/src/sagemaker/image_uri_config/tensorflow.json b/src/sagemaker/image_uri_config/tensorflow.json new file mode 100644 index 0000000000..2271cf43dd --- /dev/null +++ b/src/sagemaker/image_uri_config/tensorflow.json @@ -0,0 +1,1285 @@ +{ + "eia": { + "processors": [ + "cpu" + ], + "version_aliases": { + "1.10": "1.10.0", + "1.11": "1.11.0", + "1.12": "1.12.0", + "1.13": "1.13.0", + "1.14": "1.14.0", + "1.15": "1.15.0", + "2.0": "2.0.0" + }, + "versions": { + "1.10.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-eia" + }, + "1.11.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-serving-eia" + }, + "1.12.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-serving-eia" + }, + "1.13.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-serving-eia" + }, + "1.14.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference-eia" + }, + "1.15.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference-eia" + }, + "2.0.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference-eia" + } + } + }, + "inference": { + "processors": [ + "cpu", + "gpu" + ], + "version_aliases": { + "1.10": "1.10.0", + "1.11": "1.11.0", + "1.12": "1.12.0", + "1.13": "1.13.0", + "1.14": "1.14.0", + "1.15": "1.15.2", + "1.4": "1.4.1", + "1.5": "1.5.0", + "1.6": "1.6.0", + "1.7": "1.7.0", + "1.8": "1.8.0", + "1.9": "1.9.0", + "2.0": "2.0.1", + "2.1": "2.1.0", + "2.2": "2.2.0" + }, + "versions": { + "1.10.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.11.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-serving" + }, + "1.12.0": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-serving" + }, + "1.13.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "1.14.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "1.15.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "1.15.2": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "1.4.1": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.5.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.6.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.7.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.8.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.9.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "2.0.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "2.0.1": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "2.1.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + }, + "2.2.0": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-inference" + } + } + }, + "training": { + "processors": [ + "cpu", + "gpu" + ], + "version_aliases": { + "1.10": "1.10.0", + "1.11": "1.11.0", + "1.12": "1.12.0", + "1.13": "1.13.1", + "1.14": "1.14.0", + "1.15": "1.15.2", + "1.4": "1.4.1", + "1.5": "1.5.0", + "1.6": "1.6.0", + "1.7": "1.7.0", + "1.8": "1.8.0", + "1.9": "1.9.0", + "2.0": "2.0.1", + "2.1": "2.1.0", + "2.2": "2.2.0" + }, + "versions": { + "1.10.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.11.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-scriptmode" + }, + "1.12.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-scriptmode" + }, + "1.13.1": { + "py2": { + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow-scriptmode" + }, + "py3": { + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + } + }, + "1.14.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + }, + "1.15.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + }, + "1.15.2": { + "py_versions": [ + "py2", + "py3", + "py37" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + }, + "1.4.1": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.5.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.6.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.7.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.8.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "1.9.0": { + "py_versions": [ + "py2" + ], + "registries": { + "ap-east-1": "057415533634", + "ap-northeast-1": "520713654638", + "ap-northeast-2": "520713654638", + "ap-south-1": "520713654638", + "ap-southeast-1": "520713654638", + "ap-southeast-2": "520713654638", + "ca-central-1": "520713654638", + "cn-north-1": "422961961927", + "cn-northwest-1": "423003514399", + "eu-central-1": "520713654638", + "eu-north-1": "520713654638", + "eu-west-1": "520713654638", + "eu-west-2": "520713654638", + "eu-west-3": "520713654638", + "me-south-1": "724002660598", + "sa-east-1": "520713654638", + "us-east-1": "520713654638", + "us-east-2": "520713654638", + "us-gov-west-1": "246785580436", + "us-iso-east-1": "744548109606", + "us-west-1": "520713654638", + "us-west-2": "520713654638" + }, + "repository": "sagemaker-tensorflow" + }, + "2.0.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + }, + "2.0.1": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + }, + "2.1.0": { + "py_versions": [ + "py2", + "py3" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + }, + "2.2.0": { + "py_versions": [ + "py37" + ], + "registries": { + "ap-east-1": "871362719292", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-south-1": "763104351884", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ca-central-1": "763104351884", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-north-1": "763104351884", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "me-south-1": "217643126080", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-west-1": "442386744353", + "us-iso-east-1": "886529160074", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "repository": "tensorflow-training" + } + } + } +} \ No newline at end of file diff --git a/src/sagemaker/image_uri_config/vw.json b/src/sagemaker/image_uri_config/vw.json new file mode 100644 index 0000000000..87aa7c491b --- /dev/null +++ b/src/sagemaker/image_uri_config/vw.json @@ -0,0 +1,28 @@ +{ + "processors": ["cpu"], + "scope": ["training"], + "versions": { + "8.7.0": { + "registries": { + "ap-northeast-1": "462105765813", + "ap-northeast-2": "462105765813", + "ap-south-1": "462105765813", + "ap-southeast-1": "462105765813", + "ap-southeast-2": "462105765813", + "ca-central-1": "462105765813", + "eu-central-1": "462105765813", + "eu-north-1": "462105765813", + "eu-west-1": "462105765813", + "eu-west-2": "462105765813", + "eu-west-3": "462105765813", + "sa-east-1": "462105765813", + "us-east-1": "462105765813", + "us-east-2": "462105765813", + "us-west-1": "462105765813", + "us-west-2": "462105765813" + }, + "repository": "sagemaker-rl-vw-container", + "tag_prefix": "vw-8.7.0" + } + } +} diff --git a/src/sagemaker/image_uri_config/xgboost-neo.json b/src/sagemaker/image_uri_config/xgboost-neo.json new file mode 100644 index 0000000000..c7adf052c8 --- /dev/null +++ b/src/sagemaker/image_uri_config/xgboost-neo.json @@ -0,0 +1,31 @@ +{ + "scope": ["inference"], + "versions": { + "latest": { + "registries": { + "ap-east-1": "110948597952", + "ap-northeast-1": "941853720454", + "ap-northeast-2": "151534178276", + "ap-south-1": "763008648453", + "ap-southeast-1": "324986816169", + "ap-southeast-2": "355873309152", + "ca-central-1": "464438896020", + "cn-north-1": "472730292857", + "cn-northwest-1": "474822919863", + "eu-central-1": "746233611703", + "eu-north-1": "601324751636", + "eu-west-1": "802834080501", + "eu-west-2": "205493899709", + "eu-west-3": "254080097072", + "me-south-1": "836785723513", + "sa-east-1": "756306329178", + "us-east-1": "785573368785", + "us-east-2": "007439368137", + "us-gov-west-1": "263933020539", + "us-west-1": "710691900526", + "us-west-2": "301217895009" + }, + "repository": "xgboost-neo" + } + } +} diff --git a/src/sagemaker/image_uri_config/xgboost.json b/src/sagemaker/image_uri_config/xgboost.json new file mode 100644 index 0000000000..246f80cbd0 --- /dev/null +++ b/src/sagemaker/image_uri_config/xgboost.json @@ -0,0 +1,122 @@ +{ + "scope": ["inference", "training"], + "version_aliases": { + "latest": "1" + }, + "versions": { + "1": { + "registries": { + "ap-east-1": "286214385809", + "ap-northeast-1": "501404015308", + "ap-northeast-2": "306986355934", + "ap-south-1": "991648021394", + "ap-southeast-1": "475088953585", + "ap-southeast-2": "544295431143", + "ca-central-1": "469771592824", + "cn-north-1": "390948362332", + "cn-northwest-1": "387376663083", + "eu-central-1": "813361260812", + "eu-north-1": "669576153137", + "eu-west-1": "685385470294", + "eu-west-2": "644912444149", + "eu-west-3": "749696950732", + "me-south-1": "249704162688", + "sa-east-1": "855470959533", + "us-east-1": "811284229777", + "us-east-2": "825641698319", + "us-gov-west-1": "226302683700", + "us-iso-east-1": "490574956308", + "us-west-1": "632365934929", + "us-west-2": "433757028032" + }, + "repository": "xgboost" + }, + "0.90-1": { + "processors": ["cpu"], + "py_versions": ["py3"], + "registries": { + "ap-east-1": "651117190479", + "ap-northeast-1": "354813040037", + "ap-northeast-2": "366743142698", + "ap-south-1": "720646828776", + "ap-southeast-1": "121021644041", + "ap-southeast-2": "783357654285", + "ca-central-1": "341280168497", + "cn-north-1": "450853457545", + "cn-northwest-1": "451049120500", + "eu-central-1": "492215442770", + "eu-north-1": "662702820516", + "eu-west-1": "141502667606", + "eu-west-2": "764974769150", + "eu-west-3": "659782779980", + "me-south-1": "801668240914", + "sa-east-1": "737474898029", + "us-east-1": "683313688378", + "us-east-2": "257758044811", + "us-gov-west-1": "414596584902", + "us-iso-east-1": "833128469047", + "us-west-1": "746614075791", + "us-west-2": "246618743249" + }, + "repository": "sagemaker-xgboost" + }, + "0.90-2": { + "processors": ["cpu"], + "py_versions": ["py3"], + "registries": { + "ap-east-1": "651117190479", + "ap-northeast-1": "354813040037", + "ap-northeast-2": "366743142698", + "ap-south-1": "720646828776", + "ap-southeast-1": "121021644041", + "ap-southeast-2": "783357654285", + "ca-central-1": "341280168497", + "cn-north-1": "450853457545", + "cn-northwest-1": "451049120500", + "eu-central-1": "492215442770", + "eu-north-1": "662702820516", + "eu-west-1": "141502667606", + "eu-west-2": "764974769150", + "eu-west-3": "659782779980", + "me-south-1": "801668240914", + "sa-east-1": "737474898029", + "us-east-1": "683313688378", + "us-east-2": "257758044811", + "us-gov-west-1": "414596584902", + "us-iso-east-1": "833128469047", + "us-west-1": "746614075791", + "us-west-2": "246618743249" + }, + "repository": "sagemaker-xgboost" + }, + "1.0-1": { + "processors": ["cpu"], + "py_versions": ["py3"], + "registries": { + "ap-east-1": "651117190479", + "ap-northeast-1": "354813040037", + "ap-northeast-2": "366743142698", + "ap-south-1": "720646828776", + "ap-southeast-1": "121021644041", + "ap-southeast-2": "783357654285", + "ca-central-1": "341280168497", + "cn-north-1": "450853457545", + "cn-northwest-1": "451049120500", + "eu-central-1": "492215442770", + "eu-north-1": "662702820516", + "eu-west-1": "141502667606", + "eu-west-2": "764974769150", + "eu-west-3": "659782779980", + "me-south-1": "801668240914", + "sa-east-1": "737474898029", + "us-east-1": "683313688378", + "us-east-2": "257758044811", + "us-gov-west-1": "414596584902", + "us-iso-east-1": "833128469047", + "us-west-1": "746614075791", + "us-west-2": "246618743249" + }, + "repository": "sagemaker-xgboost" + } + } +} diff --git a/src/sagemaker/image_uris.py b/src/sagemaker/image_uris.py new file mode 100644 index 0000000000..2436424df4 --- /dev/null +++ b/src/sagemaker/image_uris.py @@ -0,0 +1,247 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Functions for generating ECR image URIs for pre-built SageMaker Docker images.""" +from __future__ import absolute_import + +import json +import logging +import os +import re + +from sagemaker import utils + +logger = logging.getLogger(__name__) + +ECR_URI_TEMPLATE = "{registry}.dkr.{hostname}/{repository}" + + +def retrieve( + framework, + region, + version=None, + py_version=None, + instance_type=None, + accelerator_type=None, + image_scope=None, +): + """Retrieves the ECR URI for the Docker image matching the given arguments. + + Args: + framework (str): The name of the framework or algorithm. + region (str): The AWS region. + version (str): The framework or algorithm version. This is required if there is + more than one supported version for the given framework or algorithm. + py_version (str): The Python version. This is required if there is + more than one supported Python version for the given framework version. + instance_type (str): The SageMaker instance type. For supported types, see + https://aws.amazon.com/sagemaker/pricing/instance-types. This is required if + there are different images for different processor types. + accelerator_type (str): Elastic Inference accelerator type. For more, see + https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html. + image_scope (str): The image type, i.e. what it is used for. + Valid values: "training", "inference", "eia". If ``accelerator_type`` is set, + ``image_scope`` is ignored. + + Returns: + str: the ECR URI for the corresponding SageMaker Docker image. + + Raises: + ValueError: If the combination of arguments specified is not supported. + """ + config = _config_for_framework_and_scope(framework, image_scope, accelerator_type) + + version = _validate_version_and_set_if_needed(version, config, framework) + version_config = config["versions"][_version_for_config(version, config)] + + py_version = _validate_py_version_and_set_if_needed(py_version, version_config) + version_config = version_config.get(py_version) or version_config + + registry = _registry_from_region(region, version_config["registries"]) + hostname = utils._botocore_resolver().construct_endpoint("ecr", region)["hostname"] + + repo = version_config["repository"] + + processor = _processor( + instance_type, config.get("processors") or version_config.get("processors") + ) + tag = _format_tag(version_config.get("tag_prefix", version), processor, py_version) + + if tag: + repo += ":{}".format(tag) + + return ECR_URI_TEMPLATE.format(registry=registry, hostname=hostname, repository=repo) + + +def _config_for_framework_and_scope(framework, image_scope, accelerator_type=None): + """Loads the JSON config for the given framework and image scope.""" + config = config_for_framework(framework) + + if accelerator_type: + _validate_accelerator_type(accelerator_type) + + if image_scope not in ("eia", "inference"): + logger.warning( + "Elastic inference is for inference only. Ignoring image scope: %s.", image_scope + ) + image_scope = "eia" + + available_scopes = config.get("scope", config.keys()) + if len(available_scopes) == 1: + if image_scope and image_scope != available_scopes[0]: + logger.warning( + "Defaulting to only supported image scope: %s. Ignoring image scope: %s.", + available_scopes[0], + image_scope, + ) + image_scope = available_scopes[0] + + if not image_scope and "scope" in config and set(available_scopes) == {"training", "inference"}: + logger.info( + "Same images used for training and inference. Defaulting to image scope: %s.", + available_scopes[0], + ) + image_scope = available_scopes[0] + + _validate_arg(image_scope, available_scopes, "image scope") + return config if "scope" in config else config[image_scope] + + +def config_for_framework(framework): + """Loads the JSON config for the given framework.""" + fname = os.path.join(os.path.dirname(__file__), "image_uri_config", "{}.json".format(framework)) + with open(fname) as f: + return json.load(f) + + +def _validate_accelerator_type(accelerator_type): + """Raises a ``ValueError`` if ``accelerator_type`` is invalid.""" + if not accelerator_type.startswith("ml.eia") and accelerator_type != "local_sagemaker_notebook": + raise ValueError( + "Invalid SageMaker Elastic Inference accelerator type: {}. " + "See https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html".format(accelerator_type) + ) + + +def _validate_version_and_set_if_needed(version, config, framework): + """Checks if the framework/algorithm version is one of the supported versions.""" + available_versions = list(config["versions"].keys()) + aliased_versions = list(config.get("version_aliases", {}).keys()) + + if len(available_versions) == 1 and version not in aliased_versions: + log_message = "Defaulting to the only supported framework/algorithm version: {}.".format( + available_versions[0] + ) + if version and version != available_versions[0]: + logger.warning("%s Ignoring framework/algorithm version: %s.", log_message, version) + elif not version: + logger.info(log_message) + + return available_versions[0] + + _validate_arg(version, available_versions + aliased_versions, "{} version".format(framework)) + return version + + +def _version_for_config(version, config): + """Returns the version string for retrieving a framework version's specific config.""" + if "version_aliases" in config: + if version in config["version_aliases"].keys(): + return config["version_aliases"][version] + + return version + + +def _registry_from_region(region, registry_dict): + """Returns the ECR registry (AWS account number) for the given region.""" + _validate_arg(region, registry_dict.keys(), "region") + return registry_dict[region] + + +def _processor(instance_type, available_processors): + """Returns the processor type for the given instance type.""" + if not available_processors: + logger.info("Ignoring unnecessary instance type: %s.", instance_type) + return None + + if len(available_processors) == 1 and not instance_type: + logger.info("Defaulting to only supported image scope: %s.", available_processors[0]) + return available_processors[0] + + if not instance_type: + raise ValueError( + "Empty SageMaker instance type. For options, see: " + "https://aws.amazon.com/sagemaker/pricing/instance-types" + ) + + if instance_type.startswith("local"): + processor = "cpu" if instance_type == "local" else "gpu" + else: + # looks for either "ml.." or "ml_" + match = re.match(r"^ml[\._]([a-z\d]+)\.?\w*$", instance_type) + if match: + family = match[1] + + # For some frameworks, we have optimized images for specific families, e.g c5 or p3. + # In those cases, we use the family name in the image tag. In other cases, we use + # 'cpu' or 'gpu'. + if family in available_processors: + processor = family + elif family.startswith("inf"): + processor = "inf" + elif family[0] in ("g", "p"): + processor = "gpu" + else: + processor = "cpu" + else: + raise ValueError( + "Invalid SageMaker instance type: {}. For options, see: " + "https://aws.amazon.com/sagemaker/pricing/instance-types".format(instance_type) + ) + + _validate_arg(processor, available_processors, "processor") + return processor + + +def _validate_py_version_and_set_if_needed(py_version, version_config): + """Checks if the Python version is one of the supported versions.""" + if "repository" in version_config: + available_versions = version_config.get("py_versions") + else: + available_versions = list(version_config.keys()) + + if not available_versions: + if py_version: + logger.info("Ignoring unnecessary Python version: %s.", py_version) + return None + + if py_version is None and len(available_versions) == 1: + logger.info("Defaulting to only available Python version: %s", available_versions[0]) + return available_versions[0] + + _validate_arg(py_version, available_versions, "Python version") + return py_version + + +def _validate_arg(arg, available_options, arg_name): + """Checks if the arg is in the available options, and raises a ``ValueError`` if not.""" + if arg not in available_options: + raise ValueError( + "Unsupported {arg_name}: {arg}. You may need to upgrade your SDK version " + "(pip install -U sagemaker) for newer {arg_name}s. Supported {arg_name}(s): " + "{options}.".format(arg_name=arg_name, arg=arg, options=", ".join(available_options)) + ) + + +def _format_tag(tag_prefix, processor, py_version): + """Creates a tag for the image URI.""" + return "-".join([x for x in (tag_prefix, processor, py_version) if x]) diff --git a/src/sagemaker/inputs.py b/src/sagemaker/inputs.py index 63dccb1555..e9eadf5344 100644 --- a/src/sagemaker/inputs.py +++ b/src/sagemaker/inputs.py @@ -13,15 +13,11 @@ """Amazon SageMaker channel configurations for S3 data sources and file system data sources""" from __future__ import absolute_import, print_function -import logging - FILE_SYSTEM_TYPES = ["FSxLustre", "EFS"] FILE_SYSTEM_ACCESS_MODES = ["ro", "rw"] -logger = logging.getLogger("sagemaker") - -class s3_input(object): +class TrainingInput(object): """Amazon SageMaker channel configurations for S3 data sources. Attributes: @@ -76,14 +72,10 @@ def __init__( found in a specified AugmentedManifestFile. target_attribute_name (str): The name of the attribute will be predicted (classified) in a SageMaker AutoML job. It is required if the input is for SageMaker AutoML job. - shuffle_config (ShuffleConfig): If specified this configuration enables shuffling on - this channel. See the SageMaker API documentation for more info: + shuffle_config (sagemaker.inputs.ShuffleConfig): If specified this configuration enables + shuffling on this channel. See the SageMaker API documentation for more info: https://docs.aws.amazon.com/sagemaker/latest/dg/API_ShuffleConfig.html """ - logger.warning( - "'s3_input' class will be renamed to 'TrainingInput' in SageMaker Python SDK v2." - ) - self.config = { "DataSource": {"S3DataSource": {"S3DataType": s3_data_type, "S3Uri": s3_data}} } @@ -110,6 +102,22 @@ def __init__( self.config["ShuffleConfig"] = {"Seed": shuffle_config.seed} +class ShuffleConfig(object): + """For configuring channel shuffling using a seed. + + For more detail, see the AWS documentation: + https://docs.aws.amazon.com/sagemaker/latest/dg/API_ShuffleConfig.html + """ + + def __init__(self, seed): + """Create a ShuffleConfig. + + Args: + seed (long): the long value used to seed the shuffled sequence. + """ + self.seed = seed + + class FileSystemInput(object): """Amazon SageMaker channel configurations for file system data sources. diff --git a/src/sagemaker/job.py b/src/sagemaker/job.py index 702c8ba7ab..3ad8760529 100644 --- a/src/sagemaker/job.py +++ b/src/sagemaker/job.py @@ -16,9 +16,8 @@ from abc import abstractmethod from six import string_types -from sagemaker.inputs import FileSystemInput +from sagemaker.inputs import FileSystemInput, TrainingInput from sagemaker.local import file_input -from sagemaker.session import s3_input class _Job(object): @@ -84,14 +83,12 @@ def _load_config(inputs, estimator, expand_role=True, validate_uri=True): ) output_config = _Job._prepare_output_config(estimator.output_path, estimator.output_kms_key) resource_config = _Job._prepare_resource_config( - estimator.train_instance_count, - estimator.train_instance_type, - estimator.train_volume_size, - estimator.train_volume_kms_key, - ) - stop_condition = _Job._prepare_stop_condition( - estimator.train_max_run, estimator.train_max_wait + estimator.instance_count, + estimator.instance_type, + estimator.volume_size, + estimator.volume_kms_key, ) + stop_condition = _Job._prepare_stop_condition(estimator.max_run, estimator.max_wait) vpc_config = estimator.get_vpc_config() model_channel = _Job._prepare_channel( @@ -144,7 +141,7 @@ def _format_inputs_to_input_config(inputs, validate_uri=True): input_dict = {} if isinstance(inputs, string_types): input_dict["training"] = _Job._format_string_uri_input(inputs, validate_uri) - elif isinstance(inputs, s3_input): + elif isinstance(inputs, TrainingInput): input_dict["training"] = inputs elif isinstance(inputs, file_input): input_dict["training"] = inputs @@ -156,7 +153,10 @@ def _format_inputs_to_input_config(inputs, validate_uri=True): elif isinstance(inputs, FileSystemInput): input_dict["training"] = inputs else: - msg = "Cannot format input {}. Expecting one of str, dict, s3_input or FileSystemInput" + msg = ( + "Cannot format input {}. Expecting one of str, dict, TrainingInput or " + "FileSystemInput" + ) raise ValueError(msg.format(inputs)) channels = [ @@ -195,7 +195,7 @@ def _format_string_uri_input( target_attribute_name: """ if isinstance(uri_input, str) and validate_uri and uri_input.startswith("s3://"): - s3_input_result = s3_input( + s3_input_result = TrainingInput( uri_input, content_type=content_type, input_mode=input_mode, @@ -211,7 +211,7 @@ def _format_string_uri_input( '"file://"'.format(uri_input) ) if isinstance(uri_input, str): - s3_input_result = s3_input( + s3_input_result = TrainingInput( uri_input, content_type=content_type, input_mode=input_mode, @@ -219,11 +219,11 @@ def _format_string_uri_input( target_attribute_name=target_attribute_name, ) return s3_input_result - if isinstance(uri_input, (s3_input, file_input, FileSystemInput)): + if isinstance(uri_input, (TrainingInput, file_input, FileSystemInput)): return uri_input raise ValueError( - "Cannot format input {}. Expecting one of str, s3_input, file_input or " + "Cannot format input {}. Expecting one of str, TrainingInput, file_input or " "FileSystemInput".format(uri_input) ) @@ -272,7 +272,7 @@ def _format_model_uri_input(model_uri, validate_uri=True): validate_uri: """ if isinstance(model_uri, string_types) and validate_uri and model_uri.startswith("s3://"): - return s3_input( + return TrainingInput( model_uri, input_mode="File", distribution="FullyReplicated", @@ -285,7 +285,7 @@ def _format_model_uri_input(model_uri, validate_uri=True): 'Model URI must be a valid S3 or FILE URI: must start with "s3://" or ' '"file://' ) if isinstance(model_uri, string_types): - return s3_input( + return TrainingInput( model_uri, input_mode="File", distribution="FullyReplicated", @@ -329,21 +329,21 @@ def _prepare_output_config(s3_path, kms_key_id): return config @staticmethod - def _prepare_resource_config(instance_count, instance_type, volume_size, train_volume_kms_key): + def _prepare_resource_config(instance_count, instance_type, volume_size, volume_kms_key): """ Args: instance_count: instance_type: volume_size: - train_volume_kms_key: + volume_kms_key: """ resource_config = { "InstanceCount": instance_count, "InstanceType": instance_type, "VolumeSizeInGB": volume_size, } - if train_volume_kms_key is not None: - resource_config["VolumeKmsKeyId"] = train_volume_kms_key + if volume_kms_key is not None: + resource_config["VolumeKmsKeyId"] = volume_kms_key return resource_config diff --git a/src/sagemaker/model.py b/src/sagemaker/model.py index b135f386fc..7730bb3ba1 100644 --- a/src/sagemaker/model.py +++ b/src/sagemaker/model.py @@ -18,8 +18,7 @@ import os import sagemaker -from sagemaker import fw_utils, local, session, utils, git_utils -from sagemaker.fw_utils import UploadedCode +from sagemaker import fw_utils, image_uris, local, s3, session, utils, git_utils from sagemaker.transformer import Transformer LOGGER = logging.getLogger("sagemaker") @@ -28,40 +27,14 @@ ["mxnet", "tensorflow", "keras", "pytorch", "onnx", "xgboost", "tflite"] ) -NEO_IMAGE_ACCOUNT = { - "us-west-1": "710691900526", - "us-west-2": "301217895009", - "us-east-1": "785573368785", - "us-east-2": "007439368137", - "eu-west-1": "802834080501", - "eu-west-2": "205493899709", - "eu-west-3": "254080097072", - "eu-central-1": "746233611703", - "eu-north-1": "601324751636", - "ap-northeast-1": "941853720454", - "ap-northeast-2": "151534178276", - "ap-east-1": "110948597952", - "ap-southeast-1": "324986816169", - "ap-southeast-2": "355873309152", - "ap-south-1": "763008648453", - "sa-east-1": "756306329178", - "ca-central-1": "464438896020", - "me-south-1": "836785723513", - "cn-north-1": "472730292857", - "cn-northwest-1": "474822919863", - "us-gov-west-1": "263933020539", -} - -INFERENTIA_INSTANCE_PREFIX = "ml_inf" - class Model(object): """A SageMaker ``Model`` that can be deployed to an ``Endpoint``.""" def __init__( self, - model_data, - image, + image_uri, + model_data=None, role=None, predictor_cls=None, env=None, @@ -74,9 +47,9 @@ def __init__( """Initialize an SageMaker ``Model``. Args: + image_uri (str): A Docker image URI. model_data (str): The S3 location of a SageMaker model data - ``.tar.gz`` file. - image (str): A Docker image URI. + ``.tar.gz`` file (default: None). role (str): An AWS IAM role (either name or full ARN). The Amazon SageMaker training jobs and APIs that create Amazon SageMaker endpoints use this role to access training data and model @@ -89,7 +62,7 @@ def __init__( function to call to create a predictor (default: None). If not None, ``deploy`` will return the result of invoking this function on the created endpoint name. - env (dict[str, str]): Environment variables to run with ``image`` + env (dict[str, str]): Environment variables to run with ``image_uri`` when hosted in SageMaker (default: None). name (str): The model name. If None, a default model name will be selected on each ``deploy``. @@ -108,17 +81,15 @@ def __init__( model_kms_key (str): KMS key ARN used to encrypt the repacked model archive file if the model is repacked """ - LOGGER.warning(fw_utils.parameter_v2_rename_warning("image", "image_uri")) - self.model_data = model_data - self.image = image + self.image_uri = image_uri self.role = role self.predictor_cls = predictor_cls self.env = env or {} self.name = name + self._base_name = None self.vpc_config = vpc_config self.sagemaker_session = sagemaker_session - self._model_name = None self.endpoint_name = None self._is_compiled_model = False self._enable_network_isolation = enable_network_isolation @@ -156,7 +127,7 @@ def prepare_container_def( Returns: dict: A container definition object usable with the CreateModel API. """ - return sagemaker.container_def(self.image, self.model_data, self.env) + return sagemaker.container_def(self.image_uri, self.model_data, self.env) def enable_network_isolation(self): """Whether to enable network isolation when creating this Model @@ -184,7 +155,10 @@ def _create_sagemaker_model(self, instance_type=None, accelerator_type=None, tag /api/latest/reference/services/sagemaker.html#SageMaker.Client.add_tags """ container_def = self.prepare_container_def(instance_type, accelerator_type=accelerator_type) - self.name = self.name or utils.name_from_image(container_def["Image"]) + + self._ensure_base_name_if_needed(container_def["Image"]) + self._set_model_name_if_needed() + enable_network_isolation = self.enable_network_isolation() self._init_sagemaker_session_if_does_not_exist(instance_type) @@ -197,9 +171,19 @@ def _create_sagemaker_model(self, instance_type=None, accelerator_type=None, tag tags=tags, ) + def _ensure_base_name_if_needed(self, image_uri): + """Create a base name from the image URI if there is no model name provided.""" + if self.name is None: + self._base_name = self._base_name or utils.base_name_from_image(image_uri) + + def _set_model_name_if_needed(self): + """Generate a new model name if ``self._base_name`` is present.""" + if self._base_name: + self.name = utils.name_from_base(self._base_name) + def _framework(self): """Placeholder docstring""" - return getattr(self, "__framework_name__", None) + return getattr(self, "_framework_name", None) def _get_framework_version(self): """Placeholder docstring""" @@ -240,7 +224,7 @@ def _compilation_job_config( "DataInputConfig": json.dumps(input_shape) if isinstance(input_shape, dict) else input_shape, - "Framework": framework, + "Framework": framework.upper(), } role = self.sagemaker_session.expand_role(role) output_model_config = { @@ -279,64 +263,23 @@ def _compilation_job_config( "job_name": job_name, } - def check_neo_region(self, region): - """Check if this ``Model`` in the available region where neo support. - - Args: - region (str): Specifies the region where want to execute compilation - - Returns: - bool: boolean value whether if neo is available in the specified - region - """ - if region in NEO_IMAGE_ACCOUNT: - return True - return False - - def _neo_image_account(self, region): - """ - Args: - region: - """ - if region not in NEO_IMAGE_ACCOUNT: - raise ValueError( - "Neo is not currently supported in {}, " - "valid regions: {}".format(region, NEO_IMAGE_ACCOUNT.keys()) - ) - return NEO_IMAGE_ACCOUNT[region] + def _compilation_image_uri(self, region, target_instance_type, framework, framework_version): + """Retrieve the Neo or Inferentia image URI. - def _neo_image(self, region, target_instance_type, framework, framework_version): - """ Args: - region: - target_instance_type: - framework: - framework_version: - """ - return fw_utils.create_image_uri( - region, - "neo-" + framework.lower(), - target_instance_type.replace("_", "."), - framework_version, - py_version="py3", - account=self._neo_image_account(region), - ) - - def _inferentia_image(self, region, target_instance_type, framework, framework_version): + region (str): The AWS region. + target_instance_type (str): Identifies the device on which you want to run + your model after compilation, for example: ml_c5. For valid values, see + https://docs.aws.amazon.com/sagemaker/latest/dg/API_OutputConfig.html. + framework (str): The framework name. + framework_version (str): The framework version. """ - Args: - region: - target_instance_type: - framework: - framework_version: - """ - return fw_utils.create_image_uri( + framework_prefix = "inferentia-" if target_instance_type.startswith("ml_inf") else "neo-" + return image_uris.retrieve( + "{}{}".format(framework_prefix, framework), region, - "neo-" + framework.lower(), - target_instance_type.replace("_", "."), - framework_version, - py_version="py3", - account=self._neo_image_account(region), + instance_type=target_instance_type, + version=framework_version, ) def compile( @@ -402,7 +345,7 @@ def compile( sagemaker.model.Model: A SageMaker ``Model`` object. See :func:`~sagemaker.model.Model` for full details. """ - framework = self._framework() or framework + framework = framework or self._framework() if framework is None: raise ValueError( "You must specify framework, allowed values {}".format(NEO_ALLOWED_FRAMEWORKS) @@ -413,9 +356,10 @@ def compile( ) if job_name is None: raise ValueError("You must provide a compilation job name") + if self.model_data is None: + raise ValueError("You must provide an S3 path to the compressed model artifacts.") - framework = framework.upper() - framework_version = self._get_framework_version() or framework_version + framework_version = framework_version or self._get_framework_version() self._init_sagemaker_session_if_does_not_exist(target_instance_family) config = self._compilation_job_config( @@ -435,17 +379,10 @@ def compile( self.sagemaker_session.compile_model(**config) job_status = self.sagemaker_session.wait_for_compilation_job(job_name) self.model_data = job_status["ModelArtifacts"]["S3ModelArtifacts"] + if target_instance_family is not None: if target_instance_family.startswith("ml_"): - self.image = self._neo_image( - self.sagemaker_session.boto_region_name, - target_instance_family, - framework, - framework_version, - ) - self._is_compiled_model = True - elif target_instance_family.startswith(INFERENTIA_INSTANCE_PREFIX): - self.image = self._inferentia_image( + self.image_uri = self._compilation_image_uri( self.sagemaker_session.boto_region_name, target_instance_family, framework, @@ -463,15 +400,17 @@ def compile( "Devices described by Target Platform OS, Architecture and Accelerator are not" "supported for deployment via SageMaker. Please deploy the model manually." ) + return self def deploy( self, initial_instance_count, instance_type, + serializer=None, + deserializer=None, accelerator_type=None, endpoint_name=None, - update_endpoint=False, tags=None, kms_key=None, wait=True, @@ -496,6 +435,16 @@ def deploy( in the ``Endpoint`` created from this ``Model``. instance_type (str): The EC2 instance type to deploy this Model to. For example, 'ml.p2.xlarge', or 'local' for local mode. + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: None). If ``serializer`` is not None, then + ``serializer`` will override the default serializer. The + default serializer is set by the ``predictor_cls``. + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: None). If ``deserializer`` is not None, then + ``deserializer`` will override the default deserializer. The + default deserializer is set by the ``predictor_cls``. accelerator_type (str): Type of Elastic Inference accelerator to deploy this model for model loading and inference, for example, 'ml.eia1.medium'. If not specified, no Elastic Inference @@ -504,11 +453,6 @@ def deploy( https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html endpoint_name (str): The name of the endpoint to create (default: None). If not specified, a unique endpoint name will be created. - update_endpoint (bool): Flag to update the model in an existing - Amazon SageMaker endpoint. If True, this will deploy a new - EndpointConfig to an already existing endpoint and delete - resources corresponding to the previous EndpointConfig. If - False, a new endpoint will be created. Default: False tags (List[dict[str, str]]): The list of tags to attach to this specific endpoint. kms_key (str): The ARN of the KMS key that is used to encrypt the @@ -537,8 +481,9 @@ def deploy( compiled_model_suffix = "-".join(instance_type.split(".")[:-1]) if self._is_compiled_model: - name_prefix = self.name or utils.name_from_image(self.image) - self.name = "{}{}".format(name_prefix, compiled_model_suffix) + self._ensure_base_name_if_needed(self.image_uri) + if self._base_name is not None: + self._base_name = "-".join((self._base_name, compiled_model_suffix)) self._create_sagemaker_model(instance_type, accelerator_type, tags) production_variant = sagemaker.production_variant( @@ -547,40 +492,31 @@ def deploy( if endpoint_name: self.endpoint_name = endpoint_name else: - self.endpoint_name = self.name - if self._is_compiled_model and not self.endpoint_name.endswith(compiled_model_suffix): - self.endpoint_name += compiled_model_suffix + base_endpoint_name = self._base_name or utils.base_from_name(self.name) + if self._is_compiled_model and not base_endpoint_name.endswith(compiled_model_suffix): + base_endpoint_name = "-".join((base_endpoint_name, compiled_model_suffix)) + self.endpoint_name = utils.name_from_base(base_endpoint_name) data_capture_config_dict = None if data_capture_config is not None: data_capture_config_dict = data_capture_config._to_request_dict() - if update_endpoint: - endpoint_config_name = self.sagemaker_session.create_endpoint_config( - name=self.name, - model_name=self.name, - initial_instance_count=initial_instance_count, - instance_type=instance_type, - accelerator_type=accelerator_type, - tags=tags, - kms_key=kms_key, - data_capture_config_dict=data_capture_config_dict, - ) - self.sagemaker_session.update_endpoint( - self.endpoint_name, endpoint_config_name, wait=wait - ) - else: - self.sagemaker_session.endpoint_from_production_variants( - name=self.endpoint_name, - production_variants=[production_variant], - tags=tags, - kms_key=kms_key, - wait=wait, - data_capture_config_dict=data_capture_config_dict, - ) + self.sagemaker_session.endpoint_from_production_variants( + name=self.endpoint_name, + production_variants=[production_variant], + tags=tags, + kms_key=kms_key, + wait=wait, + data_capture_config_dict=data_capture_config_dict, + ) if self.predictor_cls: - return self.predictor_cls(self.endpoint_name, self.sagemaker_session) + predictor = self.predictor_cls(self.endpoint_name, self.sagemaker_session) + if serializer: + predictor.serializer = serializer + if deserializer: + predictor.deserializer = deserializer + return predictor return None def transformer( @@ -647,7 +583,7 @@ def transformer( max_payload=max_payload, env=env, tags=tags, - base_transform_job_name=self.name, + base_transform_job_name=self._base_name or self.name, volume_kms_key=volume_kms_key, sagemaker_session=self.sagemaker_session, ) @@ -667,7 +603,6 @@ def delete_model(self): SCRIPT_PARAM_NAME = "sagemaker_program" DIR_PARAM_NAME = "sagemaker_submit_directory" -CLOUDWATCH_METRICS_PARAM_NAME = "sagemaker_enable_cloudwatch_metrics" CONTAINER_LOG_LEVEL_PARAM_NAME = "sagemaker_container_log_level" JOB_NAME_PARAM_NAME = "sagemaker_job_name" MODEL_SERVER_WORKERS_PARAM_NAME = "sagemaker_model_server_workers" @@ -685,14 +620,13 @@ class FrameworkModel(Model): def __init__( self, model_data, - image, + image_uri, role, entry_point, source_dir=None, predictor_cls=None, env=None, name=None, - enable_cloudwatch_metrics=False, container_log_level=logging.INFO, code_location=None, sagemaker_session=None, @@ -705,7 +639,7 @@ def __init__( Args: model_data (str): The S3 location of a SageMaker model data ``.tar.gz`` file. - image (str): A Docker image URI. + image_uri (str): A Docker image URI. role (str): An IAM role name or ARN for SageMaker to access AWS resources on your behalf. entry_point (str): Path (absolute or relative) to the Python source @@ -746,13 +680,10 @@ def __init__( function to call to create a predictor (default: None). If not None, ``deploy`` will return the result of invoking this function on the created endpoint name. - env (dict[str, str]): Environment variables to run with ``image`` + env (dict[str, str]): Environment variables to run with ``image_uri`` when hosted in SageMaker (default: None). name (str): The model name. If None, a default model name will be selected on each ``deploy``. - enable_cloudwatch_metrics (bool): Whether training and hosting - containers will generate CloudWatch metrics under the - AWS/SageMakerContainer namespace (default: False). container_log_level (int): Log level to use within the container (default: logging.INFO). Valid values are defined in the Python logging module. @@ -847,8 +778,8 @@ def __init__( :class:`~sagemaker.model.Model`. """ super(FrameworkModel, self).__init__( + image_uri, model_data, - image, role, predictor_cls=predictor_cls, env=env, @@ -860,10 +791,9 @@ def __init__( self.source_dir = source_dir self.dependencies = dependencies or [] self.git_config = git_config - self.enable_cloudwatch_metrics = enable_cloudwatch_metrics self.container_log_level = container_log_level if code_location: - self.bucket, self.key_prefix = fw_utils.parse_s3_url(code_location) + self.bucket, self.key_prefix = s3.parse_s3_url(code_location) else: self.bucket, self.key_prefix = None, None if self.git_config: @@ -893,11 +823,13 @@ def prepare_container_def(self, instance_type=None, accelerator_type=None): dict[str, str]: A container definition object usable with the CreateModel API. """ - deploy_key_prefix = fw_utils.model_code_key_prefix(self.key_prefix, self.name, self.image) + deploy_key_prefix = fw_utils.model_code_key_prefix( + self.key_prefix, self.name, self.image_uri + ) self._upload_code(deploy_key_prefix) deploy_env = dict(self.env) deploy_env.update(self._framework_env_vars()) - return sagemaker.container_def(self.image, self.model_data, deploy_env) + return sagemaker.container_def(self.image_uri, self.model_data, deploy_env) def _upload_code(self, key_prefix, repack=False): """ @@ -934,7 +866,7 @@ def _upload_code(self, key_prefix, repack=False): ) self.repacked_model_data = repacked_model_data - self.uploaded_code = UploadedCode( + self.uploaded_code = fw_utils.UploadedCode( s3_prefix=self.repacked_model_data, script_name=os.path.basename(self.entry_point) ) @@ -956,7 +888,6 @@ def _framework_env_vars(self): return { SCRIPT_PARAM_NAME.upper(): script_name, DIR_PARAM_NAME.upper(): dir_name, - CLOUDWATCH_METRICS_PARAM_NAME.upper(): str(self.enable_cloudwatch_metrics).lower(), CONTAINER_LOG_LEVEL_PARAM_NAME.upper(): str(self.container_log_level), SAGEMAKER_REGION_PARAM_NAME.upper(): self.sagemaker_session.boto_region_name, } @@ -984,7 +915,9 @@ def __init__(self, role, model_data=None, algorithm_arn=None, model_package_arn= ``model_data`` is not required. **kwargs: Additional kwargs passed to the Model constructor. """ - super(ModelPackage, self).__init__(role=role, model_data=model_data, image=None, **kwargs) + super(ModelPackage, self).__init__( + role=role, model_data=model_data, image_uri=None, **kwargs + ) if model_package_arn and algorithm_arn: raise ValueError( @@ -1073,13 +1006,18 @@ def _create_sagemaker_model(self, *args, **kwargs): # pylint: disable=unused-ar if self.env != {}: container_def["Environment"] = self.env - model_package_short_name = model_package_name.split("/")[-1] - enable_network_isolation = self.enable_network_isolation() - self.name = self.name or utils.name_from_base(model_package_short_name) + self._ensure_base_name_if_needed(model_package_name.split("/")[-1]) + self._set_model_name_if_needed() + self.sagemaker_session.create_model( self.name, self.role, container_def, vpc_config=self.vpc_config, - enable_network_isolation=enable_network_isolation, + enable_network_isolation=self.enable_network_isolation(), ) + + def _ensure_base_name_if_needed(self, base_name): + """Set the base name if there is no model name provided.""" + if self.name is None: + self._base_name = base_name diff --git a/src/sagemaker/model_monitor/model_monitoring.py b/src/sagemaker/model_monitor/model_monitoring.py index 1dbe582704..cdcc648be8 100644 --- a/src/sagemaker/model_monitor/model_monitoring.py +++ b/src/sagemaker/model_monitor/model_monitoring.py @@ -26,39 +26,16 @@ from six.moves.urllib.parse import urlparse from botocore.exceptions import ClientError +from sagemaker import image_uris from sagemaker.exceptions import UnexpectedStatusException from sagemaker.model_monitor.monitoring_files import Constraints, ConstraintViolations, Statistics from sagemaker.network import NetworkConfig from sagemaker.processing import Processor, ProcessingInput, ProcessingJob, ProcessingOutput from sagemaker.s3 import S3Uploader from sagemaker.session import Session -from sagemaker.utils import name_from_base, retries, get_ecr_image_uri_prefix - -_DEFAULT_MONITOR_IMAGE_URI_WITH_PLACEHOLDERS = "{}/sagemaker-model-monitor-analyzer" - -_DEFAULT_MONITOR_IMAGE_REGION_ACCOUNT_MAPPING = { - "eu-north-1": "895015795356", - "me-south-1": "607024016150", - "ap-south-1": "126357580389", - "us-east-2": "777275614652", - "eu-west-1": "468650794304", - "eu-central-1": "048819808253", - "sa-east-1": "539772159869", - "ap-east-1": "001633400207", - "us-east-1": "156813124566", - "ap-northeast-2": "709848358524", - "eu-west-2": "749857270468", - "eu-west-3": "680080141114", - "ap-northeast-1": "574779866223", - "us-west-2": "159807026194", - "us-west-1": "890145073186", - "ap-southeast-1": "245545462676", - "ap-southeast-2": "563025443158", - "ca-central-1": "536280801234", - "cn-north-1": "453000072557", - "cn-northwest-1": "453252182341", - "us-gov-west-1": "362178532790", -} +from sagemaker.utils import name_from_base, retries + +DEFAULT_REPOSITORY_NAME = "sagemaker-model-monitor-analyzer" STATISTICS_JSON_DEFAULT_FILE_NAME = "statistics.json" CONSTRAINTS_JSON_DEFAULT_FILE_NAME = "constraints.json" @@ -90,6 +67,8 @@ _LOGGER = logging.getLogger(__name__) +framework_name = "model-monitor" + class ModelMonitor(object): """Sets up Amazon SageMaker Monitoring Schedules and baseline suggestions. Use this class when @@ -872,7 +851,7 @@ def _normalize_baseline_inputs(self, baseline_inputs=None): S3Uploader.upload( local_path=file_input.source, desired_s3_uri=s3_uri, - session=self.sagemaker_session, + sagemaker_session=self.sagemaker_session, ) file_input.source = s3_uri normalized_inputs.append(file_input) @@ -945,7 +924,7 @@ def _s3_uri_from_local_path(self, path): str(uuid.uuid4()), ) S3Uploader.upload( - local_path=path, desired_s3_uri=s3_uri, session=self.sagemaker_session + local_path=path, desired_s3_uri=s3_uri, sagemaker_session=self.sagemaker_session ) path = os.path.join(s3_uri, os.path.basename(path)) return path @@ -1772,7 +1751,7 @@ def _upload_and_convert_to_processing_input(self, source, destination, name): name, ) S3Uploader.upload( - local_path=source, desired_s3_uri=s3_uri, session=self.sagemaker_session + local_path=source, desired_s3_uri=s3_uri, sagemaker_session=self.sagemaker_session ) source = s3_uri @@ -1788,9 +1767,7 @@ def _get_default_image_uri(region): Returns: str: The Default Model Monitoring image uri based on the region. """ - return _DEFAULT_MONITOR_IMAGE_URI_WITH_PLACEHOLDERS.format( - get_ecr_image_uri_prefix(_DEFAULT_MONITOR_IMAGE_REGION_ACCOUNT_MAPPING[region], region) - ) + return image_uris.retrieve(framework=framework_name, region=region) class BaseliningJob(ProcessingJob): diff --git a/src/sagemaker/model_monitor/monitoring_files.py b/src/sagemaker/model_monitor/monitoring_files.py index 3c8e2d407b..30d5a927b6 100644 --- a/src/sagemaker/model_monitor/monitoring_files.py +++ b/src/sagemaker/model_monitor/monitoring_files.py @@ -72,7 +72,7 @@ def save(self, new_save_location_s3_uri=None): body=json.dumps(self.body_dict), desired_s3_uri=self.file_s3_uri, kms_key=self.kms_key, - session=self.session, + sagemaker_session=self.session, ) @@ -119,7 +119,9 @@ def from_s3_uri(cls, statistics_file_s3_uri, kms_key=None, sagemaker_session=Non """ try: body_dict = json.loads( - S3Downloader.read_file(s3_uri=statistics_file_s3_uri, session=sagemaker_session) + S3Downloader.read_file( + s3_uri=statistics_file_s3_uri, sagemaker_session=sagemaker_session + ) ) except ClientError as error: print( @@ -163,7 +165,7 @@ def from_string( body=statistics_file_string, desired_s3_uri=desired_s3_uri, kms_key=kms_key, - session=sagemaker_session, + sagemaker_session=sagemaker_session, ) return Statistics.from_s3_uri( @@ -243,7 +245,9 @@ def from_s3_uri(cls, constraints_file_s3_uri, kms_key=None, sagemaker_session=No """ try: body_dict = json.loads( - S3Downloader.read_file(s3_uri=constraints_file_s3_uri, session=sagemaker_session) + S3Downloader.read_file( + s3_uri=constraints_file_s3_uri, sagemaker_session=sagemaker_session + ) ) except ClientError as error: print( @@ -290,7 +294,7 @@ def from_string( body=constraints_file_string, desired_s3_uri=desired_s3_uri, kms_key=kms_key, - session=sagemaker_session, + sagemaker_session=sagemaker_session, ) return Constraints.from_s3_uri( @@ -396,7 +400,7 @@ def from_s3_uri(cls, constraint_violations_file_s3_uri, kms_key=None, sagemaker_ try: body_dict = json.loads( S3Downloader.read_file( - s3_uri=constraint_violations_file_s3_uri, session=sagemaker_session + s3_uri=constraint_violations_file_s3_uri, sagemaker_session=sagemaker_session ) ) except ClientError as error: @@ -445,7 +449,7 @@ def from_string( body=constraint_violations_file_string, desired_s3_uri=desired_s3_uri, kms_key=kms_key, - session=sagemaker_session, + sagemaker_session=sagemaker_session, ) return ConstraintViolations.from_s3_uri( diff --git a/src/sagemaker/multidatamodel.py b/src/sagemaker/multidatamodel.py index 07edbdd987..dd40eccd81 100644 --- a/src/sagemaker/multidatamodel.py +++ b/src/sagemaker/multidatamodel.py @@ -35,7 +35,7 @@ def __init__( name, model_data_prefix, model=None, - image=None, + image_uri=None, role=None, sagemaker_session=None, **kwargs @@ -50,9 +50,9 @@ def __init__( model (sagemaker.Model): The Model object that would define the SageMaker model attributes like vpc_config, predictors, etc. If this is present, the attributes from this model are used when - deploying the ``MultiDataModel``. Parameters 'image', 'role' and 'kwargs' + deploying the ``MultiDataModel``. Parameters 'image_uri', 'role' and 'kwargs' are not permitted when model parameter is set. - image (str): A Docker image URI. It can be null if the 'model' parameter + image_uri (str): A Docker image URI. It can be null if the 'model' parameter is passed to during ``MultiDataModel`` initialization (default: None) role (str): An AWS IAM role (either name or full ARN). The Amazon SageMaker training jobs and APIs that create Amazon SageMaker @@ -82,9 +82,10 @@ def __init__( ) ) - if model and (image or role or kwargs): + if model and (image_uri or role or kwargs): raise ValueError( - "Parameters image, role or kwargs are not permitted when model parameter is passed." + "Parameters image_uri, role, and kwargs are not permitted when " + "model parameter is passed." ) self.name = name @@ -103,8 +104,8 @@ def __init__( # Set the ``Model`` parameters if the model parameter is not specified if not self.model: super(MultiDataModel, self).__init__( + image_uri, self.model_data_prefix, - image, role, name=self.name, sagemaker_session=self.sagemaker_session, @@ -121,18 +122,18 @@ def prepare_container_def(self, instance_type=None, accelerator_type=None): Returns: dict[str, str]: A complete container definition object usable with the CreateModel API """ - # Copy the trained model's image and environment variables if they exist. Models trained + # Copy the trained model's image URI and environment variables if they exist. Models trained # with FrameworkEstimator set framework specific environment variables which need to be # copied over if self.model: container_definition = self.model.prepare_container_def(instance_type, accelerator_type) - image = container_definition["Image"] + image_uri = container_definition["Image"] environment = container_definition["Environment"] else: - image = self.image + image_uri = self.image_uri environment = self.env return sagemaker.container_def( - image, + image_uri, env=environment, model_data_url=self.model_data_prefix, container_mode=self.container_mode, @@ -142,9 +143,10 @@ def deploy( self, initial_instance_count, instance_type, + serializer=None, + deserializer=None, accelerator_type=None, endpoint_name=None, - update_endpoint=False, tags=None, kms_key=None, wait=True, @@ -171,6 +173,16 @@ def deploy( in the ``Endpoint`` created from this ``Model``. instance_type (str): The EC2 instance type to deploy this Model to. For example, 'ml.p2.xlarge', or 'local' for local mode. + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: None). If ``serializer`` is not None, then + ``serializer`` will override the default serializer. The + default serializer is set by the ``predictor_cls``. + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: None). If ``deserializer`` is not None, then + ``deserializer`` will override the default deserializer. The + default deserializer is set by the ``predictor_cls``. accelerator_type (str): Type of Elastic Inference accelerator to deploy this model for model loading and inference, for example, 'ml.eia1.medium'. If not specified, no Elastic Inference @@ -179,11 +191,6 @@ def deploy( https://docs.aws.amazon.com/sagemaker/latest/dg/ei.html endpoint_name (str): The name of the endpoint to create (default: None). If not specified, a unique endpoint name will be created. - update_endpoint (bool): Flag to update the model in an existing - Amazon SageMaker endpoint. If True, this will deploy a new - EndpointConfig to an already existing endpoint and delete - resources corresponding to the previous EndpointConfig. If - False, a new endpoint will be created. Default: False tags (List[dict[str, str]]): The list of tags to attach to this specific endpoint. kms_key (str): The ARN of the KMS key that is used to encrypt the @@ -206,12 +213,12 @@ def deploy( enable_network_isolation = self.model.enable_network_isolation() role = self.model.role vpc_config = self.model.vpc_config - predictor = self.model.predictor_cls + predictor_cls = self.model.predictor_cls else: enable_network_isolation = self.enable_network_isolation() role = self.role vpc_config = self.vpc_config - predictor = self.predictor_cls + predictor_cls = self.predictor_cls if role is None: raise ValueError("Role can not be null for deploying a model") @@ -241,32 +248,22 @@ def deploy( if data_capture_config is not None: data_capture_config_dict = data_capture_config._to_request_dict() - if update_endpoint: - endpoint_config_name = self.sagemaker_session.create_endpoint_config( - name=self.name, - model_name=self.name, - initial_instance_count=initial_instance_count, - instance_type=instance_type, - accelerator_type=accelerator_type, - tags=tags, - kms_key=kms_key, - data_capture_config_dict=data_capture_config_dict, - ) - self.sagemaker_session.update_endpoint( - self.endpoint_name, endpoint_config_name, wait=wait - ) - else: - self.sagemaker_session.endpoint_from_production_variants( - name=self.endpoint_name, - production_variants=[production_variant], - tags=tags, - kms_key=kms_key, - wait=wait, - data_capture_config_dict=data_capture_config_dict, - ) + self.sagemaker_session.endpoint_from_production_variants( + name=self.endpoint_name, + production_variants=[production_variant], + tags=tags, + kms_key=kms_key, + wait=wait, + data_capture_config_dict=data_capture_config_dict, + ) - if predictor: - return predictor(self.endpoint_name, self.sagemaker_session) + if predictor_cls: + predictor = predictor_cls(self.endpoint_name, self.sagemaker_session) + if serializer: + predictor.serializer = serializer + if deserializer: + predictor.deserializer = deserializer + return predictor return None def add_model(self, model_data_source, model_data_path=None): diff --git a/src/sagemaker/mxnet/defaults.py b/src/sagemaker/mxnet/defaults.py index 25981bc2a0..ee70f73a86 100644 --- a/src/sagemaker/mxnet/defaults.py +++ b/src/sagemaker/mxnet/defaults.py @@ -13,12 +13,4 @@ """Placeholder docstring""" from __future__ import absolute_import -MXNET_VERSION = "1.2" -"""Default MXNet version for when the framework version is not specified. -This is no longer updated so as to not break existing workflows. -""" - -LATEST_VERSION = "1.6.0" -"""The latest version of MXNet included in the SageMaker pre-built Docker images.""" - LATEST_PY2_VERSION = "1.6.0" diff --git a/src/sagemaker/mxnet/estimator.py b/src/sagemaker/mxnet/estimator.py index 53a93ba144..f5fe29c324 100644 --- a/src/sagemaker/mxnet/estimator.py +++ b/src/sagemaker/mxnet/estimator.py @@ -15,14 +15,14 @@ import logging +from packaging.version import Version + from sagemaker.estimator import Framework from sagemaker.fw_utils import ( framework_name_from_image, framework_version_from_tag, - empty_framework_version_warning, python_deprecation_warning, - parameter_v2_rename_warning, - is_version_equal_or_higher, + validate_version_or_image_args, warn_if_parameter_server_with_multi_gpu, ) from sagemaker.mxnet import defaults @@ -35,20 +35,18 @@ class MXNet(Framework): """Handle end-to-end training and deployment of custom MXNet code.""" - __framework_name__ = "mxnet" + _framework_name = "mxnet" _LOWEST_SCRIPT_MODE_VERSION = ["1", "3"] - LATEST_VERSION = defaults.LATEST_VERSION - def __init__( self, entry_point, + framework_version=None, + py_version=None, source_dir=None, hyperparameters=None, - py_version="py2", - framework_version=None, - image_name=None, - distributions=None, + image_uri=None, + distribution=None, **kwargs ): """This ``Estimator`` executes an MXNet script in a managed MXNet @@ -73,6 +71,13 @@ def __init__( file which should be executed as the entry point to training. If ``source_dir`` is specified, then ``entry_point`` must point to a file located at the root of ``source_dir``. + framework_version (str): MXNet version you want to use for executing + your model training code. Defaults to `None`. Required unless + ``image_uri`` is provided. List of supported versions. + https://github.com/aws/sagemaker-python-sdk#mxnet-sagemaker-estimators. + py_version (str): Python version you want to use for executing your + model training code. One of 'py2' or 'py3'. Defaults to ``None``. Required + unless ``image_uri`` is provided. source_dir (str): Path (absolute, relative or an S3 URI) to a directory with any other training source code dependencies aside from the entry point file (default: None). If ``source_dir`` is an S3 URI, it must @@ -84,13 +89,7 @@ def __init__( SageMaker. For convenience, this accepts other types for keys and values, but ``str()`` will be called to convert them before training. - py_version (str): Python version you want to use for executing your - model training code (default: 'py2'). One of 'py2' or 'py3'. - framework_version (str): MXNet version you want to use for executing - your model training code. List of supported versions - https://github.com/aws/sagemaker-python-sdk#mxnet-sagemaker-estimators. - If not specified, this will default to 1.2.1. - image_name (str): If specified, the estimator will use this image for training and + image_uri (str): If specified, the estimator will use this image for training and hosting, instead of selecting the appropriate SageMaker official image based on framework_version and py_version. It can be an ECR url or dockerhub image and tag. @@ -98,7 +97,10 @@ def __init__( * ``123412341234.dkr.ecr.us-west-2.amazonaws.com/my-custom-image:1.0`` * ``custom-image:latest`` - distributions (dict): A dictionary with information on how to run distributed + If ``framework_version`` or ``py_version`` are ``None``, then + ``image_uri`` is required. If also ``None``, then a ``ValueError`` + will be raised. + distribution (dict): A dictionary with information on how to run distributed training (default: None). Currently we support distributed training with parameter server and MPI [Horovod]. To enable parameter server use the following setup: @@ -146,57 +148,55 @@ def __init__( :class:`~sagemaker.estimator.Framework` and :class:`~sagemaker.estimator.EstimatorBase`. """ - if framework_version is None: + validate_version_or_image_args(framework_version, py_version, image_uri) + if py_version == "py2": logger.warning( - empty_framework_version_warning(defaults.MXNET_VERSION, self.LATEST_VERSION) + python_deprecation_warning(self._framework_name, defaults.LATEST_PY2_VERSION) ) - self.framework_version = framework_version or defaults.MXNET_VERSION + self.framework_version = framework_version + self.py_version = py_version if "enable_sagemaker_metrics" not in kwargs: # enable sagemaker metrics for MXNet v1.6 or greater: - if is_version_equal_or_higher([1, 6], self.framework_version): + if self.framework_version and Version(self.framework_version) >= Version("1.6"): kwargs["enable_sagemaker_metrics"] = True super(MXNet, self).__init__( - entry_point, source_dir, hyperparameters, image_name=image_name, **kwargs + entry_point, source_dir, hyperparameters, image_uri=image_uri, **kwargs ) - if py_version == "py2": - logger.warning( - python_deprecation_warning(self.__framework_name__, defaults.LATEST_PY2_VERSION) - ) - - if distributions is not None: - logger.warning(parameter_v2_rename_warning("distributions", "distribution")) - train_instance_type = kwargs.get("train_instance_type") + if distribution is not None: + instance_type = kwargs.get("instance_type") warn_if_parameter_server_with_multi_gpu( - training_instance_type=train_instance_type, distributions=distributions + training_instance_type=instance_type, distribution=distribution ) - self.py_version = py_version - self._configure_distribution(distributions) + self._configure_distribution(distribution) - def _configure_distribution(self, distributions): + def _configure_distribution(self, distribution): """ Args: - distributions: + distribution: """ - if distributions is None: + if distribution is None: return - if self.framework_version.split(".") < self._LOWEST_SCRIPT_MODE_VERSION: + if ( + self.framework_version + and self.framework_version.split(".") < self._LOWEST_SCRIPT_MODE_VERSION + ): raise ValueError( - "The distributions option is valid for only versions {} and higher".format( + "The distribution option is valid for only versions {} and higher".format( ".".join(self._LOWEST_SCRIPT_MODE_VERSION) ) ) - if "parameter_server" in distributions: - enabled = distributions["parameter_server"].get("enabled", False) + if "parameter_server" in distribution: + enabled = distribution["parameter_server"].get("enabled", False) self._hyperparameters[self.LAUNCH_PS_ENV_NAME] = enabled - if "mpi" in distributions: - mpi_dict = distributions["mpi"] + if "mpi" in distribution: + mpi_dict = distribution["mpi"] mpi_enabled = mpi_dict.get("enabled", False) self._hyperparameters[self.LAUNCH_MPI_ENV_NAME] = mpi_enabled @@ -217,7 +217,7 @@ def create_model( entry_point=None, source_dir=None, dependencies=None, - image_name=None, + image_uri=None, **kwargs ): """Create a SageMaker ``MXNetModel`` object that can be deployed to an @@ -247,7 +247,7 @@ def create_model( any additional libraries that will be exported to the container. If not specified, the dependencies from training are used. This is not supported with "local code" in Local Mode. - image_name (str): If specified, the estimator will use this image for hosting, instead + image_uri (str): If specified, the estimator will use this image for hosting, instead of selecting the appropriate SageMaker official image based on framework_version and py_version. It can be an ECR url or dockerhub image and tag. @@ -262,22 +262,20 @@ def create_model( sagemaker.mxnet.model.MXNetModel: A SageMaker ``MXNetModel`` object. See :func:`~sagemaker.mxnet.model.MXNetModel` for full details. """ - if "image" not in kwargs: - kwargs["image"] = image_name or self.image_name + if "image_uri" not in kwargs: + kwargs["image_uri"] = image_uri or self.image_uri - if "name" not in kwargs: - kwargs["name"] = self._current_job_name + kwargs["name"] = self._get_or_create_name(kwargs.get("name")) - return MXNetModel( + model = MXNetModel( self.model_data, role or self.role, - entry_point or self.entry_point, + entry_point, + framework_version=self.framework_version, + py_version=self.py_version, source_dir=(source_dir or self._model_source_dir()), - enable_cloudwatch_metrics=self.enable_cloudwatch_metrics, container_log_level=self.container_log_level, code_location=self.code_location, - py_version=self.py_version, - framework_version=self.framework_version, model_server_workers=model_server_workers, sagemaker_session=self.sagemaker_session, vpc_config=self.get_vpc_config(vpc_config_override), @@ -285,6 +283,13 @@ def create_model( **kwargs ) + if entry_point is None: + model.entry_point = ( + self.entry_point if model._is_mms_version() else self._model_entry_point() + ) + + return model + @classmethod def _prepare_init_params_from_job_description(cls, job_details, model_channel_name=None): """Convert the job description to init params that can be handled by the @@ -302,31 +307,32 @@ class constructor init_params = super(MXNet, cls)._prepare_init_params_from_job_description( job_details, model_channel_name ) - image_name = init_params.pop("image") - framework, py_version, tag, _ = framework_name_from_image(image_name) - - if not framework: - # If we were unable to parse the framework name from the image it is not one of our - # officially supported images, in this case just add the image to the init params. - init_params["image_name"] = image_name - return init_params - - init_params["py_version"] = py_version + image_uri = init_params.pop("image_uri") + framework, py_version, tag, _ = framework_name_from_image(image_uri) # We switched image tagging scheme from regular image version (e.g. '1.0') to more # expressive containing framework version, device type and python version # (e.g. '0.12-gpu-py2'). For backward compatibility map deprecated image tag '1.0' to a # '0.12' framework version otherwise extract framework version from the tag itself. - init_params["framework_version"] = ( - "0.12" if tag == "1.0" else framework_version_from_tag(tag) - ) + if tag is None: + framework_version = None + elif tag == "1.0": + framework_version = "0.12" + else: + framework_version = framework_version_from_tag(tag) + init_params["framework_version"] = framework_version + init_params["py_version"] = py_version - training_job_name = init_params["base_job_name"] + if not framework: + # If we were unable to parse the framework name from the image it is not one of our + # officially supported images, in this case just add the image to the init params. + init_params["image_uri"] = image_uri + return init_params - if framework != cls.__framework_name__: + if framework != cls._framework_name: raise ValueError( "Training job: {} didn't use image for requested framework".format( - training_job_name + job_details["TrainingJobName"] ) ) diff --git a/src/sagemaker/mxnet/model.py b/src/sagemaker/mxnet/model.py index 093a2f37bd..2cc57d0d83 100644 --- a/src/sagemaker/mxnet/model.py +++ b/src/sagemaker/mxnet/model.py @@ -18,21 +18,23 @@ import packaging.version import sagemaker +from sagemaker import image_uris +from sagemaker.deserializers import JSONDeserializer from sagemaker.fw_utils import ( - create_image_uri, model_code_key_prefix, python_deprecation_warning, - empty_framework_version_warning, + validate_version_or_image_args, ) from sagemaker.model import FrameworkModel, MODEL_SERVER_WORKERS_PARAM_NAME from sagemaker.mxnet import defaults -from sagemaker.predictor import RealTimePredictor, json_serializer, json_deserializer +from sagemaker.predictor import Predictor +from sagemaker.serializers import JSONSerializer logger = logging.getLogger("sagemaker") -class MXNetPredictor(RealTimePredictor): - """A RealTimePredictor for inference against MXNet Endpoints. +class MXNetPredictor(Predictor): + """A Predictor for inference against MXNet Endpoints. This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for MXNet inference. @@ -50,14 +52,14 @@ def __init__(self, endpoint_name, sagemaker_session=None): using the default AWS configuration chain. """ super(MXNetPredictor, self).__init__( - endpoint_name, sagemaker_session, json_serializer, json_deserializer + endpoint_name, sagemaker_session, JSONSerializer(), JSONDeserializer() ) class MXNetModel(FrameworkModel): """An MXNet SageMaker ``Model`` that can be deployed to a SageMaker ``Endpoint``.""" - __framework_name__ = "mxnet" + _framework_name = "mxnet" _LOWEST_MMS_VERSION = "1.4.0" def __init__( @@ -65,9 +67,9 @@ def __init__( model_data, role, entry_point, - image=None, - py_version="py2", framework_version=None, + py_version=None, + image_uri=None, predictor_cls=MXNetPredictor, model_server_workers=None, **kwargs @@ -86,12 +88,18 @@ def __init__( file which should be executed as the entry point to model hosting. If ``source_dir`` is specified, then ``entry_point`` must point to a file located at the root of ``source_dir``. - image (str): A Docker image URI (default: None). If not specified, a - default image for MXNet will be used. - py_version (str): Python version you want to use for executing your - model training code (default: 'py2'). framework_version (str): MXNet version you want to use for executing - your model training code. + your model training code. Defaults to ``None``. Required unless + ``image_uri`` is provided. + py_version (str): Python version you want to use for executing your + model training code. Defaults to ``None``. Required unless + ``image_uri`` is provided. + image_uri (str): A Docker image URI (default: None). If not specified, a + default image for MXNet will be used. + + If ``framework_version`` or ``py_version`` are ``None``, then + ``image_uri`` is required. If also ``None``, then a ``ValueError`` + will be raised. predictor_cls (callable[str, sagemaker.session.Session]): A function to call to create a predictor with an endpoint name and SageMaker ``Session``. If specified, ``deploy()`` returns the @@ -108,22 +116,18 @@ def __init__( :class:`~sagemaker.model.FrameworkModel` and :class:`~sagemaker.model.Model`. """ - super(MXNetModel, self).__init__( - model_data, image, role, entry_point, predictor_cls=predictor_cls, **kwargs - ) - + validate_version_or_image_args(framework_version, py_version, image_uri) if py_version == "py2": logger.warning( - python_deprecation_warning(self.__framework_name__, defaults.LATEST_PY2_VERSION) + python_deprecation_warning(self._framework_name, defaults.LATEST_PY2_VERSION) ) + self.framework_version = framework_version + self.py_version = py_version - if framework_version is None: - logger.warning( - empty_framework_version_warning(defaults.MXNET_VERSION, defaults.LATEST_VERSION) - ) + super(MXNetModel, self).__init__( + model_data, image_uri, role, entry_point, predictor_cls=predictor_cls, **kwargs + ) - self.py_version = py_version - self.framework_version = framework_version or defaults.MXNET_VERSION self.model_server_workers = model_server_workers def prepare_container_def(self, instance_type=None, accelerator_type=None): @@ -141,7 +145,7 @@ def prepare_container_def(self, instance_type=None, accelerator_type=None): dict[str, str]: A container definition object usable with the CreateModel API. """ - deploy_image = self.image + deploy_image = self.image_uri if not deploy_image: if instance_type is None: raise ValueError( @@ -179,17 +183,14 @@ def serving_image_uri(self, region_name, instance_type, accelerator_type=None): str: The appropriate image URI based on the given parameters. """ - framework_name = self.__framework_name__ - if self._is_mms_version(): - framework_name = "{}-serving".format(framework_name) - - return create_image_uri( + return image_uris.retrieve( + self._framework_name, region_name, - framework_name, - instance_type, - self.framework_version, - self.py_version, + version=self.framework_version, + py_version=self.py_version, + instance_type=instance_type, accelerator_type=accelerator_type, + image_scope="inference", ) def _is_mms_version(self): diff --git a/src/sagemaker/parameter.py b/src/sagemaker/parameter.py index 91153db9e8..9ed7cd2731 100644 --- a/src/sagemaker/parameter.py +++ b/src/sagemaker/parameter.py @@ -14,8 +14,6 @@ from __future__ import absolute_import import json -from sagemaker.utils import to_str - class ParameterRange(object): """Base class for representing parameter ranges. This is used to define what @@ -71,8 +69,8 @@ def as_tuning_range(self, name): """ return { "Name": name, - "MinValue": to_str(self.min_value), - "MaxValue": to_str(self.max_value), + "MinValue": str(self.min_value), + "MaxValue": str(self.max_value), "ScalingType": self.scaling_type, } @@ -111,9 +109,9 @@ def __init__(self, values): # pylint: disable=super-init-not-called This input will be converted into a list of strings. """ if isinstance(values, list): - self.values = [to_str(v) for v in values] + self.values = [str(v) for v in values] else: - self.values = [to_str(values)] + self.values = [str(values)] def as_tuning_range(self, name): """Represent the parameter range as a dicionary suitable for a request @@ -158,7 +156,7 @@ def cast_to_type(cls, value): Args: value: """ - return to_str(value) + return str(value) class IntegerParameter(ParameterRange): diff --git a/src/sagemaker/pipeline.py b/src/sagemaker/pipeline.py index d343eadefa..1c6eed627b 100644 --- a/src/sagemaker/pipeline.py +++ b/src/sagemaker/pipeline.py @@ -60,7 +60,6 @@ def __init__( self.name = name self.vpc_config = vpc_config self.sagemaker_session = sagemaker_session - self._model_name = None self.endpoint_name = None def pipeline_container_def(self, instance_type): @@ -86,6 +85,8 @@ def deploy( self, initial_instance_count, instance_type, + serializer=None, + deserializer=None, endpoint_name=None, tags=None, wait=True, @@ -111,6 +112,16 @@ def deploy( in the ``Endpoint`` created from this ``Model``. instance_type (str): The EC2 instance type to deploy this Model to. For example, 'ml.p2.xlarge'. + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: None). If ``serializer`` is not None, then + ``serializer`` will override the default serializer. The + default serializer is set by the ``predictor_cls``. + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: None). If ``deserializer`` is not None, then + ``deserializer`` will override the default deserializer. The + default deserializer is set by the ``predictor_cls``. endpoint_name (str): The name of the endpoint to create (default: None). If not specified, a unique endpoint name will be created. tags (List[dict[str, str]]): The list of tags to attach to this @@ -172,7 +183,12 @@ def deploy( ) if self.predictor_cls: - return self.predictor_cls(self.endpoint_name, self.sagemaker_session) + predictor = self.predictor_cls(self.endpoint_name, self.sagemaker_session) + if serializer: + predictor.serializer = serializer + if deserializer: + predictor.deserializer = deserializer + return predictor return None def _create_sagemaker_pipeline_model(self, instance_type): diff --git a/src/sagemaker/predictor.py b/src/sagemaker/predictor.py index 0f1efb4e18..90b93c241a 100644 --- a/src/sagemaker/predictor.py +++ b/src/sagemaker/predictor.py @@ -13,38 +13,30 @@ """Placeholder docstring""" from __future__ import print_function, absolute_import -import codecs -import csv -import json -import six -from six import StringIO, BytesIO -import numpy as np - -from sagemaker.content_types import CONTENT_TYPE_JSON, CONTENT_TYPE_CSV, CONTENT_TYPE_NPY +from sagemaker.deserializers import BytesDeserializer from sagemaker.model_monitor import DataCaptureConfig -from sagemaker.session import Session +from sagemaker.serializers import IdentitySerializer +from sagemaker.session import production_variant, Session from sagemaker.utils import name_from_base from sagemaker.model_monitor.model_monitoring import ( - _DEFAULT_MONITOR_IMAGE_URI_WITH_PLACEHOLDERS, + DEFAULT_REPOSITORY_NAME, ModelMonitor, DefaultModelMonitor, ) -class RealTimePredictor(object): +class Predictor(object): """Make prediction requests to an Amazon SageMaker endpoint.""" def __init__( self, - endpoint, + endpoint_name, sagemaker_session=None, - serializer=None, - deserializer=None, - content_type=None, - accept=None, + serializer=IdentitySerializer(), + deserializer=BytesDeserializer(), ): - """Initialize a ``RealTimePredictor``. + """Initialize a ``Predictor``. Behavior for serialization of input data and deserialization of result data can be configured through initializer arguments. If not @@ -53,33 +45,23 @@ def __init__( sequence of bytes from the prediction result without any modifications. Args: - endpoint (str): Name of the Amazon SageMaker endpoint to which + endpoint_name (str): Name of the Amazon SageMaker endpoint to which requests are sent. sagemaker_session (sagemaker.session.Session): A SageMaker Session object, used for SageMaker interactions (default: None). If not specified, one is created using the default AWS configuration chain. - serializer (callable): Accepts a single argument, the input data, - and returns a sequence of bytes. It may provide a - ``content_type`` attribute that defines the endpoint request - content type. If not specified, a sequence of bytes is expected - for the data. - deserializer (callable): Accepts two arguments, the result data and - the response content type, and returns a sequence of bytes. It - may provide a ``content_type`` attribute that defines the - endpoint response's "Accept" content type. If not specified, a - sequence of bytes is expected for the data. - content_type (str): The invocation's "ContentType", overriding any - ``content_type`` from the serializer (default: None). - accept (str): The invocation's "Accept", overriding any accept from - the deserializer (default: None). - """ - self.endpoint = endpoint + serializer (:class:`~sagemaker.serializers.BaseSerializer`): A + serializer object, used to encode data for an inference endpoint + (default: :class:`~sagemaker.serializers.IdentitySerializer`). + deserializer (:class:`~sagemaker.deserializers.BaseDeserializer`): A + deserializer object, used to decode data from an inference + endpoint (default: :class:`~sagemaker.deserializers.BytesDeserializer`). + """ + self.endpoint_name = endpoint_name self.sagemaker_session = sagemaker_session or Session() self.serializer = serializer self.deserializer = deserializer - self.content_type = content_type or getattr(serializer, "content_type", None) - self.accept = accept or getattr(deserializer, "accept", None) self._endpoint_config_name = self._get_endpoint_config_name() self._model_names = self._get_model_names() @@ -89,7 +71,7 @@ def predict(self, data, initial_args=None, target_model=None, target_variant=Non Args: data (object): Input data for which you want the model to provide inference. If a serializer was specified when creating the - RealTimePredictor, the result of the serializer is sent as input + Predictor, the result of the serializer is sent as input data. Otherwise the data must be sequence of bytes, and the predict method then sends the bytes in the request body as is. initial_args (dict[str,str]): Optional. Default arguments for boto3 @@ -104,7 +86,7 @@ def predict(self, data, initial_args=None, target_model=None, target_variant=Non Returns: object: Inference for the given input. If a deserializer was specified when creating - the RealTimePredictor, the result of the deserializer is + the Predictor, the result of the deserializer is returned. Otherwise the response returns the sequence of bytes as is. """ @@ -119,12 +101,8 @@ def _handle_response(self, response): response: """ response_body = response["Body"] - if self.deserializer is not None: - # It's the deserializer's responsibility to close the stream - return self.deserializer(response_body, response["ContentType"]) - data = response_body.read() - response_body.close() - return data + content_type = response.get("ContentType", "application/octet-stream") + return self.deserializer.deserialize(response_body, content_type) def _create_request_args(self, data, initial_args=None, target_model=None, target_variant=None): """ @@ -137,13 +115,13 @@ def _create_request_args(self, data, initial_args=None, target_model=None, targe args = dict(initial_args) if initial_args else {} if "EndpointName" not in args: - args["EndpointName"] = self.endpoint + args["EndpointName"] = self.endpoint_name - if self.content_type and "ContentType" not in args: + if "ContentType" not in args: args["ContentType"] = self.content_type - if self.accept and "Accept" not in args: - args["Accept"] = self.accept + if "Accept" not in args: + args["Accept"] = ", ".join(self.accept) if target_model: args["TargetModel"] = target_model @@ -151,12 +129,111 @@ def _create_request_args(self, data, initial_args=None, target_model=None, targe if target_variant: args["TargetVariant"] = target_variant - if self.serializer is not None: - data = self.serializer(data) + data = self.serializer.serialize(data) args["Body"] = data return args + def update_endpoint( + self, + initial_instance_count=None, + instance_type=None, + accelerator_type=None, + model_name=None, + tags=None, + kms_key=None, + data_capture_config_dict=None, + wait=True, + ): + """Update the existing endpoint with the provided attributes. + + This creates a new EndpointConfig in the process. If ``initial_instance_count``, + ``instance_type``, ``accelerator_type``, or ``model_name`` is specified, then a new + ProductionVariant configuration is created; values from the existing configuration + are not preserved if any of those parameters are specified. + + Args: + initial_instance_count (int): The initial number of instances to run in the endpoint. + This is required if ``instance_type``, ``accelerator_type``, or ``model_name`` is + specified. Otherwise, the values from the existing endpoint configuration's + ProductionVariants are used. + instance_type (str): The EC2 instance type to deploy the endpoint to. + This is required if ``initial_instance_count`` or ``accelerator_type`` is specified. + Otherwise, the values from the existing endpoint configuration's + ``ProductionVariants`` are used. + accelerator_type (str): The type of Elastic Inference accelerator to attach to + the endpoint, e.g. "ml.eia1.medium". If not specified, and + ``initial_instance_count``, ``instance_type``, and ``model_name`` are also ``None``, + the values from the existing endpoint configuration's ``ProductionVariants`` are + used. Otherwise, no Elastic Inference accelerator is attached to the endpoint. + model_name (str): The name of the model to be associated with the endpoint. + This is required if ``initial_instance_count``, ``instance_type``, or + ``accelerator_type`` is specified and if there is more than one model associated + with the endpoint. Otherwise, the existing model for the endpoint is used. + tags (list[dict[str, str]]): The list of tags to add to the endpoint + config. If not specified, the tags of the existing endpoint configuration are used. + If any of the existing tags are reserved AWS ones (i.e. begin with "aws"), + they are not carried over to the new endpoint configuration. + kms_key (str): The KMS key that is used to encrypt the data on the storage volume + attached to the instance hosting the endpoint If not specified, + the KMS key of the existing endpoint configuration is used. + data_capture_config_dict (dict): The endpoint data capture configuration + for use with Amazon SageMaker Model Monitoring. If not specified, + the data capture configuration of the existing endpoint configuration is used. + + Raises: + ValueError: If there is not enough information to create a new ``ProductionVariant``: + + - If ``initial_instance_count``, ``accelerator_type``, or ``model_name`` is + specified, but ``instance_type`` is ``None``. + - If ``initial_instance_count``, ``instance_type``, or ``accelerator_type`` is + specified and either ``model_name`` is ``None`` or there are multiple models + associated with the endpoint. + """ + production_variants = None + + if initial_instance_count or instance_type or accelerator_type or model_name: + if instance_type is None or initial_instance_count is None: + raise ValueError( + "Missing initial_instance_count and/or instance_type. Provided values: " + "initial_instance_count={}, instance_type={}, accelerator_type={}, " + "model_name={}.".format( + initial_instance_count, instance_type, accelerator_type, model_name + ) + ) + + if model_name is None: + if len(self._model_names) > 1: + raise ValueError( + "Unable to choose a default model for a new EndpointConfig because " + "the endpoint has multiple models: {}".format(", ".join(self._model_names)) + ) + model_name = self._model_names[0] + else: + self._model_names = [model_name] + + production_variant_config = production_variant( + model_name, + instance_type, + initial_instance_count=initial_instance_count, + accelerator_type=accelerator_type, + ) + production_variants = [production_variant_config] + + new_endpoint_config_name = name_from_base(self._endpoint_config_name) + self.sagemaker_session.create_endpoint_config_from_existing( + self._endpoint_config_name, + new_endpoint_config_name, + new_tags=tags, + new_kms_key=kms_key, + new_data_capture_config_dict=data_capture_config_dict, + new_production_variants=production_variants, + ) + self.sagemaker_session.update_endpoint( + self.endpoint_name, new_endpoint_config_name, wait=wait + ) + self._endpoint_config_name = new_endpoint_config_name + def _delete_endpoint_config(self): """Delete the Amazon SageMaker endpoint configuration""" self.sagemaker_session.delete_endpoint_config(self._endpoint_config_name) @@ -175,7 +252,7 @@ def delete_endpoint(self, delete_endpoint_config=True): if delete_endpoint_config: self._delete_endpoint_config() - self.sagemaker_session.delete_endpoint(self.endpoint) + self.sagemaker_session.delete_endpoint(self.endpoint_name) def delete_model(self): """Deletes the Amazon SageMaker models backing this predictor.""" @@ -225,10 +302,10 @@ def update_data_capture_config(self, data_capture_config): DataCaptureConfig to update the predictor's endpoint to use. """ endpoint_desc = self.sagemaker_session.sagemaker_client.describe_endpoint( - EndpointName=self.endpoint + EndpointName=self.endpoint_name ) - new_config_name = name_from_base(base=self.endpoint) + new_config_name = name_from_base(base=self.endpoint_name) data_capture_config_dict = None if data_capture_config is not None: @@ -241,7 +318,7 @@ def update_data_capture_config(self, data_capture_config): ) self.sagemaker_session.update_endpoint( - endpoint_name=self.endpoint, endpoint_config_name=new_config_name + endpoint_name=self.endpoint_name, endpoint_config_name=new_config_name ) def list_monitors(self): @@ -254,10 +331,10 @@ def list_monitors(self): """ monitoring_schedules_dict = self.sagemaker_session.list_monitoring_schedules( - endpoint_name=self.endpoint + endpoint_name=self.endpoint_name ) if len(monitoring_schedules_dict["MonitoringScheduleSummaries"]) == 0: - print("No monitors found for endpoint. endpoint: {}".format(self.endpoint)) + print("No monitors found for endpoint. endpoint: {}".format(self.endpoint_name)) return [] monitors = [] @@ -269,10 +346,7 @@ def list_monitors(self): image_uri = schedule["MonitoringScheduleConfig"]["MonitoringJobDefinition"][ "MonitoringAppSpecification" ]["ImageUri"] - index_after_placeholders = _DEFAULT_MONITOR_IMAGE_URI_WITH_PLACEHOLDERS.rfind("{}") - if image_uri.endswith( - _DEFAULT_MONITOR_IMAGE_URI_WITH_PLACEHOLDERS[index_after_placeholders + len("{}") :] - ): + if image_uri.endswith(DEFAULT_REPOSITORY_NAME): monitors.append( DefaultModelMonitor.attach( monitor_schedule_name=schedule_name, @@ -292,7 +366,7 @@ def list_monitors(self): def _get_endpoint_config_name(self): """Placeholder docstring""" endpoint_desc = self.sagemaker_session.sagemaker_client.describe_endpoint( - EndpointName=self.endpoint + EndpointName=self.endpoint_name ) endpoint_config_name = endpoint_desc["EndpointConfigName"] return endpoint_config_name @@ -303,382 +377,14 @@ def _get_model_names(self): EndpointConfigName=self._endpoint_config_name ) production_variants = endpoint_config["ProductionVariants"] - return map(lambda d: d["ModelName"], production_variants) - - -class _CsvSerializer(object): - """Placeholder docstring""" - - def __init__(self): - """Placeholder docstring""" - self.content_type = CONTENT_TYPE_CSV - - def __call__(self, data): - """Take data of various data formats and serialize them into CSV. - - Args: - data (object): Data to be serialized. - - Returns: - object: Sequence of bytes to be used for the request body. - """ - # For inputs which represent multiple "rows", the result should be newline-separated CSV - # rows - if _is_mutable_sequence_like(data) and len(data) > 0 and _is_sequence_like(data[0]): - return "\n".join([_CsvSerializer._serialize_row(row) for row in data]) - return _CsvSerializer._serialize_row(data) - - @staticmethod - def _serialize_row(data): - # Don't attempt to re-serialize a string - """ - Args: - data: - """ - if isinstance(data, str): - return data - if isinstance(data, np.ndarray): - data = np.ndarray.flatten(data) - if hasattr(data, "__len__"): - if len(data) == 0: - raise ValueError("Cannot serialize empty array") - return _csv_serialize_python_array(data) - - # files and buffers - if hasattr(data, "read"): - return _csv_serialize_from_buffer(data) - - raise ValueError("Unable to handle input format: ", type(data)) - - -def _csv_serialize_python_array(data): - """ - Args: - data: - """ - return _csv_serialize_object(data) - - -def _csv_serialize_from_buffer(buff): - """ - Args: - buff: - """ - return buff.read() - - -def _csv_serialize_object(data): - """ - Args: - data: - """ - csv_buffer = StringIO() - - csv_writer = csv.writer(csv_buffer, delimiter=",") - csv_writer.writerow(data) - return csv_buffer.getvalue().rstrip("\r\n") - - -csv_serializer = _CsvSerializer() - - -def _is_mutable_sequence_like(obj): - """ - Args: - obj: - """ - return _is_sequence_like(obj) and hasattr(obj, "__setitem__") - - -def _is_sequence_like(obj): - """ - Args: - obj: - """ - # Need to explicitly check on str since str lacks the iterable magic methods in Python 2 - return ( # pylint: disable=consider-using-ternary - hasattr(obj, "__iter__") and hasattr(obj, "__getitem__") - ) or isinstance(obj, str) - - -def _row_to_csv(obj): - """ - Args: - obj: - """ - if isinstance(obj, str): - return obj - return ",".join(obj) - - -class _CsvDeserializer(object): - """Placeholder docstring""" - - def __init__(self, encoding="utf-8"): - """ - Args: - encoding: - """ - self.accept = CONTENT_TYPE_CSV - self.encoding = encoding - - def __call__(self, stream, content_type): - """ - Args: - stream: - content_type: - """ - try: - return list(csv.reader(stream.read().decode(self.encoding).splitlines())) - finally: - stream.close() - - -csv_deserializer = _CsvDeserializer() - - -class BytesDeserializer(object): - """Return the response as an undecoded array of bytes. - - Args: - accept (str): The Accept header to send to the server (optional). - """ - - def __init__(self, accept=None): - """ - Args: - accept: - """ - self.accept = accept - - def __call__(self, stream, content_type): - """ - Args: - stream: - content_type: - """ - try: - return stream.read() - finally: - stream.close() - - -class StringDeserializer(object): - """Return the response as a decoded string. - - Args: - encoding (str): The string encoding to use (default=utf-8). - accept (str): The Accept header to send to the server (optional). - """ - - def __init__(self, encoding="utf-8", accept=None): - """ - Args: - encoding: - accept: - """ - self.encoding = encoding - self.accept = accept - - def __call__(self, stream, content_type): - """ - Args: - stream: - content_type: - """ - try: - return stream.read().decode(self.encoding) - finally: - stream.close() - - -class StreamDeserializer(object): - """Returns the tuple of the response stream and the content-type of the response. - It is the receivers responsibility to close the stream when they're done - reading the stream. - - Args: - accept (str): The Accept header to send to the server (optional). - """ - - def __init__(self, accept=None): - """ - Args: - accept: - """ - self.accept = accept - - def __call__(self, stream, content_type): - """ - Args: - stream: - content_type: - """ - return (stream, content_type) - - -class _JsonSerializer(object): - """Placeholder docstring""" - - def __init__(self): - """Placeholder docstring""" - self.content_type = CONTENT_TYPE_JSON - - def __call__(self, data): - """Take data of various formats and serialize them into the expected - request body. This uses information about supported input formats for - the deployed model. - - Args: - data (object): Data to be serialized. - - Returns: - object: Serialized data used for the request. - """ - if isinstance(data, dict): - # convert each value in dict from a numpy array to a list if necessary, so they can be - # json serialized - return json.dumps({k: _ndarray_to_list(v) for k, v in six.iteritems(data)}) - - # files and buffers - if hasattr(data, "read"): - return _json_serialize_from_buffer(data) - - return json.dumps(_ndarray_to_list(data)) - - -json_serializer = _JsonSerializer() - - -def _ndarray_to_list(data): - """ - Args: - data: - """ - return data.tolist() if isinstance(data, np.ndarray) else data - - -def _json_serialize_from_buffer(buff): - """ - Args: - buff: - """ - return buff.read() - - -class _JsonDeserializer(object): - """Placeholder docstring""" - - def __init__(self): - """Placeholder docstring""" - self.accept = CONTENT_TYPE_JSON - - def __call__(self, stream, content_type): - """Decode a JSON object into the corresponding Python object. - - Args: - stream (stream): The response stream to be deserialized. - content_type (str): The content type of the response. - - Returns: - object: Body of the response deserialized into a JSON object. - """ - try: - return json.load(codecs.getreader("utf-8")(stream)) - finally: - stream.close() - - -json_deserializer = _JsonDeserializer() - - -class _NumpyDeserializer(object): - """Placeholder docstring""" - - def __init__(self, accept=CONTENT_TYPE_NPY, dtype=None): - """ - Args: - accept: - dtype: - """ - self.accept = accept - self.dtype = dtype - - def __call__(self, stream, content_type=CONTENT_TYPE_NPY): - """Decode from serialized data into a Numpy array. - - Args: - stream (stream): The response stream to be deserialized. - content_type (str): The content type of the response. Can accept - CSV, JSON, or NPY data. - - Returns: - object: Body of the response deserialized into a Numpy array. - """ - try: - if content_type == CONTENT_TYPE_CSV: - return np.genfromtxt( - codecs.getreader("utf-8")(stream), delimiter=",", dtype=self.dtype - ) - if content_type == CONTENT_TYPE_JSON: - return np.array(json.load(codecs.getreader("utf-8")(stream)), dtype=self.dtype) - if content_type == CONTENT_TYPE_NPY: - return np.load(BytesIO(stream.read())) - finally: - stream.close() - raise ValueError( - "content_type must be one of the following: CSV, JSON, NPY. content_type: {}".format( - content_type - ) - ) - - -numpy_deserializer = _NumpyDeserializer() - - -class _NPYSerializer(object): - """Placeholder docstring""" - - def __init__(self): - """Placeholder docstring""" - self.content_type = CONTENT_TYPE_NPY - - def __call__(self, data, dtype=None): - """Serialize data into the request body in NPY format. - - Args: - data (object): Data to be serialized. Can be a numpy array, list, - file, or buffer. - dtype: - - Returns: - object: NPY serialized data used for the request. - """ - if isinstance(data, np.ndarray): - if not data.size > 0: - raise ValueError("empty array can't be serialized") - return _npy_serialize(data) - - if isinstance(data, list): - if not len(data) > 0: - raise ValueError("empty array can't be serialized") - return _npy_serialize(np.array(data, dtype)) - - # files and buffers. Assumed to hold npy-formatted data. - if hasattr(data, "read"): - return data.read() - - return _npy_serialize(np.array(data)) - - -def _npy_serialize(data): - """ - Args: - data: - """ - buffer = BytesIO() - np.save(buffer, data) - return buffer.getvalue() + return [d["ModelName"] for d in production_variants] + @property + def content_type(self): + """The MIME type of the data sent to the inference endpoint.""" + return self.serializer.CONTENT_TYPE -npy_serializer = _NPYSerializer() + @property + def accept(self): + """The content type(s) that are expected from the inference endpoint.""" + return self.deserializer.ACCEPT diff --git a/src/sagemaker/processing.py b/src/sagemaker/processing.py index 0b267dd338..a0bfca0e90 100644 --- a/src/sagemaker/processing.py +++ b/src/sagemaker/processing.py @@ -222,7 +222,7 @@ def _normalize_inputs(self, inputs=None): s3_uri = S3Uploader.upload( local_path=file_input.source, desired_s3_uri=desired_s3_uri, - session=self.sagemaker_session, + sagemaker_session=self.sagemaker_session, ) file_input.source = s3_uri normalized_inputs.append(file_input) @@ -480,7 +480,7 @@ def _upload_code(self, code): self._CODE_CONTAINER_INPUT_NAME, ) return S3Uploader.upload( - local_path=code, desired_s3_uri=desired_s3_uri, session=self.sagemaker_session + local_path=code, desired_s3_uri=desired_s3_uri, sagemaker_session=self.sagemaker_session ) def _convert_code_and_add_to_inputs(self, inputs, s3_uri): diff --git a/src/sagemaker/pytorch/defaults.py b/src/sagemaker/pytorch/defaults.py index fd90d93844..1dd7a8c3af 100644 --- a/src/sagemaker/pytorch/defaults.py +++ b/src/sagemaker/pytorch/defaults.py @@ -13,14 +13,4 @@ """Placeholder docstring""" from __future__ import absolute_import -PYTORCH_VERSION = "0.4" -"""Default PyTorch version for when the framework version is not specified. -The default version is no longer updated so as to not break existing workflows. -""" - -LATEST_VERSION = "1.5.0" -"""The latest version of PyTorch included in the SageMaker pre-built Docker images.""" - -PYTHON_VERSION = "py3" - LATEST_PY2_VERSION = "1.3.1" diff --git a/src/sagemaker/pytorch/estimator.py b/src/sagemaker/pytorch/estimator.py index e51d6357b7..97646cea0a 100644 --- a/src/sagemaker/pytorch/estimator.py +++ b/src/sagemaker/pytorch/estimator.py @@ -15,13 +15,14 @@ import logging +from packaging.version import Version + from sagemaker.estimator import Framework from sagemaker.fw_utils import ( framework_name_from_image, framework_version_from_tag, - empty_framework_version_warning, python_deprecation_warning, - is_version_equal_or_higher, + validate_version_or_image_args, ) from sagemaker.pytorch import defaults from sagemaker.pytorch.model import PyTorchModel @@ -33,18 +34,16 @@ class PyTorch(Framework): """Handle end-to-end training and deployment of custom PyTorch code.""" - __framework_name__ = "pytorch" - - LATEST_VERSION = defaults.LATEST_VERSION + _framework_name = "pytorch" def __init__( self, entry_point, + framework_version=None, + py_version=None, source_dir=None, hyperparameters=None, - py_version=defaults.PYTHON_VERSION, - framework_version=None, - image_name=None, + image_uri=None, **kwargs ): """This ``Estimator`` executes an PyTorch script in a managed PyTorch @@ -69,6 +68,13 @@ def __init__( file which should be executed as the entry point to training. If ``source_dir`` is specified, then ``entry_point`` must point to a file located at the root of ``source_dir``. + framework_version (str): PyTorch version you want to use for + executing your model training code. Defaults to ``None``. Required unless + ``image_uri`` is provided. List of supported versions: + https://github.com/aws/sagemaker-python-sdk#pytorch-sagemaker-estimators. + py_version (str): Python version you want to use for executing your + model training code. One of 'py2' or 'py3'. Defaults to ``None``. Required + unless ``image_uri`` is provided. source_dir (str): Path (absolute, relative or an S3 URI) to a directory with any other training source code dependencies aside from the entry point file (default: None). If ``source_dir`` is an S3 URI, it must @@ -80,12 +86,7 @@ def __init__( SageMaker. For convenience, this accepts other types for keys and values, but ``str()`` will be called to convert them before training. - py_version (str): Python version you want to use for executing your - model training code (default: 'py3'). One of 'py2' or 'py3'. - framework_version (str): PyTorch version you want to use for - executing your model training code. If not specified, this will default - to 0.4. - image_name (str): If specified, the estimator will use this image + image_uri (str): If specified, the estimator will use this image for training and hosting, instead of selecting the appropriate SageMaker official image based on framework_version and py_version. It can be an ECR url or dockerhub image and tag. @@ -94,6 +95,9 @@ def __init__( * ``123412341234.dkr.ecr.us-west-2.amazonaws.com/my-custom-image:1.0`` * ``custom-image:latest`` + If ``framework_version`` or ``py_version`` are ``None``, then + ``image_uri`` is required. If also ``None``, then a ``ValueError`` + will be raised. **kwargs: Additional kwargs passed to the :class:`~sagemaker.estimator.Framework` constructor. @@ -103,28 +107,23 @@ def __init__( :class:`~sagemaker.estimator.Framework` and :class:`~sagemaker.estimator.EstimatorBase`. """ - if framework_version is None: + validate_version_or_image_args(framework_version, py_version, image_uri) + if py_version == "py2": logger.warning( - empty_framework_version_warning(defaults.PYTORCH_VERSION, self.LATEST_VERSION) + python_deprecation_warning(self._framework_name, defaults.LATEST_PY2_VERSION) ) - self.framework_version = framework_version or defaults.PYTORCH_VERSION + self.framework_version = framework_version + self.py_version = py_version if "enable_sagemaker_metrics" not in kwargs: # enable sagemaker metrics for PT v1.3 or greater: - if is_version_equal_or_higher([1, 3], self.framework_version): + if self.framework_version and Version(self.framework_version) >= Version("1.3"): kwargs["enable_sagemaker_metrics"] = True super(PyTorch, self).__init__( - entry_point, source_dir, hyperparameters, image_name=image_name, **kwargs + entry_point, source_dir, hyperparameters, image_uri=image_uri, **kwargs ) - if py_version == "py2": - logger.warning( - python_deprecation_warning(self.__framework_name__, defaults.LATEST_PY2_VERSION) - ) - - self.py_version = py_version - def create_model( self, model_server_workers=None, @@ -167,22 +166,20 @@ def create_model( sagemaker.pytorch.model.PyTorchModel: A SageMaker ``PyTorchModel`` object. See :func:`~sagemaker.pytorch.model.PyTorchModel` for full details. """ - if "image" not in kwargs: - kwargs["image"] = self.image_name + if "image_uri" not in kwargs: + kwargs["image_uri"] = self.image_uri - if "name" not in kwargs: - kwargs["name"] = self._current_job_name + kwargs["name"] = self._get_or_create_name(kwargs.get("name")) return PyTorchModel( self.model_data, role or self.role, - entry_point or self.entry_point, + entry_point or self._model_entry_point(), + framework_version=self.framework_version, + py_version=self.py_version, source_dir=(source_dir or self._model_source_dir()), - enable_cloudwatch_metrics=self.enable_cloudwatch_metrics, container_log_level=self.container_log_level, code_location=self.code_location, - py_version=self.py_version, - framework_version=self.framework_version, model_server_workers=model_server_workers, sagemaker_session=self.sagemaker_session, vpc_config=self.get_vpc_config(vpc_config_override), @@ -207,24 +204,26 @@ class constructor init_params = super(PyTorch, cls)._prepare_init_params_from_job_description( job_details, model_channel_name ) - image_name = init_params.pop("image") - framework, py_version, tag, _ = framework_name_from_image(image_name) + image_uri = init_params.pop("image_uri") + framework, py_version, tag, _ = framework_name_from_image(image_uri) + + if tag is None: + framework_version = None + else: + framework_version = framework_version_from_tag(tag) + init_params["framework_version"] = framework_version + init_params["py_version"] = py_version if not framework: # If we were unable to parse the framework name from the image it is not one of our # officially supported images, in this case just add the image to the init params. - init_params["image_name"] = image_name + init_params["image_uri"] = image_uri return init_params - init_params["py_version"] = py_version - init_params["framework_version"] = framework_version_from_tag(tag) - - training_job_name = init_params["base_job_name"] - - if framework != cls.__framework_name__: + if framework != cls._framework_name: raise ValueError( "Training job: {} didn't use image for requested framework".format( - training_job_name + job_details["TrainingJobName"] ) ) diff --git a/src/sagemaker/pytorch/model.py b/src/sagemaker/pytorch/model.py index 38babdb7e2..ca49d5fca9 100644 --- a/src/sagemaker/pytorch/model.py +++ b/src/sagemaker/pytorch/model.py @@ -17,21 +17,23 @@ import packaging.version import sagemaker +from sagemaker import image_uris +from sagemaker.deserializers import NumpyDeserializer from sagemaker.fw_utils import ( - create_image_uri, model_code_key_prefix, python_deprecation_warning, - empty_framework_version_warning, + validate_version_or_image_args, ) from sagemaker.model import FrameworkModel, MODEL_SERVER_WORKERS_PARAM_NAME from sagemaker.pytorch import defaults -from sagemaker.predictor import RealTimePredictor, npy_serializer, numpy_deserializer +from sagemaker.predictor import Predictor +from sagemaker.serializers import NumpySerializer logger = logging.getLogger("sagemaker") -class PyTorchPredictor(RealTimePredictor): - """A RealTimePredictor for inference against PyTorch Endpoints. +class PyTorchPredictor(Predictor): + """A Predictor for inference against PyTorch Endpoints. This is able to serialize Python lists, dictionaries, and numpy arrays to multidimensional tensors for PyTorch inference. @@ -49,7 +51,7 @@ def __init__(self, endpoint_name, sagemaker_session=None): using the default AWS configuration chain. """ super(PyTorchPredictor, self).__init__( - endpoint_name, sagemaker_session, npy_serializer, numpy_deserializer + endpoint_name, sagemaker_session, NumpySerializer(), NumpyDeserializer() ) @@ -58,7 +60,7 @@ class PyTorchModel(FrameworkModel): ``Endpoint``. """ - __framework_name__ = "pytorch" + _framework_name = "pytorch" _LOWEST_MMS_VERSION = "1.2" def __init__( @@ -66,9 +68,9 @@ def __init__( model_data, role, entry_point, - image=None, - py_version=defaults.PYTHON_VERSION, framework_version=None, + py_version=None, + image_uri=None, predictor_cls=PyTorchPredictor, model_server_workers=None, **kwargs @@ -87,12 +89,16 @@ def __init__( file which should be executed as the entry point to model hosting. If ``source_dir`` is specified, then ``entry_point`` must point to a file located at the root of ``source_dir``. - image (str): A Docker image URI (default: None). If not specified, a - default image for PyTorch will be used. - py_version (str): Python version you want to use for executing your - model training code (default: 'py3'). framework_version (str): PyTorch version you want to use for - executing your model training code. + executing your model training code. Defaults to None. Required + unless ``image_uri`` is provided. + py_version (str): Python version you want to use for executing your + model training code. Defaults to ``None``. Required unless + ``image_uri`` is provided. + image_uri (str): A Docker image URI (default: None). If not specified, a + default image for PyTorch will be used. If ``framework_version`` + or ``py_version`` are ``None``, then ``image_uri`` is required. If + also ``None``, then a ``ValueError`` will be raised. predictor_cls (callable[str, sagemaker.session.Session]): A function to call to create a predictor with an endpoint name and SageMaker ``Session``. If specified, ``deploy()`` returns the @@ -109,22 +115,18 @@ def __init__( :class:`~sagemaker.model.FrameworkModel` and :class:`~sagemaker.model.Model`. """ - super(PyTorchModel, self).__init__( - model_data, image, role, entry_point, predictor_cls=predictor_cls, **kwargs - ) - + validate_version_or_image_args(framework_version, py_version, image_uri) if py_version == "py2": logger.warning( - python_deprecation_warning(self.__framework_name__, defaults.LATEST_PY2_VERSION) + python_deprecation_warning(self._framework_name, defaults.LATEST_PY2_VERSION) ) + self.framework_version = framework_version + self.py_version = py_version - if framework_version is None: - logger.warning( - empty_framework_version_warning(defaults.PYTORCH_VERSION, defaults.LATEST_VERSION) - ) + super(PyTorchModel, self).__init__( + model_data, image_uri, role, entry_point, predictor_cls=predictor_cls, **kwargs + ) - self.py_version = py_version - self.framework_version = framework_version or defaults.PYTORCH_VERSION self.model_server_workers = model_server_workers def prepare_container_def(self, instance_type=None, accelerator_type=None): @@ -142,7 +144,7 @@ def prepare_container_def(self, instance_type=None, accelerator_type=None): dict[str, str]: A container definition object usable with the CreateModel API. """ - deploy_image = self.image + deploy_image = self.image_uri if not deploy_image: if instance_type is None: raise ValueError( @@ -180,17 +182,14 @@ def serving_image_uri(self, region_name, instance_type, accelerator_type=None): str: The appropriate image URI based on the given parameters. """ - framework_name = self.__framework_name__ - if self._is_mms_version(): - framework_name = "{}-serving".format(framework_name) - - return create_image_uri( + return image_uris.retrieve( + self._framework_name, region_name, - framework_name, - instance_type, - self.framework_version, - self.py_version, + version=self.framework_version, + py_version=self.py_version, + instance_type=instance_type, accelerator_type=accelerator_type, + image_scope="inference", ) def _is_mms_version(self): diff --git a/src/sagemaker/rl/estimator.py b/src/sagemaker/rl/estimator.py index 2c945d14a0..0bc21c6ef7 100644 --- a/src/sagemaker/rl/estimator.py +++ b/src/sagemaker/rl/estimator.py @@ -17,10 +17,11 @@ import logging import re +from sagemaker import image_uris, fw_utils from sagemaker.estimator import Framework -import sagemaker.fw_utils as fw_utils from sagemaker.model import FrameworkModel, SAGEMAKER_OUTPUT_LOCATION from sagemaker.mxnet.model import MXNetModel +from sagemaker.tensorflow.model import TensorFlowModel from sagemaker.vpc_utils import VPC_CONFIG_DEFAULT logger = logging.getLogger("sagemaker") @@ -35,12 +36,15 @@ "0.11.0": {"tensorflow": "1.11", "mxnet": "1.3"}, "0.11.1": {"tensorflow": "1.12"}, "0.11": {"tensorflow": "1.12", "mxnet": "1.3"}, + "1.0.0": {"tensorflow": "1.12"}, }, "ray": { "0.5.3": {"tensorflow": "1.11"}, "0.5": {"tensorflow": "1.11"}, "0.6.5": {"tensorflow": "1.12"}, "0.6": {"tensorflow": "1.12"}, + "0.8.2": {"tensorflow": "2.1"}, + "0.8.5": {"tensorflow": "2.1", "pytorch": "1.5"}, }, } @@ -57,6 +61,7 @@ class RLFramework(enum.Enum): TENSORFLOW = "tensorflow" MXNET = "mxnet" + PYTORCH = "pytorch" class RLEstimator(Framework): @@ -64,7 +69,7 @@ class RLEstimator(Framework): COACH_LATEST_VERSION_TF = "0.11.1" COACH_LATEST_VERSION_MXNET = "0.11.0" - RAY_LATEST_VERSION = "0.6.5" + RAY_LATEST_VERSION = "0.8.5" def __init__( self, @@ -74,7 +79,7 @@ def __init__( framework=None, source_dir=None, hyperparameters=None, - image_name=None, + image_uri=None, metric_definitions=None, **kwargs ): @@ -90,7 +95,7 @@ def __init__( :meth:`~sagemaker.amazon.estimator.Framework.deploy` creates a hosted SageMaker endpoint and based on the specified framework returns an :class:`~sagemaker.amazon.mxnet.model.MXNetPredictor` or - :class:`~sagemaker.amazon.tensorflow.serving.Predictor` instance that + :class:`~sagemaker.amazon.tensorflow.model.TensorFlowPredictor` instance that can be used to perform inference against the hosted model. Technical documentation on preparing RLEstimator scripts for @@ -119,7 +124,7 @@ def __init__( accessible as a dict[str, str] to the training code on SageMaker. For convenience, this accepts other types for keys and values. - image_name (str): An ECR url. If specified, the estimator will use + image_uri (str): An ECR url. If specified, the estimator will use this image for training and hosting, instead of selecting the appropriate SageMaker official image based on framework_version and py_version. Example: @@ -139,9 +144,9 @@ def __init__( :class:`~sagemaker.estimator.Framework` and :class:`~sagemaker.estimator.EstimatorBase`. """ - self._validate_images_args(toolkit, toolkit_version, framework, image_name) + self._validate_images_args(toolkit, toolkit_version, framework, image_uri) - if not image_name: + if not image_uri: self._validate_toolkit_support(toolkit.value, toolkit_version, framework.value) self.toolkit = toolkit.value self.toolkit_version = toolkit_version @@ -158,7 +163,7 @@ def __init__( entry_point, source_dir, hyperparameters, - image_name=image_name, + image_uri=image_uri, metric_definitions=metric_definitions, **kwargs ) @@ -207,30 +212,31 @@ def create_model( sagemaker.model.FrameworkModel: Depending on input parameters returns one of the following: - * :class:`~sagemaker.model.FrameworkModel` - if ``image_name`` was specified + * :class:`~sagemaker.model.FrameworkModel` - if ``image_uri`` is specified on the estimator; - * :class:`~sagemaker.mxnet.MXNetModel` - if ``image_name`` wasn't specified and - MXNet was used as the RL backend; - * :class:`~sagemaker.tensorflow.serving.Model` - if ``image_name`` wasn't specified - and TensorFlow was used as the RL backend. + * :class:`~sagemaker.mxnet.MXNetModel` - if ``image_uri`` isn't specified and + MXNet is used as the RL backend; + * :class:`~sagemaker.tensorflow.model.TensorFlowModel` - if ``image_uri`` isn't + specified and TensorFlow is used as the RL backend. Raises: - ValueError: If image_name was not specified and framework enum is not valid. + ValueError: If image_uri is not specified and framework enum is not valid. """ base_args = dict( model_data=self.model_data, role=role or self.role, - image=kwargs.get("image", self.image_name), - name=kwargs.get("name", self._current_job_name), + image_uri=kwargs.get("image_uri", self.image_uri), container_log_level=self.container_log_level, sagemaker_session=self.sagemaker_session, vpc_config=self.get_vpc_config(vpc_config_override), ) + base_args["name"] = self._get_or_create_name(kwargs.get("name")) + if not entry_point and (source_dir or dependencies): raise AttributeError("Please provide an `entry_point`.") - entry_point = entry_point or self.entry_point + entry_point = entry_point or self._model_entry_point() source_dir = source_dir or self._model_source_dir() dependencies = dependencies or self.dependencies @@ -239,11 +245,10 @@ def create_model( source_dir=source_dir, code_location=self.code_location, dependencies=dependencies, - enable_cloudwatch_metrics=self.enable_cloudwatch_metrics, ) extended_args.update(base_args) - if self.image_name: + if self.image_uri: return FrameworkModel(**extended_args) if self.toolkit == RLToolkit.RAY.value: @@ -254,9 +259,7 @@ def create_model( ) if self.framework == RLFramework.TENSORFLOW.value: - from sagemaker.tensorflow.serving import Model as tfsModel - - return tfsModel(framework_version=self.framework_version, **base_args) + return TensorFlowModel(framework_version=self.framework_version, **base_args) if self.framework == RLFramework.MXNET.value: return MXNetModel( framework_version=self.framework_version, py_version=PYTHON_VERSION, **extended_args @@ -265,7 +268,7 @@ def create_model( "An unknown RLFramework enum was passed in. framework: {}".format(self.framework) ) - def train_image(self): + def training_image_uri(self): """Return the Docker image to use for training. The :meth:`~sagemaker.estimator.EstimatorBase.fit` method, which does @@ -275,14 +278,13 @@ def train_image(self): Returns: str: The URI of the Docker image. """ - if self.image_name: - return self.image_name - return fw_utils.create_image_uri( - self.sagemaker_session.boto_region_name, + if self.image_uri: + return self.image_uri + return image_uris.retrieve( self._image_framework(), - self.train_instance_type, - self._image_version(), - py_version=PYTHON_VERSION, + self.sagemaker_session.boto_region_name, + version=self.toolkit_version, + instance_type=self.instance_type, ) @classmethod @@ -303,22 +305,21 @@ class constructor job_details, model_channel_name ) - image_name = init_params.pop("image") - framework, _, tag, _ = fw_utils.framework_name_from_image(image_name) + image_uri = init_params.pop("image_uri") + framework, _, tag, _ = fw_utils.framework_name_from_image(image_uri) if not framework: # If we were unable to parse the framework name from the image it is not one of our # officially supported images, in this case just add the image to the init params. - init_params["image_name"] = image_name + init_params["image_uri"] = image_uri return init_params toolkit, toolkit_version = cls._toolkit_and_version_from_tag(tag) if not cls._is_combination_supported(toolkit, toolkit_version, framework): - training_job_name = init_params["base_job_name"] raise ValueError( "Training job: {} didn't use image for requested framework".format( - training_job_name + job_details["TrainingJobName"] ) ) @@ -382,18 +383,18 @@ def _validate_toolkit_format(cls, toolkit): ) @classmethod - def _validate_images_args(cls, toolkit, toolkit_version, framework, image_name): + def _validate_images_args(cls, toolkit, toolkit_version, framework, image_uri): """ Args: toolkit: toolkit_version: framework: - image_name: + image_uri: """ cls._validate_toolkit_format(toolkit) cls._validate_framework_format(framework) - if not image_name: + if not image_uri: not_found_args = [] if not toolkit: not_found_args.append("toolkit") @@ -403,7 +404,7 @@ def _validate_images_args(cls, toolkit, toolkit_version, framework, image_name): not_found_args.append("framework") if not_found_args: raise AttributeError( - "Please provide `{}` or `image_name` parameter.".format( + "Please provide `{}` or `image_uri` parameter.".format( "`, `".join(not_found_args) ) ) @@ -417,7 +418,7 @@ def _validate_images_args(cls, toolkit, toolkit_version, framework, image_name): found_args.append("framework") if found_args: logger.warning( - "Parameter `image_name` is specified, " + "Parameter `image_uri` is specified, " "`%s` are going to be ignored when choosing the image.", "`, `".join(found_args), ) @@ -452,13 +453,9 @@ def _validate_toolkit_support(cls, toolkit, toolkit_version, framework): ) ) - def _image_version(self): - """Placeholder docstring""" - return "{}{}".format(self.toolkit, self.toolkit_version) - def _image_framework(self): - """Placeholder docstring""" - return "rl-{}".format(self.framework) + """Toolkit name and framework name for retrieving Docker image URI config.""" + return "-".join((self.toolkit, self.framework)) @classmethod def default_metric_definitions(cls, toolkit): diff --git a/src/sagemaker/s3.py b/src/sagemaker/s3.py index 3c92aee946..2116fe49b3 100644 --- a/src/sagemaker/s3.py +++ b/src/sagemaker/s3.py @@ -20,19 +20,6 @@ logger = logging.getLogger("sagemaker") -SESSION_V2_RENAME_MESSAGE = ( - "Parameter 'session' will be renamed to 'sagemaker_session' in SageMaker Python SDK v2." -) - - -def _session_v2_rename_warning(session): - """ - Args: - session (sagemaker.session.Session): - """ - if session is not None: - logger.warning(SESSION_V2_RENAME_MESSAGE) - def parse_s3_url(url): """Returns an (s3 bucket, key name/prefix) tuple from a url with an s3 @@ -53,7 +40,7 @@ class S3Uploader(object): """Contains static methods for uploading directories or files to S3.""" @staticmethod - def upload(local_path, desired_s3_uri, kms_key=None, session=None): + def upload(local_path, desired_s3_uri, kms_key=None, sagemaker_session=None): """Static method that uploads a given file or directory to S3. Args: @@ -61,7 +48,7 @@ def upload(local_path, desired_s3_uri, kms_key=None, session=None): desired_s3_uri (str): The desired S3 location to upload to. It is the prefix to which the local filename will be added. kms_key (str): The KMS key to use to encrypt the files. - session (sagemaker.session.Session): Session object which + sagemaker_session (sagemaker.session.Session): Session object which manages interactions with Amazon SageMaker APIs and any other AWS services needed. If not specified, the estimator creates one using the default AWS configuration chain. @@ -70,10 +57,7 @@ def upload(local_path, desired_s3_uri, kms_key=None, session=None): The S3 uri of the uploaded file(s). """ - if session is not None: - _session_v2_rename_warning(session) - - sagemaker_session = session or Session() + sagemaker_session = sagemaker_session or Session() bucket, key_prefix = parse_s3_url(url=desired_s3_uri) if kms_key is not None: extra_args = {"SSEKMSKeyId": kms_key} @@ -85,24 +69,23 @@ def upload(local_path, desired_s3_uri, kms_key=None, session=None): ) @staticmethod - def upload_string_as_file_body(body, desired_s3_uri=None, kms_key=None, session=None): + def upload_string_as_file_body(body, desired_s3_uri=None, kms_key=None, sagemaker_session=None): """Static method that uploads a given file or directory to S3. Args: body (str): String representing the body of the file. desired_s3_uri (str): The desired S3 uri to upload to. kms_key (str): The KMS key to use to encrypt the files. - session (sagemaker.session.Session): AWS session to use. Automatically - generates one if not provided. + sagemaker_session (sagemaker.session.Session): Session object which + manages interactions with Amazon SageMaker APIs and any other + AWS services needed. If not specified, the estimator creates one + using the default AWS configuration chain. Returns: str: The S3 uri of the uploaded file(s). """ - if session is not None: - _session_v2_rename_warning(session) - - sagemaker_session = session or Session() + sagemaker_session = sagemaker_session or Session() bucket, key = parse_s3_url(desired_s3_uri) sagemaker_session.upload_string_as_file_body( @@ -116,23 +99,19 @@ class S3Downloader(object): """Contains static methods for downloading directories or files from S3.""" @staticmethod - def download(s3_uri, local_path, kms_key=None, session=None): + def download(s3_uri, local_path, kms_key=None, sagemaker_session=None): """Static method that downloads a given S3 uri to the local machine. Args: s3_uri (str): An S3 uri to download from. local_path (str): A local path to download the file(s) to. kms_key (str): The KMS key to use to decrypt the files. - session (sagemaker.session.Session): Session object which + sagemaker_session (sagemaker.session.Session): Session object which manages interactions with Amazon SageMaker APIs and any other AWS services needed. If not specified, the estimator creates one using the default AWS configuration chain. - """ - if session is not None: - _session_v2_rename_warning(session) - - sagemaker_session = session or Session() + sagemaker_session = sagemaker_session or Session() bucket, key_prefix = parse_s3_url(url=s3_uri) if kms_key is not None: extra_args = {"SSECustomerKey": kms_key} @@ -144,43 +123,39 @@ def download(s3_uri, local_path, kms_key=None, session=None): ) @staticmethod - def read_file(s3_uri, session=None): + def read_file(s3_uri, sagemaker_session=None): """Static method that returns the contents of an s3 uri file body as a string. Args: s3_uri (str): An S3 uri that refers to a single file. - session (sagemaker.session.Session): AWS session to use. Automatically - generates one if not provided. + sagemaker_session (sagemaker.session.Session): Session object which + manages interactions with Amazon SageMaker APIs and any other + AWS services needed. If not specified, the estimator creates one + using the default AWS configuration chain. Returns: str: The body of the file. - """ - if session is not None: - _session_v2_rename_warning(session) - - sagemaker_session = session or Session() + sagemaker_session = sagemaker_session or Session() bucket, key_prefix = parse_s3_url(url=s3_uri) return sagemaker_session.read_s3_file(bucket=bucket, key_prefix=key_prefix) @staticmethod - def list(s3_uri, session=None): + def list(s3_uri, sagemaker_session=None): """Static method that lists the contents of an S3 uri. Args: s3_uri (str): The S3 base uri to list objects in. - session (sagemaker.session.Session): AWS session to use. Automatically - generates one if not provided. + sagemaker_session (sagemaker.session.Session): Session object which + manages interactions with Amazon SageMaker APIs and any other + AWS services needed. If not specified, the estimator creates one + using the default AWS configuration chain. Returns: [str]: The list of S3 URIs in the given S3 base uri. - """ - if session is not None: - _session_v2_rename_warning(session) - - sagemaker_session = session or Session() + sagemaker_session = sagemaker_session or Session() bucket, key_prefix = parse_s3_url(url=s3_uri) file_keys = sagemaker_session.list_s3_files(bucket=bucket, key_prefix=key_prefix) diff --git a/src/sagemaker/serializers.py b/src/sagemaker/serializers.py new file mode 100644 index 0000000000..9fbf33b6e7 --- /dev/null +++ b/src/sagemaker/serializers.py @@ -0,0 +1,303 @@ +# Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Implements methods for serializing data for an inference endpoint.""" +from __future__ import absolute_import + +import abc +from collections.abc import Iterable +import csv +import io +import json + +import numpy as np + +from sagemaker.utils import DeferredError + +try: + import scipy.sparse +except ImportError as e: + scipy = DeferredError(e) + + +class BaseSerializer(abc.ABC): + """Abstract base class for creation of new serializers. + + Provides a skeleton for customization requiring the overriding of the method + serialize and the class attribute CONTENT_TYPE. + """ + + @abc.abstractmethod + def serialize(self, data): + """Serialize data into the media type specified by CONTENT_TYPE. + + Args: + data (object): Data to be serialized. + + Returns: + object: Serialized data used for a request. + """ + + @property + @abc.abstractmethod + def CONTENT_TYPE(self): + """The MIME type of the data sent to the inference endpoint.""" + + +class CSVSerializer(BaseSerializer): + """Serialize data of various formats to a CSV-formatted string.""" + + CONTENT_TYPE = "text/csv" + + def serialize(self, data): + """Serialize data of various formats to a CSV-formatted string. + + Args: + data (object): Data to be serialized. Can be a NumPy array, list, + file, or buffer. + + Returns: + str: The data serialized as a CSV-formatted string. + """ + if hasattr(data, "read"): + return data.read() + + is_mutable_sequence_like = self._is_sequence_like(data) and hasattr(data, "__setitem__") + has_multiple_rows = len(data) > 0 and self._is_sequence_like(data[0]) + + if is_mutable_sequence_like and has_multiple_rows: + return "\n".join([self._serialize_row(row) for row in data]) + + return self._serialize_row(data) + + def _serialize_row(self, data): + """Serialize data as a CSV-formatted row. + + Args: + data (object): Data to be serialized in a row. + + Returns: + str: The data serialized as a CSV-formatted row. + """ + if isinstance(data, str): + return data + + if isinstance(data, np.ndarray): + data = np.ndarray.flatten(data) + + if hasattr(data, "__len__"): + if len(data) == 0: + raise ValueError("Cannot serialize empty array") + csv_buffer = io.StringIO() + csv_writer = csv.writer(csv_buffer, delimiter=",") + csv_writer.writerow(data) + return csv_buffer.getvalue().rstrip("\r\n") + + raise ValueError("Unable to handle input format: %s" % type(data)) + + def _is_sequence_like(self, data): + """Returns true if obj is iterable and subscriptable.""" + return hasattr(data, "__iter__") and hasattr(data, "__getitem__") + + +class NumpySerializer(BaseSerializer): + """Serialize data to a buffer using the .npy format.""" + + CONTENT_TYPE = "application/x-npy" + + def __init__(self, dtype=None): + """Initialize the dtype. + + Args: + dtype (str): The dtype of the data. + """ + self.dtype = dtype + + def serialize(self, data): + """Serialize data to a buffer using the .npy format. + + Args: + data (object): Data to be serialized. Can be a NumPy array, list, + file, or buffer. + + Returns: + io.BytesIO: A buffer containing data serialzied in the .npy format. + """ + if isinstance(data, np.ndarray): + if data.size == 0: + raise ValueError("Cannot serialize empty array.") + return self._serialize_array(data) + + if isinstance(data, list): + if len(data) == 0: + raise ValueError("Cannot serialize empty array.") + return self._serialize_array(np.array(data, self.dtype)) + + # files and buffers. Assumed to hold npy-formatted data. + if hasattr(data, "read"): + return data.read() + + return self._serialize_array(np.array(data)) + + def _serialize_array(self, array): + """Saves a NumPy array in a buffer. + + Args: + array (numpy.ndarray): The array to serialize. + + Returns: + io.BytesIO: A buffer containing the serialized array. + """ + buffer = io.BytesIO() + np.save(buffer, array) + return buffer.getvalue() + + +class JSONSerializer(BaseSerializer): + """Serialize data to a JSON formatted string.""" + + CONTENT_TYPE = "application/json" + + def serialize(self, data): + """Serialize data of various formats to a JSON formatted string. + + Args: + data (object): Data to be serialized. + + Returns: + str: The data serialized as a JSON string. + """ + if isinstance(data, dict): + return json.dumps( + { + key: value.tolist() if isinstance(value, np.ndarray) else value + for key, value in data.items() + } + ) + + if hasattr(data, "read"): + return data.read() + + if isinstance(data, np.ndarray): + return json.dumps(data.tolist()) + + return json.dumps(data) + + +class IdentitySerializer(BaseSerializer): + """Serialize data by returning data without modification.""" + + def __init__(self, content_type="application/octet-stream"): + """Initialize the ``content_type`` attribute. + + Args: + content_type (str): The MIME type of the serialized data (default: + "application/octet-stream"). + """ + self.content_type = content_type + + def serialize(self, data): + """Return data without modification. + + Args: + data (object): Data to be serialized. + + Returns: + object: The unmodified data. + """ + return data + + @property + def CONTENT_TYPE(self): + """The MIME type of the data sent to the inference endpoint.""" + return self.content_type + + +class JSONLinesSerializer(BaseSerializer): + """Serialize data to a JSON Lines formatted string.""" + + CONTENT_TYPE = "application/jsonlines" + + def serialize(self, data): + """Serialize data of various formats to a JSON Lines formatted string. + + Args: + data (object): Data to be serialized. The data can be a string, + iterable of JSON serializable objects, or a file-like object. + + Returns: + str: The data serialized as a string containing newline-separated + JSON values. + """ + if isinstance(data, str): + return data + + if hasattr(data, "read"): + return data.read() + + if isinstance(data, Iterable): + return "\n".join(json.dumps(element) for element in data) + + raise ValueError("Object of type %s is not JSON Lines serializable." % type(data)) + + +class SparseMatrixSerializer(BaseSerializer): + """Serialize a sparse matrix to a buffer using the .npz format.""" + + CONTENT_TYPE = "application/x-npz" + + def serialize(self, data): + """Serialize a sparse matrix to a buffer using the .npz format. + + Sparse matrices can be in the ``csc``, ``csr``, ``bsr``, ``dia`` or + ``coo`` formats. + + Args: + data (scipy.sparse.spmatrix): The sparse matrix to serialize. + + Returns: + io.BytesIO: A buffer containing the serialized sparse matrix. + """ + buffer = io.BytesIO() + scipy.sparse.save_npz(buffer, data) + return buffer.getvalue() + + +class LibSVMSerializer(BaseSerializer): + """Serialize data of various formats to a LibSVM-formatted string. + + The data must already be in LIBSVM file format: +