Skip to content

Commit b51c99a

Browse files
authored
Merge pull request #199 from pollockjj/codex/aimdo-device-fallback
[codex] Fix aimdo device fallback for MultiGPU
2 parents a38cbda + f652162 commit b51c99a

3 files changed

Lines changed: 139 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ What is DisTorch? Standing for "distributed torch", the DisTorch nodes in this c
5252
## 🚀 Compatibility
5353
Works with all .safetensors and GGUF-quantized models.
5454

55+
On current ComfyUI builds with DynamicVRAM/comfy-aimdo enabled, MultiGPU keeps DynamicVRAM active on CUDA devices that comfy-aimdo has initialized and falls back to legacy model patching for off-grid MultiGPU CUDA devices. This preserves MultiGPU placement for devices such as `cuda:1` even when comfy-aimdo only initialized the primary device.
56+
5557
⚙️ Expert users: Like .gguf or exl2/3 LLM loaders, use the expert_mode_alloaction for exact allocations of model shards on as many devices as your setup has!
5658

5759
<p align="center">

__init__.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ def check_module_exists(module_path):
176176
_aimdo_initialized_devices = set()
177177
if isinstance(current_device, torch.device) and current_device.type == "cuda" and current_device.index is not None:
178178
_aimdo_initialized_devices.add(current_device.index)
179+
_aimdo_readiness_cache = {}
180+
_aimdo_readiness_warning_keys = set()
181+
_aimdo_legacy_fallback_devices = set()
179182

180183
def set_current_device(device):
181184
"""Set the current device context for MultiGPU operations."""
@@ -330,8 +333,100 @@ def current_stream_device_aware(device):
330333
logger.info("[MultiGPU] Patched comfy.model_management.current_stream to honor CUDA device arguments")
331334
return True
332335

336+
def _aimdo_device_ready(device):
337+
"""Return True/False for known aimdo readiness, or None when it cannot be probed."""
338+
if not getattr(comfy.memory_management, "aimdo_enabled", False):
339+
return False
340+
341+
target_device = _coerce_torch_device(device)
342+
if target_device is None or target_device.type != "cuda" or target_device.index is None:
343+
return False
344+
if _aimdo_readiness_cache.get(target_device.index) is True:
345+
return True
346+
347+
try:
348+
from comfy_aimdo import control as aimdo_control
349+
get_devctx = getattr(aimdo_control, "get_devctx", None)
350+
if not callable(get_devctx):
351+
warning_key = (target_device.index, "missing_get_devctx")
352+
if warning_key not in _aimdo_readiness_warning_keys:
353+
logger.warning("[MultiGPU] comfy_aimdo.control.get_devctx missing; device readiness is unverified")
354+
_aimdo_readiness_warning_keys.add(warning_key)
355+
_aimdo_readiness_cache[target_device.index] = None
356+
return None
357+
get_devctx(target_device.index)
358+
_aimdo_readiness_cache[target_device.index] = True
359+
return True
360+
except RuntimeError as exc:
361+
if "not initialized" in str(exc).lower():
362+
_aimdo_readiness_cache[target_device.index] = False
363+
return False
364+
warning_key = (target_device.index, str(exc))
365+
if warning_key not in _aimdo_readiness_warning_keys:
366+
logger.warning(f"[MultiGPU] Unexpected comfy_aimdo readiness failure for {target_device}: {exc}")
367+
_aimdo_readiness_warning_keys.add(warning_key)
368+
_aimdo_readiness_cache[target_device.index] = None
369+
return None
370+
except Exception as exc:
371+
warning_key = (target_device.index, str(exc))
372+
if warning_key not in _aimdo_readiness_warning_keys:
373+
logger.warning(f"[MultiGPU] Unexpected comfy_aimdo readiness failure for {target_device}: {exc}")
374+
_aimdo_readiness_warning_keys.add(warning_key)
375+
_aimdo_readiness_cache[target_device.index] = None
376+
return None
377+
378+
def _extract_model_patcher_load_device(args, kwargs):
379+
"""Resolve the effective ModelPatcher load_device from current and future call shapes."""
380+
if "load_device" in kwargs:
381+
return kwargs["load_device"]
382+
if len(args) >= 2:
383+
return args[1]
384+
get_torch_device = getattr(mm, "get_torch_device", None)
385+
if callable(get_torch_device):
386+
return get_torch_device()
387+
return None
388+
389+
def _patch_dynamic_model_patcher_for_aimdo_devices():
390+
"""Route DynamicVRAM only to CUDA devices aimdo actually initialized."""
391+
dynamic_patcher = getattr(comfy.model_patcher, "ModelPatcherDynamic", None)
392+
if dynamic_patcher is None:
393+
return False
394+
dynamic_new = getattr(dynamic_patcher, "__new__", None)
395+
if not callable(dynamic_new):
396+
return False
397+
if getattr(dynamic_new, "_multigpu_aimdo_device_guard", False):
398+
return False
399+
400+
def dynamic_patcher_new_with_aimdo_device_guard(cls, *args, **kwargs):
401+
target_device = _coerce_torch_device(_extract_model_patcher_load_device(args, kwargs))
402+
aimdo_ready = _aimdo_device_ready(target_device)
403+
if (
404+
getattr(comfy.memory_management, "aimdo_enabled", False)
405+
and target_device is not None
406+
and target_device.type == "cuda"
407+
and target_device.index is not None
408+
and aimdo_ready is not True
409+
):
410+
if target_device.index not in _aimdo_legacy_fallback_devices:
411+
reason = (
412+
"no context"
413+
if aimdo_ready is False
414+
else "unverified context"
415+
)
416+
logger.warning(f"[MultiGPU] comfy_aimdo has {reason} for {target_device}; using legacy ModelPatcher")
417+
_aimdo_legacy_fallback_devices.add(target_device.index)
418+
return comfy.model_patcher.ModelPatcher(*args, **kwargs)
419+
420+
return dynamic_new(cls, *args, **kwargs)
421+
422+
dynamic_patcher_new_with_aimdo_device_guard._multigpu_aimdo_device_guard = True
423+
dynamic_patcher_new_with_aimdo_device_guard._multigpu_original = dynamic_new
424+
dynamic_patcher.__new__ = staticmethod(dynamic_patcher_new_with_aimdo_device_guard)
425+
logger.info("[MultiGPU] Patched ModelPatcherDynamic to guard aimdo device coverage")
426+
return True
427+
333428
def _initialize_aimdo_visible_cuda_devices():
334-
"""Ensure DynamicVRAM initializes every visible CUDA device once when enabled."""
429+
"""Configure MultiGPU behavior around comfy-aimdo's initialized CUDA devices."""
335430
if not getattr(comfy.memory_management, "aimdo_enabled", False):
336431
logger.info("[MultiGPU] DynamicVRAM not enabled; skipping multi-device aimdo initialization")
337432
return False
@@ -345,6 +440,44 @@ def _initialize_aimdo_visible_cuda_devices():
345440
logger.warning("[MultiGPU] comfy_aimdo unavailable during multi-device initialization")
346441
return False
347442

