def save_model(
    name: Tag | str,
    model: "tf_ext.KerasModel",
    *,
    tf_signatures: "tf_ext.ConcreteFunction" | None = None,
    tf_save_options: "tf_ext.SaveOptions" | None = None,
    include_optimizer: bool = False,
    signatures: (
        t.Dict[str, ModelSignature] | t.Dict[str, ModelSignatureDict] | None
    ) = None,
    labels: t.Optional[t.Dict[str, str]] = None,
    custom_objects: t.Optional[t.Dict[str, t.Any]] = None,
    external_modules: t.Optional[t.List[ModuleType]] = None,
    metadata: t.Optional[t.Dict[str, t.Any]] = None,
) -> bentoml.Model:
    """
    Save a model instance to BentoML modelstore.

    Args:
        name: Name for given model instance. This should pass Python identifier check.
        model: Instance of the Keras model to be saved to BentoML model store.
        tf_signatures: Refer to `Signatures explanation <https://www.tensorflow.org/api_docs/python/tf/saved_model/save>`_
                       from Tensorflow documentation for more information.
        tf_save_options: :obj:`tf.saved_model.SaveOptions` object that specifies options for saving.
        signatures: Methods to expose for running inference on the target model. Signatures
                    are used for creating Runner instances when serving model with bentoml.Service
        labels: user-defined labels for managing models, e.g. team=nlp, stage=dev
        custom_objects: Dictionary of Keras custom objects, if specified.
        external_modules: user-defined additional python modules to be saved alongside the model or custom objects,
                          e.g. a tokenizer module, preprocessor module, model configuration module
        metadata: Custom metadata for given model.

    Returns:
        :obj:`~bentoml.Model`: A BentoML model containing the saved Keras model instance.

    Examples:

    .. code-block:: python

        import bentoml
        import tensorflow as tf
        import tensorflow.keras as keras


        def custom_activation(x):
            return tf.nn.tanh(x) ** 2


        class CustomLayer(keras.layers.Layer):
            def __init__(self, units=32, **kwargs):
                super(CustomLayer, self).__init__(**kwargs)
                self.units = tf.Variable(units, name="units")

            def call(self, inputs, training=False):
                if training:
                    return inputs * self.units
                else:
                    return inputs

            def get_config(self):
                config = super(CustomLayer, self).get_config()
                config.update({"units": self.units.numpy()})
                return config


        def KerasSequentialModel() -> keras.models.Model:
            net = keras.models.Sequential(
                (
                    keras.layers.Dense(
                        units=1,
                        input_shape=(5,),
                        use_bias=False,
                        kernel_initializer=keras.initializers.Ones(),
                    ),
                )
            )

            opt = keras.optimizers.Adam(0.002, 0.5)
            net.compile(optimizer=opt, loss="binary_crossentropy", metrics=["accuracy"])
            return net

        model = KerasSequentialModel()

        # `save` a given model and retrieve coresponding tag:
        bento_model = bentoml.keras.save_model("keras_model", model)

        # `save` a given model with custom objects definition:
        custom_objects = {
            "CustomLayer": CustomLayer,
            "custom_activation": custom_activation,
        },
        custom_bento_model = bentoml.keras.save_model("custom_obj_keras", custom_objects=custom_objects)
    """

    if not isinstance(
        model,
        (
            t.cast("t.Type[keras.Model]", LazyType("keras.Model")),
            t.cast("t.Type[keras.Sequential]", LazyType("keras.Sequential")),
        ),
    ):
        raise TypeError(
            f"Given model ({model}) is not a keras.model.Model or keras.engine.sequential.Sequential."
        )

    context = ModelContext(
        framework_name="keras", framework_versions={"tensorflow": get_tf_version()}
    )

    if signatures is None:
        signatures = {
            "predict": {
                "batchable": False,
            }
        }
        logger.info(
            'Using the default model signature for Keras (%s) for model "%s".',
            signatures,
            name,
        )

    options = ModelOptions(include_optimizer=include_optimizer)
    kwargs = {}
    if tf_signatures is not None:
        kwargs["signatures"] = tf_signatures
    if tf_save_options is not None:
        kwargs["options"] = tf_save_options

    with bentoml.models._create(  # type: ignore
        name,
        module=MODULE_NAME,
        api_version=API_VERSION,
        options=options,
        context=context,
        labels=labels,
        custom_objects=custom_objects,
        external_modules=external_modules,
        metadata=metadata,
        signatures=signatures,
    ) as bento_model:
        if keras.__version__ >= "3.4.0":
            model.save(
                bento_model.path,
                zipped=False,
                include_optimizer=include_optimizer,
                **kwargs,
            )
        else:
            model.save(bento_model.path, include_optimizer=include_optimizer, **kwargs)

        return bento_model
Save a model instance to BentoML modelstore.

Args:
name: Name for given model instance. This should pass Python identifier check.
model: Instance of the Keras model to be saved to BentoML model store.
tf_signatures: Refer to `Signatures explanation <https://www.tensorflow.org/api_docs/python/tf/saved_model/save>`_
from Tensorflow documentation for more information.
tf_save_options: :obj:`tf.saved_model.SaveOptions` object that specifies options for saving.
signatures: Methods to expose for running inference on the target model. Signatures
are used for creating Runner instances when serving model with bentoml.Service
labels: user-defined labels for managing models, e.g. team=nlp, stage=dev
custom_objects: Dictionary of Keras custom objects, if specified.
external_modules: user-defined additional python modules to be saved alongside the model or custom objects,
e.g. a tokenizer module, preprocessor module, model configuration module
metadata: Custom metadata for given model.

Returns:
:obj:`~bentoml.Model`: A BentoML model containing the saved Keras model instance.

Examples:

.. code-block:: python

import bentoml
import tensorflow as tf
import tensorflow.keras as keras


def custom_activation(x):
return tf.nn.tanh(x) ** 2


class CustomLayer(keras.layers.Layer):
def __init__(self, units=32, **kwargs):
super(CustomLayer, self).__init__(**kwargs)
self.units = tf.Variable(units, name="units")

def call(self, inputs, training=False):
if training:
return inputs * self.units
else:
return inputs

def get_config(self):
config = super(CustomLayer, self).get_config()
config.update({"units": self.units.numpy()})
return config


def KerasSequentialModel() -> keras.models.Model:
net = keras.models.Sequential(
(
keras.layers.Dense(
units=1,
input_shape=(5,),
use_bias=False,
kernel_initializer=keras.initializers.Ones(),
),
)
)

opt = keras.optimizers.Adam(0.002, 0.5)
net.compile(optimizer=opt, loss="binary_crossentropy", metrics=["accuracy"])
return net

model = KerasSequentialModel()

# `save` a given model and retrieve coresponding tag:
bento_model = bentoml.keras.save_model("keras_model", model)

# `save` a given model with custom objects definition:
custom_objects = {
"CustomLayer": CustomLayer,
"custom_activation": custom_activation,
},
custom_bento_model = bentoml.keras.save_model("custom_obj_keras", custom_objects=custom_objects)
src/bentoml/_internal/frameworks/keras.py