Files

119 lines
4.8 KiB
Python

import torch
import gc
import comfy.model_management as mm
from . import register_node
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
def format_bytes(bytes_val):
return f"{bytes_val / (1024 ** 3):.2f} GB"
@register_node
class MeisoftVRAMDefragmenter:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"anything": (any_type,),
"force_model_unload": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = (any_type, "STRING")
RETURN_NAMES = ("anything", "log")
FUNCTION = "defragment"
CATEGORY = "🔁 Sequential Batcher/Tools"
def defragment(self, anything, force_model_unload):
log_output = []
def _log(msg):
print(msg)
log_output.append(str(msg))
_log(f"\n{'='*65}")
_log(f"🧹 [Secuencial Batcher] NODO: VRAM Defragmenter (Modo Arquitectura Profunda)")
# --- ESTADÍSTICAS ANTES ---
alloc_before = 0
res_before = 0
if torch.cuda.is_available():
alloc_before = torch.cuda.memory_allocated()
res_before = torch.cuda.memory_reserved()
_log(f" -> 📊 Estado Inicial | Asignada: {format_bytes(alloc_before)} | Reservada (Caché): {format_bytes(res_before)}")
# --- FASE 1: CONFIGURACIÓN DINÁMICA DEL ALLOCATOR ---
try:
# Permite que la reserva de VRAM crezca en bloque elástico
torch.cuda.memory._set_allocator_settings("expandable_segments:True")
_log(" -> ⚙️ PyTorch Allocator ajustado a expandable_segments:True")
except Exception as e:
_log(f" -> ⚠️ Aviso: No se pudo ajustar expandable_segments: {e}")
# --- FASE 2: EVACUACIÓN (Opcional) ---
if force_model_unload:
_log(" -> 📦 Trasladando modelos inactivos a la RAM (unload_all_models)...")
mm.unload_all_models()
# --- FASE 3: LIMPIEZA INTERNA DE COMFYUI (LA CLAVE) ---
_log(" -> 🧹 Destruyendo referencias ocultas en el gestor de ComfyUI...")
if hasattr(mm, "cleanup_models"):
mm.cleanup_models()
if hasattr(mm, "cleanup_models_gc"):
mm.cleanup_models_gc()
# --- FASE 4: ROMPER REFERENCIAS PYTHON ---
_log(" -> 🗑️ Forzando Garbage Collector de Python (Doble pasada)...")
gc.collect()
gc.collect() # Doble pasada para romper referencias circulares rebeldes
# --- FASE 5: EVACUACIÓN SUAVE ---
mm.soft_empty_cache()
# --- FASE 6: PURGADO PROFUNDO DE PYTORCH ---
if torch.cuda.is_available():
_log(" -> 🧱 Desfragmentando Caching Allocator de CUDA...")
torch.cuda.ipc_collect()
torch.cuda.synchronize() # 🚀 OPTIMIZACIÓN: Esperar a que terminen los kernels pendientes PRIMERO
torch.cuda.empty_cache() # 🚀 Luego vaciamos la caché de forma segura
# --- FASE 7: LIBERACIÓN DE MEMORIA DEL SISTEMA (Multi-Plataforma) ---
import platform
try:
import ctypes
if platform.system() == "Windows":
# API de Windows: Reduce el 'Working Set' del proceso actual al mínimo
kernel32 = ctypes.windll.kernel32
current_process = kernel32.GetCurrentProcess()
kernel32.SetProcessWorkingSetSize(current_process, -1, -1)
_log(" -> 💻 Working Set reducido (RAM del sistema purgada en Windows).")
else:
# API de Linux/WSL: Libera la memoria de la librería C
ctypes.CDLL('libc.so.6').malloc_trim(0)
_log(" -> 💻 Malloc Trim ejecutado (RAM del sistema purgada en Linux/WSL).")
except Exception as e:
# Si algo falla a nivel de SO, fallamos silenciosamente sin romper el nodo
pass
# --- ESTADÍSTICAS DESPUÉS ---
if torch.cuda.is_available():
alloc_after = torch.cuda.memory_allocated()
res_after = torch.cuda.memory_reserved()
# Cálculos de liberación
freed_alloc = alloc_before - alloc_after
freed_res = res_before - res_after
sign_alloc = "+" if freed_alloc < 0 else "-"
sign_res = "+" if freed_res < 0 else "-"
_log(f" -> 📈 Estado Final | Asignada: {format_bytes(alloc_after)} | Reservada (Caché): {format_bytes(res_after)}")
_log(f" -> ✨ TOTAL LIBERADO | {sign_alloc}{abs(freed_alloc)/(1024**3):.2f} GB (Asignada) | {sign_res}{abs(freed_res)/(1024**3):.2f} GB (Reservada purgada)")
else:
_log(" -> ✨ Limpieza completada (Modo CPU/Otra arquitectura).")
_log(f"{'='*65}\n")
return (anything, "\n".join(log_output))