Fix text encoder offload bug when caching embeddings (#868)
This commit is contained in:
1 parent
35588726de
commit
7c1a76f336
2 files changed
+100
-11
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
import torch
|
import torch
|
||||||
from .manager_modules import LinearLayerMemoryManager, ConvLayerMemoryManager
|
from .manager_modules import LinearLayerMemoryManager, ConvLayerMemoryManager, _DEVICE_STATE
|
||||||
import random
|
import random
|
||||||
|
|
||||||
LINEAR_MODULES = [
|
LINEAR_MODULES = [
|
||||||
@@ -67,9 +67,9 @@ class MemoryManager:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def attach(
|
def attach(
|
||||||
cls,
|
cls,
|
||||||
module: torch.nn.Module,
|
module: torch.nn.Module,
|
||||||
device: torch.device,
|
device: torch.device,
|
||||||
offload_percent: float = 1.0,
|
offload_percent: float = 1.0,
|
||||||
ignore_modules: list[torch.nn.Module] = []
|
ignore_modules: list[torch.nn.Module] = []
|
||||||
):
|
):
|
||||||
@@ -86,7 +86,7 @@ class MemoryManager:
|
|||||||
# add ignore modules to unmanaged list
|
# add ignore modules to unmanaged list
|
||||||
for im in ignore_modules:
|
for im in ignore_modules:
|
||||||
module._memory_manager.unmanaged_modules.append(im)
|
module._memory_manager.unmanaged_modules.append(im)
|
||||||
|
|
||||||
# count ignore modules as processed
|
# count ignore modules as processed
|
||||||
modules_processed = [x for x in ignore_modules]
|
modules_processed = [x for x in ignore_modules]
|
||||||
# attach to all modules
|
# attach to all modules
|
||||||
@@ -113,7 +113,7 @@ class MemoryManager:
|
|||||||
ara = child_module.ara_lora_ref()
|
ara = child_module.ara_lora_ref()
|
||||||
if ara not in modules_processed:
|
if ara not in modules_processed:
|
||||||
MemoryManager.attach(
|
MemoryManager.attach(
|
||||||
ara,
|
ara,
|
||||||
device,
|
device,
|
||||||
)
|
)
|
||||||
modules_processed.append(child_module)
|
modules_processed.append(child_module)
|
||||||
@@ -138,7 +138,7 @@ class MemoryManager:
|
|||||||
ara = child_module.ara_lora_ref()
|
ara = child_module.ara_lora_ref()
|
||||||
if ara not in modules_processed:
|
if ara not in modules_processed:
|
||||||
MemoryManager.attach(
|
MemoryManager.attach(
|
||||||
ara,
|
ara,
|
||||||
device,
|
device,
|
||||||
)
|
)
|
||||||
modules_processed.append(ara)
|
modules_processed.append(ara)
|
||||||
@@ -151,3 +151,76 @@ class MemoryManager:
|
|||||||
module._memory_manager.unmanaged_modules.append(child_module)
|
module._memory_manager.unmanaged_modules.append(child_module)
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def detach(cls, module: torch.nn.Module):
|
||||||
|
"""
|
||||||
|
Reverse of attach(). Moves unmanaged modules back to CPU, restores the
|
||||||
|
original .to() and forward methods on all child layers, unpins CPU weight
|
||||||
|
tensors, and clears the global CUDA device state.
|
||||||
|
|
||||||
|
Call this before unloading/replacing a module that had attach() applied.
|
||||||
|
"""
|
||||||
|
if not hasattr(module, "_memory_manager"):
|
||||||
|
return
|
||||||
|
|
||||||
|
for unmanaged in module._memory_manager.unmanaged_modules:
|
||||||
|
try:
|
||||||
|
if isinstance(unmanaged, torch.nn.Parameter):
|
||||||
|
unmanaged.data = unmanaged.data.to('cpu')
|
||||||
|
else:
|
||||||
|
unmanaged.to('cpu')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if hasattr(module, "_mm_to"):
|
||||||
|
module.to = module._mm_to
|
||||||
|
del module._mm_to
|
||||||
|
|
||||||
|
del module._memory_manager
|
||||||
|
|
||||||
|
for child in module.modules():
|
||||||
|
lmm = getattr(child, "_layer_memory_manager", None)
|
||||||
|
if lmm is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
original_forward = getattr(lmm, "_original_forward", None)
|
||||||
|
if original_forward is not None:
|
||||||
|
if hasattr(child, "ara_lora_ref"):
|
||||||
|
ara = child.ara_lora_ref()
|
||||||
|
if ara is not None:
|
||||||
|
ara.org_forward = original_forward
|
||||||
|
else:
|
||||||
|
child.forward = original_forward
|
||||||
|
|
||||||
|
for param_name in ("weight", "bias"):
|
||||||
|
param = getattr(child, param_name, None)
|
||||||
|
if param is None or not isinstance(param, torch.nn.Parameter):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if param.data.is_pinned():
|
||||||
|
object.__setattr__(
|
||||||
|
child,
|
||||||
|
param_name,
|
||||||
|
torch.nn.Parameter(
|
||||||
|
param.data.clone(),
|
||||||
|
requires_grad=param.requires_grad,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
del child._layer_memory_manager
|
||||||
|
if hasattr(child, "_memory_management_device"):
|
||||||
|
del child._memory_management_device
|
||||||
|
if hasattr(child, "_is_memory_managed"):
|
||||||
|
del child._is_memory_managed
|
||||||
|
|
||||||
|
keys_to_delete = [
|
||||||
|
dev for dev in _DEVICE_STATE
|
||||||
|
if isinstance(dev, torch.device) and dev.type == "cuda"
|
||||||
|
]
|
||||||
|
for key in keys_to_delete:
|
||||||
|
del _DEVICE_STATE[key]
|
||||||
|
|
||||||
|
torch.cuda.empty_cache()
|
||||||
+20
-4
@@ -1,5 +1,7 @@
|
|||||||
|
import gc
|
||||||
import torch
|
import torch
|
||||||
from toolkit.basic import flush
|
from toolkit.basic import flush
|
||||||
|
from toolkit.memory_management import MemoryManager
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
@@ -24,15 +26,21 @@ class FakeTextEncoder(torch.nn.Module):
|
|||||||
@property
|
@property
|
||||||
def device(self):
|
def device(self):
|
||||||
return self._device
|
return self._device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dtype(self):
|
def dtype(self):
|
||||||
return self._dtype
|
return self._dtype
|
||||||
|
|
||||||
def to(self, *args, **kwargs):
|
def to(self, *args, **kwargs):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def _detach_and_cpu(te: torch.nn.Module):
|
||||||
|
MemoryManager.detach(te)
|
||||||
|
# bypass any nopped-out .to() override and force an actual CPU move
|
||||||
|
torch.nn.Module.to(te, 'cpu')
|
||||||
|
|
||||||
|
|
||||||
def unload_text_encoder(model: "BaseModel"):
|
def unload_text_encoder(model: "BaseModel"):
|
||||||
# unload the text encoder in a way that will work with all models and will not throw errors
|
# unload the text encoder in a way that will work with all models and will not throw errors
|
||||||
# we need to make it appear as a text encoder module without actually having one so all
|
# we need to make it appear as a text encoder module without actually having one so all
|
||||||
@@ -45,13 +53,15 @@ def unload_text_encoder(model: "BaseModel"):
|
|||||||
|
|
||||||
# the pipeline stores text encoders like text_encoder, text_encoder_2, text_encoder_3, etc.
|
# the pipeline stores text encoders like text_encoder, text_encoder_2, text_encoder_3, etc.
|
||||||
if hasattr(pipe, "text_encoder"):
|
if hasattr(pipe, "text_encoder"):
|
||||||
|
_detach_and_cpu(pipe.text_encoder)
|
||||||
te = FakeTextEncoder(device=model.device_torch, dtype=model.torch_dtype)
|
te = FakeTextEncoder(device=model.device_torch, dtype=model.torch_dtype)
|
||||||
text_encoder_list.append(te)
|
text_encoder_list.append(te)
|
||||||
pipe.text_encoder.to('cpu')
|
|
||||||
pipe.text_encoder = te
|
pipe.text_encoder = te
|
||||||
|
|
||||||
i = 2
|
i = 2
|
||||||
while hasattr(pipe, f"text_encoder_{i}"):
|
while hasattr(pipe, f"text_encoder_{i}"):
|
||||||
|
real_te = getattr(pipe, f"text_encoder_{i}")
|
||||||
|
_detach_and_cpu(real_te)
|
||||||
te = FakeTextEncoder(device=model.device_torch, dtype=model.torch_dtype)
|
te = FakeTextEncoder(device=model.device_torch, dtype=model.torch_dtype)
|
||||||
text_encoder_list.append(te)
|
text_encoder_list.append(te)
|
||||||
setattr(pipe, f"text_encoder_{i}", te)
|
setattr(pipe, f"text_encoder_{i}", te)
|
||||||
@@ -59,6 +69,12 @@ def unload_text_encoder(model: "BaseModel"):
|
|||||||
model.text_encoder = text_encoder_list
|
model.text_encoder = text_encoder_list
|
||||||
else:
|
else:
|
||||||
# only has a single text encoder
|
# only has a single text encoder
|
||||||
model.text_encoder = FakeTextEncoder(device=model.device_torch, dtype=model.torch_dtype)
|
_detach_and_cpu(model.text_encoder)
|
||||||
|
model.text_encoder = FakeTextEncoder(
|
||||||
|
device=model.device_torch,
|
||||||
|
dtype=model.torch_dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
gc.collect()
|
||||||
flush()
|
flush()
|
||||||
Reference in new issue
Block a user