From 0d02a2e16c84519fe31f0bd01521cabb4246a518 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 11 Oct 2024 08:17:35 -0400 Subject: [PATCH] fix codeformer Signed-off-by: Vladimir Mandic --- modules/ggml/__init__.py | 25 ++ modules/ggml/gguf_tensor.py | 148 ++++++++++++ modules/ggml/gguf_utils.py | 309 ++++++++++++++++++++++++ modules/images_namegen.py | 8 +- modules/modeldata.py | 2 +- modules/postprocess/codeformer_model.py | 2 +- 6 files changed, 488 insertions(+), 6 deletions(-) create mode 100644 modules/ggml/__init__.py create mode 100644 modules/ggml/gguf_tensor.py create mode 100644 modules/ggml/gguf_utils.py diff --git a/modules/ggml/__init__.py b/modules/ggml/__init__.py new file mode 100644 index 000000000..5cc0b9583 --- /dev/null +++ b/modules/ggml/__init__.py @@ -0,0 +1,25 @@ +from pathlib import Path +import torch +import gguf +from .gguf_utils import TORCH_COMPATIBLE_QTYPES +from .gguf_tensor import GGMLTensor + + +def load_gguf(path: str, compute_dtype: torch.dtype) -> dict[str, GGMLTensor]: + sd: dict[str, GGMLTensor] = {} + reader = gguf.GGUFReader(path) + for tensor in reader.tensors: + torch_tensor = torch.from_numpy(tensor.data) + shape = torch.Size(tuple(int(v) for v in reversed(tensor.shape))) + if tensor.tensor_type in TORCH_COMPATIBLE_QTYPES: + torch_tensor = torch_tensor.view(*shape) + sd[tensor.name] = GGMLTensor(torch_tensor, ggml_quantization_type=tensor.tensor_type, tensor_shape=shape, compute_dtype=compute_dtype) + return sd + + +def load_model(path: str, dtype: torch.dtype) -> torch.nn.Module: + state_dict = load_gguf(path, compute_dtype=dtype) + # TODO create torch.nn.Modules, etc... + # state_dict = state_dict.get("state_dict") or state_dict + for k, v in state_dict.items(): + print(k, type(v)) diff --git a/modules/ggml/gguf_tensor.py b/modules/ggml/gguf_tensor.py new file mode 100644 index 000000000..77b11ed79 --- /dev/null +++ b/modules/ggml/gguf_tensor.py @@ -0,0 +1,148 @@ +# Original: invokeai.backend.quantization.gguf.ggml_tensor + +from typing import overload +import torch +import gguf +from gguf_utils import DEQUANTIZE_FUNCTIONS, TORCH_COMPATIBLE_QTYPES, dequantize + + +def dequantize_and_run(func, args, kwargs): + """A helper function for running math ops on GGMLTensor inputs. + + Dequantizes the inputs, and runs the function. + """ + dequantized_args = [a.get_dequantized_tensor() if hasattr(a, "get_dequantized_tensor") else a for a in args] + dequantized_kwargs = { + k: v.get_dequantized_tensor() if hasattr(v, "get_dequantized_tensor") else v for k, v in kwargs.items() + } + return func(*dequantized_args, **dequantized_kwargs) + + +def apply_to_quantized_tensor(func, args, kwargs): + """A helper function to apply a function to a quantized GGML tensor, and re-wrap the result in a GGMLTensor. + + Assumes that the first argument is a GGMLTensor. + """ + # We expect the first argument to be a GGMLTensor, and all other arguments to be non-GGMLTensors. + ggml_tensor = args[0] + assert isinstance(ggml_tensor, GGMLTensor) + assert all(not isinstance(a, GGMLTensor) for a in args[1:]) + assert all(not isinstance(v, GGMLTensor) for v in kwargs.values()) + + new_data = func(ggml_tensor.quantized_data, *args[1:], **kwargs) + + if new_data.dtype != ggml_tensor.quantized_data.dtype: + # This is intended to catch calls such as `.to(dtype-torch.float32)`, which are not supported on GGMLTensors. + raise ValueError("Operation changed the dtype of GGMLTensor unexpectedly.") + + return GGMLTensor( + new_data, ggml_tensor._ggml_quantization_type, ggml_tensor.tensor_shape, ggml_tensor.compute_dtype + ) + + +GGML_TENSOR_OP_TABLE = { + # Ops to run on the quantized tensor. + torch.ops.aten.detach.default: apply_to_quantized_tensor, # pyright: ignore + torch.ops.aten._to_copy.default: apply_to_quantized_tensor, # pyright: ignore + # Ops to run on dequantized tensors. + torch.ops.aten.t.default: dequantize_and_run, # pyright: ignore + torch.ops.aten.addmm.default: dequantize_and_run, # pyright: ignore + torch.ops.aten.mul.Tensor: dequantize_and_run, # pyright: ignore +} + + +class GGMLTensor(torch.Tensor): + """A torch.Tensor sub-class holding a quantized GGML tensor. + + The underlying tensor is quantized, but the GGMLTensor class provides a dequantized view of the tensor on-the-fly + when it is used in operations. + """ + + @staticmethod + def __new__( + cls, + data: torch.Tensor, + ggml_quantization_type: gguf.GGMLQuantizationType, + tensor_shape: torch.Size, + compute_dtype: torch.dtype, + ): + # Type hinting is not supported for torch.Tensor._make_wrapper_subclass, so we ignore the errors. + return torch.Tensor._make_wrapper_subclass( # pyright: ignore + cls, + data.shape, + dtype=data.dtype, + layout=data.layout, + device=data.device, + strides=data.stride(), + storage_offset=data.storage_offset(), + ) + + def __init__( + self, + data: torch.Tensor, + ggml_quantization_type: gguf.GGMLQuantizationType, + tensor_shape: torch.Size, + compute_dtype: torch.dtype, + ): + self.quantized_data = data + self._ggml_quantization_type = ggml_quantization_type + # The dequantized shape of the tensor. + self.tensor_shape = tensor_shape + self.compute_dtype = compute_dtype + + def __repr__(self, *, tensor_contents=None): + return f"GGMLTensor(type={self._ggml_quantization_type.name}, dequantized_shape=({self.tensor_shape})" + + @overload + def size(self, dim: None = None) -> torch.Size: ... + + @overload + def size(self, dim: int) -> int: ... + + def size(self, dim: int | None = None): + """Return the size of the tensor after dequantization. I.e. the shape that will be used in any math ops.""" + if dim is not None: + return self.tensor_shape[dim] + return self.tensor_shape + + @property + def shape(self) -> torch.Size: # pyright: ignore[reportIncompatibleVariableOverride] pyright doesn't understand this for some reason. + """The shape of the tensor after dequantization. I.e. the shape that will be used in any math ops.""" + return self.size() + + @property + def quantized_shape(self) -> torch.Size: + """The shape of the quantized tensor.""" + return self.quantized_data.shape + + def requires_grad_(self, mode: bool = True) -> torch.Tensor: + """The GGMLTensor class is currently only designed for inference (not training). Setting requires_grad to True + is not supported. This method is a no-op. + """ + return self + + def get_dequantized_tensor(self): + """Return the dequantized tensor. + + Args: + dtype: The dtype of the dequantized tensor. + """ + if self._ggml_quantization_type in TORCH_COMPATIBLE_QTYPES: + return self.quantized_data.to(self.compute_dtype) + elif self._ggml_quantization_type in DEQUANTIZE_FUNCTIONS: + # TODO(ryand): Look into how the dtype param is intended to be used. + return dequantize( + data=self.quantized_data, qtype=self._ggml_quantization_type, oshape=self.tensor_shape, dtype=None + ).to(self.compute_dtype) + else: + # There is no GPU implementation for this quantization type, so fallback to the numpy implementation. + new = gguf.quants.dequantize(self.quantized_data.cpu().numpy(), self._ggml_quantization_type) + return torch.from_numpy(new).to(self.quantized_data.device, dtype=self.compute_dtype) + + @classmethod + def __torch_dispatch__(cls, func, types, args, kwargs): + # We will likely hit cases here in the future where a new op is encountered that is not yet supported. + # The new op simply needs to be added to the GGML_TENSOR_OP_TABLE. + if func in GGML_TENSOR_OP_TABLE: + return GGML_TENSOR_OP_TABLE[func](func, args, kwargs) + return NotImplemented diff --git a/modules/ggml/gguf_utils.py b/modules/ggml/gguf_utils.py new file mode 100644 index 000000000..c6c937380 --- /dev/null +++ b/modules/ggml/gguf_utils.py @@ -0,0 +1,309 @@ +# Original: invokeai.backend.quantization.gguf.utils +# Largely based on https://github.com/city96/ComfyUI-GGUF + +from typing import Callable, Optional, Union + +import gguf +import torch + +TORCH_COMPATIBLE_QTYPES = {None, gguf.GGMLQuantizationType.F32, gguf.GGMLQuantizationType.F16} + +# K Quants # +QK_K = 256 +K_SCALE_SIZE = 12 + + +def get_scale_min(scales: torch.Tensor): + n_blocks = scales.shape[0] + scales = scales.view(torch.uint8) + scales = scales.reshape((n_blocks, 3, 4)) + + d, m, m_d = torch.split(scales, scales.shape[-2] // 3, dim=-2) + + sc = torch.cat([d & 0x3F, (m_d & 0x0F) | ((d >> 2) & 0x30)], dim=-1) + min = torch.cat([m & 0x3F, (m_d >> 4) | ((m >> 2) & 0x30)], dim=-1) + + return (sc.reshape((n_blocks, 8)), min.reshape((n_blocks, 8))) + + +# Legacy Quants # +def dequantize_blocks_Q8_0( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + d, x = split_block_dims(blocks, 2) + d = d.view(torch.float16).to(dtype) + x = x.view(torch.int8) + return d * x + + +def dequantize_blocks_Q5_1( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + d, m, qh, qs = split_block_dims(blocks, 2, 2, 4) + d = d.view(torch.float16).to(dtype) + m = m.view(torch.float16).to(dtype) + qh = to_uint32(qh) + + qh = qh.reshape((n_blocks, 1)) >> torch.arange(32, device=d.device, dtype=torch.int32).reshape(1, 32) + ql = qs.reshape((n_blocks, -1, 1, block_size // 2)) >> torch.tensor( + [0, 4], device=d.device, dtype=torch.uint8 + ).reshape(1, 1, 2, 1) + qh = (qh & 1).to(torch.uint8) + ql = (ql & 0x0F).reshape((n_blocks, -1)) + + qs = ql | (qh << 4) + return (d * qs) + m + + +def dequantize_blocks_Q5_0( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + d, qh, qs = split_block_dims(blocks, 2, 4) + d = d.view(torch.float16).to(dtype) + qh = to_uint32(qh) + + qh = qh.reshape(n_blocks, 1) >> torch.arange(32, device=d.device, dtype=torch.int32).reshape(1, 32) + ql = qs.reshape(n_blocks, -1, 1, block_size // 2) >> torch.tensor( + [0, 4], device=d.device, dtype=torch.uint8 + ).reshape(1, 1, 2, 1) + + qh = (qh & 1).to(torch.uint8) + ql = (ql & 0x0F).reshape(n_blocks, -1) + + qs = (ql | (qh << 4)).to(torch.int8) - 16 + return d * qs + + +def dequantize_blocks_Q4_1( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + d, m, qs = split_block_dims(blocks, 2, 2) + d = d.view(torch.float16).to(dtype) + m = m.view(torch.float16).to(dtype) + + qs = qs.reshape((n_blocks, -1, 1, block_size // 2)) >> torch.tensor( + [0, 4], device=d.device, dtype=torch.uint8 + ).reshape(1, 1, 2, 1) + qs = (qs & 0x0F).reshape(n_blocks, -1) + + return (d * qs) + m + + +def dequantize_blocks_Q4_0( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + d, qs = split_block_dims(blocks, 2) + d = d.view(torch.float16).to(dtype) + + qs = qs.reshape((n_blocks, -1, 1, block_size // 2)) >> torch.tensor( + [0, 4], device=d.device, dtype=torch.uint8 + ).reshape((1, 1, 2, 1)) + qs = (qs & 0x0F).reshape((n_blocks, -1)).to(torch.int8) - 8 + return d * qs + + +def dequantize_blocks_BF16( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + return (blocks.view(torch.int16).to(torch.int32) << 16).view(torch.float32) + + +def dequantize_blocks_Q6_K( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + ( + ql, + qh, + scales, + d, + ) = split_block_dims(blocks, QK_K // 2, QK_K // 4, QK_K // 16) + + scales = scales.view(torch.int8).to(dtype) + d = d.view(torch.float16).to(dtype) + d = (d * scales).reshape((n_blocks, QK_K // 16, 1)) + + ql = ql.reshape((n_blocks, -1, 1, 64)) >> torch.tensor([0, 4], device=d.device, dtype=torch.uint8).reshape( + (1, 1, 2, 1) + ) + ql = (ql & 0x0F).reshape((n_blocks, -1, 32)) + qh = qh.reshape((n_blocks, -1, 1, 32)) >> torch.tensor([0, 2, 4, 6], device=d.device, dtype=torch.uint8).reshape( + (1, 1, 4, 1) + ) + qh = (qh & 0x03).reshape((n_blocks, -1, 32)) + q = (ql | (qh << 4)).to(torch.int8) - 32 + q = q.reshape((n_blocks, QK_K // 16, -1)) + + return (d * q).reshape((n_blocks, QK_K)) + + +def dequantize_blocks_Q5_K( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + d, dmin, scales, qh, qs = split_block_dims(blocks, 2, 2, K_SCALE_SIZE, QK_K // 8) + + d = d.view(torch.float16).to(dtype) + dmin = dmin.view(torch.float16).to(dtype) + + sc, m = get_scale_min(scales) + + d = (d * sc).reshape((n_blocks, -1, 1)) + dm = (dmin * m).reshape((n_blocks, -1, 1)) + + ql = qs.reshape((n_blocks, -1, 1, 32)) >> torch.tensor([0, 4], device=d.device, dtype=torch.uint8).reshape( + (1, 1, 2, 1) + ) + qh = qh.reshape((n_blocks, -1, 1, 32)) >> torch.tensor(list(range(8)), device=d.device, dtype=torch.uint8).reshape( + (1, 1, 8, 1) + ) + ql = (ql & 0x0F).reshape((n_blocks, -1, 32)) + qh = (qh & 0x01).reshape((n_blocks, -1, 32)) + q = ql | (qh << 4) + + return (d * q - dm).reshape((n_blocks, QK_K)) + + +def dequantize_blocks_Q4_K( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + d, dmin, scales, qs = split_block_dims(blocks, 2, 2, K_SCALE_SIZE) + d = d.view(torch.float16).to(dtype) + dmin = dmin.view(torch.float16).to(dtype) + + sc, m = get_scale_min(scales) + + d = (d * sc).reshape((n_blocks, -1, 1)) + dm = (dmin * m).reshape((n_blocks, -1, 1)) + + qs = qs.reshape((n_blocks, -1, 1, 32)) >> torch.tensor([0, 4], device=d.device, dtype=torch.uint8).reshape( + (1, 1, 2, 1) + ) + qs = (qs & 0x0F).reshape((n_blocks, -1, 32)) + + return (d * qs - dm).reshape((n_blocks, QK_K)) + + +def dequantize_blocks_Q3_K( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + hmask, qs, scales, d = split_block_dims(blocks, QK_K // 8, QK_K // 4, 12) + d = d.view(torch.float16).to(dtype) + + lscales, hscales = scales[:, :8], scales[:, 8:] + lscales = lscales.reshape((n_blocks, 1, 8)) >> torch.tensor([0, 4], device=d.device, dtype=torch.uint8).reshape( + (1, 2, 1) + ) + lscales = lscales.reshape((n_blocks, 16)) + hscales = hscales.reshape((n_blocks, 1, 4)) >> torch.tensor( + [0, 2, 4, 6], device=d.device, dtype=torch.uint8 + ).reshape((1, 4, 1)) + hscales = hscales.reshape((n_blocks, 16)) + scales = (lscales & 0x0F) | ((hscales & 0x03) << 4) + scales = scales.to(torch.int8) - 32 + + dl = (d * scales).reshape((n_blocks, 16, 1)) + + ql = qs.reshape((n_blocks, -1, 1, 32)) >> torch.tensor([0, 2, 4, 6], device=d.device, dtype=torch.uint8).reshape( + (1, 1, 4, 1) + ) + qh = hmask.reshape(n_blocks, -1, 1, 32) >> torch.tensor(list(range(8)), device=d.device, dtype=torch.uint8).reshape( + (1, 1, 8, 1) + ) + ql = ql.reshape((n_blocks, 16, QK_K // 16)) & 3 + qh = (qh.reshape((n_blocks, 16, QK_K // 16)) & 1) ^ 1 + q = ql.to(torch.int8) - (qh << 2).to(torch.int8) + + return (dl * q).reshape((n_blocks, QK_K)) + + +def dequantize_blocks_Q2_K( + blocks: torch.Tensor, block_size: int, type_size: int, dtype: Optional[torch.dtype] = None +) -> torch.Tensor: + n_blocks = blocks.shape[0] + + scales, qs, d, dmin = split_block_dims(blocks, QK_K // 16, QK_K // 4, 2) + d = d.view(torch.float16).to(dtype) + dmin = dmin.view(torch.float16).to(dtype) + + # (n_blocks, 16, 1) + dl = (d * (scales & 0xF)).reshape((n_blocks, QK_K // 16, 1)) + ml = (dmin * (scales >> 4)).reshape((n_blocks, QK_K // 16, 1)) + + shift = torch.tensor([0, 2, 4, 6], device=d.device, dtype=torch.uint8).reshape((1, 1, 4, 1)) + + qs = (qs.reshape((n_blocks, -1, 1, 32)) >> shift) & 3 + qs = qs.reshape((n_blocks, QK_K // 16, 16)) + qs = dl * qs - ml + + return qs.reshape((n_blocks, -1)) + + +DEQUANTIZE_FUNCTIONS: dict[ + gguf.GGMLQuantizationType, Callable[[torch.Tensor, int, int, Optional[torch.dtype]], torch.Tensor] +] = { + gguf.GGMLQuantizationType.BF16: dequantize_blocks_BF16, + gguf.GGMLQuantizationType.Q8_0: dequantize_blocks_Q8_0, + gguf.GGMLQuantizationType.Q5_1: dequantize_blocks_Q5_1, + gguf.GGMLQuantizationType.Q5_0: dequantize_blocks_Q5_0, + gguf.GGMLQuantizationType.Q4_1: dequantize_blocks_Q4_1, + gguf.GGMLQuantizationType.Q4_0: dequantize_blocks_Q4_0, + gguf.GGMLQuantizationType.Q6_K: dequantize_blocks_Q6_K, + gguf.GGMLQuantizationType.Q5_K: dequantize_blocks_Q5_K, + gguf.GGMLQuantizationType.Q4_K: dequantize_blocks_Q4_K, + gguf.GGMLQuantizationType.Q3_K: dequantize_blocks_Q3_K, + gguf.GGMLQuantizationType.Q2_K: dequantize_blocks_Q2_K, +} + + +def is_torch_compatible(tensor: Optional[torch.Tensor]): + return getattr(tensor, "tensor_type", None) in TORCH_COMPATIBLE_QTYPES + + +def is_quantized(tensor: torch.Tensor): + return not is_torch_compatible(tensor) + + +def dequantize( + data: torch.Tensor, qtype: gguf.GGMLQuantizationType, oshape: torch.Size, dtype: Optional[torch.dtype] = None +): + """ + Dequantize tensor back to usable shape/dtype + """ + block_size, type_size = gguf.GGML_QUANT_SIZES[qtype] + dequantize_blocks = DEQUANTIZE_FUNCTIONS[qtype] + + rows = data.reshape((-1, data.shape[-1])).view(torch.uint8) + + n_blocks = rows.numel() // type_size + blocks = rows.reshape((n_blocks, type_size)) + blocks = dequantize_blocks(blocks, block_size, type_size, dtype) + return blocks.reshape(oshape) + + +def to_uint32(x: torch.Tensor) -> torch.Tensor: + x = x.view(torch.uint8).to(torch.int32) + return (x[:, 0] | x[:, 1] << 8 | x[:, 2] << 16 | x[:, 3] << 24).unsqueeze(1) + + +def split_block_dims(blocks: torch.Tensor, *args): + n_max = blocks.shape[1] + dims = list(args) + [n_max - sum(args)] + return torch.split(blocks, dims, dim=1) + + +PATCH_TYPES = Union[torch.Tensor, list[torch.Tensor], tuple[torch.Tensor]] diff --git a/modules/images_namegen.py b/modules/images_namegen.py index 3d0f37faa..d88f85a77 100644 --- a/modules/images_namegen.py +++ b/modules/images_namegen.py @@ -34,10 +34,10 @@ class FilenameGenerator: 'timestamp': lambda self: getattr(self.p, "job_timestamp", shared.state.job_timestamp), 'job_timestamp': lambda self: getattr(self.p, "job_timestamp", shared.state.job_timestamp), - 'model': lambda self: shared.sd_model.sd_checkpoint_info.title, - 'model_shortname': lambda self: shared.sd_model.sd_checkpoint_info.model_name, - 'model_name': lambda self: shared.sd_model.sd_checkpoint_info.model_name, - 'model_hash': lambda self: shared.sd_model.sd_checkpoint_info.shorthash, + 'model': lambda self: shared.sd_model.sd_checkpoint_info.title if shared.sd_loaded else '', + 'model_shortname': lambda self: shared.sd_model.sd_checkpoint_info.model_name if shared.sd_loaded else '', + 'model_name': lambda self: shared.sd_model.sd_checkpoint_info.model_name if shared.sd_loaded else '', + 'model_hash': lambda self: shared.sd_model.sd_checkpoint_info.shorthash if shared.sd_loaded else '', 'prompt': lambda self: self.prompt_full(), 'prompt_no_styles': lambda self: self.prompt_no_style(), diff --git a/modules/modeldata.py b/modules/modeldata.py index 52895857d..711d1f71b 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -56,7 +56,7 @@ class Shared(sys.modules[__name__].__class__): def sd_model(self): import modules.sd_models # pylint: disable=W0621 if modules.sd_models.model_data.sd_model is None: - shared.log.debug(f'Model requested: fn={sys._getframe().f_back.f_code.co_name}') # pylint: disable=protected-access + shared.log.debug(f'Model requested: fn={sys._getframe(1).f_code.co_filename}:{sys._getframe(1).f_code.co_name}/{sys._getframe(2).f_code.co_filename}:{sys._getframe(2).f_code.co_name}') # pylint: disable=protected-access return modules.sd_models.model_data.get_sd_model() @sd_model.setter diff --git a/modules/postprocess/codeformer_model.py b/modules/postprocess/codeformer_model.py index 327332db1..c601f2b40 100644 --- a/modules/postprocess/codeformer_model.py +++ b/modules/postprocess/codeformer_model.py @@ -40,7 +40,7 @@ def setup_model(dirname): def create_models(self): try: from modules.postprocess.codeformer_arch import CodeFormer - from facelib.utils.detailer_helper import FaceRestoreHelper + from facelib.utils.face_restoration_helper import FaceRestoreHelper from facelib.detection.retinaface import retinaface except Exception as e: shared.log.error(f"CodeFormer error: {e}")