Skip to content

Commit 5c8f1be

Browse files
authored
Merge branch 'master' into cv_s3_upload
2 parents ceee655 + ce2fb78 commit 5c8f1be

File tree

6 files changed

+190
-20
lines changed

6 files changed

+190
-20
lines changed

doc/experiments/sagemaker.experiments.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Run
77
.. autoclass:: sagemaker.experiments.Run
88
:members:
99

10-
.. automethod:: sagemaker.experiments.load_run
10+
.. automethod:: sagemaker.experiments.run.load_run
1111

1212
.. automethod:: sagemaker.experiments.list_runs
1313

src/sagemaker/clarify.py

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@
282282
in (
283283
"text/csv",
284284
"application/jsonlines",
285+
"application/json",
285286
"image/jpeg",
286287
"image/png",
287288
"application/x-npy",
@@ -296,6 +297,7 @@
296297
SchemaOptional("probability"): Or(str, int),
297298
SchemaOptional("label_headers"): [Or(str, int)],
298299
SchemaOptional("content_template"): Or(str, {str: str}),
300+
SchemaOptional("record_template"): str,
299301
SchemaOptional("custom_attributes"): str,
300302
},
301303
}
@@ -583,6 +585,7 @@ def __init__(
583585
accept_type: Optional[str] = None,
584586
content_type: Optional[str] = None,
585587
content_template: Optional[str] = None,
588+
record_template: Optional[str] = None,
586589
custom_attributes: Optional[str] = None,
587590
accelerator_type: Optional[str] = None,
588591
endpoint_name_prefix: Optional[str] = None,
@@ -609,14 +612,80 @@ def __init__(
609612
``"application/jsonlines"`` for JSON Lines, and ``"application/json"`` for JSON.
610613
Default is the same as ``content_type``.
611614
content_type (str): The model input format to be used for getting inferences with the
612-
shadow endpoint. Valid values are ``"text/csv"`` for CSV and
613-
``"application/jsonlines"`` for JSON Lines. Default is the same as
614-
``dataset_format``.
615+
shadow endpoint. Valid values are ``"text/csv"`` for CSV,
616+
``"application/jsonlines"`` for JSON Lines, and ``"application/json"`` for JSON.
617+
Default is the same as ``dataset_format``.
615618
content_template (str): A template string to be used to construct the model input from
616-
dataset instances. It is only used when ``model_content_type`` is
617-
``"application/jsonlines"``. The template should have one and only one placeholder,
618-
``"features"``, which will be replaced by a features list to form the model
619-
inference input.
619+
dataset instances. It is only used, and required, when ``model_content_type`` is
620+
``"application/jsonlines"`` or ``"application/json"``. When ``model_content_type``
621+
is ``application/jsonlines``, the template should have one and only one
622+
placeholder, ``$features``, which will be replaced by a features list for each
623+
record to form the model inference input. When ``model_content_type`` is
624+
``application/json``, the template can have either placeholder ``$record``, which
625+
will be replaced by a single record templated by ``record_template`` and only a
626+
single record at a time will be sent to the model, or placeholder ``$records``,
627+
which will be replaced by a list of records, each templated by ``record_template``.
628+
record_template (str): A template string to be used to construct each record of the
629+
model input from dataset instances. It is only used, and required, when
630+
``model_content_type`` is ``"application/json"``.
631+
The template string may contain one of the following:
632+
633+
* Placeholder ``$features`` that will be substituted by the array of feature values
634+
and/or an optional placeholder ``$feature_names`` that will be substituted by the
635+
array of feature names.
636+
* Exactly one placeholder ``$features_kvp`` that will be substituted by the
637+
key-value pairs of feature name and feature value.
638+
* Or for each feature, if "A" is the feature name in the ``headers`` configuration,
639+
then placeholder syntax ``"${A}"`` (the double-quotes are part of the
640+
placeholder) will be substituted by the feature value.
641+
642+
``record_template`` will be used in conjunction with ``content_template`` to
643+
construct the model input.
644+
645+
**Examples:**
646+
647+
Given:
648+
649+
* ``headers``: ``["A", "B"]``
650+
* ``features``: ``[[0, 1], [3, 4]]``
651+
652+
Example model input 1::
653+
654+
{
655+
"instances": [[0, 1], [3, 4]],
656+
"feature_names": ["A", "B"]
657+
}
658+
659+
content_template and record_template to construct above:
660+
661+
* ``content_template``: ``"{\"instances\": $records}"``
662+
* ``record_template``: ``"$features"``
663+
664+
Example model input 2::
665+
666+
[
667+
{ "A": 0, "B": 1 },
668+
{ "A": 3, "B": 4 },
669+
]
670+
671+
content_template and record_template to construct above:
672+
673+
* ``content_template``: ``"$records"``
674+
* ``record_template``: ``"$features_kvp"``
675+
676+
Or, alternatively:
677+
678+
* ``content_template``: ``"$records"``
679+
* ``record_template``: ``"{\"A\": \"${A}\", \"B\": \"${B}\"}"``
680+
681+
Example model input 3 (single record only)::
682+
683+
{ "A": 0, "B": 1 }
684+
685+
content_template and record_template to construct above:
686+
687+
* ``content_template``: ``"$record"``
688+
* ``record_template``: ``"$features_kvp"``
620689
custom_attributes (str): Provides additional information about a request for an
621690
inference submitted to a model hosted at an Amazon SageMaker endpoint. The
622691
information is an opaque value that is forwarded verbatim. You could use this
@@ -687,6 +756,7 @@ def __init__(
687756
if content_type not in [
688757
"text/csv",
689758
"application/jsonlines",
759+
"application/json",
690760
"image/jpeg",
691761
"image/jpg",
692762
"image/png",
@@ -696,14 +766,32 @@ def __init__(
696766
f"Invalid content_type {content_type}."
697767
f" Please choose text/csv or application/jsonlines."
698768
)
769+
if content_type == "application/jsonlines":
770+
if content_template is None:
771+
raise ValueError(
772+
f"content_template field is required for content_type {content_type}"
773+
)
774+
if "$features" not in content_template:
775+
raise ValueError(
776+
f"Invalid content_template {content_template}."
777+
f" Please include a placeholder $features."
778+
)
779+
if content_type == "application/json":
780+
if content_template is None or record_template is None:
781+
raise ValueError(
782+
f"content_template and record_template are required for content_type "
783+
f"{content_type}"
784+
)
785+
if "$record" not in content_template:
786+
raise ValueError(
787+
f"Invalid content_template {content_template}."
788+
f" Please include either placeholder $records or $record."
789+
)
699790
self.predictor_config["content_type"] = content_type
700791
if content_template is not None:
701-
if "$features" not in content_template:
702-
raise ValueError(
703-
f"Invalid content_template {content_template}."
704-
f" Please include a placeholder $features."
705-
)
706792
self.predictor_config["content_template"] = content_template
793+
if record_template is not None:
794+
self.predictor_config["record_template"] = record_template
707795
_set(custom_attributes, "custom_attributes", self.predictor_config)
708796
_set(accelerator_type, "accelerator_type", self.predictor_config)
709797
_set(target_model, "target_model", self.predictor_config)

src/sagemaker/fw_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"local_gpu",
8989
)
9090
SM_DATAPARALLEL_SUPPORTED_FRAMEWORK_VERSIONS = {
91+
# tf 2.12 should not be supported: smdataparallel excludes support for tf 2.12.
9192
"tensorflow": [
9293
"2.3",
9394
"2.3.1",

tests/integ/sagemaker/experiments/test_run.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import datetime
1616
import json
1717
import os
18+
import time
1819

1920
import pytest
2021

@@ -245,6 +246,12 @@ def test_run_from_local_and_train_job_and_all_exp_cfg_match(
245246
sagemaker_session=sagemaker_session,
246247
)
247248

249+
# the above estimator has wait=True but the job TC could still be receiving updates
250+
# after wait is complete resulting in run TC being updated, then when the above with
251+
# statement is exited another update trial component call is made _sometimes_
252+
# resulting in a ConflictException
253+
time.sleep(3)
254+
248255
_check_tc_status_when_exiting(
249256
trial_component_name=run._trial_component.trial_component_name,
250257
init_start_time=init_start_time,

tests/unit/sagemaker/monitor/test_clarify_model_monitor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@
365365
MODEL_NAME = "xgboost-model"
366366
ACCEPT_TYPE = "text/csv"
367367
CONTENT_TYPE = "application/jsonlines"
368+
JSONLINES_CONTENT_TEMPLATE = '{"instances":$features}'
368369
EXPLAINABILITY_ANALYSIS_CONFIG = {
369370
"headers": ANALYSIS_CONFIG_HEADERS_OF_FEATURES,
370371
"methods": {
@@ -382,6 +383,7 @@
382383
"initial_instance_count": INSTANCE_COUNT,
383384
"accept_type": ACCEPT_TYPE,
384385
"content_type": CONTENT_TYPE,
386+
"content_template": JSONLINES_CONTENT_TEMPLATE,
385387
},
386388
}
387389
EXPLAINABILITY_ANALYSIS_CONFIG_WITH_LABEL_HEADERS = copy.deepcopy(EXPLAINABILITY_ANALYSIS_CONFIG)
@@ -489,6 +491,7 @@ def model_config():
489491
instance_count=INSTANCE_COUNT,
490492
content_type=CONTENT_TYPE,
491493
accept_type=ACCEPT_TYPE,
494+
content_template=JSONLINES_CONTENT_TEMPLATE,
492495
)
493496

494497

tests/unit/test_clarify.py

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ def test_facet_of_bias_config(facet_name, facet_values_or_threshold, expected_re
395395
("text/csv", "application/json"),
396396
("application/jsonlines", "application/json"),
397397
("application/jsonlines", "text/csv"),
398+
("application/json", "application/json"),
399+
("application/json", "application/jsonlines"),
400+
("application/json", "text/csv"),
398401
("image/jpeg", "text/csv"),
399402
("image/jpg", "text/csv"),
400403
("image/png", "text/csv"),
@@ -408,12 +411,22 @@ def test_valid_model_config(content_type, accept_type):
408411
custom_attributes = "c000b4f9-df62-4c85-a0bf-7c525f9104a4"
409412
target_model = "target_model_name"
410413
accelerator_type = "ml.eia1.medium"
414+
content_template = (
415+
'{"instances":$features}'
416+
if content_type == "application/jsonlines"
417+
else "$records"
418+
if content_type == "application/json"
419+
else None
420+
)
421+
record_template = "$features_kvp" if content_type == "application/json" else None
411422
model_config = ModelConfig(
412423
model_name=model_name,
413424
instance_type=instance_type,
414425
instance_count=instance_count,
415426
accept_type=accept_type,
416427
content_type=content_type,
428+
content_template=content_template,
429+
record_template=record_template,
417430
custom_attributes=custom_attributes,
418431
accelerator_type=accelerator_type,
419432
target_model=target_model,
@@ -428,21 +441,79 @@ def test_valid_model_config(content_type, accept_type):
428441
"accelerator_type": accelerator_type,
429442
"target_model": target_model,
430443
}
444+
if content_template is not None:
445+
expected_config["content_template"] = content_template
446+
if record_template is not None:
447+
expected_config["record_template"] = record_template
431448
assert expected_config == model_config.get_predictor_config()
432449

433450

434-
def test_invalid_model_config():
435-
with pytest.raises(ValueError) as error:
451+
@pytest.mark.parametrize(
452+
("error", "content_type", "accept_type", "content_template", "record_template"),
453+
[
454+
(
455+
"Invalid accept_type invalid_accept_type. Please choose text/csv or application/jsonlines.",
456+
"text/csv",
457+
"invalid_accept_type",
458+
None,
459+
None,
460+
),
461+
(
462+
"Invalid content_type invalid_content_type. Please choose text/csv or application/jsonlines.",
463+
"invalid_content_type",
464+
"text/csv",
465+
None,
466+
None,
467+
),
468+
(
469+
"content_template field is required for content_type",
470+
"application/jsonlines",
471+
"text/csv",
472+
None,
473+
None,
474+
),
475+
(
476+
"content_template and record_template are required for content_type",
477+
"application/json",
478+
"text/csv",
479+
None,
480+
None,
481+
),
482+
(
483+
"content_template and record_template are required for content_type",
484+
"application/json",
485+
"text/csv",
486+
"$records",
487+
None,
488+
),
489+
(
490+
r"Invalid content_template invalid_content_template. Please include a placeholder \$features.",
491+
"application/jsonlines",
492+
"text/csv",
493+
"invalid_content_template",
494+
None,
495+
),
496+
(
497+
r"Invalid content_template invalid_content_template. Please include either placeholder "
498+
r"\$records or \$record.",
499+
"application/json",
500+
"text/csv",
501+
"invalid_content_template",
502+
"$features",
503+
),
504+
],
505+
)
506+
def test_invalid_model_config(error, content_type, accept_type, content_template, record_template):
507+
with pytest.raises(ValueError, match=error):
436508
ModelConfig(
437509
model_name="xgboost-model",
438510
instance_type="ml.c5.xlarge",
439511
instance_count=1,
440-
accept_type="invalid_accept_type",
512+
content_type=content_type,
513+
accept_type=accept_type,
514+
content_template=content_template,
515+
record_template=record_template,
441516
)
442-
assert (
443-
"Invalid accept_type invalid_accept_type. Please choose text/csv or application/jsonlines."
444-
in str(error.value)
445-
)
446517

447518

448519
def test_invalid_model_config_with_bad_endpoint_name_prefix():

0 commit comments

Comments
 (0)