443+
device_ids = list(range(torch.cuda.device_count()))
444+
ready_device_ids = []
445+
unverified_device_ids = []
446+
for device_id in device_ids:
447+
ready = _aimdo_device_ready(torch.device("cuda", device_id))
448+
if ready is None:
449+
unverified_device_ids.append(device_id)
450+
continue
451+
if ready:
452+
ready_device_ids.append(device_id)
453+
454+
_aimdo_initialized_devices.clear()
455+
_aimdo_initialized_devices.update(ready_device_ids)
456+
457+
if len(ready_device_ids) == len(device_ids):
458+
logger.info(f"[MultiGPU] comfy_aimdo already initialized for CUDA devices {device_ids}")
459+
return False
460+
461+
if ready_device_ids:
462+
logger.warning(
463+
"[MultiGPU] comfy_aimdo initialized CUDA devices "
464+
f"{ready_device_ids}, not every visible device {device_ids}; "
465+
"leaving initialized devices on DynamicVRAM and falling back per-device elsewhere"
466+
)
467+
elif unverified_device_ids:
468+
logger.warning(
469+
"[MultiGPU] comfy_aimdo readiness could not be verified for CUDA devices "
470+
f"{unverified_device_ids}; falling back to legacy ModelPatcher per device"
471+
)
472+
else:
473+
logger.warning(
474+
"[MultiGPU] comfy_aimdo has no initialized CUDA devices; "
475+
"falling back to legacy ModelPatcher per device"
476+
)
477+
_patch_dynamic_model_patcher_for_aimdo_devices()
478+
if len(device_ids) > 1:
479+
return False
480+
348481
init_device = getattr(aimdo_control, "init_device", None)
349482
if not callable(init_device):
350483
logger.warning("[MultiGPU] comfy_aimdo.control.init_device missing; skipping multi-device initialization")
@@ -357,8 +490,10 @@ def _initialize_aimdo_visible_cuda_devices():
357490
logger.info(f"[MultiGPU] Initializing comfy_aimdo for CUDA device {device_index}")
358491
initialized = bool(init_device(device_index))
359492
logger.info(f"[MultiGPU] comfy_aimdo init_device({device_index}) -> {initialized}")
493+
_aimdo_readiness_cache[device_index] = initialized
360494
if initialized:
361495
_aimdo_initialized_devices.add(device_index)
496+
_aimdo_legacy_fallback_devices.discard(device_index)
362497
initialized_any = True
363498

364499
return initialized_any

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-multigpu"
33
description = "Provides a suite of custom nodes to manage multiple GPUs for ComfyUI, including advanced model offloading for both GGUF and Safetensor formats with DisTorch, and bespoke MultiGPU support for WanVideoWrapper and other custom nodes."
4-
version = "2.6.3"
4+
version = "2.6.4"
55
license = {file = "LICENSE"}
66

77
[project.urls]

0 commit comments

Comments
 (0)