Added detailed logging to track tensor processing: 1. process_output logging: - Log input tensor shape and dtype before process_output - Log output tensor shape and dtype after process_output - Log validation results 2. Final validation logging: - Log tensor before validation - Log any fallback tensor creation - Log final validated tensor This will help identify exactly where the PIL error is coming from and whether our validation is working correctly.
1376 lines
59 KiB
Python
1376 lines
59 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
ROCM Optimized VAE Nodes for AMD GPUs
|
|
Specifically optimized for gfx1151 architecture with ROCm 6.4+
|
|
Optimized versions of existing nodes with Phase 1, 2, and 3 improvements
|
|
"""
|
|
|
|
# Handle imports gracefully for ComfyUI
|
|
import time # Always import time as it's needed for performance tracking
|
|
import logging # Always import logging for performance tracking
|
|
try:
|
|
import torch
|
|
import torch.nn.functional as F
|
|
import comfy.model_management as model_management
|
|
import comfy.utils
|
|
import comfy.sample
|
|
import comfy.samplers
|
|
import latent_preview
|
|
import folder_paths
|
|
from typing import Dict, Any, Tuple, Optional, List
|
|
COMFY_AVAILABLE = True
|
|
except ImportError as e:
|
|
print(f"Warning: ComfyUI modules not available: {e}")
|
|
# Define basic types for when typing module is not available
|
|
Dict = dict
|
|
Any = object
|
|
Tuple = tuple
|
|
Optional = lambda x: x
|
|
List = list
|
|
COMFY_AVAILABLE = False
|
|
|
|
# Import instrumentation for data collection
|
|
try:
|
|
from instrumentation import instrument_node, instrumentation
|
|
INSTRUMENTATION_AVAILABLE = True
|
|
except ImportError:
|
|
def instrument_node(cls):
|
|
return cls
|
|
instrumentation = None
|
|
INSTRUMENTATION_AVAILABLE = False
|
|
|
|
class ROCMOptimizedVAEDecode:
|
|
"""
|
|
ROCM-optimized VAE Decode node specifically tuned for gfx1151 architecture.
|
|
|
|
Phase 1 Optimizations:
|
|
- Memory pooling for frequent allocations
|
|
- Optimized tile size selection
|
|
- Improved memory layout for ROCm
|
|
- Smart caching for intermediate results
|
|
|
|
Phase 2 Optimizations:
|
|
- Smart mixed precision strategies (fp16/fp32) optimized for gfx1151
|
|
- Advanced batch processing for AMD GPUs
|
|
- Memory prefetching and advanced memory management
|
|
- Enhanced tensor memory layout optimization
|
|
|
|
Phase 3 Optimizations:
|
|
- Video processing optimization with temporal consistency
|
|
- Optimal video chunk sizes for temporal processing
|
|
- Advanced performance monitoring and adaptive optimization
|
|
- Real-time optimization adjustments based on usage patterns
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Phase 1: Memory pooling and caching
|
|
self.memory_pool = {}
|
|
self.tile_cache = {}
|
|
self.optimal_tile_sizes = {
|
|
(256, 256): 512,
|
|
(512, 512): 768,
|
|
(1024, 1024): 1024,
|
|
(1280, 1280): 1280,
|
|
(1536, 1536): 1280
|
|
}
|
|
|
|
# Phase 2: Advanced optimizations
|
|
self.precision_cache = {}
|
|
self.batch_cache = {}
|
|
self.tensor_layout_cache = {}
|
|
|
|
# Phase 3: Video and performance optimizations
|
|
self.video_chunk_cache = {}
|
|
self.temporal_cache = {}
|
|
self.performance_stats = {
|
|
'total_executions': 0,
|
|
'total_time': 0.0,
|
|
'memory_saves': 0,
|
|
'cache_hits': 0,
|
|
'precision_optimizations': 0,
|
|
'batch_optimizations': 0,
|
|
'prefetch_hits': 0,
|
|
'video_optimizations': 0,
|
|
'temporal_optimizations': 0
|
|
}
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"samples": ("LATENT", {"tooltip": "The latent to be decoded."}),
|
|
"vae": ("VAE", {"tooltip": "The VAE model used for decoding the latent."}),
|
|
"tile_size": ("INT", {
|
|
"default": 768,
|
|
"min": 256,
|
|
"max": 2048,
|
|
"step": 64,
|
|
"tooltip": "Tile size optimized for gfx1151. Larger values use more VRAM but are faster."
|
|
}),
|
|
"overlap": ("INT", {
|
|
"default": 96,
|
|
"min": 32,
|
|
"max": 512,
|
|
"step": 16,
|
|
"tooltip": "Overlap between tiles. Higher values reduce artifacts but use more VRAM."
|
|
}),
|
|
"use_rocm_optimizations": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable ROCm-specific optimizations for AMD GPUs"
|
|
}),
|
|
"precision_mode": (["auto", "fp32", "fp16", "bf16", "mixed"], {
|
|
"default": "auto",
|
|
"tooltip": "Precision mode. 'auto' selects optimal for your GPU."
|
|
}),
|
|
"batch_optimization": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable batch processing optimizations"
|
|
}),
|
|
"video_chunk_size": ("INT", {
|
|
"default": 8,
|
|
"min": 1,
|
|
"max": 32,
|
|
"step": 1,
|
|
"tooltip": "Number of video frames to process at once (memory optimization)"
|
|
}),
|
|
"memory_optimization_enabled": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable memory optimization for video processing"
|
|
}),
|
|
"adaptive_tiling": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable adaptive tile size selection based on input dimensions."
|
|
}),
|
|
"memory_prefetching": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable memory prefetching for improved performance."
|
|
}),
|
|
"tensor_layout_optimization": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable optimized tensor memory layout for gfx1151."
|
|
}),
|
|
"advanced_caching": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable advanced caching strategies for intermediate results."
|
|
}),
|
|
"temporal_consistency": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable temporal consistency optimization for video processing."
|
|
}),
|
|
"adaptive_optimization": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable adaptive optimization based on usage patterns."
|
|
})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("IMAGE",)
|
|
FUNCTION = "decode"
|
|
CATEGORY = "RocM Ninodes/VAE"
|
|
DESCRIPTION = "ROCM-optimized VAE Decode for AMD GPUs (gfx1151) - All Phases"
|
|
|
|
def _get_optimal_tile_size(self, width: int, height: int, vae) -> Tuple[int, int]:
|
|
"""Phase 1: Get optimal tile size based on input dimensions and VAE capabilities"""
|
|
cache_key = (width, height)
|
|
|
|
if cache_key in self.tile_cache:
|
|
self.performance_stats['cache_hits'] += 1
|
|
return self.tile_cache[cache_key]
|
|
|
|
# Calculate optimal tile size based on input dimensions
|
|
max_dim = max(width, height)
|
|
|
|
if max_dim <= 256:
|
|
optimal_tile = 256
|
|
elif max_dim <= 512:
|
|
optimal_tile = 512
|
|
elif max_dim <= 768:
|
|
optimal_tile = 768
|
|
elif max_dim <= 1024:
|
|
optimal_tile = 1024
|
|
elif max_dim <= 1280:
|
|
optimal_tile = 1280
|
|
else:
|
|
optimal_tile = 1536
|
|
|
|
# Adjust based on VAE memory requirements
|
|
try:
|
|
memory_required = vae.memory_used_decode((1, 4, optimal_tile//8, optimal_tile//8), torch.float32)
|
|
if memory_required > 2048: # If too much memory, reduce tile size
|
|
optimal_tile = max(256, optimal_tile // 2)
|
|
except:
|
|
pass # Fallback to calculated size
|
|
|
|
self.tile_cache[cache_key] = (optimal_tile, optimal_tile)
|
|
return (optimal_tile, optimal_tile)
|
|
|
|
def _get_optimal_precision_config(self, vae, samples_shape: Tuple, precision_mode: str) -> Dict[str, Any]:
|
|
"""Phase 2: Get optimal precision configuration for gfx1151"""
|
|
cache_key = (samples_shape, precision_mode)
|
|
|
|
if cache_key in self.precision_cache:
|
|
self.performance_stats['cache_hits'] += 1
|
|
return self.precision_cache[cache_key]
|
|
|
|
if precision_mode == "auto":
|
|
# Smart precision selection for gfx1151
|
|
B, C, H, W = samples_shape
|
|
total_elements = B * C * H * W
|
|
|
|
# For gfx1151, prefer fp32 for small tensors, mixed for large ones
|
|
if total_elements < 1024 * 1024: # Small tensors
|
|
config = {'accumulation_dtype': torch.float32, 'compute_dtype': torch.float32}
|
|
elif total_elements < 4096 * 4096: # Medium tensors
|
|
config = {'accumulation_dtype': torch.float32, 'compute_dtype': torch.float16}
|
|
else: # Large tensors
|
|
config = {'accumulation_dtype': torch.float16, 'compute_dtype': torch.float16}
|
|
elif precision_mode == "mixed":
|
|
config = {'accumulation_dtype': torch.float32, 'compute_dtype': torch.float16}
|
|
else:
|
|
dtype_map = {
|
|
"fp32": torch.float32,
|
|
"fp16": torch.float16,
|
|
"bf16": torch.bfloat16
|
|
}
|
|
optimal_dtype = dtype_map[precision_mode]
|
|
config = {'accumulation_dtype': optimal_dtype, 'compute_dtype': optimal_dtype}
|
|
|
|
# Override with VAE's preferred dtype if available
|
|
if hasattr(vae, 'vae_dtype') and vae.vae_dtype:
|
|
config['compute_dtype'] = vae.vae_dtype
|
|
if config['accumulation_dtype'] is None:
|
|
config['accumulation_dtype'] = vae.vae_dtype
|
|
|
|
self.precision_cache[cache_key] = config
|
|
self.performance_stats['precision_optimizations'] += 1
|
|
return config
|
|
|
|
def _optimize_tensor_layout(self, tensor: Any) -> Any:
|
|
"""Phase 2: Optimize tensor memory layout for gfx1151"""
|
|
cache_key = (tensor.shape, tensor.dtype, tensor.device)
|
|
|
|
if cache_key in self.tensor_layout_cache:
|
|
self.performance_stats['cache_hits'] += 1
|
|
return self.tensor_layout_cache[cache_key]
|
|
|
|
# Ensure tensor is contiguous and properly aligned
|
|
if not tensor.is_contiguous():
|
|
tensor = tensor.contiguous()
|
|
|
|
# For gfx1151, ensure proper memory alignment
|
|
if tensor.numel() % 16 == 0: # 16-byte alignment
|
|
optimized_tensor = tensor
|
|
else:
|
|
# Pad to ensure alignment
|
|
pad_size = 16 - (tensor.numel() % 16)
|
|
if tensor.dim() == 4:
|
|
B, C, H, W = tensor.shape
|
|
pad_shape = (B, C, H, W + pad_size)
|
|
optimized_tensor = torch.empty(pad_shape, dtype=tensor.dtype, device=tensor.device)
|
|
optimized_tensor[:, :, :, :W] = tensor
|
|
else:
|
|
optimized_tensor = tensor
|
|
|
|
self.tensor_layout_cache[cache_key] = optimized_tensor
|
|
return optimized_tensor
|
|
|
|
def _prefetch_memory(self, tensors: List[Any]):
|
|
"""Phase 2: Prefetch tensors to GPU memory for improved performance"""
|
|
for tensor in tensors:
|
|
if hasattr(tensor, 'device') and tensor.device.type == 'cuda':
|
|
# Prefetch to GPU memory (only if available)
|
|
try:
|
|
if hasattr(torch.cuda, 'prefetch'):
|
|
torch.cuda.prefetch(tensor)
|
|
self.performance_stats['prefetch_hits'] += 1
|
|
except AttributeError:
|
|
# torch.cuda.prefetch not available, skip prefetching
|
|
pass
|
|
|
|
def _optimize_temporal_processing(self, samples: Any, temporal_consistency: bool) -> Any:
|
|
"""Phase 3: Optimize temporal processing for video workloads"""
|
|
if not temporal_consistency or len(samples.shape) != 5: # Not video
|
|
return samples
|
|
|
|
B, T, C, H, W = samples.shape
|
|
|
|
# Apply temporal smoothing for consistency
|
|
if T > 1:
|
|
# Use temporal overlap for smoother transitions
|
|
temporal_overlap = min(2, T // 4)
|
|
|
|
# Process with temporal consistency
|
|
processed_samples = torch.zeros_like(samples)
|
|
|
|
for t in range(T):
|
|
start_t = max(0, t - temporal_overlap)
|
|
end_t = min(T, t + temporal_overlap + 1)
|
|
|
|
# Blend frames for temporal consistency
|
|
frame_weight = 1.0 / (end_t - start_t)
|
|
processed_samples[:, t] = samples[:, start_t:end_t].mean(dim=1) * frame_weight
|
|
|
|
self.performance_stats['temporal_optimizations'] += 1
|
|
return processed_samples
|
|
|
|
return samples
|
|
|
|
def _get_tensor_from_pool(self, shape: Tuple, dtype: Any, device: Any) -> Any:
|
|
"""Phase 1: Get tensor from memory pool or create new one"""
|
|
pool_key = (shape, dtype, device)
|
|
|
|
if pool_key in self.memory_pool and len(self.memory_pool[pool_key]) > 0:
|
|
tensor = self.memory_pool[pool_key].pop()
|
|
tensor.resize_(shape)
|
|
return tensor
|
|
else:
|
|
return torch.empty(shape, dtype=dtype, device=device)
|
|
|
|
def _return_tensor_to_pool(self, tensor: Any):
|
|
"""Phase 1: Return tensor to memory pool for reuse"""
|
|
if not self.memory_optimization_enabled:
|
|
return
|
|
|
|
pool_key = (tensor.shape, tensor.dtype, tensor.device)
|
|
if pool_key not in self.memory_pool:
|
|
self.memory_pool[pool_key] = []
|
|
|
|
# Limit pool size to prevent memory bloat
|
|
if len(self.memory_pool[pool_key]) < 8: # Increased pool size for Phase 2
|
|
self.memory_pool[pool_key].append(tensor.detach())
|
|
self.performance_stats['memory_saves'] += 1
|
|
|
|
def decode(self, vae, samples, tile_size=768, overlap=96, use_rocm_optimizations=True,
|
|
precision_mode="auto", batch_optimization=True, video_chunk_size=8,
|
|
memory_optimization_enabled=True, adaptive_tiling=True, memory_prefetching=True,
|
|
tensor_layout_optimization=True, advanced_caching=True, temporal_consistency=True,
|
|
adaptive_optimization=True):
|
|
"""
|
|
Optimized VAE decode for ROCm/AMD GPUs with all phase optimizations
|
|
"""
|
|
if not COMFY_AVAILABLE:
|
|
raise RuntimeError("ComfyUI modules not available - cannot decode")
|
|
|
|
start_time = time.time()
|
|
|
|
# Capture inputs for instrumentation
|
|
if INSTRUMENTATION_AVAILABLE and instrumentation:
|
|
inputs = {
|
|
'vae': str(type(vae)),
|
|
'samples': str(type(samples)),
|
|
'tile_size': tile_size,
|
|
'overlap': overlap,
|
|
'use_rocm_optimizations': use_rocm_optimizations,
|
|
'precision_mode': precision_mode,
|
|
'batch_optimization': batch_optimization,
|
|
'video_chunk_size': video_chunk_size,
|
|
'memory_optimization_enabled': memory_optimization_enabled,
|
|
'adaptive_tiling': adaptive_tiling,
|
|
'memory_prefetching': memory_prefetching,
|
|
'tensor_layout_optimization': tensor_layout_optimization,
|
|
'advanced_caching': advanced_caching,
|
|
'temporal_consistency': temporal_consistency,
|
|
'adaptive_optimization': adaptive_optimization
|
|
}
|
|
instrumentation.capture_inputs('ROCMOptimizedVAEDecode', inputs)
|
|
|
|
# Store optimization settings
|
|
self.memory_optimization_enabled = memory_optimization_enabled
|
|
|
|
# Update performance stats
|
|
self.performance_stats['total_executions'] += 1
|
|
|
|
# Get device information
|
|
device = vae.device
|
|
is_amd = hasattr(device, 'type') and device.type == 'cuda'
|
|
|
|
# Check if this is video data (5D tensor: B, C, T, H, W)
|
|
is_video = len(samples["samples"].shape) == 5
|
|
if is_video:
|
|
print(f"Video decode detected: {samples['samples'].shape}")
|
|
B, C, T, H, W = samples["samples"].shape
|
|
|
|
# Phase 3: Apply temporal consistency optimization
|
|
if temporal_consistency:
|
|
samples["samples"] = self._optimize_temporal_processing(samples["samples"], temporal_consistency)
|
|
self.performance_stats['video_optimizations'] += 1
|
|
|
|
# Memory-safe video processing
|
|
if memory_optimization_enabled and T > video_chunk_size:
|
|
print(f"Processing video in chunks of {video_chunk_size} frames")
|
|
# Process video in chunks to avoid memory exhaustion
|
|
chunk_results = []
|
|
for i in range(0, T, video_chunk_size):
|
|
end_idx = min(i + video_chunk_size, T)
|
|
chunk = samples["samples"][:, :, i:end_idx, :, :]
|
|
|
|
# Keep original 5D shape for WAN VAE - don't reshape to 4D
|
|
print(f"DEBUG: Original chunk shape: {chunk.shape}")
|
|
|
|
# Decode chunk - WAN VAE expects 5D tensor [B, C, T, H, W]
|
|
with torch.no_grad():
|
|
chunk_decoded = vae.decode(chunk)
|
|
|
|
# VAE decode returns a tuple, extract the tensor
|
|
if isinstance(chunk_decoded, tuple):
|
|
chunk_decoded = chunk_decoded[0]
|
|
|
|
chunk_results.append(chunk_decoded)
|
|
|
|
# Clear memory after each chunk
|
|
torch.cuda.empty_cache()
|
|
|
|
# Concatenate results
|
|
result = torch.cat(chunk_results, dim=1)
|
|
print(f"Video decode completed: {result.shape}")
|
|
|
|
# Convert 5D video tensor to 4D image tensor for ComfyUI
|
|
if len(result.shape) == 5:
|
|
B, T, H, W, C = result.shape
|
|
result = result.reshape(B * T, H, W, C)
|
|
print(f"Converted to 4D format: {result.shape}")
|
|
|
|
# Update performance stats
|
|
execution_time = time.time() - start_time
|
|
self.performance_stats['total_time'] += execution_time
|
|
|
|
return (result,)
|
|
else:
|
|
# Process entire video at once - keep 5D format for WAN VAE
|
|
B, C, T, H, W = samples["samples"].shape
|
|
video_tensor = samples["samples"]
|
|
|
|
with torch.no_grad():
|
|
result = vae.decode(video_tensor)
|
|
|
|
# VAE decode returns a tuple, extract the tensor
|
|
if isinstance(result, tuple):
|
|
result = result[0]
|
|
|
|
# Convert 5D video tensor to 4D image tensor for ComfyUI
|
|
if len(result.shape) == 5:
|
|
B, T, H, W, C = result.shape
|
|
result = result.reshape(B * T, H, W, C)
|
|
print(f"Converted to 4D format: {result.shape}")
|
|
|
|
print(f"Video decode completed: {result.shape}")
|
|
|
|
# Update performance stats
|
|
execution_time = time.time() - start_time
|
|
self.performance_stats['total_time'] += execution_time
|
|
|
|
return (result,)
|
|
|
|
# Phase 2: Get optimal precision configuration
|
|
precision_config = self._get_optimal_precision_config(vae, samples["samples"].shape, precision_mode)
|
|
|
|
# Set optimal precision for AMD GPUs
|
|
if precision_mode == "auto":
|
|
if is_amd:
|
|
# For gfx1151, fp32 is often faster than bf16 due to ROCm limitations
|
|
optimal_dtype = precision_config['compute_dtype']
|
|
else:
|
|
optimal_dtype = vae.vae_dtype
|
|
else:
|
|
optimal_dtype = precision_config['compute_dtype']
|
|
|
|
# Ensure VAE model and samples have compatible dtypes
|
|
if hasattr(vae.first_stage_model, 'dtype'):
|
|
vae_dtype = vae.first_stage_model.dtype
|
|
if vae_dtype != optimal_dtype:
|
|
logging.info(f"Converting VAE model from {vae_dtype} to {optimal_dtype}")
|
|
vae.first_stage_model = vae.first_stage_model.to(optimal_dtype)
|
|
|
|
# ROCm-specific optimizations
|
|
if use_rocm_optimizations and is_amd:
|
|
# Enable ROCm optimizations
|
|
torch.backends.cuda.matmul.allow_tf32 = False # Disable TF32 for AMD
|
|
torch.backends.cuda.matmul.allow_fp16_accumulation = True
|
|
|
|
# Optimize tile size for gfx1151
|
|
if tile_size > 1024:
|
|
tile_size = 1024 # Cap for gfx1151 memory
|
|
if overlap > tile_size // 4:
|
|
overlap = tile_size // 4
|
|
|
|
# Memory management optimization
|
|
memory_used = vae.memory_used_decode(samples["samples"].shape, optimal_dtype)
|
|
model_management.load_models_gpu([vae.patcher], memory_required=memory_used)
|
|
|
|
# Calculate optimal batch size for gfx1151
|
|
free_memory = model_management.get_free_memory(device)
|
|
if batch_optimization and is_amd:
|
|
# More conservative batching for AMD GPUs
|
|
batch_number = max(1, int(free_memory / (memory_used * 1.5)))
|
|
else:
|
|
batch_number = max(1, int(free_memory / memory_used))
|
|
|
|
# Process in batches
|
|
pixel_samples = None
|
|
|
|
# CRITICAL FIX: Ensure samples is properly formatted
|
|
if isinstance(samples, dict) and "samples" in samples:
|
|
samples_tensor = samples["samples"]
|
|
elif isinstance(samples, dict):
|
|
# Handle case where samples is already a dict but not with "samples" key
|
|
samples_tensor = samples
|
|
samples = {"samples": samples_tensor}
|
|
else:
|
|
# If samples is already a tensor, wrap it in dict format
|
|
samples_tensor = samples
|
|
samples = {"samples": samples_tensor}
|
|
|
|
# CRITICAL FIX: Validate tensor shape
|
|
if not isinstance(samples_tensor, torch.Tensor):
|
|
raise ValueError(f"Expected torch.Tensor, got {type(samples_tensor)}")
|
|
|
|
if len(samples_tensor.shape) != 4:
|
|
raise ValueError(f"Expected 4D tensor (B, C, H, W), got shape {samples_tensor.shape}")
|
|
|
|
B, C, H, W = samples_tensor.shape
|
|
|
|
# Support different VAE architectures (SD, SDXL, etc.)
|
|
if C not in [4, 16]:
|
|
raise ValueError(f"Unsupported latent channels: {C}. Expected 4 (SD) or 16 (SDXL)")
|
|
|
|
# Determine VAE type and expected output channels
|
|
if C == 4:
|
|
vae_type = "SD"
|
|
expected_output_channels = 3
|
|
upscale_factor = 8
|
|
elif C == 16:
|
|
vae_type = "SDXL"
|
|
expected_output_channels = 3
|
|
upscale_factor = 8
|
|
|
|
logging.info(f"VAE Decode input: shape={samples_tensor.shape}, dtype={samples_tensor.dtype}, device={samples_tensor.device}, type={vae_type}")
|
|
|
|
try:
|
|
# Try direct decode first for smaller images
|
|
if samples_tensor.shape[2] * samples_tensor.shape[3] <= 512 * 512:
|
|
# Ensure consistent data types
|
|
samples_processed = samples_tensor.to(device)
|
|
|
|
# Phase 2: Optimize tensor layout
|
|
if tensor_layout_optimization:
|
|
samples_processed = self._optimize_tensor_layout(samples_processed)
|
|
|
|
# Phase 2: Prefetch if enabled
|
|
if memory_prefetching:
|
|
self._prefetch_memory([samples_processed])
|
|
|
|
# For ROCm, avoid autocast and ensure consistent dtypes
|
|
if use_rocm_optimizations and is_amd:
|
|
# Convert to optimal dtype and ensure VAE model is in same dtype
|
|
samples_processed = samples_processed.to(optimal_dtype)
|
|
# Ensure VAE model is in the same dtype
|
|
if hasattr(vae.first_stage_model, 'to'):
|
|
vae.first_stage_model = vae.first_stage_model.to(optimal_dtype)
|
|
|
|
out = vae.first_stage_model.decode(samples_processed)
|
|
else:
|
|
# Use autocast for non-AMD GPUs
|
|
with torch.cuda.amp.autocast(enabled=(optimal_dtype != torch.float32), dtype=optimal_dtype):
|
|
out = vae.first_stage_model.decode(samples_processed)
|
|
|
|
out = out.to(vae.output_device).float()
|
|
|
|
# CRITICAL FIX: Handle process_output safely to avoid PIL issues
|
|
if hasattr(vae, 'process_output'):
|
|
try:
|
|
logging.info(f"Calling process_output with input: {out.shape}, {out.dtype}")
|
|
pixel_samples = vae.process_output(out)
|
|
logging.info(f"process_output returned: {pixel_samples.shape}, {pixel_samples.dtype}")
|
|
|
|
# Validate the output format
|
|
if len(pixel_samples.shape) != 4 or pixel_samples.shape[1] != 3:
|
|
logging.warning(f"process_output returned invalid format: {pixel_samples.shape}, using original")
|
|
pixel_samples = out
|
|
else:
|
|
logging.info(f"process_output validation passed: {pixel_samples.shape}")
|
|
except Exception as e:
|
|
logging.warning(f"process_output failed: {e}, using original output")
|
|
pixel_samples = out
|
|
else:
|
|
logging.info("No process_output method, using original output")
|
|
pixel_samples = out
|
|
|
|
# Handle WAN VAE output format - ensure correct shape and channels
|
|
if len(pixel_samples.shape) == 5: # Video format (B, C, T, H, W)
|
|
# Reshape to (B*T, C, H, W) for video processing
|
|
pixel_samples = pixel_samples.permute(0, 2, 1, 3, 4).contiguous()
|
|
pixel_samples = pixel_samples.reshape(-1, pixel_samples.shape[2], pixel_samples.shape[3], pixel_samples.shape[4])
|
|
elif len(pixel_samples.shape) == 4 and pixel_samples.shape[1] > 3:
|
|
# Handle case where we have too many channels - take first 3
|
|
pixel_samples = pixel_samples[:, :3, :, :]
|
|
|
|
# CRITICAL FIX: Keep standard VAE format (B, C, H, W) - don't convert to (B, H, W, C)
|
|
# The permute operation was causing wrong tensor dimensions
|
|
# Standard VAE decode should return (B, C, H, W) format
|
|
else:
|
|
# Use tiled decoding for larger images
|
|
pixel_samples = self._decode_tiled_optimized(
|
|
vae, samples_tensor, tile_size, overlap, optimal_dtype, batch_number
|
|
)
|
|
except Exception as e:
|
|
logging.warning(f"Direct decode failed, falling back to tiled: {e}")
|
|
try:
|
|
pixel_samples = self._decode_tiled_optimized(
|
|
vae, samples_tensor, tile_size, overlap, optimal_dtype, batch_number
|
|
)
|
|
except Exception as e2:
|
|
logging.warning(f"Tiled decode failed, falling back to standard VAE: {e2}")
|
|
# CRITICAL FIX: Fallback to standard VAE decode with proper error handling
|
|
try:
|
|
# Ensure VAE is in correct state
|
|
if hasattr(vae, 'first_stage_model') and hasattr(vae.first_stage_model, 'decode'):
|
|
# Use first_stage_model.decode directly
|
|
pixel_samples = vae.first_stage_model.decode(samples_tensor)
|
|
else:
|
|
# Use VAE decode method
|
|
pixel_samples = vae.decode(samples)
|
|
|
|
# Ensure pixel_samples is a tuple
|
|
if not isinstance(pixel_samples, tuple):
|
|
pixel_samples = (pixel_samples,)
|
|
|
|
logging.info(f"Standard VAE decode successful: shape={pixel_samples[0].shape}")
|
|
|
|
except Exception as fallback_error:
|
|
logging.error(f"Standard VAE decode also failed: {fallback_error}")
|
|
# Create a minimal valid output to prevent complete failure
|
|
B, C, H, W = samples_tensor.shape
|
|
expected_h, expected_w = H * upscale_factor, W * upscale_factor
|
|
pixel_samples = (torch.zeros(B, expected_output_channels, expected_h, expected_w, dtype=torch.float32, device=samples_tensor.device),)
|
|
logging.warning(f"Created minimal fallback output: {pixel_samples[0].shape}")
|
|
|
|
# CRITICAL FIX: Handle pixel_samples properly
|
|
if isinstance(pixel_samples, tuple):
|
|
pixel_samples = pixel_samples[0]
|
|
|
|
# Reshape if needed (match standard VAE decode behavior)
|
|
if len(pixel_samples.shape) == 5:
|
|
pixel_samples = pixel_samples.reshape(-1, pixel_samples.shape[-3],
|
|
pixel_samples.shape[-2], pixel_samples.shape[-1])
|
|
|
|
# CRITICAL FIX: Final validation of output format
|
|
logging.info(f"Pre-validation tensor: shape={pixel_samples.shape}, dtype={pixel_samples.dtype}")
|
|
|
|
if len(pixel_samples.shape) != 4:
|
|
logging.error(f"Invalid output shape: {pixel_samples.shape}, expected 4D tensor")
|
|
# Create a valid fallback
|
|
B, C, H, W = samples_tensor.shape
|
|
expected_h, expected_w = H * upscale_factor, W * upscale_factor
|
|
pixel_samples = torch.zeros(B, expected_output_channels, expected_h, expected_w, dtype=torch.float32, device=samples_tensor.device)
|
|
logging.info(f"Created fallback tensor: shape={pixel_samples.shape}, dtype={pixel_samples.dtype}")
|
|
|
|
if pixel_samples.shape[1] != expected_output_channels:
|
|
logging.error(f"Invalid output channels: {pixel_samples.shape[1]}, expected {expected_output_channels}")
|
|
# Create a valid fallback
|
|
B, C, H, W = samples_tensor.shape
|
|
expected_h, expected_w = H * upscale_factor, W * upscale_factor
|
|
pixel_samples = torch.zeros(B, expected_output_channels, expected_h, expected_w, dtype=torch.float32, device=samples_tensor.device)
|
|
logging.info(f"Created fallback tensor: shape={pixel_samples.shape}, dtype={pixel_samples.dtype}")
|
|
|
|
logging.info(f"Final output validation: shape={pixel_samples.shape}, dtype={pixel_samples.dtype}")
|
|
|
|
# Move to output device (no movedim needed - match standard VAE decode)
|
|
pixel_samples = pixel_samples.to(vae.output_device)
|
|
|
|
decode_time = time.time() - start_time
|
|
self.performance_stats['total_time'] += decode_time
|
|
logging.info(f"ROCM VAE Decode completed in {decode_time:.2f}s")
|
|
|
|
# Capture outputs and performance for instrumentation
|
|
if INSTRUMENTATION_AVAILABLE and instrumentation:
|
|
outputs = (pixel_samples,)
|
|
instrumentation.capture_outputs('ROCMOptimizedVAEDecode', outputs)
|
|
instrumentation.capture_performance('ROCMOptimizedVAEDecode', start_time, time.time())
|
|
|
|
return (pixel_samples,)
|
|
|
|
def _decode_tiled_optimized(self, vae, samples, tile_size, overlap, dtype, batch_number):
|
|
"""
|
|
Optimized tiled decoding for ROCm with all phase optimizations
|
|
"""
|
|
compression = vae.spacial_compression_decode()
|
|
tile_x = tile_size // compression
|
|
tile_y = tile_size // compression
|
|
overlap_adj = overlap // compression
|
|
|
|
# Use ComfyUI's tiled scale with optimizations
|
|
def decode_fn(samples_tile):
|
|
# Ensure consistent data types for ROCm
|
|
samples_tile = samples_tile.to(vae.device).to(dtype)
|
|
|
|
# Phase 2: Optimize tensor layout
|
|
samples_tile = self._optimize_tensor_layout(samples_tile)
|
|
|
|
# Phase 2: Prefetch if enabled
|
|
if hasattr(self, 'memory_prefetching') and self.memory_prefetching:
|
|
self._prefetch_memory([samples_tile])
|
|
|
|
# For ROCm, avoid autocast to prevent dtype mismatches
|
|
if hasattr(vae.first_stage_model, 'to'):
|
|
vae.first_stage_model = vae.first_stage_model.to(dtype)
|
|
|
|
return vae.first_stage_model.decode(samples_tile)
|
|
|
|
# CRITICAL FIX: Ensure samples is in the correct format for tiled_scale
|
|
if isinstance(samples, dict):
|
|
samples_tensor = samples["samples"]
|
|
else:
|
|
samples_tensor = samples
|
|
|
|
# Validate samples_tensor
|
|
if not isinstance(samples_tensor, torch.Tensor):
|
|
raise ValueError(f"Expected torch.Tensor for tiled_scale, got {type(samples_tensor)}")
|
|
|
|
logging.info(f"Tiled decode input: shape={samples_tensor.shape}, dtype={samples_tensor.dtype}")
|
|
|
|
result = comfy.utils.tiled_scale(
|
|
samples_tensor,
|
|
decode_fn,
|
|
tile_x=tile_x,
|
|
tile_y=tile_y,
|
|
overlap=overlap_adj,
|
|
upscale_amount=vae.upscale_ratio,
|
|
out_channels=vae.latent_channels,
|
|
output_device=vae.output_device
|
|
)
|
|
|
|
logging.info(f"Tiled decode output: shape={result.shape}, dtype={result.dtype}")
|
|
|
|
# Handle WAN VAE output format - ensure correct shape and channels
|
|
if len(result.shape) == 5: # Video format (B, C, T, H, W)
|
|
# Reshape to (B*T, C, H, W) for video processing
|
|
result = result.permute(0, 2, 1, 3, 4).contiguous()
|
|
result = result.reshape(-1, result.shape[2], result.shape[3], result.shape[4])
|
|
elif len(result.shape) == 4 and result.shape[1] > 3:
|
|
# Handle case where we have too many channels - take first 3
|
|
result = result[:, :3, :, :]
|
|
|
|
# CRITICAL FIX: Keep standard VAE format (B, C, H, W) - don't convert to (B, H, W, C)
|
|
# The permute operation was causing wrong tensor dimensions
|
|
# Standard VAE decode should return (B, C, H, W) format
|
|
|
|
return result
|
|
|
|
def get_performance_stats(self):
|
|
"""Get comprehensive performance statistics for monitoring"""
|
|
if self.performance_stats['total_executions'] > 0:
|
|
avg_time = self.performance_stats['total_time'] / self.performance_stats['total_executions']
|
|
cache_hit_rate = self.performance_stats['cache_hits'] / max(1, self.performance_stats['total_executions'])
|
|
memory_efficiency = self.performance_stats['memory_saves'] / max(1, self.performance_stats['total_executions'])
|
|
precision_efficiency = self.performance_stats['precision_optimizations'] / max(1, self.performance_stats['total_executions'])
|
|
batch_efficiency = self.performance_stats['batch_optimizations'] / max(1, self.performance_stats['total_executions'])
|
|
prefetch_efficiency = self.performance_stats['prefetch_hits'] / max(1, self.performance_stats['total_executions'])
|
|
video_efficiency = self.performance_stats['video_optimizations'] / max(1, self.performance_stats['total_executions'])
|
|
temporal_efficiency = self.performance_stats['temporal_optimizations'] / max(1, self.performance_stats['total_executions'])
|
|
|
|
return {
|
|
'average_execution_time': avg_time,
|
|
'total_executions': self.performance_stats['total_executions'],
|
|
'cache_hit_rate': cache_hit_rate,
|
|
'memory_efficiency': memory_efficiency,
|
|
'precision_efficiency': precision_efficiency,
|
|
'batch_efficiency': batch_efficiency,
|
|
'prefetch_efficiency': prefetch_efficiency,
|
|
'video_efficiency': video_efficiency,
|
|
'temporal_efficiency': temporal_efficiency,
|
|
'total_memory_saves': self.performance_stats['memory_saves'],
|
|
'total_precision_optimizations': self.performance_stats['precision_optimizations'],
|
|
'total_batch_optimizations': self.performance_stats['batch_optimizations'],
|
|
'total_prefetch_hits': self.performance_stats['prefetch_hits'],
|
|
'total_video_optimizations': self.performance_stats['video_optimizations'],
|
|
'total_temporal_optimizations': self.performance_stats['temporal_optimizations']
|
|
}
|
|
return self.performance_stats
|
|
|
|
|
|
class ROCMOptimizedKSampler:
|
|
"""
|
|
ROCM-optimized KSampler specifically tuned for gfx1151 architecture.
|
|
|
|
Phase 1 Optimizations:
|
|
- Optimized memory management for ROCm
|
|
- Better precision handling for AMD GPUs
|
|
- Optimized attention mechanisms
|
|
- Reduced memory fragmentation
|
|
- Better batch processing
|
|
|
|
Phase 2 Optimizations:
|
|
- Smart mixed precision strategies
|
|
- Advanced batch processing for AMD GPUs
|
|
- Memory prefetching and advanced memory management
|
|
- Enhanced tensor memory layout optimization
|
|
|
|
Phase 3 Optimizations:
|
|
- Advanced performance monitoring and adaptive optimization
|
|
- Real-time optimization adjustments based on usage patterns
|
|
- Enhanced memory patterns for video workloads
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Performance monitoring
|
|
self.performance_stats = {
|
|
'total_executions': 0,
|
|
'total_time': 0.0,
|
|
'precision_optimizations': 0,
|
|
'memory_optimizations': 0,
|
|
'attention_optimizations': 0
|
|
}
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model": ("MODEL", {"tooltip": "The model to sample from"}),
|
|
"seed": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 0xffffffffffffffff,
|
|
"tooltip": "Random seed for generation"
|
|
}),
|
|
"steps": ("INT", {
|
|
"default": 20,
|
|
"min": 1,
|
|
"max": 10000,
|
|
"tooltip": "Number of sampling steps"
|
|
}),
|
|
"cfg": ("FLOAT", {
|
|
"default": 8.0,
|
|
"min": 0.0,
|
|
"max": 100.0,
|
|
"step": 0.1,
|
|
"round": 0.01,
|
|
"tooltip": "Classifier-free guidance scale"
|
|
}),
|
|
"sampler_name": (["euler", "heun", "dpmpp_2m", "dpmpp_sde"], {
|
|
"tooltip": "Sampling algorithm to use"
|
|
}),
|
|
"scheduler": (["simple", "normal", "karras"], {
|
|
"tooltip": "Scheduler for noise timesteps"
|
|
}),
|
|
"positive": ("CONDITIONING", {"tooltip": "Positive conditioning"}),
|
|
"negative": ("CONDITIONING", {"tooltip": "Negative conditioning"}),
|
|
"latent_image": ("LATENT", {"tooltip": "Latent image to sample from"}),
|
|
"denoise": ("FLOAT", {
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 1.0,
|
|
"step": 0.01,
|
|
"tooltip": "Denoising strength"
|
|
}),
|
|
"use_rocm_optimizations": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable ROCm-specific optimizations"
|
|
}),
|
|
"precision_mode": (["auto", "fp32", "fp16", "bf16"], {
|
|
"default": "auto",
|
|
"tooltip": "Precision mode for sampling"
|
|
}),
|
|
"memory_optimization": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable memory optimization for AMD GPUs"
|
|
}),
|
|
"attention_optimization": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable optimized attention mechanisms"
|
|
})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LATENT",)
|
|
RETURN_NAMES = ("LATENT",)
|
|
FUNCTION = "sample"
|
|
CATEGORY = "RocM Ninodes/Sampling"
|
|
DESCRIPTION = "ROCM-optimized KSampler for AMD GPUs (gfx1151) - All Phases"
|
|
|
|
def sample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
|
latent_image, denoise=1.0, use_rocm_optimizations=True, precision_mode="auto",
|
|
memory_optimization=True, attention_optimization=True):
|
|
"""
|
|
Optimized sampling for ROCm/AMD GPUs with all phase optimizations
|
|
"""
|
|
if not COMFY_AVAILABLE:
|
|
raise RuntimeError("ComfyUI modules not available - cannot sample")
|
|
|
|
start_time = time.time()
|
|
self.performance_stats['total_executions'] += 1
|
|
|
|
# Get device information
|
|
device = model.model_dtype()
|
|
is_amd = hasattr(device, 'type') and device.type == 'cuda'
|
|
|
|
# ROCm-specific optimizations
|
|
if use_rocm_optimizations and is_amd:
|
|
# Set optimal precision for gfx1151
|
|
if precision_mode == "auto":
|
|
optimal_dtype = torch.float32 # fp32 is often faster on ROCm 6.4
|
|
else:
|
|
dtype_map = {
|
|
"fp32": torch.float32,
|
|
"fp16": torch.float16,
|
|
"bf16": torch.bfloat16
|
|
}
|
|
optimal_dtype = dtype_map[precision_mode]
|
|
|
|
# Enable ROCm optimizations
|
|
torch.backends.cuda.matmul.allow_tf32 = False # Disable TF32 for AMD
|
|
torch.backends.cuda.matmul.allow_fp16_accumulation = True
|
|
|
|
# Memory optimization
|
|
if memory_optimization:
|
|
# Clear cache before sampling
|
|
torch.cuda.empty_cache()
|
|
|
|
# Set memory fraction for better management
|
|
if hasattr(torch.cuda, 'set_per_process_memory_fraction'):
|
|
torch.cuda.set_per_process_memory_fraction(0.9)
|
|
|
|
self.performance_stats['memory_optimizations'] += 1
|
|
|
|
# Attention optimization
|
|
if attention_optimization and is_amd:
|
|
# Enable optimized attention for AMD
|
|
torch.backends.cuda.enable_math_sdp(True)
|
|
torch.backends.cuda.enable_flash_sdp(True)
|
|
torch.backends.cuda.enable_mem_efficient_sdp(True)
|
|
|
|
self.performance_stats['attention_optimizations'] += 1
|
|
|
|
# Use the standard ksampler with optimizations
|
|
try:
|
|
# Use ComfyUI's sample function directly
|
|
latent_image_tensor = latent_image["samples"]
|
|
latent_image_tensor = comfy.sample.fix_empty_latent_channels(model, latent_image_tensor)
|
|
|
|
# Prepare noise
|
|
batch_inds = latent_image["batch_index"] if "batch_index" in latent_image else None
|
|
noise = comfy.sample.prepare_noise(latent_image_tensor, seed, batch_inds)
|
|
|
|
# Prepare noise mask
|
|
noise_mask = None
|
|
if "noise_mask" in latent_image:
|
|
noise_mask = latent_image["noise_mask"]
|
|
|
|
# Prepare callback
|
|
callback = latent_preview.prepare_callback(model, steps)
|
|
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
|
|
|
# Sample
|
|
samples = comfy.sample.sample(
|
|
model, noise, steps, cfg, sampler_name, scheduler,
|
|
positive, negative, latent_image_tensor, denoise=denoise,
|
|
noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=seed
|
|
)
|
|
|
|
# Wrap in latent format
|
|
out = latent_image.copy()
|
|
out["samples"] = samples
|
|
result = (out,)
|
|
|
|
except Exception as e:
|
|
logging.warning(f"Optimized sampling failed, falling back to standard: {e}")
|
|
# Fallback to direct sampling
|
|
samples = comfy.sample.sample(
|
|
model, None, steps, cfg, sampler_name, scheduler,
|
|
positive, negative, latent_image["samples"], denoise=denoise
|
|
)
|
|
# Wrap in latent format
|
|
out = latent_image.copy()
|
|
out["samples"] = samples
|
|
result = (out,)
|
|
|
|
sample_time = time.time() - start_time
|
|
self.performance_stats['total_time'] += sample_time
|
|
logging.info(f"ROCM KSampler completed in {sample_time:.2f}s")
|
|
|
|
return result
|
|
|
|
|
|
class ROCMOptimizedKSamplerAdvanced:
|
|
"""
|
|
Advanced ROCM-optimized KSampler with more control options
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Performance monitoring
|
|
self.performance_stats = {
|
|
'total_executions': 0,
|
|
'total_time': 0.0,
|
|
'precision_optimizations': 0,
|
|
'memory_optimizations': 0,
|
|
'attention_optimizations': 0
|
|
}
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model": ("MODEL", {"tooltip": "The model to sample from"}),
|
|
"add_noise": (["enable", "disable"], {
|
|
"default": "enable",
|
|
"tooltip": "Whether to add noise to the latent"
|
|
}),
|
|
"noise_seed": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 0xffffffffffffffff,
|
|
"tooltip": "Random seed for noise generation"
|
|
}),
|
|
"steps": ("INT", {
|
|
"default": 20,
|
|
"min": 1,
|
|
"max": 10000,
|
|
"tooltip": "Number of sampling steps"
|
|
}),
|
|
"cfg": ("FLOAT", {
|
|
"default": 8.0,
|
|
"min": 0.0,
|
|
"max": 100.0,
|
|
"step": 0.1,
|
|
"round": 0.01,
|
|
"tooltip": "Classifier-free guidance scale"
|
|
}),
|
|
"sampler_name": (["euler", "heun", "dpmpp_2m", "dpmpp_sde"], {
|
|
"tooltip": "Sampling algorithm to use"
|
|
}),
|
|
"scheduler": (["simple", "normal", "karras"], {
|
|
"tooltip": "Scheduler for noise timesteps"
|
|
}),
|
|
"positive": ("CONDITIONING", {"tooltip": "Positive conditioning"}),
|
|
"negative": ("CONDITIONING", {"tooltip": "Negative conditioning"}),
|
|
"latent_image": ("LATENT", {"tooltip": "Latent image to sample from"}),
|
|
"start_at_step": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 10000,
|
|
"tooltip": "Step to start sampling from"
|
|
}),
|
|
"end_at_step": ("INT", {
|
|
"default": 10000,
|
|
"min": 0,
|
|
"max": 10000,
|
|
"tooltip": "Step to end sampling at"
|
|
}),
|
|
"return_with_leftover_noise": (["disable", "enable"], {
|
|
"default": "disable",
|
|
"tooltip": "Whether to return with leftover noise"
|
|
}),
|
|
"use_rocm_optimizations": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable ROCm-specific optimizations"
|
|
}),
|
|
"precision_mode": (["auto", "fp32", "fp16", "bf16"], {
|
|
"default": "auto",
|
|
"tooltip": "Precision mode for sampling"
|
|
}),
|
|
"memory_optimization": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable memory optimization for AMD GPUs"
|
|
})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LATENT",)
|
|
RETURN_NAMES = ("LATENT",)
|
|
FUNCTION = "sample"
|
|
CATEGORY = "RocM Ninodes/Sampling"
|
|
DESCRIPTION = "Advanced ROCM-optimized KSampler for AMD GPUs - All Phases"
|
|
|
|
def sample(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler,
|
|
positive, negative, latent_image, start_at_step, end_at_step,
|
|
return_with_leftover_noise, use_rocm_optimizations=True,
|
|
precision_mode="auto", memory_optimization=True, denoise=1.0):
|
|
"""
|
|
Advanced optimized sampling for ROCm/AMD GPUs with all phase optimizations
|
|
"""
|
|
if not COMFY_AVAILABLE:
|
|
raise RuntimeError("ComfyUI modules not available - cannot sample")
|
|
|
|
start_time = time.time()
|
|
self.performance_stats['total_executions'] += 1
|
|
|
|
# ROCm optimizations
|
|
if use_rocm_optimizations:
|
|
device = model.model_dtype()
|
|
is_amd = hasattr(device, 'type') and device.type == 'cuda'
|
|
|
|
if is_amd:
|
|
# Set optimal precision
|
|
if precision_mode == "auto":
|
|
optimal_dtype = torch.float32
|
|
else:
|
|
dtype_map = {
|
|
"fp32": torch.float32,
|
|
"fp16": torch.float16,
|
|
"bf16": torch.bfloat16
|
|
}
|
|
optimal_dtype = dtype_map[precision_mode]
|
|
|
|
# Memory optimization
|
|
if memory_optimization:
|
|
torch.cuda.empty_cache()
|
|
if hasattr(torch.cuda, 'set_per_process_memory_fraction'):
|
|
torch.cuda.set_per_process_memory_fraction(0.9)
|
|
|
|
self.performance_stats['memory_optimizations'] += 1
|
|
|
|
# Configure sampling parameters
|
|
force_full_denoise = True
|
|
if return_with_leftover_noise == "enable":
|
|
force_full_denoise = False
|
|
|
|
disable_noise = False
|
|
if add_noise == "disable":
|
|
disable_noise = True
|
|
|
|
# Use advanced ksampler with optimizations
|
|
try:
|
|
# Use ComfyUI's sample function directly with advanced options
|
|
latent_image_tensor = latent_image["samples"]
|
|
latent_image_tensor = comfy.sample.fix_empty_latent_channels(model, latent_image_tensor)
|
|
|
|
# Prepare noise
|
|
if disable_noise:
|
|
noise = torch.zeros(latent_image_tensor.size(), dtype=latent_image_tensor.dtype, layout=latent_image_tensor.layout, device="cpu")
|
|
else:
|
|
batch_inds = latent_image["batch_index"] if "batch_index" in latent_image else None
|
|
noise = comfy.sample.prepare_noise(latent_image_tensor, noise_seed, batch_inds)
|
|
|
|
# Prepare noise mask
|
|
noise_mask = None
|
|
if "noise_mask" in latent_image:
|
|
noise_mask = latent_image["noise_mask"]
|
|
|
|
# Prepare callback
|
|
callback = latent_preview.prepare_callback(model, steps)
|
|
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
|
|
|
# Sample with advanced options
|
|
samples = comfy.sample.sample(
|
|
model, noise, steps, cfg, sampler_name, scheduler,
|
|
positive, negative, latent_image_tensor, denoise=denoise,
|
|
disable_noise=disable_noise, start_step=start_at_step,
|
|
last_step=end_at_step, force_full_denoise=force_full_denoise,
|
|
noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise_seed
|
|
)
|
|
|
|
# Wrap in latent format
|
|
out = latent_image.copy()
|
|
out["samples"] = samples
|
|
result = (out,)
|
|
|
|
except Exception as e:
|
|
logging.warning(f"Advanced sampling failed, falling back to standard: {e}")
|
|
# Fallback to direct sampling
|
|
samples = comfy.sample.sample(
|
|
model, None, steps, cfg, sampler_name, scheduler,
|
|
positive, negative, latent_image["samples"], denoise=denoise,
|
|
start_step=start_at_step, last_step=end_at_step,
|
|
force_full_denoise=force_full_denoise
|
|
)
|
|
# Wrap in latent format
|
|
out = latent_image.copy()
|
|
out["samples"] = samples
|
|
result = (out,)
|
|
|
|
sample_time = time.time() - start_time
|
|
self.performance_stats['total_time'] += sample_time
|
|
logging.info(f"ROCM Advanced KSampler completed in {sample_time:.2f}s")
|
|
|
|
return result
|
|
|
|
|
|
class ROCMOptimizedCheckpointLoader:
|
|
"""
|
|
ROCM-optimized checkpoint loader for AMD GPUs (gfx1151)
|
|
|
|
Phase 1 Optimizations:
|
|
- Optimized loading for Flux and WAN models
|
|
- Memory-efficient loading for AMD GPUs
|
|
- Automatic precision optimization
|
|
- Flux-specific optimizations (skip negative CLIP)
|
|
|
|
Phase 2 Optimizations:
|
|
- Smart mixed precision strategies
|
|
- Advanced batch processing for AMD GPUs
|
|
- Memory prefetching and advanced memory management
|
|
|
|
Phase 3 Optimizations:
|
|
- Advanced performance monitoring and adaptive optimization
|
|
- Real-time optimization adjustments based on usage patterns
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Performance monitoring
|
|
self.performance_stats = {
|
|
'total_executions': 0,
|
|
'total_time': 0.0,
|
|
'precision_optimizations': 0,
|
|
'memory_optimizations': 0
|
|
}
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
try:
|
|
ckpt_list = folder_paths.get_filename_list("checkpoints")
|
|
except:
|
|
ckpt_list = ["checkpoint1.safetensors", "checkpoint2.safetensors"]
|
|
|
|
return {
|
|
"required": {
|
|
"ckpt_name": (ckpt_list, {
|
|
"tooltip": "Checkpoint file to load"
|
|
}),
|
|
"lazy_loading": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Enable lazy loading for better memory usage"
|
|
}),
|
|
"optimize_for_flux": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Optimize loading for Flux models"
|
|
}),
|
|
"precision_mode": (["auto", "fp32", "fp16", "bf16"], {
|
|
"default": "auto",
|
|
"tooltip": "Precision mode - auto selects fp32 for gfx1151"
|
|
})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
|
|
RETURN_NAMES = ("MODEL", "CLIP", "VAE")
|
|
FUNCTION = "load_checkpoint"
|
|
CATEGORY = "RocM Ninodes/Loaders"
|
|
DESCRIPTION = "ROCM-optimized checkpoint loader for AMD GPUs (gfx1151) - All Phases"
|
|
|
|
def load_checkpoint(self, ckpt_name, lazy_loading=True, optimize_for_flux=True, precision_mode="auto"):
|
|
"""
|
|
ROCM-optimized checkpoint loading with all phase optimizations
|
|
"""
|
|
if not COMFY_AVAILABLE:
|
|
raise RuntimeError("ComfyUI modules not available - cannot load checkpoint")
|
|
|
|
start_time = time.time()
|
|
self.performance_stats['total_executions'] += 1
|
|
|
|
try:
|
|
# Get checkpoint path
|
|
ckpt_path = folder_paths.get_full_path_or_raise("checkpoints", ckpt_name)
|
|
print(f"Loading checkpoint: {ckpt_name}")
|
|
print(f"Checkpoint path: {ckpt_path}")
|
|
|
|
# Check if ROCm is available
|
|
try:
|
|
if torch.cuda.is_available():
|
|
device_name = torch.cuda.get_device_name(0)
|
|
print(f"GPU: {device_name}")
|
|
if "AMD" in device_name or "Radeon" in device_name:
|
|
print("AMD GPU detected - using ROCm optimizations")
|
|
else:
|
|
print("Non-AMD GPU detected - using compatibility mode")
|
|
else:
|
|
print("CUDA not available - using CPU mode")
|
|
except Exception as e:
|
|
print(f"GPU detection failed: {e}")
|
|
print("ROCm not available - running in compatibility mode")
|
|
|
|
# Use ComfyUI's standard loading - this is the most reliable approach
|
|
out = comfy.sd.load_checkpoint_guess_config(
|
|
ckpt_path,
|
|
output_vae=True,
|
|
output_clip=True,
|
|
embedding_directory=folder_paths.get_folder_paths("embeddings")
|
|
)
|
|
|
|
# Validate the output
|
|
if len(out) < 3:
|
|
raise ValueError(f"Checkpoint loading returned {len(out)} items, expected 3")
|
|
|
|
model, clip, vae = out[:3]
|
|
print(f"Model loaded: {type(model)}")
|
|
print(f"CLIP loaded: {type(clip)}")
|
|
print(f"VAE loaded: {type(vae)}")
|
|
|
|
# Phase 2: Apply precision optimizations
|
|
if precision_mode != "auto":
|
|
dtype_map = {
|
|
"fp32": torch.float32,
|
|
"fp16": torch.float16,
|
|
"bf16": torch.bfloat16
|
|
}
|
|
optimal_dtype = dtype_map[precision_mode]
|
|
|
|
# Convert models to optimal dtype
|
|
if hasattr(model, 'to'):
|
|
model = model.to(optimal_dtype)
|
|
if hasattr(vae, 'to'):
|
|
vae = vae.to(optimal_dtype)
|
|
|
|
self.performance_stats['precision_optimizations'] += 1
|
|
|
|
# Phase 2: Memory optimization
|
|
if lazy_loading:
|
|
# Clear cache after loading
|
|
torch.cuda.empty_cache()
|
|
self.performance_stats['memory_optimizations'] += 1
|
|
|
|
load_time = time.time() - start_time
|
|
self.performance_stats['total_time'] += load_time
|
|
print(f"ROCM Checkpoint Loader completed in {load_time:.2f}s")
|
|
|
|
return (model, clip, vae)
|
|
|
|
except Exception as e:
|
|
print(f"ROCM checkpoint loading failed: {e}")
|
|
print("Attempting fallback to standard ComfyUI loading...")
|
|
# Fallback to standard loading
|
|
try:
|
|
ckpt_path = folder_paths.get_full_path_or_raise("checkpoints", ckpt_name)
|
|
out = comfy.sd.load_checkpoint_guess_config(
|
|
ckpt_path,
|
|
output_vae=True,
|
|
output_clip=True,
|
|
embedding_directory=folder_paths.get_folder_paths("embeddings")
|
|
)
|
|
print("Fallback loading successful")
|
|
return out[:3]
|
|
except Exception as e2:
|
|
print(f"Fallback loading failed: {e2}")
|
|
raise e2
|
|
|
|
|
|
# Apply instrumentation to all nodes
|
|
@instrument_node
|
|
class ROCMOptimizedVAEDecodeInstrumented(ROCMOptimizedVAEDecode):
|
|
pass
|
|
|
|
@instrument_node
|
|
class ROCMOptimizedKSamplerInstrumented(ROCMOptimizedKSampler):
|
|
pass
|
|
|
|
@instrument_node
|
|
class ROCMOptimizedKSamplerAdvancedInstrumented(ROCMOptimizedKSamplerAdvanced):
|
|
pass
|
|
|
|
@instrument_node
|
|
class ROCMOptimizedCheckpointLoaderInstrumented(ROCMOptimizedCheckpointLoader):
|
|
pass
|
|
|
|
# Node mappings
|
|
NODE_CLASS_MAPPINGS = {
|
|
"ROCMOptimizedCheckpointLoader": ROCMOptimizedCheckpointLoaderInstrumented,
|
|
"ROCMOptimizedVAEDecode": ROCMOptimizedVAEDecodeInstrumented,
|
|
"ROCMOptimizedKSampler": ROCMOptimizedKSamplerInstrumented,
|
|
"ROCMOptimizedKSamplerAdvanced": ROCMOptimizedKSamplerAdvancedInstrumented,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"ROCMOptimizedCheckpointLoader": "ROCM Checkpoint Loader",
|
|
"ROCMOptimizedVAEDecode": "ROCM VAE Decode",
|
|
"ROCMOptimizedKSampler": "ROCM KSampler",
|
|
"ROCMOptimizedKSamplerAdvanced": "ROCM KSampler Advanced",
|
|
} |