13 KiB
Enhanced GPU Model Detection with Metadata Extraction
Status: ✅ Implemented
Date: November 29, 2025
Component: Resource Monitor - GPU Profiling
Related Files:
nodes/utils/resource_monitor/profiler_core.py(lines 1-600)web/js/resource_monitor.js(tooltip display)
Problem Statement
Initial GPU model scanning detected 603 individual layer components (WanLayerNorm, WanRMSNorm, WanSelfAttention) instead of showing meaningful parent models. Users saw:
- ❌ 603 entries showing "WanLayerNorm (model, 0.0 GB)"
- ❌ No correlation to user-selected model filenames
- ❌ Technical class names instead of user-friendly names
- ❌ All layers showing 0.0 GB (incorrect sizing)
User Requirement: "The average comfyui user won't understand these levels"
Solution Overview
Enhanced three-phase approach that:
- Reads safetensors metadata during model loading
- Groups child layers under parent models
- Displays user-friendly names with accurate VRAM usage
Example Output
Before: 603 × WanLayerNorm (model, 0.0 GB)
After:
Wan2_2-T2V-A14B-HIGH (checkpoint, 2.1 GB, 145 layers)VACE-Depth (model, 512 MB, 45 layers)MyLoRA (lora, 128 MB, 32 layers)
Implementation Details
1. Safetensors Metadata Extraction
File: profiler_core.py (lines 1-20)
# Import safetensors library for metadata reading
try:
from safetensors import safe_open
SAFETENSORS_AVAILABLE = True
except ImportError:
SAFETENSORS_AVAILABLE = False
Installation:
pip3 install safetensors
Purpose: Read model_type and other metadata directly from .safetensors files
2. Pending Model Loads Tracking
File: profiler_core.py (lines 136-141)
# Track pending model loads with metadata (filename, timestamp, metadata)
self._pending_model_loads: List[Dict[str, Any]] = []
self._pending_loads_max_age: float = 5.0 # Keep pending loads for 5 seconds
Purpose: Store model filenames and metadata during node execution for later correlation with loaded modules
3. Model Metadata Capture
File: profiler_core.py (_capture_model_metadata() method, lines 449-489)
Triggered: During start_node() before model execution
Process:
- Scan node inputs for file path keys:
model,checkpoint,ckpt_name,lora_name, etc. - Filter for
.safetensorsfiles - Read metadata using
safe_open() - Store in
_pending_model_loadswith timestamp
Example:
# Input: {'ckpt_name': 'Wan2_2-T2V-A14B-HIGH_fp8.safetensors'}
# Reads metadata: {'model_type': 'Wan2_2-T2V-A14B-HIGH', ...}
# Stores: {
# 'filename': 'Wan2_2-T2V-A14B-HIGH_fp8.safetensors',
# 'timestamp': 1732901234.56,
# 'node_type': 'WanVideoModelLoader',
# 'metadata': {'model_type': 'Wan2_2-T2V-A14B-HIGH'}
# }
4. Enhanced GPU Scanning with Grouping
File: profiler_core.py (_scan_gpu_models() method, lines 270-444)
Five-Phase Process:
Phase 1: Collect All CUDA Modules
- Scan all Python objects using
gc.get_objects() - Filter for
torch.nn.Moduleinstances - Build parent/child relationship map using
named_children() - Only count direct parameters (
recurse=False)
for param in obj.parameters(recurse=False): # Only direct params
if param.device.type == 'cuda':
size_bytes += param.nelement() * param.element_size()
Phase 2: Identify Top-Level Parents
- Find modules that are NOT children of other modules
- These represent complete models (not individual layers)
top_level_parents = set(all_cuda_modules.keys()) - set(parent_child_map.keys())
Phase 3: Calculate Total VRAM
- For each parent, traverse all descendants using depth-first search
- Sum VRAM across all child layers
- Count total layers and parameters
# Depth-first traversal through module hierarchy
stack = [parent_id]
while stack:
current_id = stack.pop()
total_size_bytes += current_data['size_bytes']
layer_count += 1
stack.extend(current_data['children'])
Phase 4: Extract Model Names
- Try module attributes:
name,model_name,config._name_or_path - Try module metadata:
__metadata__['model_type'] - Match to
_pending_model_loadsby timestamp - Use safetensors
model_typefield if available
model_name = self._extract_model_name(module, model_class)
metadata = self._match_pending_metadata(size_mb, model_class)
if metadata and 'model_type' in metadata:
model_name = metadata['model_type']
Phase 5: Format Display Names
- Show model type, size (GB or MB), layer count
- Sort by VRAM usage (descending)
- Limit to top 10 models per GPU
display_name = f"{model_name} ({model_type}, {size_str}, {layer_count} layers)"
# Example: "Wan2_2-T2V-A14B-HIGH (checkpoint, 2.1 GB, 145 layers)"
5. Helper Methods
_read_safetensors_metadata(filepath: str)
Lines: 491-527
- Resolves relative paths to common ComfyUI model directories
- Opens safetensors file with
safe_open() - Extracts metadata dictionary
- Returns
{'model_type': '...', ...}orNone
Paths Checked:
possible_dirs = [
Path('/workspace/ComfyUI/models/checkpoints'),
Path('/workspace/ComfyUI/models/loras'),
Path('/workspace/ComfyUI/models/vae'),
Path('/workspace/ComfyUI/models/unet'),
Path('models/checkpoints'),
Path('models/loras'),
# ... etc
]
_extract_model_name(module, default_name: str)
Lines: 529-552
- Tries common attribute patterns on PyTorch module
- Checks
config._name_or_pathfor HuggingFace models - Checks
__metadata__['model_type']for safetensors - Falls back to class name if nothing found
_match_pending_metadata(size_mb: float, class_name: str)
Lines: 554-578
- Matches loaded module to pending metadata by timestamp
- Returns most recent load with metadata
- Fallback: returns most recent load even without metadata
- Used for correlating GPU modules to user-selected files
_classify_model_type(class_name: str)
Lines: 580-597
- Updated with WanVideo patterns:
wanvideo,wan2 - Returns:
checkpoint,lora,vae,clip,controlnet,upscaler,depth,model
Technical Challenges Solved
Challenge 1: Layer-Level Detection
Problem: Detecting 603 individual layers instead of 5-10 models
Solution: Build parent/child hierarchy and only report top-level parents
Impact: Reduced from 603 entries to ~5-10 meaningful models
Challenge 2: Zero VRAM Reporting
Problem: All layers showing 0.0 GB
Solution:
- Only count direct parameters with
recurse=False - Traverse children separately and sum VRAM
- Calculate at parent level including all descendants
Impact: Accurate VRAM reporting (e.g., 2.1 GB for Wan2 model)
Challenge 3: Filename Correlation
Problem: No way to match detected modules to user-selected files
Solution:
- Capture filenames during
start_node() - Read safetensors metadata immediately
- Store with timestamp for correlation
- Match by timing when scanning GPU
Impact: Display "Wan2_2-T2V-A14B-HIGH" instead of "WanTransformerDecoder"
Challenge 4: Block Swapping / RAM Offloading
Problem: WanVideo models use block swapping - parts may be in RAM
Solution:
- Only scan CUDA memory (
param.device.type == 'cuda') - Automatically handles offloaded models
- Only reports what's actually on GPU
Impact: Accurate representation of GPU state
Performance Optimizations
1. Scan Caching
Implementation: get_loaded_models() method (lines 600+)
# Cache scan results for 1 second
if self._model_scan_cache_timestamp:
age = time.time() - self._model_scan_cache_timestamp
if age < self._model_scan_cache_ttl:
return self.loaded_models # Return cached
Impact: Prevents expensive scans on every API request
2. Cache Invalidation
Implementation: end_node() method
# Invalidate if VRAM changed by more than 100MB
vram_change = abs(node_profile.vram_after - node_profile.vram_before)
if vram_change > 100 * 1024 * 1024: # 100MB threshold
self._model_scan_cache_timestamp = None
Impact: Fresh scans only when models actually load/unload
3. Garbage Collection
Implementation: _scan_gpu_models() line 288
# Force GC before scanning to get accurate state
gc.collect()
Impact: Ensures dead references are cleaned up before counting
4. Top 10 Limit
Implementation: Phase 5 of scanning
# Sort by VRAM and limit to top 10
result[gpu_id].sort(key=lambda x: x['vram_mb'], reverse=True)
result[gpu_id] = result[gpu_id][:10]
Impact: Prevents overwhelming UI with hundreds of models
5. Pending Loads Cleanup
Implementation: Lines 291-295
# Clean up old pending loads (>5 seconds)
self._pending_model_loads = [
load for load in self._pending_model_loads
if current_time - load.get('timestamp', 0) < 5.0
]
Impact: Prevents memory leak from accumulating metadata
Testing & Validation
Test Scenario 1: WanVideo Model Loading
Setup: Load Wan2_2-T2V-A14B-HIGH model with block swapping
Expected Output:
GPU 0: 5 models
- Wan2_2-T2V-A14B-HIGH (checkpoint, 2.1 GB, 145 layers)
- VACE-Depth (model, 512 MB, 45 layers)
- MyLoRA (lora, 128 MB, 32 layers)
Validation:
- ✅ Shows grouped parent model, not 603 layers
- ✅ Accurate VRAM calculation (2.1 GB)
- ✅ Displays user-friendly name from metadata
- ✅ Shows layer count for context
Test Scenario 2: Multiple Model Types
Setup: Load checkpoint + VAE + LoRA + CLIP
Expected Output: 4-5 distinct models sorted by VRAM
Test Scenario 3: Model Unloading
Setup: Clear models and observe cache invalidation
Expected Output: Empty model list after VRAM drops
Test Scenario 4: Without Safetensors Library
Setup: Remove safetensors package
Expected Output: Graceful degradation, shows class names instead of metadata
Dependencies
Required
torch- PyTorch module inspectiongc- Python garbage collection
Optional
safetensors- Metadata extraction (highly recommended)
Installation:
pip3 install safetensors
Graceful Degradation: If safetensors not available:
- Still performs grouping and VRAM calculation
- Uses class names instead of metadata
- Shows warning on startup
Future Enhancements
Enhancement 1: Multi-GPU Tooltip
Current: Single tooltip for all GPUs
Proposed: Separate tooltip per GPU with per-GPU model list
Enhancement 2: Model Age Tracking
Current: Shows only current models
Proposed: Show "loaded 2m ago" timestamp
Enhancement 3: Offloaded Model Indicator
Current: Only shows GPU-resident models
Proposed: Show RAM-offloaded models with "(offloaded)" indicator
Enhancement 4: Model File Paths
Current: Shows model name only
Proposed: Show full file path in tooltip
Enhancement 5: More Metadata Fields
Current: Only uses model_type
Proposed: Extract and display architecture, base_model, etc.
Related Documentation
- Core Implementation: GPU_MEMORY_SCANNING.md - Original scanning approach
- Widget Display: CONTROL_PANEL.md - Frontend tooltip rendering
- API Endpoint:
/swissarmyknife/profiler/loaded_models- REST API for model data - Node Tracking:
profiler_core.py- Complete profiler implementation
Troubleshooting
Issue: No Models Shown
Cause: No CUDA modules in memory
Solution: Load a model and wait for node execution to complete
Issue: Shows Class Names Instead of Metadata
Cause: safetensors library not installed
Solution: pip3 install safetensors
Issue: Incorrect VRAM Numbers
Cause: Block swapping or model sharing
Solution: VRAM is per-GPU, shared layers counted once
Issue: Old Models Still Showing
Cause: Cache not invalidated
Solution: Wait 1 second or trigger VRAM change >100MB
Issue: Too Many Models Listed
Cause: Many small models loaded
Solution: Only top 10 shown, sorted by VRAM usage
Logging & Debugging
Console Output:
[SwissArmyKnife][Profiler] 📄 Captured model: Wan2_2-HIGH.safetensors (metadata: True)
[SwissArmyKnife][Profiler] 🔍 Found 603 CUDA modules, 5 top-level parents
[SwissArmyKnife][Profiler] ✅ Grouped into 5 parent models across 1 GPU(s)
[SwissArmyKnife][Profiler] GPU 0: 5 models
[SwissArmyKnife][Profiler] - Wan2_2-T2V-A14B-HIGH (checkpoint, 2.1 GB, 145 layers)
Enable Debug Logging:
logger.setLevel(logging.DEBUG)
Commit Reference
Feature Branch: feat/Resource-monitor/CU-86b7mzbtx
Implementation Date: November 29, 2025
Key Changes:
- Added safetensors import and metadata extraction
- Enhanced
_scan_gpu_models()with 5-phase approach - Added
_capture_model_metadata()for filename tracking - Added helper methods for name extraction and matching
- Fixed VRAM calculation using parent/child traversal
- Limited display to top 10 models per GPU