179 lines
6.5 KiB
Python
179 lines
6.5 KiB
Python
|
|
import torch
|
|
import math
|
|
|
|
class AnyType(str):
|
|
"""A special type that compares equal to any other type.
|
|
This allows the node to connect to any input/output without validation errors."""
|
|
def __ne__(self, __value: object) -> bool:
|
|
return False
|
|
def __eq__(self, __value: object) -> bool:
|
|
return True
|
|
|
|
# Create a universal type instance
|
|
ANY_TYPE = AnyType("*")
|
|
|
|
class DebugPrintNode:
|
|
"""
|
|
A node that prints detailed information about the input data to the console
|
|
(which is captured by the logger). Acts as a pass-through with Deep Inspection.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"data": (ANY_TYPE,), # Universal input
|
|
"prefix": ("STRING", {"default": "DEBUG"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (ANY_TYPE,)
|
|
RETURN_NAMES = ("data",)
|
|
FUNCTION = "debug_print"
|
|
CATEGORY = "ComfyUI-Doctor"
|
|
|
|
def debug_print(self, data, prefix):
|
|
print(f"\n[{prefix}] Data Inspection:")
|
|
|
|
try:
|
|
self._recursive_inspect(data, depth=0)
|
|
except Exception as e:
|
|
print(f" ERROR: failed to inspect data: {e}")
|
|
|
|
return (data,)
|
|
|
|
def _recursive_inspect(self, data, depth=0):
|
|
indent = " " * (depth + 1)
|
|
|
|
# Max depth safety
|
|
if depth > 3:
|
|
print(f"{indent}Max recursion depth reached...")
|
|
return
|
|
|
|
# --- 1. Tensor Inspection ---
|
|
if isinstance(data, torch.Tensor):
|
|
self._inspect_tensor(data, indent)
|
|
return
|
|
|
|
# --- 2. List/Tuple (Potentially CONDITIONING or LATENT batches) ---
|
|
if isinstance(data, (list, tuple)):
|
|
print(f"{indent}Type: {type(data).__name__} (len={len(data)})")
|
|
|
|
# Special Case: CONDITIONING is usually [[Tensor, Dict], [Tensor, Dict]...]
|
|
if len(data) > 0 and isinstance(data[0], (list, tuple)) and len(data[0]) == 2:
|
|
if isinstance(data[0][0], torch.Tensor) and isinstance(data[0][1], dict):
|
|
print(f"{indent}INFO: Detected Structure: CONDITIONING (Prompt + Meta)")
|
|
|
|
# Inspect first and last item if long
|
|
if len(data) > 0:
|
|
print(f"{indent}[0]:")
|
|
self._recursive_inspect(data[0], depth + 1)
|
|
|
|
if len(data) > 1:
|
|
print(f"{indent}... (hidden {len(data)-1} items)")
|
|
return
|
|
|
|
# --- 3. Dictionary (Potentially LATENT or Model Config) ---
|
|
if isinstance(data, dict):
|
|
print(f"{indent}Type: Dict (keys={list(data.keys())})")
|
|
|
|
# Special Case: LATENT
|
|
if "samples" in data:
|
|
print(f"{indent}INFO: Detected Structure: LATENT")
|
|
print(f"{indent}Key 'samples':")
|
|
self._recursive_inspect(data["samples"], depth + 1)
|
|
|
|
# Check for other latent keys
|
|
for k in ["noise_mask", "batch_index"]:
|
|
if k in data:
|
|
print(f"{indent}Key '{k}': Present")
|
|
|
|
# Generic Dict inspection (first few keys)
|
|
else:
|
|
for i, (k, v) in enumerate(data.items()):
|
|
if i >= 3:
|
|
print(f"{indent}... (remaining keys hidden)")
|
|
break
|
|
print(f"{indent}Key '{k}':")
|
|
self._recursive_inspect(v, depth + 1)
|
|
return
|
|
|
|
# --- 4. Objects (Model, VAE, CLIP) ---
|
|
# Heuristic: Check for common attributes in Comfy/Torch models
|
|
if hasattr(data, "model_config") or hasattr(data, "get_model") or hasattr(data, "encode_token_weights"):
|
|
print(f"{indent}Type: Object ({type(data).__name__})")
|
|
|
|
# Try to grab useful attributes
|
|
if hasattr(data, "model_type"):
|
|
print(f"{indent}Attr 'model_type': {data.model_type}")
|
|
|
|
if hasattr(data, "load_device"):
|
|
print(f"{indent}Attr 'load_device': {data.load_device}")
|
|
|
|
if hasattr(data, "current_device"):
|
|
print(f"{indent}Attr 'current_device': {data.current_device}")
|
|
|
|
if hasattr(data, "dtype"):
|
|
print(f"{indent}Attr 'dtype': {data.dtype}")
|
|
|
|
return
|
|
|
|
# --- Base Case: Primitives ---
|
|
print(f"{indent}Value: {str(data)[:200]}")
|
|
|
|
|
|
def _inspect_tensor(self, data, indent):
|
|
print(f"{indent}Type: Tensor")
|
|
print(f"{indent}Shape: {data.shape}")
|
|
print(f"{indent}Dtype: {data.dtype}")
|
|
print(f"{indent}Device: {data.device}")
|
|
|
|
# Critical Checks
|
|
if data.is_meta:
|
|
print(f"{indent}WARNING: Meta Tensor (No data)")
|
|
return
|
|
|
|
if hasattr(data, "requires_grad") and data.requires_grad:
|
|
print(f"{indent}WARNING: requires_grad=True (might waste VRAM/time if not training)")
|
|
|
|
# Stats
|
|
if data.numel() > 0:
|
|
# Check for NaN/Inf (torch.isnan/isinf work on GPU)
|
|
has_nan = torch.isnan(data).any().item()
|
|
has_inf = torch.isinf(data).any().item()
|
|
|
|
if has_nan:
|
|
print(f"{indent}CRITICAL: Tensor contains NaN (Not a Number)!")
|
|
if has_inf:
|
|
print(f"{indent}CRITICAL: Tensor contains Inf (Infinity)!")
|
|
|
|
if not has_nan and not has_inf:
|
|
# Only calc stats if safe
|
|
try:
|
|
# P3 optimization: use view + contiguous to avoid full copy
|
|
numel = data.numel()
|
|
if numel > 100000:
|
|
# Use first 10k elements via view (no copy until .contiguous())
|
|
sample = data.view(-1)[:10000].contiguous()
|
|
sample_label = "First 10k"
|
|
else:
|
|
sample = data.view(-1)
|
|
sample_label = "All"
|
|
|
|
d_min = sample.min().item()
|
|
d_max = sample.max().item()
|
|
d_mean = sample.float().mean().item() # Cast to float for mean
|
|
print(f"{indent}Stats ({sample_label}): Min={d_min:.4f}, Max={d_max:.4f}, Mean={d_mean:.4f}")
|
|
except Exception:
|
|
print(f"{indent}Stats: Could not calculate (Error)")
|
|
|
|
# Node Registration
|
|
NODE_CLASS_MAPPINGS = {
|
|
"DebugPrintNode": DebugPrintNode
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"DebugPrintNode": "Smart Debug Node"
|
|
}
|