Import preprocessor classes from the dynamically loaded NODE_CLASS_MAPPINGS dictionary rather than trying to import directly from module paths. This matches how comfyui_controlnet_aux actually exports its classes. Verified correct node names: - DepthAnythingV2Preprocessor ✓ - DWPreprocessor ✓ - CannyEdgePreprocessor ✓ - OpenposePreprocessor ✓ - MiDaS-DepthMapPreprocessor ✓
210 lines
7.8 KiB
Python
210 lines
7.8 KiB
Python
import comfy.model_management as model_management
|
|
import torch
|
|
import logging
|
|
|
|
# Set up logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MultiGPUPreprocessorWrapper:
|
|
"""
|
|
Base wrapper class that temporarily overrides device placement during preprocessor model loading
|
|
to prevent multi-GPU device conflicts in ControlNet preprocessors.
|
|
|
|
The problem: Preprocessors auto-download models and load them using model_management.get_torch_device(),
|
|
but ComfyUI-MultiGPU monkey-patches this function with dynamic device assignment, causing model
|
|
components to split across devices and trigger "Expected all tensors to be on the same device" errors.
|
|
|
|
The solution: Temporarily override get_torch_device() to return consistent device (cuda:0) during
|
|
model loading, then restore normal MultiGPU behavior.
|
|
"""
|
|
|
|
def __init__(self, preprocessor_class, target_device='cuda:0'):
|
|
self.preprocessor_class = preprocessor_class
|
|
self.target_device = target_device
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
# Must dynamically return the wrapped preprocessor's INPUT_TYPES
|
|
# This will be overridden in specific wrapper subclasses
|
|
if hasattr(cls, 'preprocessor_class'):
|
|
return cls.preprocessor_class.INPUT_TYPES()
|
|
else:
|
|
# Fallback for base class - should not be used directly
|
|
return {"required": {}}
|
|
|
|
RETURN_TYPES = ("IMAGE",) # Most ControlNet preprocessors return IMAGE
|
|
FUNCTION = "execute"
|
|
CATEGORY = "preprocessors/gpu_wrapper"
|
|
|
|
def execute(self, **kwargs):
|
|
"""
|
|
Execute preprocessor with temporary device override to prevent multi-GPU conflicts.
|
|
"""
|
|
# Critical: Save original function
|
|
original_get_device = model_management.get_torch_device
|
|
|
|
try:
|
|
# Temporarily override with consistent device
|
|
model_management.get_torch_device = lambda: torch.device(self.target_device)
|
|
|
|
# Create and execute original preprocessor
|
|
preprocessor = self.preprocessor_class()
|
|
result = preprocessor.execute(**kwargs)
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in MultiGPUPreprocessorWrapper execution: {e}")
|
|
raise
|
|
|
|
finally:
|
|
# ALWAYS restore original function, even on exception
|
|
model_management.get_torch_device = original_get_device
|
|
|
|
|
|
# Import and create specific wrapper instances with error handling
|
|
|
|
# DepthAnything V2 Wrapper
|
|
try:
|
|
from comfyui_controlnet_aux import NODE_CLASS_MAPPINGS as AUX_NODE_MAPPINGS
|
|
DepthAnythingV2Preprocessor = AUX_NODE_MAPPINGS["DepthAnythingV2Preprocessor"]
|
|
|
|
class DepthAnythingV2Wrapper(MultiGPUPreprocessorWrapper):
|
|
def __init__(self):
|
|
super().__init__(DepthAnythingV2Preprocessor, 'cuda:0')
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return DepthAnythingV2Preprocessor.INPUT_TYPES()
|
|
|
|
RETURN_TYPES = DepthAnythingV2Preprocessor.RETURN_TYPES
|
|
CATEGORY = "preprocessors/gpu_wrapper"
|
|
|
|
logger.info("DepthAnythingV2Wrapper loaded successfully")
|
|
|
|
except (ImportError, KeyError) as e:
|
|
logger.warning(f"DepthAnythingV2Preprocessor not available: {e}")
|
|
DepthAnythingV2Wrapper = None
|
|
|
|
|
|
# DWPose Wrapper
|
|
try:
|
|
if 'AUX_NODE_MAPPINGS' not in locals():
|
|
from comfyui_controlnet_aux import NODE_CLASS_MAPPINGS as AUX_NODE_MAPPINGS
|
|
DWPreprocessor = AUX_NODE_MAPPINGS["DWPreprocessor"]
|
|
|
|
class DWPreprocessorWrapper(MultiGPUPreprocessorWrapper):
|
|
def __init__(self):
|
|
super().__init__(DWPreprocessor, 'cuda:0')
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return DWPreprocessor.INPUT_TYPES()
|
|
|
|
RETURN_TYPES = DWPreprocessor.RETURN_TYPES
|
|
CATEGORY = "preprocessors/gpu_wrapper"
|
|
|
|
logger.info("DWPreprocessorWrapper loaded successfully")
|
|
|
|
except (ImportError, KeyError) as e:
|
|
logger.warning(f"DWPreprocessor not available: {e}")
|
|
DWPreprocessorWrapper = None
|
|
|
|
|
|
# Canny Edge Wrapper
|
|
try:
|
|
if 'AUX_NODE_MAPPINGS' not in locals():
|
|
from comfyui_controlnet_aux import NODE_CLASS_MAPPINGS as AUX_NODE_MAPPINGS
|
|
CannyEdgePreprocessor = AUX_NODE_MAPPINGS["CannyEdgePreprocessor"]
|
|
|
|
class CannyEdgePreprocessorWrapper(MultiGPUPreprocessorWrapper):
|
|
def __init__(self):
|
|
super().__init__(CannyEdgePreprocessor, 'cuda:0')
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return CannyEdgePreprocessor.INPUT_TYPES()
|
|
|
|
RETURN_TYPES = CannyEdgePreprocessor.RETURN_TYPES
|
|
CATEGORY = "preprocessors/gpu_wrapper"
|
|
|
|
logger.info("CannyEdgePreprocessorWrapper loaded successfully")
|
|
|
|
except (ImportError, KeyError) as e:
|
|
logger.warning(f"CannyEdgePreprocessor not available: {e}")
|
|
CannyEdgePreprocessorWrapper = None
|
|
|
|
|
|
# OpenPose Wrapper
|
|
try:
|
|
if 'AUX_NODE_MAPPINGS' not in locals():
|
|
from comfyui_controlnet_aux import NODE_CLASS_MAPPINGS as AUX_NODE_MAPPINGS
|
|
OpenposePreprocessor = AUX_NODE_MAPPINGS["OpenposePreprocessor"]
|
|
|
|
class OpenposePreprocessorWrapper(MultiGPUPreprocessorWrapper):
|
|
def __init__(self):
|
|
super().__init__(OpenposePreprocessor, 'cuda:0')
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return OpenposePreprocessor.INPUT_TYPES()
|
|
|
|
RETURN_TYPES = OpenposePreprocessor.RETURN_TYPES
|
|
CATEGORY = "preprocessors/gpu_wrapper"
|
|
|
|
logger.info("OpenposePreprocessorWrapper loaded successfully")
|
|
|
|
except (ImportError, KeyError) as e:
|
|
logger.warning(f"OpenposePreprocessor not available: {e}")
|
|
OpenposePreprocessorWrapper = None
|
|
|
|
|
|
# Midas Depth Map Wrapper
|
|
try:
|
|
if 'AUX_NODE_MAPPINGS' not in locals():
|
|
from comfyui_controlnet_aux import NODE_CLASS_MAPPINGS as AUX_NODE_MAPPINGS
|
|
MidasDepthMapPreprocessor = AUX_NODE_MAPPINGS["MiDaS-DepthMapPreprocessor"]
|
|
|
|
class MidasDepthMapWrapper(MultiGPUPreprocessorWrapper):
|
|
def __init__(self):
|
|
super().__init__(MidasDepthMapPreprocessor, 'cuda:0')
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return MidasDepthMapPreprocessor.INPUT_TYPES()
|
|
|
|
RETURN_TYPES = MidasDepthMapPreprocessor.RETURN_TYPES
|
|
CATEGORY = "preprocessors/gpu_wrapper"
|
|
|
|
logger.info("MidasDepthMapWrapper loaded successfully")
|
|
|
|
except (ImportError, KeyError) as e:
|
|
logger.warning(f"MidasDepthMapPreprocessor not available: {e}")
|
|
MidasDepthMapWrapper = None
|
|
|
|
|
|
# Registration dictionaries
|
|
NODE_CLASS_MAPPINGS = {}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {}
|
|
|
|
# Only register wrappers for available preprocessors
|
|
if DepthAnythingV2Wrapper:
|
|
NODE_CLASS_MAPPINGS["DepthAnythingV2Wrapper"] = DepthAnythingV2Wrapper
|
|
NODE_DISPLAY_NAME_MAPPINGS["DepthAnythingV2Wrapper"] = "DepthAnything V2 (GPU Wrapper)"
|
|
|
|
if DWPreprocessorWrapper:
|
|
NODE_CLASS_MAPPINGS["DWPreprocessorWrapper"] = DWPreprocessorWrapper
|
|
NODE_DISPLAY_NAME_MAPPINGS["DWPreprocessorWrapper"] = "DWPose (GPU Wrapper)"
|
|
|
|
if CannyEdgePreprocessorWrapper:
|
|
NODE_CLASS_MAPPINGS["CannyEdgePreprocessorWrapper"] = CannyEdgePreprocessorWrapper
|
|
NODE_DISPLAY_NAME_MAPPINGS["CannyEdgePreprocessorWrapper"] = "Canny Edge (GPU Wrapper)"
|
|
|
|
if OpenposePreprocessorWrapper:
|
|
NODE_CLASS_MAPPINGS["OpenposePreprocessorWrapper"] = OpenposePreprocessorWrapper
|
|
NODE_DISPLAY_NAME_MAPPINGS["OpenposePreprocessorWrapper"] = "OpenPose (GPU Wrapper)"
|
|
|
|
if MidasDepthMapWrapper:
|
|
NODE_CLASS_MAPPINGS["MidasDepthMapWrapper"] = MidasDepthMapWrapper
|
|
NODE_DISPLAY_NAME_MAPPINGS["MidasDepthMapWrapper"] = "Midas Depth (GPU Wrapper)"
|
|
|
|
logger.info(f"Registered {len(NODE_CLASS_MAPPINGS)} GPU wrapper nodes: {list(NODE_CLASS_MAPPINGS.keys())}") |