Skip to content
This repository was archived by the owner on May 23, 2024. It is now read-only.

Commit a27e1f9

Browse files
jesterhazyJonathan Esterhazy
authored andcommitted
add support for TensorFlow Serving 1.12
1 parent a4b647b commit a27e1f9

File tree

8 files changed

+127
-11
lines changed

8 files changed

+127
-11
lines changed

container/sagemaker/nginx.conf.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ http {
2727
client_body_buffer_size 100m;
2828
subrequest_output_buffer_size 100m;
2929

30+
set $tfs_version %TFS_VERSION%;
3031
set $default_tfs_model %TFS_DEFAULT_MODEL_NAME%;
3132

3233
location /tfs {

container/sagemaker/serve.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ def __init__(self):
2626
self._state = 'initializing'
2727
self._nginx = None
2828
self._tfs = None
29+
self._tfs_version = os.environ.get('SAGEMAKER_TFS_VERSION', '1.12')
2930
self._nginx_http_port = os.environ.get('SAGEMAKER_BIND_TO_PORT', '8080')
3031
self._nginx_loglevel = os.environ.get('SAGEMAKER_TFS_NGINX_LOGLEVEL', 'error')
31-
3232
self._tfs_default_model_name = os.environ.get('SAGEMAKER_TFS_DEFAULT_MODEL_NAME', None)
3333

3434
if 'SAGEMAKER_SAFE_PORT_RANGE' in os.environ:
@@ -94,6 +94,7 @@ def _create_nginx_config(self):
9494
template = self._read_nginx_template()
9595
pattern = re.compile(r'%(\w+)%')
9696
template_values = {
97+
'TFS_VERSION': self._tfs_version,
9798
'TFS_REST_PORT': self._tfs_rest_port,
9899
'TFS_DEFAULT_MODEL_NAME': self._tfs_default_model_name,
99100
'NGINX_HTTP_PORT': self._nginx_http_port,
@@ -115,6 +116,7 @@ def _read_nginx_template(self):
115116
return template
116117

117118
def _start_tfs(self):
119+
self._log_version('tensorflow_model_server --version', 'tensorflow version info:')
118120
tfs_config_path = '/sagemaker/model-config.cfg'
119121
cmd = "tensorflow_model_server --port={} --rest_api_port={} --model_config_file={}".format(
120122
self._tfs_grpc_port, self._tfs_rest_port, tfs_config_path)
@@ -124,10 +126,20 @@ def _start_tfs(self):
124126
self._tfs = p
125127

126128
def _start_nginx(self):
129+
self._log_version('/usr/sbin/nginx -V', 'nginx version info:')
127130
p = subprocess.Popen('/usr/sbin/nginx -c /sagemaker/nginx.conf'.split())
128131
log.info('started nginx (pid: %d)', p.pid)
129132
self._nginx = p
130133

134+
def _log_version(self, command, message):
135+
try:
136+
output = subprocess.check_output(
137+
command.split(),
138+
stderr=subprocess.STDOUT).decode('utf-8', 'backslashreplace').strip()
139+
log.info('{}\n{}'.format(message, output))
140+
except subprocess.CalledProcessError:
141+
log.warning('failed to run command: %s', command)
142+
131143
def _stop(self, *args):
132144
self._state = 'stopping'
133145
log.info('stopping services')
@@ -148,7 +160,6 @@ def start(self):
148160
self._state = 'starting'
149161
signal.signal(signal.SIGTERM, self._stop)
150162

151-
# TODO set env vars for ports etc
152163
self._create_tfs_config()
153164
self._create_nginx_config()
154165

container/sagemaker/tensorflow-serving.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,31 @@ function invocations(r) {
1414
}
1515

1616
function ping(r) {
17-
// TODO replace with call to Model Status API when Tensorflow Serving 1.12 is available
17+
if ('1.11' == r.variables.tfs_version) {
18+
return ping_tfs_1_11(r)
19+
}
20+
21+
var uri = make_tfs_uri(r, false)
22+
23+
function callback (reply) {
24+
if (reply.status == 200 && reply.responseBody.includes('"AVAILABLE"')) {
25+
r.return(200)
26+
} else {
27+
r.error('failed ping' + reply.responseBody)
28+
r.return(502)
29+
}
30+
}
31+
32+
r.subrequest(uri, callback)
33+
}
34+
35+
function ping_tfs_1_11(r) {
1836
// hack for TF 1.11
1937
// send an arbitrary fixed request to the default model.
2038
// if response is 400, the model is ok (but input was bad), so return 200
2139
// also return 200 in unlikely case our request was really valid
22-
var uri = make_tfs_uri(r)
40+
41+
var uri = make_tfs_uri(r, true)
2342
var options = {
2443
method: 'POST',
2544
body: '{"instances": "invalid"}'
@@ -46,7 +65,7 @@ function return_error(r, code, message) {
4665
}
4766

4867
function tfs_json_request(r, json) {
49-
var uri = make_tfs_uri(r)
68+
var uri = make_tfs_uri(r, true)
5069
var options = {
5170
method: 'POST',
5271
body: json
@@ -65,14 +84,18 @@ function tfs_json_request(r, json) {
6584
r.subrequest(uri, options, callback)
6685
}
6786

68-
function make_tfs_uri(r) {
87+
function make_tfs_uri(r, with_method) {
6988
var attributes = parse_custom_attributes(r)
7089

7190
var uri = tfs_base_uri + (attributes['tfs-model-name'] || r.variables.default_tfs_model)
7291
if ('tfs-model-version' in attributes) {
7392
uri += '/versions/' + attributes['tfs-model-version']
7493
}
75-
uri += ':' + (attributes['tfs-method'] || 'predict')
94+
95+
if (with_method) {
96+
uri += ':' + (attributes['tfs-method'] || 'predict')
97+
}
98+
7699
return uri
77100
}
78101

docker/1.11/Dockerfile.cpu

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ RUN \
1616

1717
COPY ./ /
1818

19+
ENV SAGEMAKER_TFS_VERSION "1.11"
1920
ENV PATH "$PATH:/sagemaker"

docker/1.11/Dockerfile.gpu

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ RUN \
5555

5656
COPY ./ /
5757

58+
ENV SAGEMAKER_TFS_VERSION "1.11"
5859
ENV PATH "$PATH:/sagemaker"

docker/1.12/Dockerfile.cpu

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM tensorflow/serving:1.12.0 as tfs
2+
FROM ubuntu:16.04
3+
LABEL com.amazonaws.sagemaker.capabilities.accept-bind-to-port=true
4+
5+
COPY --from=tfs /usr/bin/tensorflow_model_server /usr/bin/tensorflow_model_server
6+
7+
# nginx + njs
8+
RUN \
9+
apt-get update && \
10+
apt-get -y install --no-install-recommends curl && \
11+
curl -s http://nginx.org/keys/nginx_signing.key | apt-key add - && \
12+
echo 'deb http://nginx.org/packages/ubuntu/ xenial nginx' >> /etc/apt/sources.list && \
13+
apt-get update && \
14+
apt-get -y install --no-install-recommends nginx nginx-module-njs python3 python3-pip && \
15+
apt-get clean
16+
17+
COPY ./ /
18+
19+
ENV SAGEMAKER_TFS_VERSION "1.12"
20+
ENV PATH "$PATH:/sagemaker"

docker/1.12/Dockerfile.gpu

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
FROM tensorflow/serving:1.12.0-gpu as tfs
2+
FROM nvidia/cuda:9.0-base-ubuntu16.04
3+
LABEL com.amazonaws.sagemaker.capabilities.accept-bind-to-port=true
4+
5+
COPY --from=tfs /usr/bin/tensorflow_model_server /usr/bin/tensorflow_model_server
6+
7+
# https://github.com/tensorflow/serving/blob/1.12.0/tensorflow_serving/tools/docker/Dockerfile.gpu
8+
ENV NCCL_VERSION=2.2.13
9+
ENV CUDNN_VERSION=7.2.1.38
10+
ENV TF_TENSORRT_VERSION=4.1.2
11+
12+
RUN \
13+
apt-get update && apt-get install -y --no-install-recommends \
14+
ca-certificates \
15+
cuda-command-line-tools-9-0 \
16+
cuda-command-line-tools-9-0 \
17+
cuda-cublas-9-0 \
18+
cuda-cufft-9-0 \
19+
cuda-curand-9-0 \
20+
cuda-cusolver-9-0 \
21+
cuda-cusparse-9-0 \
22+
libcudnn7=${CUDNN_VERSION}-1+cuda9.0 \
23+
libnccl2=${NCCL_VERSION}-1+cuda9.0 \
24+
libgomp1 && \
25+
apt-get clean && \
26+
rm -rf /var/lib/apt/lists/*
27+
28+
# The 'apt-get install' of nvinfer-runtime-trt-repo-ubuntu1604-4.0.1-ga-cuda9.0
29+
# adds a new list which contains libnvinfer library, so it needs another
30+
# 'apt-get update' to retrieve that list before it can actually install the
31+
# library.
32+
# We don't install libnvinfer-dev since we don't need to build against TensorRT,
33+
# and libnvinfer4 doesn't contain libnvinfer.a static library.
34+
RUN apt-get update && \
35+
apt-get install --no-install-recommends \
36+
nvinfer-runtime-trt-repo-ubuntu1604-4.0.1-ga-cuda9.0 && \
37+
apt-get update && \
38+
apt-get install --no-install-recommends \
39+
libnvinfer4=${TF_TENSORRT_VERSION}-1+cuda9.0 && \
40+
apt-get clean && \
41+
rm -rf /var/lib/apt/lists/* && \
42+
rm /usr/lib/x86_64-linux-gnu/libnvinfer_plugin* && \
43+
rm /usr/lib/x86_64-linux-gnu/libnvcaffe_parser* && \
44+
rm /usr/lib/x86_64-linux-gnu/libnvparsers*
45+
46+
# nginx + njs
47+
RUN \
48+
apt-get update && \
49+
apt-get -y install --no-install-recommends curl && \
50+
curl -s http://nginx.org/keys/nginx_signing.key | apt-key add - && \
51+
echo 'deb http://nginx.org/packages/ubuntu/ xenial nginx' >> /etc/apt/sources.list && \
52+
apt-get update && \
53+
apt-get -y install --no-install-recommends nginx nginx-module-njs python3 python3-pip && \
54+
apt-get clean
55+
56+
COPY ./ /
57+
58+
ENV SAGEMAKER_TFS_VERSION "1.12"
59+
ENV PATH "$PATH:/sagemaker"

test/integration/test_container.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@
2323
BASE_URL = 'http://localhost:8080/invocations'
2424

2525

26-
@pytest.fixture(scope='module', autouse=True)
27-
def container():
26+
@pytest.fixture(scope='module', autouse=True, params=['1.11', '1.12'])
27+
def container(request):
2828
model_dir = os.path.abspath('test/resources/models')
2929
command = 'docker run --name sagemaker-tensorflow-serving-test -v {}:/opt/ml/model:ro -p 8080:8080'.format(
3030
model_dir)
3131
command += ' -e SAGEMAKER_TFS_DEFAULT_MODEL_NAME=half_plus_three'
3232
command += ' -e SAGEMAKER_TFS_NGINX_LOGLEVEL=info'
3333
command += ' -e SAGEMAKER_BIND_TO_PORT=8080'
3434
command += ' -e SAGEMAKER_SAFE_PORT_RANGE=9000-9999'
35-
command += ' sagemaker-tensorflow-serving:1.11.1-cpu serve'
35+
command += ' sagemaker-tensorflow-serving:{}-cpu serve'.format(request.param)
3636
proc = subprocess.Popen(command.split(), stdout=sys.stdout, stderr=subprocess.STDOUT)
3737

3838
attempts = 0
@@ -56,7 +56,7 @@ def make_request(data, content_type='application/json', method='predict'):
5656
'tfs-model-name=half_plus_three,tfs-method=%s' % method
5757
}
5858
response = requests.post(BASE_URL, data=data, headers=headers)
59-
return json.loads(response.content)
59+
return json.loads(response.content.decode('utf-8'))
6060

6161

6262
def test_predict():

0 commit comments

Comments
 (0)