Amazon SageMaker AI Deployment¶
Use the TensorFlow Serving DLC to host SavedModel artifacts on Amazon SageMaker AI real-time endpoints and batch transform jobs. The images bundle
tensorflow_model_server, nginx, and the SageMaker TensorFlow Serving handler stack, so no custom container code is required for the common case.
The TensorFlow Serving 2.20 DLC is a SageMaker-only release — there is no EC2 or EKS variant of this image.
Packaging Model Artifacts¶
SageMaker extracts your model.tar.gz into /opt/ml/model. The container discovers SavedModels by looking for saved_model.pb under a numeric
version directory, so the archive must keep the standard TensorFlow Serving layout:
model.tar.gz
└── model/ # model name as served (any name)
└── 1/ # numeric version directory
├── saved_model.pb
└── variables/
├── variables.data-00000-of-00001
└── variables.index
To add pre/post-processing or extra Python dependencies, include a code/ directory at the root of the archive:
At container start, requirements.txt is installed with pip before the model server accepts traffic, and inference.py is imported by the Python
handler. Multiple version directories under one model name are all loaded and can be addressed individually per request.
SageMaker Python SDK v2¶
Pass the DLC image URI via image_uri= rather than framework_version=:
from sagemaker.tensorflow import TensorFlowModel
model = TensorFlowModel(
image_uri="public.ecr.aws/deep-learning-containers/tensorflow-inference:2.20-gpu-py312-cu129-amzn2023-sagemaker",
model_data="s3://<bucket>/models/model.tar.gz",
role="arn:aws:iam::<account_id>:role/<role_name>",
)
predictor = model.deploy(
instance_type="ml.g5.xlarge",
initial_instance_count=1,
)
# The default handler accepts the TensorFlow Serving REST predict payload.
response = predictor.predict({"instances": [[1.0, 2.0, 5.0]]})
print(response) # {"predictions": [[...]]}
# Cleanup
predictor.delete_model()
predictor.delete_endpoint(delete_endpoint_config=True)
For CPU instances (e.g. ml.c6i, ml.m6i), use the 2.20-cpu-py312-amzn2023-sagemaker tag instead.
SageMaker Python SDK v3¶
import json
import boto3
from sagemaker.core.resources import Endpoint, EndpointConfig, Model
from sagemaker.core.shapes import ContainerDefinition, ProductionVariant
# Requires a configured AWS region (always set inside SageMaker environments).
region = boto3.session.Session().region_name
model = Model.create(
model_name="tfs-model",
primary_container=ContainerDefinition(
image="public.ecr.aws/deep-learning-containers/tensorflow-inference:2.20-gpu-py312-cu129-amzn2023-sagemaker",
model_data_url="s3://<bucket>/models/model.tar.gz",
),
execution_role_arn="arn:aws:iam::<account_id>:role/<role_name>",
region=region,
)
ep_cfg = EndpointConfig.create(
endpoint_config_name="tfs-config",
production_variants=[
ProductionVariant(
variant_name="default",
model_name="tfs-model",
instance_type="ml.g5.xlarge",
initial_instance_count=1,
),
],
region=region,
)
endpoint = Endpoint.create(
endpoint_name="tfs-endpoint",
endpoint_config_name="tfs-config",
region=region,
)
endpoint.wait_for_status("InService")
smrt = boto3.client("sagemaker-runtime", region_name=region)
resp = smrt.invoke_endpoint(
EndpointName="tfs-endpoint",
ContentType="application/json",
Body=json.dumps({"instances": [[1.0, 2.0, 5.0]]}),
)
print(json.loads(resp["Body"].read())) # {"predictions": [[...]]}
# Cleanup
endpoint.delete()
ep_cfg.delete()
model.delete()
Request Formats¶
All traffic reaches the container as POST /invocations on port 8080. When no inference.py is supplied, the request is forwarded to the TensorFlow
Serving REST API by nginx. A body that is already a predict request (instances for row format, inputs for columnar format) is passed through as
is; a bare payload is wrapped into an instances request first, as shown below.
The default handler accepts these content types:
Content-Type |
Notes |
|---|---|
application/json |
TensorFlow Serving predict request body, or a bare payload that is wrapped into an instances request |
application/jsons |
Concatenated JSON objects, combined into an instances request |
application/jsonlines |
One JSON object per line, combined into an instances request |
text/csv |
Rows converted to an instances request |
Any other content type is rejected unless your inference.py declares an input_handler that can parse it.
Targeting a Specific Model or Version¶
Pass the X-Amzn-SageMaker-Custom-Attributes header to select the model name, version, or TensorFlow Serving method for a single request:
resp = smrt.invoke_endpoint(
EndpointName="tfs-endpoint",
ContentType="application/json",
CustomAttributes="tfs-model-name=model,tfs-model-version=1,tfs-method=predict",
Body=json.dumps({"instances": [[1.0, 2.0, 5.0]]}),
)
Custom Pre/Post-Processing¶
Provide code/inference.py with either a single handler(data, context) function, or an input_handler(data, context) /
output_handler(data, context) pair. Defining both handler and the pair is an error.
import json
def input_handler(data, context):
"""Transform the request payload into a TensorFlow Serving predict body."""
if context.request_content_type == "application/json":
payload = json.loads(data.read().decode("utf-8"))
return json.dumps({"instances": payload["rows"]})
raise ValueError(f"unsupported content type: {context.request_content_type}")
def output_handler(data, context):
"""Post-process the TensorFlow Serving response."""
if data.status_code != 200:
raise ValueError(data.content.decode("utf-8"))
predictions = json.loads(data.content.decode("utf-8"))["predictions"]
return json.dumps({"labels": [int(p[0] > 0.5) for p in predictions]}), "application/json"
The context object exposes the request metadata (request_content_type, accept_header, custom_attributes, content_length, the resolved
model_name / model_version / method) along with rest_uri, grpc_port, and timeout for calling TensorFlow Serving directly.
Multi-Model Endpoints¶
Set SAGEMAKER_MULTI_MODEL=true to host many models behind one endpoint and manage them at runtime through the SageMaker model-management API.
Models are loaded on demand into /opt/ml/models/<model_name>/model, and each may carry its own code/inference.py; a universal handler is used for
models that do not.
model = Model.create(
model_name="tfs-mme",
primary_container=ContainerDefinition(
image="public.ecr.aws/deep-learning-containers/tensorflow-inference:2.20-cpu-py312-amzn2023-sagemaker",
mode="MultiModel",
model_data_url="s3://<bucket>/models/",
environment={"SAGEMAKER_MULTI_MODEL": "true"},
),
execution_role_arn="arn:aws:iam::<account_id>:role/<role_name>",
region=region,
)
Configuration¶
The container is tuned through environment variables passed in the model's Environment map:
| Environment Variable | Purpose |
|---|---|
SAGEMAKER_TFS_DEFAULT_MODEL_NAME |
Model name to serve when the request does not name one (otherwise derived from the artifact layout) |
SAGEMAKER_TFS_ENABLE_BATCHING |
Enable TensorFlow Serving server-side batching (true/false) |
SAGEMAKER_TFS_MAX_BATCH_SIZE |
Maximum number of requests per batch |
SAGEMAKER_TFS_BATCH_TIMEOUT_MICROS |
How long to wait for a batch to fill, in microseconds |
SAGEMAKER_TFS_NUM_BATCH_THREADS |
Number of threads processing batches |
SAGEMAKER_TFS_MAX_ENQUEUED_BATCHES |
Queue depth before requests are rejected |
SAGEMAKER_TFS_INTRA_OP_PARALLELISM |
TensorFlow intra-op thread pool size |
SAGEMAKER_TFS_INTER_OP_PARALLELISM |
TensorFlow inter-op thread pool size |
SAGEMAKER_TFS_INSTANCE_COUNT |
Number of tensorflow_model_server processes to run |
SAGEMAKER_TFS_WAIT_TIME_SECONDS |
How long to wait for TensorFlow Serving to become ready before giving up |
SAGEMAKER_TFS_FRACTIONAL_GPU_MEM_MARGIN |
GPU memory margin when running several model server processes on one GPU |
SAGEMAKER_GUNICORN_WORKERS |
Worker count for the Python handler |
SAGEMAKER_GUNICORN_THREADS |
Threads per handler worker |
SAGEMAKER_GUNICORN_WORKER_CLASS |
Worker class for the Python handler (defaults to gevent) |
SAGEMAKER_GUNICORN_TIMEOUT_SECONDS |
Handler worker timeout |
SAGEMAKER_NGINX_PROXY_READ_TIMEOUT_SECONDS |
nginx read timeout for upstream responses |
SAGEMAKER_TFS_NGINX_LOGLEVEL |
nginx error log level |
SAGEMAKER_GUNICORN_LOGLEVEL |
Handler log level |
SAGEMAKER_MULTI_MODEL |
Enable multi-model endpoint mode |
SAGEMAKER_MULTI_MODEL_UNIVERSAL_BUCKET |
S3 bucket to download code/ from when it is not part of the model artifact — set together with the prefix below |
SAGEMAKER_MULTI_MODEL_UNIVERSAL_PREFIX |
S3 prefix for the above; setting both also enables the Python handler |
SAGEMAKER_BIND_TO_PORT |
HTTP port to bind, set by SageMaker when it does not use 8080 |
SAGEMAKER_BATCH |
Set by SageMaker for batch transform jobs |
OMP_NUM_THREADS |
OpenMP thread count for the model server |
Batching is off by default. Turning it on trades latency for throughput and is most useful on GPU instances with steady traffic.
Container Layout¶
| Path | Purpose |
|---|---|
/opt/ml/model/ |
Extracted model.tar.gz — SageMaker mounts your SavedModel here |
/opt/ml/model/code/ |
Optional inference.py and requirements.txt shipped inside the archive |
/opt/ml/models/ |
Per-model mount points in multi-model endpoint mode |
/sagemaker/ |
Handler stack, nginx config template, and the serve entry point |
/opt/venv/ |
Python venv with the handler stack and AWS libraries |
Notes¶
- The image serves SageMaker traffic on port 8080 via nginx. TensorFlow Serving's own gRPC and REST ports are container-local and are not
reachable through a SageMaker endpoint. They are also assigned at container start — when SageMaker supplies
SAGEMAKER_SAFE_PORT_RANGE, one non-overlapping gRPC/REST pair perSAGEMAKER_TFS_INSTANCE_COUNTis allocated from that range; otherwise the defaults gRPC 9000 and REST 8501 are used. Readcontext.grpc_portandcontext.rest_uriin your handler instead of hardcoding a port. - If your
inference.pyneeds thetensorflowPython package (e.g. to buildtf.Exampleprotos), add it tocode/requirements.txt. The framework wheel is deliberately not preinstalled; only the TensorFlow Serving gRPC stubs are (tensorflow-serving-api-gpuin the GPU image,tensorflow-serving-apiin the CPU image). - For a baseline driver/AMI compatible with these CUDA 12.9 images, use a current SageMaker inference instance — the image is labelled
com.amazonaws.sagemaker.inference.cuda.verified_versions=12.9and the entrypoint applies CUDA forward compatibility automatically when the host driver is older.