Skip to content

Commit 455ee5a

Browse files
committed
Add tests
1 parent 58b37b0 commit 455ee5a

File tree

8 files changed

+200
-3
lines changed

8 files changed

+200
-3
lines changed

buildspec.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: 0.2
33
env:
44
variables:
55
FRAMEWORK_VERSION: '1.6.0'
6-
EIA_FRAMEWORK_VERSION: '1.3.1'
6+
EIA_FRAMEWORK_VERSION: '1.5.1'
77
CPU_INSTANCE_TYPE: 'ml.c4.xlarge'
88
GPU_INSTANCE_TYPE: 'ml.p2.8xlarge'
99
EIA_ACCELERATOR_TYPE: 'ml.eia2.medium'

test/integration/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
gpu_sub_dir = 'model_gpu'
2525
eia_sub_dir = 'model_eia'
2626
code_sub_dir = 'code'
27+
default_sub_dir = 'default_model'
28+
default_sub_eia_dir = 'default_model_eia'
2729

2830
model_cpu_dir = os.path.join(mnist_path, cpu_sub_dir)
2931
mnist_cpu_script = os.path.join(model_cpu_dir, code_sub_dir, 'mnist.py')
@@ -59,6 +61,18 @@
5961
"model_call_model_fn_once.tar.gz",
6062
script_path="code")
6163

