284 lines
9.9 KiB
Python
284 lines
9.9 KiB
Python
"""
|
|
F14 Proactive Diagnostics - Runtime Performance Check
|
|
|
|
Analyzes workflow for potential runtime performance issues:
|
|
1. VRAM risk estimation (resolution * batch size vs available VRAM)
|
|
2. Excessive batch sizes
|
|
3. Extreme resolutions
|
|
4. Optimization suggestions (tiled VAE, etc.)
|
|
"""
|
|
|
|
import logging
|
|
import math
|
|
from typing import Dict, Any, List, Optional, Tuple
|
|
|
|
from ..models import (
|
|
HealthIssue,
|
|
HealthCheckRequest,
|
|
IssueCategory,
|
|
IssueSeverity,
|
|
IssueTarget,
|
|
)
|
|
from . import register_check
|
|
from .env_deps import _get_env_info
|
|
|
|
logger = logging.getLogger("comfyui-doctor.diagnostics.checks.runtime_performance")
|
|
|
|
|
|
# ============================================================================
|
|
# Configuration & Heuristics
|
|
# ============================================================================
|
|
|
|
# Resolution thresholds (width * height)
|
|
RES_SD15_OPTIMAL = 512 * 512
|
|
RES_SDXL_OPTIMAL = 1024 * 1024
|
|
RES_WARNING_THRESHOLD = 2048 * 2048 # 4MP
|
|
RES_CRITICAL_THRESHOLD = 4096 * 4096 # 16MP
|
|
|
|
# Batch size thresholds
|
|
BATCH_WARNING_THRESHOLD = 8
|
|
BATCH_CRITICAL_THRESHOLD = 32
|
|
|
|
# VRAM Heuristics (Estimates)
|
|
VRAM_SYSTEM_OVERHEAD_GB = 1.5 # OS + Display + ComfyUI base
|
|
VRAM_MODEL_SD15_GB = 3.0 # UNet + VAE + CLIP
|
|
VRAM_MODEL_SDXL_GB = 8.0 # Base + Refiner approx
|
|
VRAM_MODEL_FLUX_GB = 16.0 # Large model
|
|
|
|
# Node types of interest
|
|
LATENT_NODES = {"EmptyLatentImage", "EmptyLatentAudio"}
|
|
IMAGE_NODES = {"LoadImage", "ImageScale", "ImageResize"}
|
|
BATCH_NODES = {"RepeatImage", "ImageBatch"}
|
|
|
|
|
|
# ============================================================================
|
|
# Check Implementation
|
|
# ============================================================================
|
|
|
|
@register_check("runtime_performance")
|
|
async def check_runtime_performance(
|
|
workflow: Dict[str, Any],
|
|
request: HealthCheckRequest,
|
|
) -> List[HealthIssue]:
|
|
"""
|
|
Check workflow for runtime performance risks.
|
|
|
|
Returns list of HealthIssues for detected problems.
|
|
"""
|
|
issues: List[HealthIssue] = []
|
|
|
|
# Get environment info (VRAM, etc.)
|
|
env = _get_env_info()
|
|
total_vram_gb = env.get("gpu_memory_gb", 0) or 0
|
|
has_gpu = env.get("cuda_available", False) or env.get("mps_available", False)
|
|
|
|
# 1. Analyze Latent/Image Dimensions & Batch Sizes
|
|
max_dims, max_batch = _analyze_dimensions(workflow)
|
|
|
|
# Check resolution risks
|
|
issues.extend(_check_resolution(max_dims, has_gpu))
|
|
|
|
# Check batch size risks
|
|
issues.extend(_check_batch_size(max_batch))
|
|
|
|
# 2. VRAM Estimates (Rough heuristic)
|
|
# Only run if we have GPU info
|
|
if has_gpu and total_vram_gb > 0:
|
|
issues.extend(_check_vram_risk(max_dims, max_batch, total_vram_gb))
|
|
|
|
return issues
|
|
|
|
|
|
def _analyze_dimensions(workflow: Dict[str, Any]) -> Tuple[Tuple[int, int], int]:
|
|
"""
|
|
Find maximum dimensions and batch sizes in the workflow.
|
|
Returns ((width, height), batch_size)
|
|
"""
|
|
max_width = 0
|
|
max_height = 0
|
|
max_batch = 1
|
|
|
|
nodes = workflow.get("nodes", [])
|
|
if not isinstance(nodes, list):
|
|
return (0, 0), 1
|
|
|
|
for node in nodes:
|
|
if not isinstance(node, dict):
|
|
continue
|
|
|
|
node_type = node.get("type", "")
|
|
widgets_values = node.get("widgets_values", [])
|
|
|
|
# EmptyLatentImage: [width, height, batch_size]
|
|
if node_type == "EmptyLatentImage" and len(widgets_values) >= 3:
|
|
try:
|
|
w = int(widgets_values[0])
|
|
h = int(widgets_values[1])
|
|
b = int(widgets_values[2])
|
|
|
|
if w * h > max_width * max_height:
|
|
max_width, max_height = w, h
|
|
if b > max_batch:
|
|
max_batch = b
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
# LoadImage: Usually just one image, check if we can get dims?
|
|
# (Dimensions are not in widget values usually, so skip for now)
|
|
|
|
# Primitive nodes handling is complex, skipping for v1
|
|
|
|
return (max_width, max_height), max_batch
|
|
|
|
|
|
def _check_resolution(dims: Tuple[int, int], has_gpu: bool) -> List[HealthIssue]:
|
|
"""Check for extreme resolutions."""
|
|
issues = []
|
|
width, height = dims
|
|
pixels = width * height
|
|
|
|
target = IssueTarget(setting="resolution")
|
|
|
|
if pixels >= RES_CRITICAL_THRESHOLD:
|
|
issues.append(HealthIssue(
|
|
issue_id=HealthIssue.generate_issue_id("extreme_resolution", target, f"{width}x{height}"),
|
|
category=IssueCategory.PERFORMANCE,
|
|
severity=IssueSeverity.WARNING,
|
|
title="Extreme Resolution Detected",
|
|
summary=f"Workflow uses very high resolution: {width}x{height} ({pixels/1e6:.1f} MP)",
|
|
evidence=[
|
|
f"Max Dimensions: {width}x{height}",
|
|
f"Pixel Count: {pixels}",
|
|
"Significantly exceeds standard generation sizes"
|
|
],
|
|
recommendation=[
|
|
"Ensure you have sufficient VRAM (typically >24GB for this size)",
|
|
"Consider using Tiled VAE Decoding to prevent OOM",
|
|
"Consider generating at lower res and upscaling"
|
|
],
|
|
target=target
|
|
))
|
|
elif pixels >= RES_WARNING_THRESHOLD:
|
|
issues.append(HealthIssue(
|
|
issue_id=HealthIssue.generate_issue_id("high_resolution", target, f"{width}x{height}"),
|
|
category=IssueCategory.PERFORMANCE,
|
|
severity=IssueSeverity.INFO,
|
|
title="High Resolution",
|
|
summary=f"Workflow uses high resolution: {width}x{height}",
|
|
evidence=[
|
|
f"Dimensions: {width}x{height}",
|
|
"Exceeds typical SDXL native resolution"
|
|
],
|
|
recommendation=[
|
|
"Verify VAE Decode capability",
|
|
"Use Tiled VAE if you encounter Out of Memory errors"
|
|
],
|
|
target=target
|
|
))
|
|
|
|
return issues
|
|
|
|
|
|
def _check_batch_size(batch_size: int) -> List[HealthIssue]:
|
|
"""Check for large batch sizes."""
|
|
issues = []
|
|
target = IssueTarget(setting="batch_size")
|
|
|
|
if batch_size >= BATCH_CRITICAL_THRESHOLD:
|
|
issues.append(HealthIssue(
|
|
issue_id=HealthIssue.generate_issue_id("critical_batch_size", target, str(batch_size)),
|
|
category=IssueCategory.PERFORMANCE,
|
|
severity=IssueSeverity.WARNING,
|
|
title="Review Batch Size",
|
|
summary=f"Large batch size detected: {batch_size}",
|
|
evidence=[
|
|
f"Batch Size: {batch_size}",
|
|
"Can cause significant VRAM spikes"
|
|
],
|
|
recommendation=[
|
|
"Reduce batch size if OOM occurs",
|
|
"Ensure total VRAM usage stays within limits"
|
|
],
|
|
target=target
|
|
))
|
|
|
|
return issues
|
|
|
|
|
|
def _check_vram_risk(dims: Tuple[int, int], batch_size: int, available_vram: float) -> List[HealthIssue]:
|
|
"""Estimate VRAM usage and warn if risky."""
|
|
issues = []
|
|
width, height = dims
|
|
|
|
if width == 0 or height == 0:
|
|
return issues
|
|
|
|
# Heuristic:
|
|
# VRAM ~= Baseline + Model(avg) + (Pixels * Batch * BytesPerPixel * ProcessMultiplier)
|
|
# BytesPerPixel: float32=4, float16=2. Multipliers depend on attention, etc.
|
|
# We'll use a conservative multiplier for latent processing.
|
|
# 1 Latent Pixel = 8x8 Image Pixels. But attention maps are huge.
|
|
|
|
# Simplified estimation for "Working Memory" peak
|
|
# Assuming float16 (2 bytes)
|
|
# Resolution-dependent buffer size (Attention, VAE, etc.)
|
|
# Empirically: 1024x1024 batch=1 takes ~2GB over model size on SDXL
|
|
|
|
base_model_gb = VRAM_MODEL_SDXL_GB # Assume worst case (SDXL) for safety
|
|
|
|
# Pixels in millions
|
|
mp = (width * height) / 1e6
|
|
|
|
# Rough formula:
|
|
# Extra VRAM = (MP * Batch * 1.5) GB
|
|
# Example: 1MP * 1 * 1.5 = 1.5GB working
|
|
# Example: 1MP * 4 * 1.5 = 6.0GB working
|
|
estimated_working_gb = (mp * batch_size * 1.5)
|
|
|
|
estimated_total_gb = VRAM_SYSTEM_OVERHEAD_GB + base_model_gb + estimated_working_gb
|
|
|
|
risk_ratio = estimated_total_gb / available_vram
|
|
|
|
if risk_ratio > 1.1: # >110% of VRAM
|
|
target = IssueTarget(setting="vram_estimate")
|
|
issues.append(HealthIssue(
|
|
issue_id=HealthIssue.generate_issue_id("vram_oom_risk", target, str(risk_ratio)),
|
|
category=IssueCategory.PERFORMANCE,
|
|
severity=IssueSeverity.WARNING,
|
|
title="High Risk of Out-of-Memory (OOM)",
|
|
summary=f"Estimated VRAM usage ({estimated_total_gb:.1f}GB) exceeds available ({available_vram:.1f}GB)",
|
|
evidence=[
|
|
f"Available VRAM: {available_vram:.1f} GB",
|
|
f"Estimated Usage: ~{estimated_total_gb:.1f} GB",
|
|
f"Resolution: {width}x{height}",
|
|
f"Batch Size: {batch_size}",
|
|
"Based on SDXL checks (worst-case heuristic)"
|
|
],
|
|
recommendation=[
|
|
"Reduce resolution or batch size",
|
|
"Use --lowvram command line argument if not enabled",
|
|
"Close other GPU applications"
|
|
],
|
|
target=target
|
|
))
|
|
elif risk_ratio > 0.9: # >90% of VRAM
|
|
target = IssueTarget(setting="vram_estimate")
|
|
issues.append(HealthIssue(
|
|
issue_id=HealthIssue.generate_issue_id("vram_tight_risk", target, str(risk_ratio)),
|
|
category=IssueCategory.PERFORMANCE,
|
|
severity=IssueSeverity.INFO,
|
|
title="High VRAM Usage Expected",
|
|
summary=f"Estimated VRAM usage is close to limit ({estimated_total_gb:.1f}/{available_vram:.1f} GB)",
|
|
evidence=[
|
|
f"Available VRAM: {available_vram:.1f} GB",
|
|
f"Estimated Usage: ~{estimated_total_gb:.1f} GB"
|
|
],
|
|
recommendation=[
|
|
"Monitor VRAM usage during generation",
|
|
"Close browser tabs/apps to free up shared GPU memory"
|
|
],
|
|
target=target
|
|
))
|
|
|
|
return issues
|