A trained TensorFlow model needs a portable handoff point before it can move from training code into serving, batch inference, conversion, or release testing. A SavedModel directory carries the inference graph, variables, assets, and callable signatures that those downstream runtimes can load without rebuilding the original Python model definition.
Current Keras models can use model.export() when the target is an inference-focused SavedModel instead of a full .keras training archive. The export should be checked from a separate load path with tf.saved_model.load() and inspected with saved_model_cli before another runtime depends on the tensor names and shapes.
The export is a directory tree, not one standalone file. Keep the numeric version folder shape when the model may later move into TensorFlow Serving, and use explicit serving signatures only when the default serve and serving_default endpoints do not give clients stable enough field names.
Validation used TensorFlow 2.21.0 and Keras 3.15.0 from the campaign CPU image.
export_savedmodel_demo.py
.
import os import pathlib import shutil os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" import tensorflow as tf tf.get_logger().setLevel("ERROR") tf.keras.utils.set_random_seed(7) export_dir = pathlib.Path("exported/number_classifier/1") model_root = export_dir.parents[1] if model_root.exists(): shutil.rmtree(model_root) features = tf.constant( [ [0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 1.0], [1.0, 0.0, 1.0, 0.0], [1.0, 1.0, 1.0, 1.0], [0.2, 0.9, 0.1, 0.8], [0.8, 0.2, 0.9, 0.1], ], dtype=tf.float32, ) labels = tf.constant([[0.0], [0.0], [1.0], [1.0], [0.0], [1.0]], dtype=tf.float32) inputs = tf.keras.Input(shape=(4,), name="features") x = tf.keras.layers.Dense(8, activation="relu")(inputs) outputs = tf.keras.layers.Dense(1, activation="sigmoid")(x) model = tf.keras.Model(inputs=inputs, outputs=outputs, name="number_classifier") model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"]) model.fit(features, labels, epochs=12, verbose=0) model.export(export_dir) artifact = tf.saved_model.load(str(export_dir)) signature = artifact.signatures["serving_default"] _, keyword_inputs = signature.structured_input_signature input_name, input_spec = next(iter(keyword_inputs.items())) sample_batch = tf.constant( [ [0.1, 0.9, 0.2, 0.8], [0.9, 0.1, 0.8, 0.2], ], dtype=input_spec.dtype, ) prediction = signature(**{input_name: sample_batch}) print(f"TensorFlow {tf.__version__}") print(f"Keras {tf.keras.__version__}") print(f"SavedModel dir: {export_dir}") print(f"Contains SavedModel: {tf.saved_model.contains_saved_model(str(export_dir))}") print(f"Signature names: {sorted(artifact.signatures.keys())}") print(f"Input key: {input_name} shape={tuple(input_spec.shape)} dtype={input_spec.dtype.name}") print(f"Output keys: {sorted(prediction.keys())}") print(f"Output shape: {tuple(next(iter(prediction.values())).shape)}")
The numeric path exported/number_classifier/1 mirrors the directory shape expected by TensorFlow Serving, where the model base path contains one or more version folders.
$ python3 export_savedmodel_demo.py Saved artifact at 'exported/number_classifier/1'. The following endpoints are available: * Endpoint 'serve' args_0 (POSITIONAL_ONLY): TensorSpec(shape=(None, 4), dtype=tf.float32, name='features') Output Type: TensorSpec(shape=(None, 1), dtype=tf.float32, name=None) ##### snipped ##### TensorFlow 2.21.0 Keras 3.15.0 SavedModel dir: exported/number_classifier/1 Contains SavedModel: True Signature names: ['serve', 'serving_default'] Input key: features shape=(None, 4) dtype=float32 Output keys: ['output_0'] Output shape: (2, 1)
Contains SavedModel: True confirms that the directory is a SavedModel, and the signature, input, and output lines show the contract another caller must use.
$ ls exported/number_classifier/1 assets fingerprint.pb saved_model.pb variables
Copy the whole version directory, not only saved_model.pb, because variables and assets can live outside the protobuf file.
$ saved_model_cli show --dir exported/number_classifier/1 --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
inputs['features'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 4)
name: serving_default_features:0
The given SavedModel SignatureDef contains the following output(s):
outputs['output_0'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall_1:0
Method name is: tensorflow/serving/predict
The input key features and output key output_0 are the values to map into a serving request, batch inference call, or client adapter.
Related: How to inspect a TensorFlow SavedModel with saved_model_cli
Related: How to deploy TensorFlow Serving with Docker
$ rm export_savedmodel_demo.py
Keep the exported exported/number_classifier/1 directory if it will be passed to serving, conversion, or release validation.