64+
default_model_dir = os.path.join(mnist_path, default_sub_dir)
65+
default_model_script = os.path.join(default_model_dir, "mnist.py")
66+
default_model_tar = file_utils.make_tarfile(
67+
default_model_script, os.path.join(default_model_dir, "model.pt"), default_model_dir, script_path="code"
68+
)
69+
70+
default_model_eia_dir = os.path.join(mnist_path, default_sub_dir)
71+
default_model_eia_script = os.path.join(default_model_eia_dir, "mnist.py")
72+
default_model_eia_tar = file_utils.make_tarfile(
73+
default_model_eia_script, os.path.join(default_model_eia_dir, "model.pt"), default_model_eia_dir
74+
)
75+
6276
ROLE = 'dummy/unused-role'
6377
DEFAULT_TIMEOUT = 20
6478
PYTHON3 = 'py3'
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright 2019-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
from __future__ import absolute_import
14+
15+
import numpy as np
16+
import pytest
17+
import sagemaker
18+
from sagemaker.pytorch import PyTorchModel
19+
20+
from integration import (
21+
model_cpu_tar,
22+
model_gpu_tar,
23+
mnist_cpu_script,
24+
mnist_gpu_script,
25+
model_eia_tar,
26+
mnist_eia_script,
27+
default_model_script,
28+
default_model_tar,
29+
default_model_eia_script,
30+
default_model_eia_tar,
31+
)
32+
from integration.sagemaker.timeout import timeout_and_delete_endpoint
33+
34+
35+
@pytest.mark.cpu_test
36+
def test_default_inference_cpu(sagemaker_session, image_uri, instance_type):
37+
instance_type = instance_type or "ml.c4.xlarge"
38+
# Scripted model is serialized with torch.jit.save().
39+
# Default inference test doesn't need to instantiate model definition
40+
_test_default_inference(sagemaker_session, image_uri, instance_type, default_model_tar, default_model_script)
41+
42+
43+
@pytest.mark.gpu_test
44+
def test_default_inference_gpu(sagemaker_session, image_uri, instance_type):
45+
instance_type = instance_type or "ml.p2.xlarge"
46+
# Scripted model is serialized with torch.jit.save().
47+
# Default inference test doesn't need to instantiate model definition
48+
_test_default_inference(sagemaker_session, image_uri, instance_type, default_model_tar, default_model_script)
49+
50+
51+
@pytest.mark.eia_test
52+
def test_default_inference_eia(sagemaker_session, image_uri, instance_type, accelerator_type):
53+
instance_type = instance_type or "ml.c4.xlarge"
54+
# Scripted model is serialized with torch.jit.save().
55+
# Default inference test doesn't need to instantiate model definition
56+
_test_default_inference(
57+
sagemaker_session,
58+
image_uri,
59+
instance_type,
60+
default_model_eia_tar,
61+
default_model_eia_script,
62+
accelerator_type=accelerator_type,
63+
)
64+
65+
66+
def _test_default_inference(
67+
sagemaker_session, image_uri, instance_type, model_tar, mnist_script, accelerator_type=None
68+
):
69+
endpoint_name = sagemaker.utils.unique_name_from_base("sagemaker-pytorch-serving")
70+
71+
model_data = sagemaker_session.upload_data(
72+
path=model_tar,
73+
key_prefix="sagemaker-pytorch-serving/models",
74+
)
75+
76+
pytorch = PyTorchModel(
77+
model_data=model_data,
78+
role="SageMakerRole",
79+
entry_point=mnist_script,
80+
image=image_uri,
81+
sagemaker_session=sagemaker_session,
82+
)
83+
with timeout_and_delete_endpoint(endpoint_name, sagemaker_session, minutes=30):
84+
# Use accelerator type to differentiate EI vs. CPU and GPU. Don't use processor value
85+
if accelerator_type is not None:
86+
predictor = pytorch.deploy(
87+
initial_instance_count=1,
88+
instance_type=instance_type,
89+
accelerator_type=accelerator_type,
90+
endpoint_name=endpoint_name,
91+
)
92+
else:
93+
predictor = pytorch.deploy(
94+
initial_instance_count=1, instance_type=instance_type, endpoint_name=endpoint_name
95+
)
96+
97+
batch_size = 100
98+
data = np.random.rand(batch_size, 1, 28, 28).astype(np.float32)
99+
output = predictor.predict(data)
100+
101+
assert output.shape == (batch_size, 10)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2019-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
from __future__ import absolute_import
14+
import logging
15+
import os
16+
import sys
17+
18+
import torch
19+
20+
logger = logging.getLogger(__name__)
21+
logger.setLevel(logging.DEBUG)
22+
logger.addHandler(logging.StreamHandler(sys.stdout))
23+
24+
25+
def predict_fn(input_data, model):
26+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
27+
model = model.to(device)
28+
input_data = input_data.to(device)
29+
return model(input_data)
30+
31+
32+
def save_model(model, model_dir):
33+
logger.info("Saving the model to {}.".format(model_dir))
34+
path = os.path.join(model_dir, 'model.pt')
35+
torch.jit.save(model, path)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2019-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
from __future__ import absolute_import
14+
import logging
15+
import os
16+
import sys
17+
18+
import torch
19+
20+
logger = logging.getLogger(__name__)
21+
logger.setLevel(logging.DEBUG)
22+
logger.addHandler(logging.StreamHandler(sys.stdout))
23+
24+
25+
def predict_fn(input_data, model):
26+
logger.info('Performing EIA inference with Torch JIT context with input of size {}'.format(input_data.shape))
27+
# With EI, client instance should be CPU for cost-efficiency.
28+
# Sub-graphs with unsupported arguments run locally. Server runs with CUDA
29+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
30+
model = model.to(device)
31+
input_data = input_data.to(device)
32+
with torch.no_grad():
33+
# Set the target device to the accelerator ordinal
34+
with torch.jit.optimized_execution(True, {'target_device': 'eia:0'}):
35+
return model(input_data)
Binary file not shown.

test/resources/mnist/model_cpu/code/mnist.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,9 @@ def model_fn(model_dir):
5555
with open(os.path.join(model_dir, 'torch_model.pth'), 'rb') as f:
5656
model.load_state_dict(torch.load(f))
5757
return model
58+
59+
60+
def save_model(model, model_dir):
61+
logger.info("Saving the model to {}.".format(model_dir))
62+
path = os.path.join(model_dir, 'model.pt')
63+
torch.jit.save(model, path)

test/unit/test_default_inference_handler.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ def eia_inference_handler():
5858

5959

6060
def test_default_model_fn(inference_handler):
61-
with pytest.raises(NotImplementedError):
62-
inference_handler.default_model_fn("model_dir")
61+
with mock.patch("sagemaker_pytorch_serving_container.default_pytorch_inference_handler.os") as mock_os:
62+
mock_os.getenv.return_value = "true"
63+
mock_os.path.join.return_value = "model_dir"
64+
mock_os.path.exists.return_value = True
65+
with mock.patch("torch.jit.load") as mock_torch:
66+
mock_torch.return_value = DummyModel()
67+
model = inference_handler.default_model_fn("model_dir")
68+
assert model is not None
6369

6470

6571
def test_default_input_fn_json(inference_handler, tensor):

0 commit comments

Comments
 (0)