mirror of
https://github.com/deepfakes/faceswap
synced 2025-06-07 10:43:27 -04:00
* Remove tensorflow_probability requirement * setup.py - fix progress bars * requirements.txt: Remove pre python 3.9 packages * update apple requirements.txt * update INSTALL.md * Remove python<3.9 code * setup.py - fix Windows Installer * typing: python3.9 compliant * Update pytest and readthedocs python versions * typing fixes * Python Version updates - Reduce max version to 3.10 - Default to 3.10 in installers - Remove incompatible 3.11 tests * Update dependencies * Downgrade imageio dep for Windows * typing: merge optional unions and fixes * Updates - min python version 3.10 - typing to python 3.10 spec - remove pre-tf2.10 code - Add conda tests * train: re-enable optimizer saving * Update dockerfiles * Update setup.py - Apple Conda deps to setup.py - Better Cuda + dependency handling * bugfix: Patch logging to prevent Autograph errors * Update dockerfiles * Setup.py - Setup.py - stdout to utf-8 * Add more OSes to github Actions * suppress mac-os end to end test
208 lines
8.8 KiB
Python
208 lines
8.8 KiB
Python
#!/usr/bin python3
|
|
""" Settings manager for Keras Backend """
|
|
from __future__ import annotations
|
|
from contextlib import nullcontext
|
|
import logging
|
|
import typing as T
|
|
|
|
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
# Ignore linting errors from Tensorflow's thoroughly broken import system
|
|
from tensorflow.keras.layers import Activation # pylint:disable=import-error
|
|
from tensorflow.keras.models import load_model as k_load_model, Model # noqa:E501 # pylint:disable=import-error
|
|
|
|
from lib.utils import get_backend
|
|
|
|
if T.TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
logger = logging.getLogger(__name__) # pylint:disable=invalid-name
|
|
|
|
|
|
class KSession():
|
|
""" Handles the settings of backend sessions for inference models.
|
|
|
|
This class acts as a wrapper for various :class:`keras.Model()` functions, ensuring that
|
|
actions performed on a model are handled consistently and can be performed in parallel in
|
|
separate threads.
|
|
|
|
This is an early implementation of this class, and should be expanded out over time.
|
|
|
|
Notes
|
|
-----
|
|
The documentation refers to :mod:`keras`. This is a pseudonym for either :mod:`keras` or
|
|
:mod:`tensorflow.keras` depending on the backend in use.
|
|
|
|
Parameters
|
|
----------
|
|
name: str
|
|
The name of the model that is to be loaded
|
|
model_path: str
|
|
The path to the keras model file
|
|
model_kwargs: dict, optional
|
|
Any kwargs that need to be passed to :func:`keras.models.load_models()`. Default: ``None``
|
|
allow_growth: bool, optional
|
|
Enable the Tensorflow GPU allow_growth configuration option. This option prevents
|
|
Tensorflow from allocating all of the GPU VRAM, but can lead to higher fragmentation and
|
|
slower performance. Default: ``False``
|
|
exclude_gpus: list, optional
|
|
A list of indices correlating to connected GPUs that Tensorflow should not use. Pass
|
|
``None`` to not exclude any GPUs. Default: ``None``
|
|
cpu_mode: bool, optional
|
|
``True`` run the model on CPU. Default: ``False``
|
|
"""
|
|
def __init__(self,
|
|
name: str,
|
|
model_path: str,
|
|
model_kwargs: dict | None = None,
|
|
allow_growth: bool = False,
|
|
exclude_gpus: list[int] | None = None,
|
|
cpu_mode: bool = False) -> None:
|
|
logger.trace("Initializing: %s (name: %s, model_path: %s, " # type:ignore
|
|
"model_kwargs: %s, allow_growth: %s, exclude_gpus: %s, cpu_mode: %s)",
|
|
self.__class__.__name__, name, model_path, model_kwargs, allow_growth,
|
|
exclude_gpus, cpu_mode)
|
|
self._name = name
|
|
self._backend = get_backend()
|
|
self._context = self._set_session(allow_growth,
|
|
[] if exclude_gpus is None else exclude_gpus,
|
|
cpu_mode)
|
|
self._model_path = model_path
|
|
self._model_kwargs = {} if not model_kwargs else model_kwargs
|
|
self._model: Model | None = None
|
|
logger.trace("Initialized: %s", self.__class__.__name__,) # type:ignore
|
|
|
|
def predict(self,
|
|
feed: list[np.ndarray] | np.ndarray,
|
|
batch_size: int | None = None) -> list[np.ndarray] | np.ndarray:
|
|
""" Get predictions from the model.
|
|
|
|
This method is a wrapper for :func:`keras.predict()` function. For Tensorflow backends
|
|
this is a straight call to the predict function.
|
|
|
|
Parameters
|
|
----------
|
|
feed: numpy.ndarray or list
|
|
The feed to be provided to the model as input. This should be a :class:`numpy.ndarray`
|
|
for single inputs or a `list` of :class:`numpy.ndarray` objects for multiple inputs.
|
|
batchsize: int, optional
|
|
The batch size to run prediction at. Default ``None``
|
|
|
|
Returns
|
|
-------
|
|
:class:`numpy.ndarray`
|
|
The predictions from the model
|
|
"""
|
|
assert self._model is not None
|
|
with self._context:
|
|
return self._model.predict(feed, verbose=0, batch_size=batch_size)
|
|
|
|
def _set_session(self,
|
|
allow_growth: bool,
|
|
exclude_gpus: list,
|
|
cpu_mode: bool) -> T.ContextManager:
|
|
""" Sets the backend session options.
|
|
|
|
For CPU backends, this hides any GPUs from Tensorflow.
|
|
|
|
For Nvidia backends, this hides any GPUs that Tensorflow should not use and applies
|
|
any allow growth settings
|
|
|
|
Parameters
|
|
----------
|
|
allow_growth: bool
|
|
Enable the Tensorflow GPU allow_growth configuration option. This option prevents
|
|
Tensorflow from allocating all of the GPU VRAM, but can lead to higher fragmentation
|
|
and slower performance
|
|
exclude_gpus: list
|
|
A list of indices correlating to connected GPUs that Tensorflow should not use. Pass
|
|
``None`` to not exclude any GPUs
|
|
cpu_mode: bool
|
|
``True`` run the model on CPU. Default: ``False``
|
|
"""
|
|
retval = nullcontext()
|
|
if self._backend == "cpu":
|
|
logger.verbose("Hiding GPUs from Tensorflow") # type:ignore
|
|
tf.config.set_visible_devices([], "GPU")
|
|
return retval
|
|
|
|
gpus = tf.config.list_physical_devices('GPU')
|
|
if exclude_gpus:
|
|
gpus = [gpu for idx, gpu in enumerate(gpus) if idx not in exclude_gpus]
|
|
logger.debug("Filtering devices to: %s", gpus)
|
|
tf.config.set_visible_devices(gpus, "GPU")
|
|
|
|
if allow_growth and self._backend == "nvidia":
|
|
for gpu in gpus:
|
|
logger.info("Setting allow growth for GPU: %s", gpu)
|
|
tf.config.experimental.set_memory_growth(gpu, True)
|
|
|
|
if cpu_mode:
|
|
retval = tf.device("/device:cpu:0")
|
|
return retval
|
|
|
|
def load_model(self) -> None:
|
|
""" Loads a model.
|
|
|
|
This method is a wrapper for :func:`keras.models.load_model()`. Loads a model and its
|
|
weights from :attr:`model_path` defined during initialization of this class. Any additional
|
|
``kwargs`` to be passed to :func:`keras.models.load_model()` should also be defined during
|
|
initialization of the class.
|
|
|
|
For Tensorflow backends, the `make_predict_function` method is called on the model to make
|
|
it thread safe.
|
|
"""
|
|
logger.verbose("Initializing plugin model: %s", self._name) # type:ignore
|
|
with self._context:
|
|
self._model = k_load_model(self._model_path, compile=False, **self._model_kwargs)
|
|
self._model.make_predict_function()
|
|
|
|
def define_model(self, function: Callable) -> None:
|
|
""" Defines a model from the given function.
|
|
|
|
This method acts as a wrapper for :class:`keras.models.Model()`.
|
|
|
|
Parameters
|
|
----------
|
|
function: function
|
|
A function that defines a :class:`keras.Model` and returns it's ``inputs`` and
|
|
``outputs``. The function that generates these results should be passed in, NOT the
|
|
results themselves, as the function needs to be executed within the correct context.
|
|
"""
|
|
with self._context:
|
|
self._model = Model(*function())
|
|
|
|
def load_model_weights(self) -> None:
|
|
""" Load model weights for a defined model inside the correct session.
|
|
|
|
This method is a wrapper for :class:`keras.load_weights()`. Once a model has been defined
|
|
in :func:`define_model()` this method can be called to load its weights from the
|
|
:attr:`model_path` defined during initialization of this class.
|
|
|
|
For Tensorflow backends, the `make_predict_function` method is called on the model to make
|
|
it thread safe.
|
|
"""
|
|
logger.verbose("Initializing plugin model: %s", self._name) # type:ignore
|
|
assert self._model is not None
|
|
with self._context:
|
|
self._model.load_weights(self._model_path)
|
|
self._model.make_predict_function()
|
|
|
|
def append_softmax_activation(self, layer_index: int = -1) -> None:
|
|
""" Append a softmax activation layer to a model
|
|
|
|
Occasionally a softmax activation layer needs to be added to a model's output.
|
|
This is a convenience function to append this layer to the loaded model.
|
|
|
|
Parameters
|
|
----------
|
|
layer_index: int, optional
|
|
The layer index of the model to select the output from to use as an input to the
|
|
softmax activation layer. Default: `-1` (The final layer of the model)
|
|
"""
|
|
logger.debug("Appending Softmax Activation to model: (layer_index: %s)", layer_index)
|
|
assert self._model is not None
|
|
with self._context:
|
|
softmax = Activation("softmax", name="softmax")(self._model.layers[layer_index].output)
|
|
self._model = Model(inputs=self._model.input, outputs=[softmax])
|