def save_model(
    name: Tag | str,
    learner_: learner.Learner,
    *,
    signatures: ModelSignaturesType | None = None,
    labels: dict[str, str] | None = None,
    custom_objects: dict[str, t.Any] | None = None,
    external_modules: t.List[ModuleType] | None = None,
    metadata: dict[str, t.Any] | None = None,
) -> bentoml.Model:
    """
    Save a :code:`fastai.learner.Learner` model instance to the BentoML model store.

    If the :func:`save_model` method failed while saving a given learner,
    your learner may contain a :obj:`~fastai.callback.core.Callback` that is not picklable.
    All FastAI callbacks are stateful, which makes some of them not picklable.
    Use :func:`Learner.remove_cbs` to remove unpicklable callbacks.

    Args:
        name: The name to give to the model in the BentoML store. This must be a valid
              :obj:`~bentoml.Tag` name.
        learner: :obj:`~fastai.learner.Learner` to be saved.
        signatures: Signatures of predict methods to be used. If not provided, the signatures default to
                    ``predict``. See :obj:`~bentoml.types.ModelSignature` for more details.
        labels: A default set of management labels to be associated with the model. An example is ``{"training-set": "data-1"}``.
        custom_objects: Custom objects to be saved with the model. An example is ``{"my-normalizer": normalizer}``.
                        Custom objects are currently serialized with cloudpickle, but this implementation is subject to change.
        external_modules (:code:`List[ModuleType]`, `optional`, default to :code:`None`):
            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: Metadata to be associated with the model. An example is ``{"bias": 4}``.
                  Metadata is intended for display in a model management UI and therefore must be a
                  default Python type, such as :obj:`str` or :obj:`int`.

    Returns:
        :obj:`~bentoml.Tag`: A tag that can be used to access the saved model from the BentoML model store.

    Example:

    .. code-block:: python

       from fastai.metrics import accuracy
       from fastai.text.data import URLs
       from fastai.text.data import untar_data
       from fastai.text.data import TextDataLoaders
       from fastai.text.models import AWD_LSTM
       from fastai.text.learner import text_classifier_learner

       dls = TextDataLoaders.from_folder(untar_data(URLs.IMDB), valid="test")

       learner = text_classifier_learner(dls, AWD_LSTM, drop_mult=0.5, metrics=accuracy)
       learner.fine_tune(4, 1e-2)

       # Test run the model
       learner.model.eval()
       learner.predict("I love that movie!")

       # `Save` the model with BentoML
       tag = bentoml.fastai.save_model("fai_learner", learner)
    """
    import cloudpickle

    if isinstance(learner_, nn.Module):
        raise BentoMLException(
            "'bentoml.fastai.save_model()' does not support saving pytorch 'Module's directly. You should create a new 'Learner' object from the model, or use 'bentoml.pytorch.save_model()' to save your PyTorch model instead."
        )
    if not isinstance(learner_, learner.Learner):
        raise BentoMLException(
            f"'bentoml.fastai.save_model()' only support saving fastai 'Learner' object. Got {learner.__class__.__name__} instead."
        )

    context = ModelContext(
        framework_name="fastai",
        framework_versions={
            "fastai": get_pkg_version("fastai"),
            "fastcore": get_pkg_version("fastcore"),
            "torch": get_pkg_version("torch"),
        },
    )

    if signatures is None:
        signatures = {"predict": {"batchable": False}}
        logger.info(
            'Using the default model signature for fastai (%s) for model "%s".',
            signatures,
            name,
        )
    batchable_enabled_signatures = [v for v in signatures if signatures[v]["batchable"]]
    if len(batchable_enabled_signatures) > 0:
        message = f"Batchable signatures are not supported for fastai models. The following signatures have batchable sets to 'True': {batchable_enabled_signatures}. Consider using PyTorch layer from the learner model. To learn more, visit https://docs.bentoml.com/en/latest/frameworks/fastai.html."
        raise BentoMLException(message)

    with bentoml.models._create(  # type: ignore
        name,
        module=MODULE_NAME,
        api_version=API_VERSION,
        signatures=signatures,
        labels=labels,
        options=None,
        custom_objects=custom_objects,
        external_modules=external_modules,
        metadata=metadata,
        context=context,
    ) as bento_model:
        learner_.export(bento_model.path_of(MODEL_FILENAME), pickle_module=cloudpickle)
        return bento_model
Save a :code:`fastai.learner.Learner` model instance to the BentoML model store.

If the :func:`save_model` method failed while saving a given learner,
your learner may contain a :obj:`~fastai.callback.core.Callback` that is not picklable.
All FastAI callbacks are stateful, which makes some of them not picklable.
Use :func:`Learner.remove_cbs` to remove unpicklable callbacks.

Args:
name: The name to give to the model in the BentoML store. This must be a valid
:obj:`~bentoml.Tag` name.
learner: :obj:`~fastai.learner.Learner` to be saved.
signatures: Signatures of predict methods to be used. If not provided, the signatures default to
``predict``. See :obj:`~bentoml.types.ModelSignature` for more details.
labels: A default set of management labels to be associated with the model. An example is ``{"training-set": "data-1"}``.
custom_objects: Custom objects to be saved with the model. An example is ``{"my-normalizer": normalizer}``.
Custom objects are currently serialized with cloudpickle, but this implementation is subject to change.
external_modules (:code:`List[ModuleType]`, `optional`, default to :code:`None`):
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: Metadata to be associated with the model. An example is ``{"bias": 4}``.
Metadata is intended for display in a model management UI and therefore must be a
default Python type, such as :obj:`str` or :obj:`int`.

Returns:
:obj:`~bentoml.Tag`: A tag that can be used to access the saved model from the BentoML model store.

Example:

.. code-block:: python

from fastai.metrics import accuracy
from fastai.text.data import URLs
from fastai.text.data import untar_data
from fastai.text.data import TextDataLoaders
from fastai.text.models import AWD_LSTM
from fastai.text.learner import text_classifier_learner

dls = TextDataLoaders.from_folder(untar_data(URLs.IMDB), valid="test")

learner = text_classifier_learner(dls, AWD_LSTM, drop_mult=0.5, metrics=accuracy)
learner.fine_tune(4, 1e-2)

# Test run the model
learner.model.eval()
learner.predict("I love that movie!")

# `Save` the model with BentoML
tag = bentoml.fastai.save_model("fai_learner", learner)
src/bentoml/_internal/frameworks/fastai.py