Skip to content

Commit 2c4fa5d

Browse files
committed
Merge branch 'fix/make-instance-type-optional' of https://github.com/rahven14/sagemaker-python-sdk into fix/make-instance-type-optional
2 parents effd3ee + 6dcfd45 commit 2c4fa5d

20 files changed

+505
-93
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## v2.92.0 (2022-05-26)
4+
5+
### Features
6+
7+
* add 'Domain' property to RegisterModel step
8+
9+
### Bug Fixes and Other Changes
10+
11+
* support estimator output path parameterization
12+
* Add back Prevent passing PipelineVariable object into image_uris.retrieve
13+
* jumpstart amt tracking
14+
* fix missing register method params for framework models
15+
* fix docstring for decorated functions
16+
* Documents: add sagemaker model building pipeline readthedocs
17+
318
## v2.91.1 (2022-05-19)
419

520
### Bug Fixes and Other Changes

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.91.2.dev0
1+
2.92.1.dev0

setup.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ def read_requirements(filename):
4848
# Declare minimal set for installation
4949
required_packages = [
5050
"attrs==20.3.0",
51-
"boto3>=1.20.21",
51+
"boto3>=1.20.21,<2.0",
5252
"google-pasta",
53-
"numpy>=1.9.0",
54-
"protobuf>=3.1",
55-
"protobuf3-to-dict>=0.1.5",
53+
"numpy>=1.9.0,<2.0",
54+
"protobuf>=3.1,<4.0",
55+
"protobuf3-to-dict>=0.1.5,<1.0",
5656
"smdebug_rulesconfig==1.0.1",
57-
"importlib-metadata>=1.4.0",
57+
"importlib-metadata>=1.4.0,<2.0",
5858
"packaging>=20.0",
5959
"pandas",
6060
"pathos",

src/sagemaker/estimator.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -695,26 +695,45 @@ def _stage_user_code_in_s3(self) -> str:
695695
696696
Returns: S3 URI
697697
"""
698-
local_mode = self.output_path.startswith("file://")
699-
700-
if self.code_location is None and local_mode:
701-
code_bucket = self.sagemaker_session.default_bucket()
702-
code_s3_prefix = "{}/{}".format(self._current_job_name, "source")
703-
kms_key = None
704-
elif self.code_location is None:
705-
code_bucket, _ = parse_s3_url(self.output_path)
706-
code_s3_prefix = "{}/{}".format(self._current_job_name, "source")
707-
kms_key = self.output_kms_key
708-
elif local_mode:
709-
code_bucket, key_prefix = parse_s3_url(self.code_location)
710-
code_s3_prefix = "/".join(filter(None, [key_prefix, self._current_job_name, "source"]))
711-
kms_key = None
698+
if is_pipeline_variable(self.output_path):
699+
if self.code_location is None:
700+
code_bucket = self.sagemaker_session.default_bucket()
701+
code_s3_prefix = "{}/{}".format(self._current_job_name, "source")
702+
kms_key = None
703+
else:
704+
code_bucket, key_prefix = parse_s3_url(self.code_location)
705+
code_s3_prefix = "/".join(
706+
filter(None, [key_prefix, self._current_job_name, "source"])
707+
)
708+
709+
output_bucket = self.sagemaker_session.default_bucket()
710+
kms_key = self.output_kms_key if code_bucket == output_bucket else None
712711
else:
713-
code_bucket, key_prefix = parse_s3_url(self.code_location)
714-
code_s3_prefix = "/".join(filter(None, [key_prefix, self._current_job_name, "source"]))
712+
local_mode = self.output_path.startswith("file://")
713+
if local_mode:
714+
if self.code_location is None:
715+
code_bucket = self.sagemaker_session.default_bucket()
716+
code_s3_prefix = "{}/{}".format(self._current_job_name, "source")
717+
kms_key = None
718+
else:
719+
code_bucket, key_prefix = parse_s3_url(self.code_location)
720+
code_s3_prefix = "/".join(
721+
filter(None, [key_prefix, self._current_job_name, "source"])
722+
)
723+
kms_key = None
724+
else:
725+
if self.code_location is None:
726+
code_bucket, _ = parse_s3_url(self.output_path)
727+
code_s3_prefix = "{}/{}".format(self._current_job_name, "source")
728+
kms_key = self.output_kms_key
729+
else:
730+
code_bucket, key_prefix = parse_s3_url(self.code_location)
731+
code_s3_prefix = "/".join(
732+
filter(None, [key_prefix, self._current_job_name, "source"])
733+
)
715734

716-
output_bucket, _ = parse_s3_url(self.output_path)
717-
kms_key = self.output_kms_key if code_bucket == output_bucket else None
735+
output_bucket, _ = parse_s3_url(self.output_path)
736+
kms_key = self.output_kms_key if code_bucket == output_bucket else None
718737

719738
return tar_and_upload_dir(
720739
session=self.sagemaker_session.boto_session,

src/sagemaker/image_uris.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from sagemaker.jumpstart.utils import is_jumpstart_model_input
2424
from sagemaker.spark import defaults
2525
from sagemaker.jumpstart import artifacts
26+
from sagemaker.workflow import is_pipeline_variable
2627

2728
logger = logging.getLogger(__name__)
2829

@@ -104,11 +105,17 @@ def retrieve(
104105
105106
Raises:
106107
NotImplementedError: If the scope is not supported.
107-
ValueError: If the combination of arguments specified is not supported.
108+
ValueError: If the combination of arguments specified is not supported or
109+
any PipelineVariable object is passed in.
108110
VulnerableJumpStartModelError: If any of the dependencies required by the script have
109111
known security vulnerabilities.
110112
DeprecatedJumpStartModelError: If the version of the model is deprecated.
111113
"""
114+
args = dict(locals())
115+
for name, val in args.items():
116+
if is_pipeline_variable(val):
117+
raise ValueError("%s should not be a pipeline variable (%s)" % (name, type(val)))
118+
112119
if is_jumpstart_model_input(model_id, model_version):
113120
return artifacts._retrieve_image_uri(
114121
model_id,

src/sagemaker/tuner.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from sagemaker.estimator import Framework
3333
from sagemaker.inputs import TrainingInput
3434
from sagemaker.job import _Job
35+
from sagemaker.jumpstart.utils import add_jumpstart_tags, get_jumpstart_base_name_if_jumpstart_model
3536
from sagemaker.parameter import (
3637
CategoricalParameter,
3738
ContinuousParameter,
@@ -319,6 +320,42 @@ def _prepare_for_tuning(self, job_name=None, include_cls_metadata=False):
319320
"""Prepare the tuner instance for tuning (fit)."""
320321
self._prepare_job_name_for_tuning(job_name=job_name)
321322
self._prepare_static_hyperparameters_for_tuning(include_cls_metadata=include_cls_metadata)
323+
self._prepare_tags_for_tuning()
324+
325+
def _get_model_uri(
326+
self,
327+
estimator,
328+
):
329+
"""Return the model artifact URI used by the Estimator instance.
330+
331+
This attribute can live in multiple places, and accessing the attribute can
332+
raise a TypeError, which needs to be handled.
333+
"""
334+
try:
335+
return getattr(estimator, "model_data", None)
336+
except TypeError:
337+
return getattr(estimator, "model_uri", None)
338+
339+
def _prepare_tags_for_tuning(self):
340+
"""Add tags to tuning job (from Estimator and JumpStart tags)."""
341+
342+
# Add tags from Estimator class
343+
estimator = self.estimator or self.estimator_dict[sorted(self.estimator_dict.keys())[0]]
344+
345+
estimator_tags = getattr(estimator, "tags", []) or []
346+
347+
if self.tags is None and len(estimator_tags) > 0:
348+
self.tags = []
349+
350+
for tag in estimator_tags:
351+
if tag not in self.tags:
352+
self.tags.append(tag)
353+
354+
self.tags = add_jumpstart_tags(
355+
tags=self.tags,
356+
training_script_uri=getattr(estimator, "source_dir", None),
357+
training_model_uri=self._get_model_uri(estimator),
358+
)
322359

323360
def _prepare_job_name_for_tuning(self, job_name=None):
324361
"""Set current job name before starting tuning."""
@@ -331,6 +368,12 @@ def _prepare_job_name_for_tuning(self, job_name=None):
331368
self.estimator or self.estimator_dict[sorted(self.estimator_dict.keys())[0]]
332369
)
333370
base_name = base_name_from_image(estimator.training_image_uri())
371+
372+
jumpstart_base_name = get_jumpstart_base_name_if_jumpstart_model(
373+
getattr(estimator, "source_dir", None),
374+
self._get_model_uri(estimator),
375+
)
376+
base_name = jumpstart_base_name or base_name
334377
self._current_job_name = name_from_base(
335378
base_name, max_length=self.TUNING_JOB_NAME_MAX_LENGTH, short=True
336379
)

src/sagemaker/workflow/entities.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ def __add__(self, other: Union[Expression, PrimitiveType]):
7878

7979
def __str__(self):
8080
"""Override built-in String function for PipelineVariable"""
81-
raise TypeError("Pipeline variables do not support __str__ operation.")
81+
raise TypeError(
82+
"Pipeline variables do not support __str__ operation. "
83+
"Please use `.to_string()` to convert it to string type in execution time"
84+
"or use `.expr` to translate it to Json for display purpose in Python SDK."
85+
)
8286

8387
def __int__(self):
8488
"""Override built-in Integer function for PipelineVariable"""

tests/integ/sagemaker/workflow/test_model_create_and_registration.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,12 @@ def test_conditional_pytorch_training_model_registration(
9090
inputs = TrainingInput(s3_data=input_path)
9191

9292
instance_count = ParameterInteger(name="InstanceCount", default_value=1)
93-
instance_type = ParameterString(name="InstanceType", default_value="ml.m5.xlarge")
93+
instance_type = "ml.m5.xlarge"
9494
good_enough_input = ParameterInteger(name="GoodEnoughInput", default_value=1)
9595
in_condition_input = ParameterString(name="Foo", default_value="Foo")
9696

97+
# If image_uri is not provided, the instance_type should not be a pipeline variable
98+
# since instance_type is used to retrieve image_uri in compile time (PySDK)
9799
pytorch_estimator = PyTorch(
98100
entry_point=entry_point,
99101
role=role,
@@ -153,7 +155,6 @@ def test_conditional_pytorch_training_model_registration(
153155
in_condition_input,
154156
good_enough_input,
155157
instance_count,
156-
instance_type,
157158
],
158159
steps=[step_train, step_cond],
159160
sagemaker_session=sagemaker_session,
@@ -259,8 +260,10 @@ def test_sklearn_xgboost_sip_model_registration(
259260
prefix = "sip"
260261
bucket_name = sagemaker_session.default_bucket()
261262
instance_count = ParameterInteger(name="InstanceCount", default_value=1)
262-
instance_type = ParameterString(name="InstanceType", default_value="ml.m5.xlarge")
263+
instance_type = "ml.m5.xlarge"
263264

265+
# The instance_type should not be a pipeline variable
266+
# since it is used to retrieve image_uri in compile time (PySDK)
264267
sklearn_processor = SKLearnProcessor(
265268
role=role,
266269
instance_type=instance_type,
@@ -331,6 +334,8 @@ def test_sklearn_xgboost_sip_model_registration(
331334
source_dir = base_dir
332335
code_location = "s3://{0}/{1}/code".format(bucket_name, prefix)
333336

337+
# If image_uri is not provided, the instance_type should not be a pipeline variable
338+
# since instance_type is used to retrieve image_uri in compile time (PySDK)
334339
estimator = XGBoost(
335340
entry_point=entry_point,
336341
source_dir=source_dir,
@@ -416,7 +421,6 @@ def test_sklearn_xgboost_sip_model_registration(
416421
train_data_path_param,
417422
val_data_path_param,
418423
model_path_param,
419-
instance_type,
420424
instance_count,
421425
output_path_param,
422426
],
@@ -462,7 +466,7 @@ def test_model_registration_with_drift_check_baselines(
462466
pipeline_name,
463467
):
464468
instance_count = ParameterInteger(name="InstanceCount", default_value=1)
465-
instance_type = ParameterString(name="InstanceType", default_value="ml.m5.xlarge")
469+
instance_type = "ml.m5.xlarge"
466470

467471
# upload model data to s3
468472
model_local_path = os.path.join(DATA_DIR, "mxnet_mnist/model.tar.gz")
@@ -551,6 +555,9 @@ def test_model_registration_with_drift_check_baselines(
551555
)
552556
customer_metadata_properties = {"key1": "value1"}
553557
domain = "COMPUTER_VISION"
558+
559+
# If image_uri is not provided, the instance_type should not be a pipeline variable
560+
# since instance_type is used to retrieve image_uri in compile time (PySDK)
554561
estimator = XGBoost(
555562
entry_point="training.py",
556563
source_dir=os.path.join(DATA_DIR, "sip"),
@@ -581,7 +588,6 @@ def test_model_registration_with_drift_check_baselines(
581588
parameters=[
582589
model_uri_param,
583590
metrics_uri_param,
584-
instance_type,
585591
instance_count,
586592
],
587593
steps=[step_register],
@@ -670,9 +676,11 @@ def test_model_registration_with_model_repack(
670676
inputs = TrainingInput(s3_data=input_path)
671677

672678
instance_count = ParameterInteger(name="InstanceCount", default_value=1)
673-
instance_type = ParameterString(name="InstanceType", default_value="ml.m5.xlarge")
679+
instance_type = "ml.m5.xlarge"
674680
good_enough_input = ParameterInteger(name="GoodEnoughInput", default_value=1)
675681

682+
# If image_uri is not provided, the instance_type should not be a pipeline variable
683+
# since instance_type is used to retrieve image_uri in compile time (PySDK)
676684
pytorch_estimator = PyTorch(
677685
entry_point=entry_point,
678686
role=role,
@@ -727,7 +735,7 @@ def test_model_registration_with_model_repack(
727735

728736
pipeline = Pipeline(
729737
name=pipeline_name,
730-
parameters=[good_enough_input, instance_count, instance_type],
738+
parameters=[good_enough_input, instance_count],
731739
steps=[step_cond],
732740
sagemaker_session=sagemaker_session,
733741
)
@@ -770,8 +778,10 @@ def test_model_registration_with_tensorflow_model_with_pipeline_model(
770778
inputs = TrainingInput(s3_data=input_path)
771779

772780
instance_count = ParameterInteger(name="InstanceCount", default_value=1)
773-
instance_type = ParameterString(name="InstanceType", default_value="ml.m5.xlarge")
781+
instance_type = "ml.m5.xlarge"
774782

783+
# If image_uri is not provided, the instance_type should not be a pipeline variable
784+
# since instance_type is used to retrieve image_uri in compile time (PySDK)
775785
tensorflow_estimator = TensorFlow(
776786
entry_point=entry_point,
777787
role=role,
@@ -812,10 +822,7 @@ def test_model_registration_with_tensorflow_model_with_pipeline_model(
812822

813823
pipeline = Pipeline(
814824
name=pipeline_name,
815-
parameters=[
816-
instance_count,
817-
instance_type,
818-
],
825+
parameters=[instance_count],
819826
steps=[step_train, step_register_model],
820827
sagemaker_session=sagemaker_session,
821828
)

0 commit comments

Comments
 (0)